llm
Download
135 lines 5.2 KiBTomoModified 2026-08-30 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
# k-nucleotide, from The Computer Language Benchmarks Game
#
# A Tomo port. The only inline C is the final `%.3f` frequency formatting
# (Tomo has no zero-padded float format), which the benchmark's ground rules
# allow; all counting, encoding, sorting, and lookup is pure Tomo.
#
# Reads a FASTA file on stdin, extracts the third sequence (">THREE"), and:
#   - prints the frequency (%) of every 1- and 2-nucleotide, sorted by count
#     descending then code ascending (which is alphabetical, since A<C<G<T
#     maps to 0<1<2<3 packed big-endian);
#   - prints the count of five specific oligonucleotides.
#
# Each nucleotide is packed into a 2-bit code (A=0, C=1, G=2, T=3) and a
# length-k oligonucleotide into a 2k-bit integer key, so counting is done in
# an `{Int64:Int64}` table keyed by that integer rather than by text.
#
# Usage: knucleotide < fasta_output   (e.g. ./fasta 250000 | ./knucleotide)

struct Count{key:Int64, count:Int64}

# Sort by count descending, then by key ascending (ties broken toward the
# smaller code, i.e. alphabetically).
func by_count_then_key(a, b: Count -> Int32)
    if a.count != b.count
        return b.count <> a.count
    return a.key <> b.key

# Byte -> 2-bit code, indexed by `(byte and 7) + 1` (1-indexed). Only the
# entries for A/C/G/T are ever hit; upper/lower case share the low 3 bits.
CODE8 : [Byte] = [0, 0, 0, 1, 3, 0, 0, 2]

func nucleotide_for_code(code:Int64 -> Text)
    return "ACGT".slice(code+1, code+1)

func pct3(x:Float64 -> Text)
    return C_code:Text`({ char buf[32]; snprintf(buf, sizeof(buf), "%.3f", @x); Text$from_str(buf); })`

# Read all of stdin, then collect the codes of the ">THREE" sequence: continue to
# just past that header line, then map every non-newline byte until the next
# header (or EOF) to its 2-bit code.
func read_third_sequence(-> [Byte])
    data := (/dev/stdin).read_bytes()!
    n := Int64(data.length)
    # Find the byte just after the ">THREE" header's newline.
    i := Int64(1)
    start := Int64(0)
    while i <= n
        if Int64(data[i]!) == 62 # '>'
            # A header line; check whether it is ">THREE".
            if i+5 <= n and Int64(data[i+1]!) == 84 and Int64(data[i+2]!) == 72 and Int64(data[i+3]!) == 82 and Int64(data[i+4]!) == 69 and Int64(data[i+5]!) == 69
                # Advance to the end of this header line.
                j := i
                while j <= n and Int64(data[j]!) != 10
                    j += 1
                start = j + 1
                break
        i += 1

    if start == 0
        return []

    # THREE is the last record, so its sequence runs to EOF: take every
    # non-newline byte from `start` on and map it to its 2-bit code. As a
    # single-list-source comprehension this pre-sizes and fills with inlined
    # appends (no grow-from-empty reallocation).
    return [CODE8[(Int64(b) and 7) + 1]! for b in data.from(start) if Int64(b) != 10]

# Count every length-k oligonucleotide and print each with its frequency.
func write_frequencies(seq:[Byte], k:Int64)
    n := Int64(seq.length)
    mask := (Int64(1) << (2*k)) - 1
    counts : &{Int64:Int64; default=Int64(0)} = &{}
    key := Int64(0)
    # Prime the rolling key with the first k-1 codes (step=1 keeps the range
    # ascending, so k=1 yields an empty range instead of descending).
    for i in Int64(1).to(k-1, step=1)
        key = ((key << 2) and mask) or Int64(seq[i]!)
    for i in k.to(n, step=1)
        key = ((key << 2) and mask) or Int64(seq[i]!)
        counts[key] += 1

    entries := &[Count{kk, vv} for kk, vv in counts.entries()]
    entries.sort(
        # Sort by count descending, then by key ascending (ties broken toward the
        # smaller code, i.e. alphabetically).
        func(a, b: Count -> Int32)
            if a.count != b.count
                return b.count <> a.count
            return a.key <> b.key
    )

    total := Float64(n - k + 1)
    out := ""
    for e in entries
        # Decode the key back to its nucleotide string (high 2 bits first).
        name := ""
        shift := 2*(k-1)
        while shift >= 0
            name = name ++ nucleotide_for_code((e.key >> shift) and 3)
            shift -= 2
        out = out ++ name ++ " " ++ pct3(100.0 * Float64(e.count) / total) ++ "\n"
    say(out, newline=no)

# Count occurrences of one specific oligonucleotide by sliding its exact key
# over the sequence (no table needed, since only one key matters).
func write_count(seq:[Byte], oligo:Text)
    n := Int64(seq.length)
    bytes := oligo.utf8()
    k := Int64(bytes.length)
    target := Int64(0)
    for b in bytes
        target = (target << 2) or Int64(CODE8[(Int64(b) and 7) + 1]!)
    mask := (Int64(1) << (2*k)) - 1

    key := Int64(0)
    count := Int64(0)
    for i in Int64(1).to(k-1, step=1)
        key = ((key << 2) and mask) or Int64(seq[i]!)
    for i in k.to(n, step=1)
        key = ((key << 2) and mask) or Int64(seq[i]!)
        if key == target
            count += 1
    say("$count\t$oligo")

func main()
    seq := read_third_sequence()
    write_frequencies(seq, 1)
    say("")
    write_frequencies(seq, 2)
    say("")
    write_count(seq, "GGT")
    write_count(seq, "GGTA")
    write_count(seq, "GGTATT")
    write_count(seq, "GGTATTTTAATT")
    write_count(seq, "GGTATTTTAATTTATAGT")