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 rand
import rand.seed
 
const bases = "ACGT"
 
// 'w' contains the weights out of 300 for each
// of swap, delete or insert in that order.
fn mutate(dna string, w [3]int) string {
    le := dna.len
    // get a random position in the dna to mutate
    p := rand.intn(le) or {0}
    // get a random number between 0 and 299 inclusive
    r := rand.intn(300) or {0}
    mut bytes := dna.bytes()
    match true {
		r < w[0] { // swap
			base := bases[rand.intn(4) or {0}]
			println("  Change @${p:3} ${[bytes[p]].bytestr()} to ${[base].bytestr()}")
			bytes[p] = base
		}
		r < w[0]+w[1] { // delete
			println("  Delete @${p:3} ${bytes[p]}")
			bytes.delete(p)
			//copy(bytes[p:], bytes[p+1:])
			bytes = bytes[0..le-1]
		}
		else { // insert
			base := bases[rand.intn(4) or {0}]
			bytes << 0
			bytes.insert(p,bytes[p])
			//copy(bytes[p+1:], bytes[p:])
			println("  Insert @${p:3} $base")
			bytes[p] = base
		}
    }
    return bytes.bytestr()
}
 
// Generate a random dna sequence of given length.
fn generate(le int) string {
    mut bytes := []u8{len:le}
    for i := 0; i < le; i++ {
        bytes[i] = bases[rand.intn(4) or {0}]
    }
    return bytes.bytestr()
}
 
// Pretty print dna and stats.
fn pretty_print(dna string, rowLen int) {
    println("SEQUENCE:")
    le := dna.len
    for i := 0; i < le; i += rowLen {
        mut k := i + rowLen
        if k > le {
            k = le
        }
        println("${i:5}: ${dna[i..k]}")
    }
    mut base_map := map[byte]int{} // allows for 'any' base
    for i in 0..le {
        base_map[dna[i]]++
    }
    mut bb := base_map.keys()
	bb.sort()
 
    println("\nBASE COUNT:")
    for base in bb {
        println("    $base: ${base_map[base]:3}")
    }
    println("    ------")
    println("    Σ: $le")
    println("    ======\n")
}
 
// Express weights as a string.
fn wstring(w [3]int) string {
    return "  Change: ${w[0]}\n  Delete: ${w[1]}\n  Insert: ${w[2]}\n"
}
 
fn main() {
    rand.seed(seed.time_seed_array(2))
    mut dna := generate(250)
    pretty_print(dna, 50)
    muts := 10
    w := [100, 100, 100]! // use e.g. {0, 300, 0} to choose only deletions
    println("WEIGHTS (ex 300):\n${wstring(w)}")
    println("MUTATIONS ($muts):")
    for _ in 0..muts {
        dna = mutate(dna, w)
    }
    println('')
    pretty_print(dna, 50)
}