1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! Motif alphabets.
use crate::error::{Result, arg_err};
/// The four DNA letters, in row order.
pub const DNA: [char; 4] = ['A', 'C', 'G', 'T'];
/// The four RNA letters, in row order.
pub const RNA: [char; 4] = ['A', 'C', 'G', 'U'];
/// The twenty standard amino acids, in row order.
pub const AMINO_ACIDS: [char; 20] = [
'A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W',
'Y',
];
/// DNA IUPAC letters accepted in consensus strings.
pub const DNA_IUPAC: &str = "ACGTMRWSYKVHDBN";
/// Amino acid letters accepted in consensus strings, including ambiguity codes.
pub const AA_IUPAC: &str = "ACDEFGHIKLMNPQRSTVWYUOBJZX";
/// The alphabet of a motif. The rows of a motif matrix always follow the
/// order of [`Alphabet::letters`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Alphabet {
/// DNA: A, C, G and T.
Dna,
/// RNA: A, C, G and U.
Rna,
/// The 20 standard amino acids.
Aa,
/// Any other set of single-character letters, kept sorted and unique.
Custom(Vec<char>),
}
impl Alphabet {
/// Build a custom alphabet from arbitrary letters. The letters are sorted
/// and deduplicated, and at least two distinct letters are required.
pub fn custom(letters: impl IntoIterator<Item = char>) -> Result<Self> {
let mut letters: Vec<char> = letters.into_iter().collect();
letters.sort_unstable();
letters.dedup();
if letters.len() < 2 {
return arg_err("a custom alphabet needs at least two distinct letters");
}
Ok(Alphabet::Custom(letters))
}
/// The letters of the alphabet, in row order.
pub fn letters(&self) -> &[char] {
match self {
Alphabet::Dna => &DNA,
Alphabet::Rna => &RNA,
Alphabet::Aa => &AMINO_ACIDS,
Alphabet::Custom(l) => l,
}
}
/// The number of letters.
pub fn len(&self) -> usize {
self.letters().len()
}
/// Whether the alphabet has no letters, which no valid alphabet does.
pub fn is_empty(&self) -> bool {
self.letters().is_empty()
}
/// Whether this is DNA or RNA.
pub fn is_nucleotide(&self) -> bool {
matches!(self, Alphabet::Dna | Alphabet::Rna)
}
/// The alphabet's name: `"DNA"`, `"RNA"`,
/// `"AA"`, or the concatenated letters of a custom alphabet.
pub fn name(&self) -> String {
match self {
Alphabet::Dna => "DNA".into(),
Alphabet::Rna => "RNA".into(),
Alphabet::Aa => "AA".into(),
Alphabet::Custom(l) => l.iter().collect(),
}
}
/// Row index of a letter, if it belongs to the alphabet.
pub fn index_of(&self, letter: char) -> Option<usize> {
self.letters().iter().position(|&l| l == letter)
}
}
impl std::str::FromStr for Alphabet {
type Err = crate::error::Error;
/// Parse an alphabet name: `"DNA"`, `"RNA"`, `"AA"`, or a string of
/// letters making up a custom alphabet.
fn from_str(name: &str) -> Result<Alphabet> {
match name {
"DNA" => Ok(Alphabet::Dna),
"RNA" => Ok(Alphabet::Rna),
"AA" => Ok(Alphabet::Aa),
"custom" => {
arg_err("alphabet 'custom' is not accepted; please provide the actual letters")
}
_ => Alphabet::custom(name.chars()),
}
}
}
impl std::fmt::Display for Alphabet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.name())
}
}