Kmers

4 problems from Rosalind — Bioinformatics Textbook Track. Press Run on any block to execute it in your browser.

BA3A — Generate the k-mer Composition of a String

solvedbrowser + CLI Problem statement Open in the workbench Download .bl

# Rosalind: BA3A — Generate the k-mer Composition of a String
# https://rosalind.info/problems/ba3a/
#
# Given: An integer k and a string Text.
# Return: Composition_k(Text), the k-mers of Text in lexicographic order.

let text = "CAATCCAAC"
let k = 5

let result = kmers(dna(text), k) |> map(|km| str(km)) |> sort()

println("Result:")
result |> each(|km| println("  " + km))
println("Expected: AATCC ATCCA CAATC CCAAC TCCAA")

fn test_ba3a_kmer_composition() {
    assert len(result) == len(text) - k + 1, "BA3A: got " + str(len(result)) + " k-mers"
    let joined = result |> join(" ")
    assert joined == "AATCC ATCCA CAATC CCAAC TCCAA", "BA3A: got " + joined
}

BA1K — Generate the Frequency Array of a String

solvedbrowser + CLI Problem statement Open in the workbench Download .bl

Indexed by PatternToNumber, so it lists every possible k-mer including those that never occur. That is what separates it from a tally.

# Rosalind: BA1K — Generate the Frequency Array of a String
# https://rosalind.info/problems/ba1k/
#
# Given: A DNA string Text and an integer k.
# Return: The frequency array of k-mers in Text.

let text = "ACGCGGCTCTGAAA"
let k = 2

# The frequency array is indexed by PatternToNumber, so it has 4^k entries and
# lists every possible k-mer in lexicographic order — including the ones that
# never occur, which is what distinguishes it from a tally.
fn base_of(symbol) {
    if symbol == "A" then 0
    else if symbol == "C" then 1
    else if symbol == "G" then 2
    else 3
}

fn pattern_to_number(pattern) {
    range(0, len(pattern)) |> reduce(|acc, i| acc * 4 + base_of(substr(pattern, i, 1)), 0)
}

let size = 1
let e = 0
while e < k {
    size = size * 4
    e = e + 1
}

let freq_array = repeat([0], size)
for i in range(0, len(text) - k + 1) {
    let index = pattern_to_number(substr(text, i, k))
    freq_array[index] = freq_array[index] + 1
}

let result = freq_array |> map(|n| str(n)) |> join(" ")

println("Result:   " + result)
println("Expected: 2 1 0 0 0 0 2 2 1 2 1 0 0 1 1 0")

fn test_ba1k_frequency_array() {
    assert result == "2 1 0 0 0 0 2 2 1 2 1 0 0 1 1 0", "BA1K: got " + result
    assert len(freq_array) == 16, "BA1K: an array of 4^k entries is expected"
    assert sum(freq_array) == len(text) - k + 1, "BA1K: counts do not add up to the windows"
}

BA1L — Implement PatternToNumber

solvedbrowser + CLI Problem statement Open in the workbench Download .bl

k-mers in lexicographic order are the base-4 numbers with A=0, C=1, G=2, T=3.

# Rosalind: BA1L — Implement PatternToNumber
# https://rosalind.info/problems/ba1l/
#
# Given: A DNA string Pattern.
# Return: PatternToNumber(Pattern).

let pattern = "AGT"

# The k-mers in lexicographic order are the base-4 numbers, with A=0, C=1, G=2,
# T=3. So the index is just the pattern read as a number in that base.
fn base_of(symbol) {
    if symbol == "A" then 0
    else if symbol == "C" then 1
    else if symbol == "G" then 2
    else 3
}

let number = range(0, len(pattern))
  |> reduce(|acc, i| acc * 4 + base_of(substr(pattern, i, 1)), 0)

println("Result:   " + str(number))
println("Expected: 11")

fn test_ba1l_pattern_to_number() {
    assert number == 11, "BA1L: got " + str(number)
    # The first and last k-mers of length 3 anchor the range.
    assert range(0, 3) |> reduce(|a, i| a * 4 + base_of("A"), 0) == 0, "BA1L: AAA should be 0"
}

BA1M — Implement NumberToPattern

solvedbrowser + CLI Problem statement Open in the workbench Download .bl

The inverse of BA1L.

# Rosalind: BA1M — Implement NumberToPattern
# https://rosalind.info/problems/ba1m/
#
# Given: Integers index and k.
# Return: NumberToPattern(index, k).

let index = 45
let k = 4

# The inverse of BA1L: read the index as a base-4 number, most significant digit
# first, with A=0, C=1, G=2, T=3.
let symbols = ["A", "C", "G", "T"]

let pattern = ""
let remaining = index
let position = 0
while position < k {
    let power = 1
    let e = 0
    while e < k - position - 1 {
        power = power * 4
        e = e + 1
    }
    let digit = int(remaining / power)
    pattern = pattern + symbols[digit]
    remaining = remaining - digit * power
    position = position + 1
}

println("Result:   " + pattern)
println("Expected: AGTC")

fn test_ba1m_number_to_pattern() {
    assert pattern == "AGTC", "BA1M: got " + pattern
    assert len(pattern) == k, "BA1M: wrong length"
}