weave_draft/
data.rs

1//! data module provides data structures used in the [`crate::Draft`]
2
3use std::cmp::{Ordering, max};
4use std::collections::{HashMap, HashSet};
5use std::iter::Extend;
6use std::ops::{Add, Index, RangeBounds};
7
8/// Wrapper for shaft
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct Shaft(pub u32);
11
12/// Wrapper for Treadle
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct Treadle(pub u32);
15
16/// Threading in a weaving draft. 1 thread can only be on one shaft
17#[derive(Debug, PartialEq, Clone)]
18pub struct Threading {
19    shaft_count: u32,
20    threading: Vec<u32>,
21}
22
23impl Index<usize> for Threading {
24    type Output = u32;
25
26    fn index(&self, index: usize) -> &Self::Output {
27        &self.threading[index]
28    }
29}
30
31impl Threading {
32    /// Constructs a new threading, verifying that the `shaft_count` is respected.
33    ///
34    /// # Panics
35    /// If there are threads outside the shaft count
36    #[must_use]
37    pub fn new(shaft_count: u32, threading: Vec<u32>) -> Self {
38        for shaft in &threading {
39            assert!(
40                shaft <= &shaft_count,
41                "shaft count is {shaft_count} but found shaft {shaft}"
42            );
43        }
44
45        Threading {
46            shaft_count,
47            threading,
48        }
49    }
50
51    fn validate(&self, other: &[u32]) -> Result<(), usize> {
52        let index = other.iter().position(|s| s > &self.shaft_count);
53        match index {
54            None => Ok(()),
55            Some(i) => Err(i),
56        }
57    }
58
59    /// Based on [`Vec::splice`], it splices the given sequence into the given range. It validates that
60    /// the elements in `replace_with` are inside the shaft bounds, and it returns the replaced elements.
61    ///
62    /// # Examples
63    /// ```
64    /// # use weave_draft::Threading;
65    /// let mut threading = Threading::new(4, vec![1,2,3,4]);
66    /// let removed = threading.splice(1..3, &[4,3,4,1]).unwrap();
67    /// assert_eq!(threading, Threading::new(4, vec![1,4,3,4,1,4]));
68    /// assert_eq!(removed, vec![2,3]);
69    ///
70    /// let error = threading.splice(1..3, &[5]).unwrap_err();
71    /// assert_eq!(error, 0);
72    /// assert_eq!(threading.len(), 6); // no removal on failure
73    /// ```
74    ///
75    /// # Errors
76    /// If an element in `replace_with` is larger than the shaft count, returns index of first
77    /// out-of-bounds element
78    pub fn splice<R>(&mut self, range: R, replace_with: &[u32]) -> Result<Vec<u32>, usize>
79    where
80        R: RangeBounds<usize>,
81    {
82        self.validate(replace_with)?;
83        let replaced: Vec<u32> = self
84            .threading
85            .splice(range, replace_with.to_owned())
86            .collect();
87
88        Ok(replaced)
89    }
90
91    /// Number of threads in the threading
92    #[must_use]
93    pub fn len(&self) -> usize {
94        self.threading.len()
95    }
96
97    /// Is the threading empty
98    #[must_use]
99    pub fn is_empty(&self) -> bool {
100        self.threading.is_empty()
101    }
102
103    /// Get the raw threading
104    #[must_use]
105    pub fn threading(&self) -> &Vec<u32> {
106        &self.threading
107    }
108
109    /// Add a new thread at the end of the threading
110    /// # Errors
111    /// Returns the shaft if greater than shaft count
112    pub fn push(&mut self, shaft: u32) -> Result<(), u32> {
113        if shaft > self.shaft_count {
114            return Err(shaft);
115        }
116        self.threading.push(shaft);
117        Ok(())
118    }
119
120    /// Insert a thread at the given index, shifting later threads
121    ///
122    /// # Panics
123    /// If index is greater than the length
124    ///
125    /// # Errors
126    /// If `shaft` is greater than `shaft_count`
127    pub fn insert(&mut self, shaft: Shaft, index: usize) -> Result<(), Shaft> {
128        if shaft.0 > self.shaft_count {
129            return Err(shaft);
130        }
131        self.threading.insert(index, shaft.0);
132        Ok(())
133    }
134
135    /// Insert a thread at the given index, shifting later threads
136    ///
137    /// # Errors
138    /// Returns the current length if index is greater than length
139    pub fn try_insert(&mut self, shaft: Shaft, index: usize) -> Result<Result<(), Shaft>, usize> {
140        let len = self.threading.len();
141        if index > len {
142            Err(len)
143        } else {
144            Ok(self.insert(shaft, index))
145        }
146    }
147
148    /// Remove the thread at the given index, returning it as a [Shaft]
149    ///
150    /// # Panics
151    /// If index is out of bounds
152    pub fn remove(&mut self, index: usize) -> Shaft {
153        Shaft(self.threading.remove(index))
154    }
155
156    /// Get shaft at index
157    #[must_use]
158    pub fn get(&self, index: usize) -> Option<&u32> {
159        self.threading.get(index)
160    }
161
162    /// Overwrite thread at given index, returns old thread value
163    ///
164    /// # Panics
165    /// If index is out of bounds
166    pub fn put(&mut self, index: usize, shaft: Shaft) -> Shaft {
167        let old = self.threading[index];
168        self.threading[index] = shaft.0;
169        Shaft(old)
170    }
171
172    /// Overwrite thread at given index. Returns replaced shaft, or none if inserting at the end
173    ///
174    /// # Errors
175    ///
176    /// Returns current length if index out of bounds
177    pub fn try_put(&mut self, index: usize, shaft: Shaft) -> Result<Option<Shaft>, usize> {
178        let len = self.threading.len();
179        match index.cmp(&len) {
180            Ordering::Less => Ok(Some(self.put(index, shaft))),
181            Ordering::Equal => {
182                self.threading.push(shaft.0);
183                Ok(None)
184            }
185            Ordering::Greater => Err(len),
186        }
187    }
188
189    /// Highest used shaft in threading
190    #[must_use]
191    pub fn max_shaft(&self) -> u32 {
192        let max = self.threading.iter().max();
193        *max.unwrap_or(&0)
194    }
195
196    /// Non-destructively set shaft count
197    ///
198    /// # Panics
199    /// If `shaft_count` is 0
200    ///
201    /// # Errors
202    /// If `shaft_count` is less than max shaft used
203    pub fn set_shaft_count(&mut self, shaft_count: u32) -> Result<(), usize> {
204        if shaft_count == 0 {
205            panic!("shaft count is 0");
206        } else if shaft_count >= self.max_shaft() {
207            self.shaft_count = shaft_count;
208            Ok(())
209        } else {
210            // guaranteed to exist because of max shaft check
211            let pos = self
212                .threading
213                .iter()
214                .position(|s| s > &shaft_count)
215                .unwrap();
216            Err(pos)
217        }
218    }
219
220    /// Shrinks the shaft count to the maximum shaft present in the threading. Returns the old
221    /// shaft count if the count changed
222    pub fn trim_shafts(&mut self) -> &Self {
223        if let Some(&max) = self.threading.iter().max() {
224            if max < self.shaft_count {
225                self.shaft_count = max;
226            }
227        }
228        self
229    }
230
231    /// Returns set of shafts used in the threading
232    ///
233    /// # Examples
234    ///
235    /// ```
236    /// # use std::collections::HashSet;
237    /// # use weave_draft::Threading;
238    /// assert_eq!(HashSet::from([1,2,4]), Threading::new(4, vec![4, 1, 2, 1]).used_shafts());
239    /// ```
240    #[must_use]
241    pub fn used_shafts(&self) -> HashSet<u32> {
242        let mut set = HashSet::new();
243        for shaft in &self.threading {
244            set.insert(*shaft);
245        }
246        set
247    }
248
249    /// Removes any empty shafts, shifting threads down
250    #[allow(clippy::cast_possible_truncation)]
251    #[allow(clippy::missing_panics_doc)]
252    pub fn trim_and_squish_shafts(&mut self) -> &Self {
253        let mut used_shafts: Vec<u32> = self.used_shafts().into_iter().collect();
254        used_shafts.sort_unstable();
255        self.shaft_count = used_shafts.len() as u32;
256        let mapping: HashMap<u32, u32> = used_shafts
257            .into_iter()
258            .enumerate()
259            .map(|(i, s)| (s, i as u32 + 1))
260            .collect();
261
262        self.threading
263            .iter_mut()
264            // construction of map guarantees entry exists
265            .for_each(|s| *s = *mapping.get(s).unwrap());
266        self
267    }
268
269    /// Flips the threading vertically. On an 8 shaft threading, this means that shaft 1 becomes shaft 8
270    /// shaft 2 becomes shaft 7, and so on.
271    pub fn flip_vertical(&mut self) -> &Self {
272        for thread in &mut self.threading {
273            *thread = self.shaft_count - *thread;
274        }
275        self
276    }
277
278    /// Repeats the threading in reverse, does not repeat the final/center thread
279    ///
280    /// # Examples
281    /// ```
282    /// # use weave_draft::Threading;
283    /// let mut threading = Threading::new(4, vec![1, 2, 3, 4]);
284    /// assert_eq!(threading.mirror().threading(), &vec![1, 2, 3, 4, 3, 2, 1]);
285    /// ```
286    pub fn mirror(&mut self) -> &Self {
287        if self.threading.len() <= 1 {
288            return self;
289        }
290        let mirror_section = self.threading[..(self.threading.len() - 1)].to_vec();
291        self.threading.extend(mirror_section.iter().rev());
292
293        self
294    }
295
296    /// Reverse threading horizontally
297    pub fn reverse(&mut self) -> &Self {
298        self.threading.reverse();
299        self
300    }
301}
302
303impl IntoIterator for Threading {
304    type Item = u32;
305    type IntoIter = std::vec::IntoIter<Self::Item>;
306
307    fn into_iter(self) -> Self::IntoIter {
308        self.threading.into_iter()
309    }
310}
311
312impl Add for &Threading {
313    type Output = Threading;
314
315    fn add(self, rhs: Self) -> Self::Output {
316        let mut threading = self.threading.clone();
317        threading.extend(&rhs.threading);
318        Threading {
319            shaft_count: max(self.shaft_count, rhs.shaft_count),
320            threading,
321        }
322    }
323}
324
325impl Add<Threading> for &Threading {
326    type Output = Threading;
327
328    fn add(self, rhs: Threading) -> Self::Output {
329        let mut threading = self.threading.clone();
330        threading.extend(rhs.threading);
331        Threading {
332            shaft_count: max(self.shaft_count, rhs.shaft_count),
333            threading,
334        }
335    }
336}
337
338impl Add for Threading {
339    type Output = Self;
340
341    fn add(self, rhs: Self) -> Self::Output {
342        let mut threading = self.threading;
343        threading.extend(rhs.threading);
344
345        Threading {
346            shaft_count: max(self.shaft_count, rhs.shaft_count),
347            threading,
348        }
349    }
350}
351
352impl Add<&Threading> for Threading {
353    type Output = Threading;
354
355    fn add(self, rhs: &Threading) -> Self::Output {
356        let mut threading = self.threading;
357        threading.extend(&rhs.threading);
358
359        Threading {
360            shaft_count: max(self.shaft_count, rhs.shaft_count),
361            threading,
362        }
363    }
364}
365
366/// The treadling or lift plan for a draft, also includes the tie-up and whether the loom is rising
367/// or sinking shed
368#[derive(Debug, PartialEq, Clone)]
369pub struct TreadlingInfo {
370    shaft_count: u32,
371    rise_sink: RiseSink,
372    tie_up: TieUpKind,
373    treadling: Treadling,
374}
375
376/// Options when creating a new [`TieUpKind`]
377#[derive(Debug, Clone, Copy, PartialEq)]
378pub enum TieUpCreate {
379    /// Create a direct tie-up
380    Direct,
381    /// Create an indirect tie-up with given number of treadles
382    Indirect(u32),
383}
384
385impl TreadlingInfo {
386    /// Construct new treadling
387    ///
388    /// # Panics
389    /// If shaft or treadle count is 0
390    #[must_use]
391    pub fn new(shaft_count: u32, tie_up: TieUpCreate, rise_sink: RiseSink) -> Self {
392        assert_ne!(shaft_count, 0, "shaft count is 0");
393        let tie_up = match tie_up {
394            TieUpCreate::Direct => TieUpKind::Direct,
395            TieUpCreate::Indirect(treadles) => {
396                assert_ne!(treadles, 0, "treadle count is 0");
397
398                TieUpKind::Indirect(TieUp {
399                    treadle_count: treadles,
400                    tie_up: vec![HashSet::new(); treadles as usize],
401                })
402            }
403        };
404
405        TreadlingInfo {
406            shaft_count,
407            tie_up,
408            treadling: Treadling::new(),
409            rise_sink,
410        }
411    }
412
413    /// Get the shaft count
414    #[must_use]
415    pub fn shaft_count(&self) -> u32 {
416        self.shaft_count
417    }
418
419    /// Returns max shaft used. If this is a direct tie-up, it's the max shaft in the lift plan. If
420    /// it's an indirect tie-up, it's the max shaft used in the tie-up, even if no picks use a treadle
421    /// tied to that shaft
422    #[must_use]
423    pub fn max_shaft_used(&self) -> u32 {
424        match &self.tie_up {
425            TieUpKind::Direct => self.treadling.max_shaft(),
426            TieUpKind::Indirect(tie_up) => tie_up.max_shaft(),
427        }
428    }
429
430    /// Non-destructively sets shaft count
431    ///
432    /// # Errors
433    /// If `shaft_count` is less than the max shaft used, returns max shaft
434    pub fn set_shaft_count(&mut self, shaft_count: u32) -> Result<(), u32> {
435        let max = self.max_shaft_used();
436        if shaft_count >= max {
437            self.shaft_count = shaft_count;
438            Ok(())
439        } else {
440            Err(max)
441        }
442    }
443
444    /// Get the tie-up info
445    #[must_use]
446    pub fn tie_up(&self) -> &TieUpKind {
447        &self.tie_up
448    }
449
450    /// Get the treadle count. Returns the shaft count if directly tied up
451    #[must_use]
452    pub fn treadle_count(&self) -> u32 {
453        match self.tie_up {
454            TieUpKind::Direct => self.shaft_count,
455            TieUpKind::Indirect(TieUp { treadle_count, .. }) => treadle_count,
456        }
457    }
458
459    /// Whether the treadling is for a rising shaft or sinking shaft loom
460    #[must_use]
461    pub fn rise_sink(&self) -> RiseSink {
462        self.rise_sink
463    }
464
465    /// Number of picks in the treadling
466    #[must_use]
467    pub fn len(&self) -> usize {
468        self.treadling.0.len()
469    }
470
471    /// Is the treadling empty
472    #[must_use]
473    pub fn is_empty(&self) -> bool {
474        self.treadling.0.len() == 0
475    }
476
477    /// Add a new pick at the end using just the given treadle
478    ///
479    /// # Errors
480    /// If treadle is higher than number of shafts, returns treadle
481    pub fn push_single(&mut self, treadle: u32) -> Result<(), u32> {
482        if treadle > self.treadle_count() {
483            return Err(treadle);
484        } else if treadle == 0 {
485            // tread 0 as no treadle
486            self.treadling.0.push(HashSet::new());
487        } else {
488            self.treadling.0.push(HashSet::from([treadle]));
489        }
490        Ok(())
491    }
492
493    fn validate_treadle(&self, treadle: u32) -> Result<(), u32> {
494        if treadle == 0 || treadle > self.treadle_count() {
495            Err(treadle)
496        } else {
497            Ok(())
498        }
499    }
500
501    fn validate(&self, treadles: &HashSet<u32>) -> Result<(), u32> {
502        let t_count = self.treadle_count();
503        match treadles.iter().find(|&&t| t == 0 || t > t_count) {
504            None => Ok(()),
505            Some(&t) => Err(t),
506        }
507    }
508
509    /// Add a new pick at the end using all given treadles/shafts
510    ///
511    /// # Errors
512    /// If any treadle is over the number of treadles/shafts, returns that value
513    pub fn push(&mut self, treadles: HashSet<u32>) -> Result<(), u32> {
514        self.validate(&treadles)?;
515        self.treadling.0.push(treadles);
516
517        Ok(())
518    }
519
520    /// Toggle treadle at given index. Return `true` if treadle has been toggled on, `false` if toggled off
521    ///
522    /// # Errors
523    /// If treadle is invalid
524    /// # Panics
525    /// If index is out of bounds
526    pub fn toggle_treadle(&mut self, index: usize, treadle: Treadle) -> Result<bool, u32> {
527        self.validate_treadle(treadle.0)?;
528        let pick = &mut self.treadling.0[index];
529        if pick.contains(&treadle.0) {
530            pick.remove(&treadle.0);
531            Ok(false)
532        } else {
533            pick.insert(treadle.0);
534            Ok(true)
535        }
536    }
537
538    /// Inserts treadling at given index
539    ///
540    /// # Errors
541    /// If any treadles are invalid
542    /// # Panics
543    /// If index is out of bounds
544    pub fn insert(&mut self, index: usize, treadles: HashSet<u32>) -> Result<(), u32> {
545        self.validate(&treadles)?;
546        self.treadling.0.insert(index, treadles);
547
548        Ok(())
549    }
550
551    /// Based on [`Vec::splice`], it splices the given sequence into the given range. It validates that
552    /// the elements in `replace_with` are inside the shaft bounds, and it returns the replaced elements.
553    ///
554    /// # Errors
555    /// If an element in `replace_with` is larger than the shaft count, returns index of first
556    /// out-of-bounds element
557    pub fn splice<R>(
558        &mut self,
559        range: R,
560        replace_with: Vec<HashSet<u32>>,
561    ) -> Result<Vec<HashSet<u32>>, u32>
562    where
563        R: RangeBounds<usize>,
564    {
565        for pick in &replace_with {
566            self.validate(pick)?;
567        }
568
569        let replaced: Vec<HashSet<u32>> = self.treadling.0.splice(range, replace_with).collect();
570
571        Ok(replaced)
572    }
573
574    /// Overwrites the treadling at the given index with the new treadles
575    ///
576    /// # Errors
577    /// If treadling is invalid
578    ///
579    /// # Panics
580    /// If index is greater than the length of the treadling
581    pub fn put(
582        &mut self,
583        index: usize,
584        treadles: HashSet<u32>,
585    ) -> Result<Option<HashSet<u32>>, u32> {
586        self.validate(&treadles)?;
587
588        match index.cmp(&self.len()) {
589            Ordering::Less => {
590                let old = self.treadling.0[index].clone();
591                self.treadling.0[index] = treadles;
592                Ok(Some(old))
593            }
594            Ordering::Equal => {
595                self.treadling.0.push(treadles);
596                Ok(None)
597            }
598            Ordering::Greater => {
599                panic!("Index {index} out of bounds")
600            }
601        }
602    }
603
604    /// Convert in place to a rising shaft treadling
605    pub fn make_rising(&mut self) {
606        match self.rise_sink {
607            RiseSink::Rising => (),
608            RiseSink::Sinking => self.invert(),
609        }
610    }
611
612    /// Convert in place to a sinking shaft treadling
613    pub fn make_sinking(&mut self) {
614        match self.rise_sink {
615            RiseSink::Rising => self.invert(),
616            RiseSink::Sinking => (),
617        }
618    }
619
620    /// Goes from a treadling to a lift plan. Returns false if already a lift plan,
621    /// true if conversion happened
622    pub fn make_lift_plan(&mut self) -> bool {
623        match &mut self.tie_up {
624            TieUpKind::Direct => false,
625            TieUpKind::Indirect(tie_up) => {
626                for entry in &mut self.treadling.0 {
627                    *entry = tie_up.compute_shafts(entry);
628                }
629                self.tie_up = TieUpKind::Direct;
630                true
631            }
632        }
633    }
634
635    /// Switch from rising to sinking or vice versa
636    pub fn invert(&mut self) {
637        match &mut self.tie_up {
638            TieUpKind::Direct => self.treadling.invert(self.shaft_count),
639            TieUpKind::Indirect(tie_up) => tie_up.invert(self.shaft_count),
640        }
641
642        self.rise_sink = self.rise_sink.invert();
643    }
644}
645
646impl Index<usize> for TreadlingInfo {
647    type Output = HashSet<u32>;
648    fn index(&self, index: usize) -> &Self::Output {
649        &self.treadling[index]
650    }
651}
652
653fn invert(set: &HashSet<u32>, max: u32) -> HashSet<u32> {
654    assert_ne!(max, 0, "cannot invert when max is 0");
655    let inversion = (1..=max).collect::<HashSet<u32>>();
656
657    &inversion - set
658}
659
660/// Whether the loom is a direct tie-up or whether treadles can be tied to multiple shafts
661#[derive(Debug, Clone, PartialEq)]
662pub enum TieUpKind {
663    /// Direct tie up (table loom, some 4 shaft looms, dobby looms)
664    Direct,
665    /// Indirect tie up (most 4+ shaft manual floor looms)
666    Indirect(TieUp),
667}
668
669impl TieUpKind {
670    /// Get [`TieUp`] if indirect
671    #[must_use]
672    pub fn tie_up(&self) -> Option<&TieUp> {
673        match self {
674            TieUpKind::Direct => None,
675            TieUpKind::Indirect(tie_up) => Some(tie_up),
676        }
677    }
678
679    /// Get underlying tie up data if indirect
680    #[must_use]
681    pub fn raw_tie_up(&self) -> Option<&Vec<HashSet<u32>>> {
682        match self {
683            TieUpKind::Direct => None,
684            TieUpKind::Indirect(tie_up) => Some(&tie_up.tie_up),
685        }
686    }
687}
688
689/// A tie-up of a loom
690#[derive(Debug, Clone, PartialEq)]
691pub struct TieUp {
692    treadle_count: u32,
693    tie_up: Vec<HashSet<u32>>,
694}
695
696impl TieUp {
697    fn invert(&mut self, shaft_count: u32) {
698        self.tie_up
699            .iter_mut()
700            .for_each(|t| *t = invert(t, shaft_count));
701    }
702
703    fn max_shaft(&self) -> u32 {
704        max_vec_hash(&self.tie_up)
705    }
706
707    /// Returns the shafts tied up to the given treadle. Returns an empty set if treadle is out of bounds
708    ///
709    /// Note: treadles (and shafts) are 1-indexed
710    #[must_use]
711    pub fn get_shafts(&self, treadle: &u32) -> Option<&HashSet<u32>> {
712        self.tie_up.get((treadle - 1) as usize)
713    }
714
715    /// Computes which shafts are raised when given set of treadles are pressed
716    #[must_use]
717    pub fn compute_shafts(&self, treadles: &HashSet<u32>) -> HashSet<u32> {
718        let mut shafts = HashSet::new();
719        for treadle in treadles {
720            if let Some(tied_shafts) = self.get_shafts(treadle) {
721                for shaft in tied_shafts {
722                    shafts.insert(*shaft);
723                }
724            }
725        }
726        shafts
727    }
728}
729
730/// Whether this draft is written for a rising shaft or sinking shaft loom
731#[derive(Debug, Clone, PartialEq, Copy)]
732pub enum RiseSink {
733    /// Rising shaft loom (most US jack looms)
734    Rising,
735    /// Sinking shaft loom (counterbalance, direct tie up, etc. looms)
736    Sinking,
737}
738
739impl RiseSink {
740    /// Swap to other kind
741    #[must_use]
742    pub fn invert(self) -> Self {
743        match self {
744            RiseSink::Rising => Self::Sinking,
745            RiseSink::Sinking => Self::Rising,
746        }
747    }
748}
749
750impl Default for RiseSink {
751    /// Modern US looms tend to be rising. I am in the US so that's what I'm making the default
752    fn default() -> Self {
753        Self::Rising
754    }
755}
756
757#[derive(Debug, Clone, PartialEq)]
758pub struct Treadling(Vec<HashSet<u32>>);
759
760fn max_vec_hash(vec: &[HashSet<u32>]) -> u32 {
761    *vec.iter()
762        .map(|s| s.iter().max().unwrap_or(&0))
763        .max()
764        .unwrap_or(&0)
765}
766
767impl Treadling {
768    fn new() -> Treadling {
769        Treadling(Vec::new())
770    }
771
772    fn invert(&mut self, shaft_count: u32) {
773        self.0.iter_mut().for_each(|t| *t = invert(t, shaft_count));
774    }
775
776    fn max_shaft(&self) -> u32 {
777        max_vec_hash(&self.0)
778    }
779}
780
781impl Add for Treadling {
782    type Output = Treadling;
783
784    fn add(self, rhs: Self) -> Self::Output {
785        let mut new = self.0;
786        new.extend(rhs.0);
787        Treadling(new)
788    }
789}
790
791impl Add<&Treadling> for Treadling {
792    type Output = Treadling;
793    fn add(self, rhs: &Treadling) -> Self::Output {
794        let mut new = self.0;
795        new.extend_from_slice(&rhs.0);
796
797        Treadling(new)
798    }
799}
800
801impl Add for &Treadling {
802    type Output = Treadling;
803
804    fn add(self, rhs: Self) -> Self::Output {
805        let mut new = self.0.clone();
806        new.extend_from_slice(&rhs.0);
807        Treadling(new)
808    }
809}
810
811impl Add<Treadling> for &Treadling {
812    type Output = Treadling;
813    fn add(self, rhs: Treadling) -> Self::Output {
814        let mut new = self.0.clone();
815        new.extend(rhs.0);
816        Treadling(new)
817    }
818}
819
820impl Index<usize> for Treadling {
821    type Output = HashSet<u32>;
822    fn index(&self, index: usize) -> &Self::Output {
823        &self.0[index]
824    }
825}
826
827#[cfg(test)]
828mod tests {
829    use super::*;
830
831    #[test]
832    #[should_panic(expected = "shaft count is 2 but found shaft 3")]
833    fn test_validate_threading() {
834        let _ = Threading::new(2, vec![1, 2, 3, 4]);
835    }
836
837    #[test]
838    fn test_add_threading() {
839        let threading_1 = Threading::new(4, vec![1, 2, 3, 4]);
840        let threading_2 = Threading::new(6, vec![3, 4, 5, 6, 1]);
841        assert_eq!(
842            threading_1 + threading_2,
843            Threading::new(6, vec![1, 2, 3, 4, 3, 4, 5, 6, 1])
844        );
845    }
846
847    #[test]
848    fn test_squish_threading() {
849        let mut threading = Threading::new(8, vec![1, 3, 4, 6, 3]);
850        assert_eq!(
851            threading.trim_and_squish_shafts(),
852            &Threading::new(4, vec![1, 2, 3, 4, 2])
853        );
854    }
855
856    #[test]
857    fn test_invert_set() {
858        let set = HashSet::from([1, 3, 5, 7]);
859        assert_eq!(invert(&set, 8), HashSet::from([2, 4, 6, 8]));
860    }
861
862    #[test]
863    #[should_panic(expected = "cannot invert when max is 0")]
864    fn test_invert_panic() {
865        let set = HashSet::from([1, 3, 5, 7]);
866        let _ = invert(&set, 0);
867    }
868
869    #[test]
870    fn test_invert_tie_up() {
871        let mut tie_up = TieUp {
872            treadle_count: 4,
873            tie_up: vec![
874                HashSet::from([1, 2]),
875                HashSet::from([2, 3]),
876                HashSet::from([3, 4]),
877                HashSet::from([4, 1]),
878            ],
879        };
880        tie_up.invert(4);
881        assert_eq!(
882            tie_up,
883            TieUp {
884                treadle_count: 4,
885                tie_up: vec![
886                    HashSet::from([3, 4]),
887                    HashSet::from([1, 4]),
888                    HashSet::from([1, 2]),
889                    HashSet::from([2, 3])
890                ]
891            }
892        );
893    }
894}