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.
let bases: [Character] = ["A", "C", "G", "T"]
 
enum Action: CaseIterable {
  case swap, delete, insert
}
 
@discardableResult
func mutate(dna: inout String) -> Action {
  guard let i = dna.indices.shuffled().first(where: { $0 != dna.endIndex }) else {
    fatalError()
  }
 
  let action = Action.allCases.randomElement()!
 
  switch action {
  case .swap:
    dna.replaceSubrange(i..<i, with: [bases.randomElement()!])
  case .delete:
    dna.remove(at: i)
  case .insert:
    dna.insert(bases.randomElement()!, at: i)
  }
 
  return action
}
 
var d = ""
 
for _ in 0..<200 {
  d.append(bases.randomElement()!)
}
 
func printSeq(_ dna: String) {
  for startI in stride(from: 0, to: dna.count, by: 50) {
    print("\(startI): \(dna.dropFirst(startI).prefix(50))")
  }
 
  print()
  print("Size: \(dna.count)")
  print()
 
  let counts = dna.reduce(into: [:], { $0[$1, default: 0] += 1 })
 
  for (char, count) in counts.sorted(by: { $0.key < $1.key }) {
    print("\(char): \(count)")
  }
}
 
printSeq(d)
 
print()
 
for _ in 0..<20 {
  mutate(dna: &d)
}
 
printSeq(d)