///|
/// Remove formatting characters and normalize a sequence to uppercase.
pub fn normalize_sequence(text : String) -> String {
let out = StringBuilder()
for ch in text.to_upper() {
if is_iupac_base(ch) {
out.write_char(ch)
}
}
out.to_string()
}
///|
/// Hamming distance, or -1 when strings have different lengths.
pub fn hamming_distance(left : String, right : String) -> Int {
if left.length() != right.length() {
return -1
}
let mut distance = 0
for i in 0.. Int {
let mut g = 0
let mut c = 0
for ch in sequence.to_upper() {
if ch == 'G' {
g += 1
}
if ch == 'C' {
c += 1
}
}
g - c
}
///|
/// Check whether a sequence equals its reverse complement.
pub fn sequence_palindrome(sequence : String) -> Bool {
let normalized = normalize_sequence(sequence)
normalized == reverse_complement(normalized)
}
///|
/// Extract an inclusive one-based subsequence.
pub fn sequence_subsequence(
sequence : String,
start : Int,
end : Int,
) -> String {
if start < 1 || end < start || end > sequence.length() {
return ""
}
sequence.unsafe_substring(start=start - 1, end~)
}
///|
/// Return the length of the longest run of the same base.
pub fn longest_homopolymer(sequence : String) -> Int {
let mut best = 0
let mut current = 0
let mut previous : Char? = None
for ch in sequence.to_upper() {
match previous {
Some(old) if old == ch => current += 1
_ => current = 1
}
previous = Some(ch)
if current > best {
best = current
}
}
best
}
///|
/// Build a majority consensus; ties and absent positions become N.
pub fn consensus_sequence(sequences : Array[String]) -> String {
if sequences.length() == 0 {
return ""
}
let width = sequences
.map(fn(item) { item.length() })
.fold(init=0, fn(a, b) { Int::max(a, b) })
let out = StringBuilder()
for position in 0.. counts[0] += 1
'C' | 'c' => counts[1] += 1
'G' | 'g' => counts[2] += 1
'T' | 't' | 'U' | 'u' => counts[3] += 1
_ => ()
}
}
}
let mut best = 0
let mut winner = 'N'
let mut tied = false
let letters = ['A', 'C', 'G', 'T']
for i in 0..<4 {
if counts[i] > best {
best = counts[i]
winner = letters[i]
tied = false
} else if counts[i] > 0 && counts[i] == best {
tied = true
}
}
out.write_char(if tied { 'N' } else { winner })
}
out.to_string()
}
///|
pub fn is_valid_dna(sequence : String) -> Bool {
for ch in sequence.to_upper() {
if !is_iupac_base(ch) {
return false
}
}
true
}
///|
pub fn rna_sequence(sequence : String) -> String {
normalize_sequence(sequence).replace_all(old="T", new="U")
}
///|
pub fn dna_sequence(sequence : String) -> String {
normalize_sequence(sequence).replace_all(old="U", new="T")
}
///|
pub fn mask_ambiguous(sequence : String) -> String {
let out = StringBuilder()
for ch in sequence.to_upper() {
match ch {
'A' | 'C' | 'G' | 'T' => out.write_char(ch)
_ if is_iupac_base(ch) => out.write_char('N')
_ => ()
}
}
out.to_string()
}
///|
/// Return all positions where a sequence changes base.
pub fn change_points(sequence : String) -> Array[Int] {
let positions : Array[Int] = []
if sequence.length() < 2 {
return positions
}
for i in 1.. Int {
let wanted = base.to_string().to_upper()
sequence
.to_upper()
.fold(init=0, fn(total, ch) {
total + (if ch.to_string() == wanted { 1 } else { 0 })
})
}
///|
pub fn base_frequencies(sequence : String) -> Array[(Char, Int)] {
[
('A', count_base(sequence, 'A')),
('C', count_base(sequence, 'C')),
('G', count_base(sequence, 'G')),
('T', count_base(sequence, 'T')),
('N', count_base(sequence, 'N')),
]
}
///|
pub fn has_motif(sequence : String, motif : String) -> Bool {
find_motif_positions(sequence, motif).length() > 0
}
///|
pub fn motif_count(sequence : String, motif : String) -> Int {
find_motif_positions(sequence, motif).length()
}
///|
pub fn is_palindromic_motif(motif : String) -> Bool {
let normalized = normalize_sequence(motif)
normalized == reverse_complement(normalized)
}
///|
pub fn trim_sequence(sequence : String, left : Int, right : Int) -> String {
if left < 0 || right < 0 || left + right >= sequence.length() {
return ""
}
sequence.unsafe_substring(start=left, end=sequence.length() - right)
}
///|
pub fn pad_sequence(sequence : String, length : Int, base : Char) -> String {
let out = StringBuilder()
out.write_string(sequence)
let mut index = sequence.length()
while index < length {
out.write_char(base)
index += 1
}
out.to_string()
}
///|
pub fn sequence_chunks(sequence : String, size : Int) -> Array[String] {
let chunks : Array[String] = []
if size <= 0 {
return chunks
}
let mut start = 0
while start < sequence.length() {
let end = Int::min(start + size, sequence.length())
chunks.push(sequence.unsafe_substring(start~, end~))
start = end
}
chunks
}
///|
pub fn join_sequences(sequences : Array[String]) -> String {
sequences.join("")
}
///|
pub fn remove_gaps(sequence : String) -> String {
sequence.replace_all(old="-", new="")
}
///|
pub fn gap_count(sequence : String) -> Int {
count_base(sequence, '-')
}
///|
pub fn nucleotide_length(sequence : String) -> Int {
normalize_sequence(sequence).length()
}
///|
pub fn sequence_identity(left : String, right : String) -> Double {
let distance = hamming_distance(left, right)
if distance < 0 || left.length() == 0 {
0.0
} else {
(left.length() - distance).to_double() * 100.0 / left.length().to_double()
}
}