Skip to content

Commit adcc321

Browse files
authored
Merge pull request #58 from molpopgen/table_row_access
Add table row API
2 parents 6b9631e + 860a647 commit adcc321

9 files changed

+235
-0
lines changed

src/_macros.rs

+17
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,23 @@ macro_rules! err_if_not_tracking_samples {
160160
};
161161
}
162162

163+
// This macro assumes that table row access helper
164+
// functions have a standard interface.
165+
// Here, we convert the None type to an Error,
166+
// as it applies $row is out of range.
167+
macro_rules! table_row_access {
168+
($row: expr, $decode_metadata: expr, $table: expr, $row_fn: ident) => {
169+
if $row < 0 {
170+
Err(TskitError::IndexError)
171+
} else {
172+
match $row_fn($table, $row, $decode_metadata) {
173+
Some(x) => Ok(x),
174+
None => Err(TskitError::IndexError),
175+
}
176+
}
177+
};
178+
}
179+
163180
#[cfg(test)]
164181
mod test {
165182
use crate::error::TskitError;

src/edge_table.rs

+28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::bindings as ll_bindings;
22
use crate::metadata;
33
use crate::{tsk_id_t, tsk_size_t, TskitError};
44

5+
/// Row of an [`EdgeTable`]
56
pub struct EdgeTableRow {
67
pub left: f64,
78
pub right: f64,
@@ -10,6 +11,16 @@ pub struct EdgeTableRow {
1011
pub metadata: Option<Vec<u8>>,
1112
}
1213

14+
impl PartialEq for EdgeTableRow {
15+
fn eq(&self, other: &Self) -> bool {
16+
self.parent == other.parent
17+
&& self.child == other.child
18+
&& crate::util::f64_partial_cmp_equal(&self.left, &other.left)
19+
&& crate::util::f64_partial_cmp_equal(&self.right, &other.right)
20+
&& crate::util::metadata_like_are_equal(&self.metadata, &other.metadata)
21+
}
22+
}
23+
1324
fn make_edge_table_row(
1425
table: &EdgeTable,
1526
pos: tsk_id_t,
@@ -138,4 +149,21 @@ impl<'a> EdgeTable<'a> {
138149
pub fn iter(&self, decode_metadata: bool) -> EdgeTableRefIterator {
139150
crate::table_iterator::make_table_iterator::<&EdgeTable<'a>>(&self, decode_metadata)
140151
}
152+
153+
/// Return row `r` of the table.
154+
///
155+
/// # Parameters
156+
///
157+
/// * `r`: the row id.
158+
/// * `decode_metadata`: if `true`, then a *copy* of row metadata
159+
/// will be provided in [`EdgeTableRow::metadata`].
160+
/// The meta data are *not* decoded.
161+
/// Rows with no metadata will contain the value `None`.
162+
///
163+
/// # Errors
164+
///
165+
/// [`TskitError::IndexError`] if `r` is out of range.
166+
pub fn row(&self, r: tsk_id_t, decode_metadata: bool) -> Result<EdgeTableRow, TskitError> {
167+
table_row_access!(r, decode_metadata, self, make_edge_table_row)
168+
}
141169
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod table_collection;
1919
mod table_iterator;
2020
mod trees;
2121
pub mod types;
22+
mod util;
2223

2324
// re-export fundamental constants that
2425
// we can't live without

src/mutation_table.rs

+28
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ pub struct MutationTableRow {
1212
pub metadata: Option<Vec<u8>>,
1313
}
1414

15+
impl PartialEq for MutationTableRow {
16+
fn eq(&self, other: &Self) -> bool {
17+
self.site == other.site
18+
&& self.node == other.node
19+
&& self.parent == other.parent
20+
&& crate::util::f64_partial_cmp_equal(&self.time, &other.time)
21+
&& crate::util::metadata_like_are_equal(&self.derived_state, &other.derived_state)
22+
&& crate::util::metadata_like_are_equal(&self.metadata, &other.metadata)
23+
}
24+
}
25+
1526
fn make_mutation_table_row(
1627
table: &MutationTable,
1728
pos: tsk_id_t,
@@ -160,4 +171,21 @@ impl<'a> MutationTable<'a> {
160171
pub fn iter(&self, decode_metadata: bool) -> MutationTableRefIterator {
161172
crate::table_iterator::make_table_iterator::<&MutationTable<'a>>(&self, decode_metadata)
162173
}
174+
175+
/// Return row `r` of the table.
176+
///
177+
/// # Parameters
178+
///
179+
/// * `r`: the row id.
180+
/// * `decode_metadata`: if `true`, then a *copy* of row metadata
181+
/// will be provided in [`MutationTableRow::metadata`].
182+
/// The meta data are *not* decoded.
183+
/// Rows with no metadata will contain the value `None`.
184+
///
185+
/// # Errors
186+
///
187+
/// [`TskitError::IndexError`] if `r` is out of range.
188+
pub fn row(&self, r: tsk_id_t, decode_metadata: bool) -> Result<MutationTableRow, TskitError> {
189+
table_row_access!(r, decode_metadata, self, make_mutation_table_row)
190+
}
163191
}

src/node_table.rs

+27
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ pub struct NodeTableRow {
1111
pub metadata: Option<Vec<u8>>,
1212
}
1313

14+
impl PartialEq for NodeTableRow {
15+
fn eq(&self, other: &Self) -> bool {
16+
self.flags == other.flags
17+
&& self.population == other.population
18+
&& self.individual == other.individual
19+
&& crate::util::metadata_like_are_equal(&self.metadata, &other.metadata)
20+
&& crate::util::f64_partial_cmp_equal(&self.time, &other.time)
21+
}
22+
}
23+
1424
fn make_node_table_row(
1525
table: &NodeTable,
1626
pos: tsk_id_t,
@@ -148,4 +158,21 @@ impl<'a> NodeTable<'a> {
148158
pub fn iter(&self, decode_metadata: bool) -> NodeTableRefIterator {
149159
crate::table_iterator::make_table_iterator::<&NodeTable<'a>>(&self, decode_metadata)
150160
}
161+
162+
/// Return row `r` of the table.
163+
///
164+
/// # Parameters
165+
///
166+
/// * `r`: the row id.
167+
/// * `decode_metadata`: if `true`, then a *copy* of row metadata
168+
/// will be provided in [`NodeTableRow::metadata`].
169+
/// The meta data are *not* decoded.
170+
/// Rows with no metadata will contain the value `None`.
171+
///
172+
/// # Errors
173+
///
174+
/// [`TskitError::IndexError`] if `r` is out of range.
175+
pub fn row(&self, r: tsk_id_t, decode_metadata: bool) -> Result<NodeTableRow, TskitError> {
176+
table_row_access!(r, decode_metadata, self, make_node_table_row)
177+
}
151178
}

src/population_table.rs

+28
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@ use crate::TskitError;
44
use crate::{tsk_id_t, tsk_size_t};
55

66
/// Row of a [`PopulationTable`]
7+
#[derive(Eq)]
78
pub struct PopulationTableRow {
89
pub metadata: Option<Vec<u8>>,
910
}
1011

12+
impl PartialEq for PopulationTableRow {
13+
fn eq(&self, other: &Self) -> bool {
14+
crate::util::metadata_like_are_equal(&self.metadata, &other.metadata)
15+
}
16+
}
17+
1118
fn make_population_table_row(
1219
table: &PopulationTable,
1320
pos: tsk_id_t,
@@ -93,4 +100,25 @@ impl<'a> PopulationTable<'a> {
93100
pub fn iter(&self, decode_metadata: bool) -> PopulationTableRefIterator {
94101
crate::table_iterator::make_table_iterator::<&PopulationTable<'a>>(&self, decode_metadata)
95102
}
103+
104+
/// Return row `r` of the table.
105+
///
106+
/// # Parameters
107+
///
108+
/// * `r`: the row id.
109+
/// * `decode_metadata`: if `true`, then a *copy* of row metadata
110+
/// will be provided in [`PopulationTableRow::metadata`].
111+
/// The meta data are *not* decoded.
112+
/// Rows with no metadata will contain the value `None`.
113+
///
114+
/// # Errors
115+
///
116+
/// [`TskitError::IndexError`] if `r` is out of range.
117+
pub fn row(
118+
&self,
119+
r: tsk_id_t,
120+
decode_metadata: bool,
121+
) -> Result<PopulationTableRow, TskitError> {
122+
table_row_access!(r, decode_metadata, self, make_population_table_row)
123+
}
96124
}

src/site_table.rs

+25
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ pub struct SiteTableRow {
1010
pub metadata: Option<Vec<u8>>,
1111
}
1212

13+
impl PartialEq for SiteTableRow {
14+
fn eq(&self, other: &Self) -> bool {
15+
crate::util::f64_partial_cmp_equal(&self.position, &other.position)
16+
&& crate::util::metadata_like_are_equal(&self.ancestral_state, &other.ancestral_state)
17+
&& crate::util::metadata_like_are_equal(&self.metadata, &other.metadata)
18+
}
19+
}
20+
1321
fn make_site_table_row(
1422
table: &SiteTable,
1523
pos: tsk_id_t,
@@ -126,4 +134,21 @@ impl<'a> SiteTable<'a> {
126134
pub fn iter(&self, decode_metadata: bool) -> SiteTableRefIterator {
127135
crate::table_iterator::make_table_iterator::<&SiteTable<'a>>(&self, decode_metadata)
128136
}
137+
138+
/// Return row `r` of the table.
139+
///
140+
/// # Parameters
141+
///
142+
/// * `r`: the row id.
143+
/// * `decode_metadata`: if `true`, then a *copy* of row metadata
144+
/// will be provided in [`SiteTableRow::metadata`].
145+
/// The meta data are *not* decoded.
146+
/// Rows with no metadata will contain the value `None`.
147+
///
148+
/// # Errors
149+
///
150+
/// [`TskitError::IndexError`] if `r` is out of range.
151+
pub fn row(&self, r: tsk_id_t, decode_metadata: bool) -> Result<SiteTableRow, TskitError> {
152+
table_row_access!(r, decode_metadata, self, make_site_table_row)
153+
}
129154
}

src/table_collection.rs

+23
Original file line numberDiff line numberDiff line change
@@ -865,4 +865,27 @@ mod test {
865865
let dumps = tables.deepcopy().unwrap();
866866
assert!(tables.equals(&dumps, 0));
867867
}
868+
869+
#[test]
870+
fn test_edge_table_row_equality() {
871+
let tables = make_small_table_collection();
872+
for (i, row) in tables.edges_iter(true).enumerate() {
873+
assert!(row == tables.edges().row(i as tsk_id_t, true).unwrap());
874+
assert!(!(row != tables.edges().row(i as tsk_id_t, true).unwrap()));
875+
if i > 0 {
876+
assert!(row != tables.edges().row(i as tsk_id_t - 1, true).unwrap());
877+
}
878+
}
879+
}
880+
881+
#[test]
882+
fn test_node_table_row_equality() {
883+
let tables = make_small_table_collection();
884+
for (i, row) in tables.nodes_iter(true).enumerate() {
885+
assert!(row == tables.nodes().row(i as tsk_id_t, true).unwrap());
886+
assert!(!(row != tables.nodes().row(i as tsk_id_t, true).unwrap()));
887+
}
888+
assert!(tables.nodes().row(0, true) != tables.nodes().row(1, true));
889+
assert!(tables.nodes().row(1, true) == tables.nodes().row(2, true));
890+
}
868891
}

src/util.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
pub(crate) fn metadata_like_are_equal(a: &Option<Vec<u8>>, b: &Option<Vec<u8>>) -> bool {
2+
match a {
3+
Some(x) => match b {
4+
Some(y) => x == y,
5+
None => false,
6+
},
7+
None => match b.is_none() {
8+
true => true,
9+
false => false,
10+
},
11+
}
12+
}
13+
14+
pub(crate) fn f64_partial_cmp_equal(a: &f64, b: &f64) -> bool {
15+
match a.partial_cmp(b) {
16+
Some(std::cmp::Ordering::Equal) => true,
17+
Some(std::cmp::Ordering::Less) => false,
18+
Some(std::cmp::Ordering::Greater) => false,
19+
None => false,
20+
}
21+
}
22+
23+
#[cfg(test)]
24+
mod test {
25+
use super::metadata_like_are_equal;
26+
27+
#[test]
28+
fn compare_some_to_none() {
29+
let v: Vec<u8> = vec![1, 2, 3];
30+
assert!(!metadata_like_are_equal(&Some(v), &None));
31+
}
32+
33+
#[test]
34+
fn compare_none_to_some() {
35+
let v: Vec<u8> = vec![1, 2, 3];
36+
assert!(!metadata_like_are_equal(&None, &Some(v)));
37+
}
38+
39+
#[test]
40+
fn compare_none_to_none() {
41+
assert!(metadata_like_are_equal(&None, &None));
42+
}
43+
44+
#[test]
45+
fn compare_some_to_some_are_equal() {
46+
let v: Vec<u8> = vec![1, 2, 3];
47+
let vc = v.clone();
48+
assert!(metadata_like_are_equal(&Some(v), &Some(vc)));
49+
}
50+
51+
#[test]
52+
fn compare_some_to_some_are_not_equal() {
53+
let v: Vec<u8> = vec![1, 2, 3];
54+
let mut vc = v.clone();
55+
vc.push(11);
56+
assert!(!metadata_like_are_equal(&Some(v), &Some(vc)));
57+
}
58+
}

0 commit comments

Comments
 (0)