Skip to main content

bio_seq/seq/
iterators.rs

1// Copyright 2021, 2022 Jeff Knaggs
2// Licensed under the MIT license (http://opensource.org/licenses/MIT)
3// This file may not be copied, modified, or distributed
4// except according to those terms.
5
6use crate::codec::Codec;
7use crate::kmer::KmerIter;
8use crate::seq::{Seq, SeqSlice};
9use core::iter::Chain;
10use core::marker::PhantomData;
11
12/// An iterator over fixed-size non-overlapping chunks of a sequence
13#[must_use = "iterators are lazy and do nothing unless consumed"]
14pub struct SeqChunks<'a, A: Codec> {
15    slice: &'a SeqSlice<A>,
16    width: usize,
17    skip: usize,
18    index: usize,
19}
20
21/// An iterator over the elements of a sequence
22#[must_use = "iterators are lazy and do nothing unless consumed"]
23pub struct SeqIter<'a, A: Codec> {
24    slice: &'a SeqSlice<A>,
25    index: usize,
26}
27
28impl<'a, A: Codec> SeqSlice<A> {
29    pub fn chain(
30        self: &'a SeqSlice<A>,
31        second: &'a SeqSlice<A>,
32    ) -> Chain<SeqIter<'a, A>, SeqIter<'a, A>> {
33        self.into_iter().chain(second)
34    }
35
36    pub fn iter(&'a self) -> SeqIter<'a, A> {
37        <&Self as IntoIterator>::into_iter(self)
38    }
39
40    /// Iterate over sliding windows of length K
41    pub fn kmers<const K: usize>(&self) -> KmerIter<'_, A, K> {
42        KmerIter::<A, K> {
43            slice: self,
44            index: 0,
45            len: self.len(),
46            _p: PhantomData,
47        }
48    }
49
50    /// Iterate over the sequence in reverse order
51    pub fn rev_iter(&self) -> RevIter<'_, A> {
52        RevIter {
53            slice: self,
54            index: self.len(),
55        }
56    }
57
58    /// Iterate over the sequence in overlapping windows of a specified width
59    ///
60    /// ```
61    /// use bio_seq::prelude::*;
62    ///
63    /// let seq: Seq<Dna> = "ACTGATCG".try_into().unwrap();
64    /// let windows: Vec<String> = seq.windows(3).map(String::from).collect();
65    /// assert_eq!(windows, vec!["ACT", "CTG", "TGA", "GAT", "ATC", "TCG"]);
66    /// ```
67    ///
68    /// # Panics
69    ///
70    /// Panics if `width` is 0.
71    pub fn windows(&self, width: usize) -> SeqChunks<'_, A> {
72        assert!(width != 0, "window must be non-zero");
73        SeqChunks {
74            slice: self,
75            width,
76            skip: 1,
77            index: 0,
78        }
79    }
80
81    /// Iterate over the sequence in non-overlapping chunks of a specified width
82    ///
83    /// The last incomplete chunk will be excluded if the sequence length is not divisible by the specified
84    /// width.
85    ///
86    /// ```
87    /// use bio_seq::prelude::*;
88    ///
89    /// let seq: Seq<Dna> = "ACTGATCG".try_into().unwrap();
90    /// let chunks: Vec<Seq<Dna>> = seq.chunks(3).collect();
91    /// assert_eq!(chunks, vec![dna!("ACT"), dna!("GAT")]);
92    /// ```
93    ///
94    /// # Panics
95    ///
96    /// Panics if `width` is 0.
97    pub fn chunks(&self, width: usize) -> SeqChunks<'_, A> {
98        assert!(width != 0, "chunk must be non-zero");
99        SeqChunks {
100            slice: self,
101            width,
102            skip: width,
103            index: 0,
104        }
105    }
106}
107
108/// An iterator over the elements of a sequence in reverse order
109#[must_use = "iterators are lazy and do nothing unless consumed"]
110pub struct RevIter<'a, A: Codec> {
111    pub(crate) slice: &'a SeqSlice<A>,
112    pub(crate) index: usize,
113}
114
115impl<A: Codec> Iterator for RevIter<'_, A> {
116    type Item = A;
117    fn next(&mut self) -> Option<A> {
118        let i = self.index;
119
120        if self.index == 0 {
121            return None;
122        }
123        self.index -= 1;
124        Some(A::unsafe_from_bits(self.slice[i - 1].into()))
125    }
126
127    fn size_hint(&self) -> (usize, Option<usize>) {
128        (self.index, Some(self.index))
129    }
130}
131
132impl<A: Codec> ExactSizeIterator for RevIter<'_, A> {}
133
134impl<'a, A: Codec> Iterator for SeqChunks<'a, A> {
135    type Item = &'a SeqSlice<A>;
136
137    fn next(&mut self) -> Option<Self::Item> {
138        let i = self.index;
139        if i + self.width > self.slice.len() {
140            return None;
141        }
142        self.index += self.skip;
143        Some(&self.slice[i..i + self.width])
144    }
145
146    fn size_hint(&self) -> (usize, Option<usize>) {
147        let n = self
148            .slice
149            .len()
150            .saturating_sub(self.index)
151            .saturating_sub(self.width - 1)
152            .div_ceil(self.skip);
153        (n, Some(n))
154    }
155}
156
157impl<A: Codec> ExactSizeIterator for SeqChunks<'_, A> {}
158
159impl<'a, A: Codec> IntoIterator for &'a Seq<A> {
160    type Item = A;
161    type IntoIter = SeqIter<'a, A>;
162
163    fn into_iter(self) -> Self::IntoIter {
164        self.as_ref().into_iter()
165    }
166}
167
168impl<'a, A: Codec> IntoIterator for &'a SeqSlice<A> {
169    type Item = A;
170    type IntoIter = SeqIter<'a, A>;
171
172    fn into_iter(self) -> Self::IntoIter {
173        SeqIter {
174            slice: self,
175            index: 0,
176        }
177    }
178}
179
180impl<'a, A: Codec> FromIterator<&'a SeqSlice<A>> for Vec<Seq<A>> {
181    fn from_iter<T: IntoIterator<Item = &'a SeqSlice<A>>>(iter: T) -> Self {
182        iter.into_iter().map(ToOwned::to_owned).collect()
183    }
184}
185
186impl<A: Codec> Iterator for SeqIter<'_, A> {
187    type Item = A;
188    fn next(&mut self) -> Option<A> {
189        let i = self.index;
190        if self.index >= self.slice.len() {
191            return None;
192        }
193        self.index += 1;
194        Some(A::unsafe_from_bits(self.slice[i].into()))
195    }
196
197    fn size_hint(&self) -> (usize, Option<usize>) {
198        let n = self.slice.len() - self.index;
199        (n, Some(n))
200    }
201}
202
203impl<A: Codec> ExactSizeIterator for SeqIter<'_, A> {}
204
205#[cfg(test)]
206mod tests {
207    use crate::codec::dna::Dna::*;
208    use crate::prelude::*;
209
210    #[test]
211    fn seq_iter() {
212        let seq: Seq<Dna> = dna!("ACTGATCGATAC").into();
213        let elements: Vec<Dna> = seq.into_iter().collect();
214        assert_eq!(elements, vec![A, C, T, G, A, T, C, G, A, T, A, C]);
215        assert_ne!(elements, vec![A, C, T, G, A, T, C, G, A, T, A, C, A]);
216        assert_ne!(elements, vec![C, A, T, A, G, C, T, A, G, T, C, A]);
217    }
218
219    #[test]
220    fn rev_iter() {
221        let seq: Seq<Dna> = dna!("ACTGATCGATAC").into();
222        let rev_elements: Vec<Dna> = seq.rev_iter().collect();
223        assert_ne!(rev_elements, vec![A, C, T, G, A, T, C, G, A, T, A, C]);
224        assert_eq!(rev_elements, vec![C, A, T, A, G, C, T, A, G, T, C, A]);
225    }
226
227    #[test]
228    fn iterators() {
229        let seq: Seq<Dna> = dna!("ACTGATCGATAC").into();
230        let slice = &seq[2..9];
231        let elements: Vec<Dna> = slice.into_iter().collect();
232        assert_eq!(elements, vec![T, G, A, T, C, G, A]);
233    }
234
235    #[test]
236    fn chunks() {
237        let seq: Seq<Dna> = dna!("ACTGATCGATAC").into();
238        let cs: Vec<Seq<Dna>> = seq.chunks(5).collect();
239        assert_eq!(cs[0], dna!("ACTGA"));
240        assert_eq!(cs[1], dna!("TCGAT"));
241        assert_eq!(cs.len(), 2);
242    }
243
244    #[test]
245    fn test_chain() {
246        let seq1 = Seq::<Dna>::try_from("ATG").unwrap();
247        let seq2 = Seq::<Dna>::try_from("TAC").unwrap();
248
249        let chained = seq1.chain(&seq2);
250
251        let expected_seq = Seq::<Dna>::try_from("ATGTAC").unwrap();
252        for (a, b) in chained.zip(&expected_seq) {
253            assert_eq!(a, b);
254        }
255
256        let chained = seq1.chain(&seq2);
257        for (a, b) in chained.map(|b| b.to_comp()).zip(&expected_seq) {
258            assert_ne!(a, b);
259        }
260    }
261
262    #[test]
263    fn windows() {
264        let seq: Seq<Dna> = dna!("ACTGATACG").into();
265        let windows: Vec<String> = seq.windows(5).map(String::from).collect();
266        assert_eq!(windows, vec!["ACTGA", "CTGAT", "TGATA", "GATAC", "ATACG"]);
267    }
268
269    #[test]
270    #[should_panic(expected = "chunk must be non-zero")]
271    fn zero_width_chunk_panics() {
272        let _ = dna!("ACGTACGTGA").chunks(0);
273    }
274
275    #[test]
276    #[should_panic(expected = "window must be non-zero")]
277    fn zero_width_window_panics() {
278        let _ = dna!("ACGTACGTGA").windows(0);
279    }
280}