Task

Given a string of characters A, C, G, and T representing a DNA sequence write a routine to mutate the sequence, (string) by:

 Choosing a random base position in the sequence.
 Mutate the sequence by doing one of either:
 Swap the base at that position by changing it to one of A, C, G, or T. (which has a chance of swapping the base for the same base)
 Delete the chosen base at the position.
 Insert another base randomly chosen from A,C, G, or T into the sequence at that position.
 Randomly generate a test DNA sequence of at least 200 bases
 "Pretty print" the sequence and a count of its size, and the count of each base in the sequence
 Mutate the sequence ten times.
 "Pretty print" the sequence after all mutations, and a count of its size, and the count of each base in the sequence.
Extra credit

 Give more information on the individual mutations applied.
 Allow mutations to be weighted and/or chosen.
import "random" for Random
import "/fmt" for Fmt
import "/sort" for Sort
 
var rand = Random.new()
var bases = "ACGT"
 
// 'w' contains the weights out of 300 for each
// of swap, delete or insert in that order.
var mutate = Fn.new { |dna, w|
    var le = dna.count 
    // get a random position in the dna to mutate
    var p = rand.int(le)
    // get a random number between 0 and 299 inclusive
    var r = rand.int(300)
    var chars = dna.toList
    if (r < w[0]) {   // swap
        var base = bases[rand.int(4)]
        Fmt.print("  Change @$3d $q to $q", p, chars[p], base)
        chars[p] = base
    } else if (r < w[0] + w[1]) {  // delete
        Fmt.print("  Delete @$3d $q", p, chars[p])
        chars.removeAt(p)
    } else { // insert
        var base = bases[rand.int(4)]
        Fmt.print("  Insert @$3d $q", p, base)
        chars.insert(p, base)
    }
    return chars.join()
}
 
// Generate a random dna sequence of given length.
var generate = Fn.new { |le|
    var chars = [""] * le
    for (i in 0...le) chars[i] = bases[rand.int(4)] 
    return chars.join()
}
 
// Pretty print dna and stats.
var prettyPrint = Fn.new { |dna, rowLen|
    System.print("SEQUENCE:")
    var le = dna.count
    var i = 0
    while (i < le) {
        var k = i + rowLen
        if (k > le) k = le
        Fmt.print("$5d: $s", i, dna[i...k])
        i =  i + rowLen
    }
    var baseMap = {}
    for (i in 0...le) {
        var v = baseMap[dna[i]]
        baseMap[dna[i]] = (v) ? v + 1 : 1
    }
    var bases = []
    for (k in baseMap.keys) bases.add(k)
    Sort.insertion(bases) // get bases into alphabetic order
    System.print("\nBASE COUNT:")
    for (base in bases) Fmt.print("    $s: $3d", base, baseMap[base])
    System.print("    ------")
    System.print("    Σ: %(le)")
    System.print("    ======\n")
}
 
// Express weights as a string.
var wstring = Fn.new { |w|
    return Fmt.swrite("  Change: $d\n  Delete: $d\n  Insert: $d\n", w[0], w[1], w[2])
}
 
var dna = generate.call(250)
prettyPrint.call(dna, 50)
var muts = 10
var w = [100, 100, 100] // use e.g. {0, 300, 0} to choose only deletions
Fmt.print("WEIGHTS (ex 300):\n$s", wstring.call(w))
Fmt.print("MUTATIONS ($d):", muts)
for (i in 0...muts) dna = mutate.call(dna, w)
System.print()
prettyPrint.call(dna, 50)