easy_ml/matrices/slices.rs
1/*!
2 * Ways to transform and access matrices
3 *
4 * Slicing can only be used to downsize matrices with [retain](super::Matrix::retain()) and
5 * [retain_mut](super::Matrix::retain_mut()). In the future it may be available
6 * for further use with the views APIs, which can already apply many more transformations.
7 *
8 * See also
9 * - [views](crate::matrices::views)
10 */
11
12use std::ops::Range;
13
14use crate::matrices::{Column, Row};
15
16/**
17 * A slice defines across one dimension what values are accepted,
18 * it can act like a filter. Slices can also be constructed via
19 * boolean logic operations in the same way as in predicate logic expressions.
20 */
21#[non_exhaustive]
22pub enum Slice {
23 /** A slice that accepts all indexes */
24 All(),
25 /** A slice that accepts no indexes */
26 None(),
27 /** A slice that accepts only the provided index */
28 Single(usize),
29 /** A slice that accepts only indexes within the range */
30 Range(Range<usize>),
31 /**
32 * A slice which rejects all indexes accepted by the argument, and accepts all indexes
33 * rejected by the argument.
34 */
35 Not(Box<Slice>),
36 /**
37 * A slice which accepts only indexes accepted by both arguments, and rejects all others.
38 */
39 And(Box<Slice>, Box<Slice>),
40 /**
41 * A slice which accepts indexes accepted by either arguments, and rejects only
42 * indexes accepted by neither. This is an inclusive or.
43 *
44 * You could construct an exclusive or by using combinations of AND, OR and NOT as
45 * (a AND (NOT b)) OR ((NOT a) AND b) = a XOR b.
46 */
47 Or(Box<Slice>, Box<Slice>),
48}
49
50/**
51 * A kind of slice that can be taken on a matrix, over its rows and columns.
52 */
53pub struct Slice2D {
54 pub(crate) rows: Slice,
55 pub(crate) columns: Slice,
56}
57
58impl Slice {
59 /**
60 * Checks if this slice accepts some index.
61 */
62 pub fn accepts(&self, index: usize) -> bool {
63 match self {
64 Slice::All() => true,
65 Slice::None() => false,
66 Slice::Single(i) => i == &index,
67 Slice::Range(range) => range.contains(&index),
68 Slice::Not(slice) => !slice.accepts(index),
69 Slice::And(slice1, slice2) => slice1.accepts(index) && slice2.accepts(index),
70 Slice::Or(slice1, slice2) => slice1.accepts(index) || slice2.accepts(index),
71 }
72 }
73
74 /**
75 * Returns the negation of this slice
76 */
77 #[allow(clippy::should_implement_trait)]
78 pub fn not(self) -> Slice {
79 Slice::Not(Box::new(self))
80 }
81
82 /**
83 * Returns the and of this slice and the other one
84 */
85 pub fn and(self, other: Slice) -> Slice {
86 Slice::And(Box::new(self), Box::new(other))
87 }
88
89 /**
90 * Returns the or of this slice and the other one
91 */
92 pub fn or(self, other: Slice) -> Slice {
93 Slice::Or(Box::new(self), Box::new(other))
94 }
95}
96
97/**
98 * A builder object to create a slice. This exists to make forgetting to specify rows
99 * *and* columns a compilation error rather than a runtime one.
100 */
101pub struct EmptySlice2DBuilder {}
102/**
103 * A builder object to create a slice. This exists to make forgetting to specify rows
104 * *and* columns a compilation error rather than a runtime one.
105 */
106pub struct RowSlice2DBuilder {
107 rows: Slice,
108}
109/**
110 * A builder object to create a slice. This exists to make forgetting to specify rows
111 * *and* columns a compilation error rather than a runtime one.
112 */
113pub struct ColumnSlice2DBuilder {
114 columns: Slice,
115}
116
117/**
118 * Constructs a builder object to create a 2d slice
119 *
120 * The full syntax to create a `Slice2D` is like so:
121 *
122 * ```
123 * use easy_ml::matrices::slices;
124 * use easy_ml::matrices::slices::Slice;
125 * slices::new()
126 * .rows(Slice::All())
127 * .columns(Slice::Single(1));
128 * ```
129 *
130 * Rows and Column slices can be specified in either order but both must be given.
131 */
132pub fn new() -> EmptySlice2DBuilder {
133 Slice2D::new()
134}
135
136impl Slice2D {
137 /**
138 * Constructs a builder object to create a 2d slice
139 *
140 * The full syntax to create a `Slice2D` is like so:
141 *
142 * ```
143 * use easy_ml::matrices::slices::{Slice2D, Slice};
144 * Slice2D::new()
145 * .rows(Slice::All())
146 * .columns(Slice::Single(1));
147 * ```
148 *
149 * Rows and Column slices can be specified in either order but both must be given.
150 */
151 #[allow(clippy::new_ret_no_self)]
152 pub fn new() -> EmptySlice2DBuilder {
153 EmptySlice2DBuilder {}
154 }
155
156 /**
157 * Checks if this 2 dimensional slice accepts some index. The row and column
158 * slices it is composed from must accept the row and column respectively.
159 */
160 pub fn accepts(&self, row: Row, column: Column) -> bool {
161 self.rows.accepts(row) && self.columns.accepts(column)
162 }
163}
164
165impl EmptySlice2DBuilder {
166 /**
167 * Constructs a new builder object with the rows defined first.
168 */
169 pub fn rows(self, rows: Slice) -> RowSlice2DBuilder {
170 RowSlice2DBuilder { rows }
171 }
172
173 /**
174 * Constructs a new builder object with the columns defined first.
175 */
176 pub fn columns(self, columns: Slice) -> ColumnSlice2DBuilder {
177 ColumnSlice2DBuilder { columns }
178 }
179}
180
181impl RowSlice2DBuilder {
182 /**
183 * Constructs a 2d slice with rows and columns defined.
184 */
185 pub fn columns(self, columns: Slice) -> Slice2D {
186 Slice2D {
187 rows: self.rows,
188 columns,
189 }
190 }
191}
192
193impl ColumnSlice2DBuilder {
194 /**
195 * Constructs a 2d slice with rows and columns defined.
196 */
197 pub fn rows(self, rows: Slice) -> Slice2D {
198 Slice2D {
199 rows,
200 columns: self.columns,
201 }
202 }
203}