use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EdgeRange {
pub start_offset: usize,
pub len: usize,
}
impl EdgeRange {
pub fn new(start_offset: usize, len: usize) -> Self {
Self { start_offset, len }
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Match {
pub a: EdgeRange,
pub b: EdgeRange,
}
impl Match {
pub fn new(a: EdgeRange, b: EdgeRange) -> Self {
debug_assert_eq!(
a.len, b.len,
"Match: a and b must have equal length (got a.len={}, b.len={})",
a.len, b.len,
);
Self { a, b }
}
pub fn len(&self) -> usize {
debug_assert_eq!(self.a.len, self.b.len);
self.a.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn involution(self, len_a: usize, len_b: usize) -> Self {
let len = self.len();
Self {
a: EdgeRange::new((self.b.start_offset + len_b - len) % len_b, len),
b: EdgeRange::new((self.a.start_offset + len) % len_a, len),
}
}
pub fn with_tiles(self, a_tile: usize, b_tile: usize) -> TileMatch {
TileMatch {
a: Segment {
tile_id: a_tile,
range: self.a,
},
b: Segment {
tile_id: b_tile,
range: self.b,
},
}
}
pub fn with_b_tile(self, b_tile: usize) -> PatchMatch {
PatchMatch {
a_range: self.a,
b: Segment {
tile_id: b_tile,
range: self.b,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Segment {
pub tile_id: usize,
pub range: EdgeRange,
}
impl Segment {
pub fn new(tile_id: usize, range: EdgeRange) -> Self {
Self { tile_id, range }
}
pub fn len(&self) -> usize {
self.range.len
}
pub fn is_empty(&self) -> bool {
self.range.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct PatchSegment {
pub range: EdgeRange,
pub tile_seg: Segment,
}
impl PatchSegment {
pub fn new(range: EdgeRange, tile_seg: Segment) -> Self {
debug_assert_eq!(
range.len, tile_seg.range.len,
"PatchSegment: range and tile_seg.range must have equal length",
);
Self { range, tile_seg }
}
pub fn len(&self) -> usize {
debug_assert_eq!(self.range.len, self.tile_seg.range.len);
self.range.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct PatchMatch {
pub a_range: EdgeRange,
pub b: Segment,
}
impl PatchMatch {
pub fn new(a_range: EdgeRange, b: Segment) -> Self {
debug_assert_eq!(
a_range.len, b.range.len,
"PatchMatch: a_range and b.range must have equal length",
);
Self { a_range, b }
}
pub fn spec(&self) -> Match {
Match::new(self.a_range, self.b.range)
}
pub fn len(&self) -> usize {
debug_assert_eq!(self.a_range.len, self.b.range.len);
self.a_range.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct TileMatch {
pub a: Segment,
pub b: Segment,
}
impl TileMatch {
pub fn new(a: Segment, b: Segment) -> Self {
debug_assert_eq!(
a.range.len, b.range.len,
"TileMatch: a and b segments must have equal length",
);
Self { a, b }
}
pub fn spec(&self) -> Match {
Match::new(self.a.range, self.b.range)
}
pub fn len(&self) -> usize {
debug_assert_eq!(self.a.range.len, self.b.range.len);
self.a.range.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn involution(self, len_a: usize, len_b: usize) -> Self {
let inv = self.spec().involution(len_a, len_b);
TileMatch {
a: Segment {
tile_id: self.b.tile_id,
range: inv.a,
},
b: Segment {
tile_id: self.a.tile_id,
range: inv.b,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn edge_range_construction() {
let r = EdgeRange::new(3, 5);
assert_eq!(r.start_offset, 3);
assert_eq!(r.len, 5);
assert!(!r.is_empty());
assert!(EdgeRange::new(0, 0).is_empty());
}
#[test]
fn match_len_matches_ranges() {
let m = Match::new(EdgeRange::new(0, 3), EdgeRange::new(4, 3));
assert_eq!(m.len(), 3);
assert!(!m.is_empty());
}
#[test]
fn match_empty_when_zero_len() {
let m = Match::new(EdgeRange::new(0, 0), EdgeRange::new(0, 0));
assert!(m.is_empty());
}
#[test]
#[should_panic(expected = "equal length")]
#[cfg(debug_assertions)]
fn match_unequal_len_panics_in_debug() {
let _m = Match::new(EdgeRange::new(0, 3), EdgeRange::new(4, 4));
}
#[test]
fn match_involution_shifts_offsets_and_is_idempotent() {
let m = Match::new(EdgeRange::new(1, 2), EdgeRange::new(3, 2));
let inv = m.involution(6, 5);
assert_eq!(inv.a, EdgeRange::new(1, 2));
assert_eq!(inv.b, EdgeRange::new(3, 2));
assert_eq!(inv.involution(5, 6), m);
}
#[test]
fn match_with_tiles_round_trips_via_spec() {
let m = Match::new(EdgeRange::new(1, 2), EdgeRange::new(5, 2));
let tm = m.with_tiles(7, 9);
assert_eq!(tm.a.tile_id, 7);
assert_eq!(tm.b.tile_id, 9);
assert_eq!(tm.spec(), m);
}
#[test]
fn match_with_b_tile_round_trips_via_spec() {
let m = Match::new(EdgeRange::new(1, 2), EdgeRange::new(5, 2));
let pm = m.with_b_tile(42);
assert_eq!(pm.b.tile_id, 42);
assert_eq!(pm.spec(), m);
}
#[test]
fn segment_basics() {
let s = Segment::new(3, EdgeRange::new(1, 4));
assert_eq!(s.tile_id, 3);
assert_eq!(s.range.start_offset, 1);
assert_eq!(s.len(), 4);
assert!(!s.is_empty());
}
#[test]
fn tile_match_spec_strips_tile_ids() {
let tm = TileMatch::new(
Segment::new(0, EdgeRange::new(1, 2)),
Segment::new(1, EdgeRange::new(3, 2)),
);
let m = tm.spec();
assert_eq!(m.a, EdgeRange::new(1, 2));
assert_eq!(m.b, EdgeRange::new(3, 2));
assert_eq!(tm.len(), 2);
}
#[test]
fn tile_match_involution_swaps_tile_ids_and_shifts() {
let tm = TileMatch::new(
Segment::new(7, EdgeRange::new(1, 2)),
Segment::new(9, EdgeRange::new(3, 2)),
);
let inv = tm.involution(6, 5);
assert_eq!(inv.a.tile_id, 9);
assert_eq!(inv.a.range, EdgeRange::new(1, 2));
assert_eq!(inv.b.tile_id, 7);
assert_eq!(inv.b.range, EdgeRange::new(3, 2));
assert_eq!(inv.involution(5, 6), tm);
}
#[test]
fn patch_match_spec_strips_b_tile_id() {
let pm = PatchMatch::new(EdgeRange::new(1, 2), Segment::new(5, EdgeRange::new(9, 2)));
let m = pm.spec();
assert_eq!(m.a, EdgeRange::new(1, 2));
assert_eq!(m.b, EdgeRange::new(9, 2));
assert_eq!(pm.len(), 2);
}
#[test]
#[should_panic(expected = "equal length")]
#[cfg(debug_assertions)]
fn patch_match_unequal_len_panics() {
let _pm = PatchMatch::new(EdgeRange::new(1, 3), Segment::new(5, EdgeRange::new(9, 2)));
}
#[test]
fn ordering_is_lex_on_fields() {
let r1 = EdgeRange::new(0, 5);
let r2 = EdgeRange::new(1, 3);
assert!(r1 < r2);
let r3 = EdgeRange::new(0, 6);
assert!(r1 < r3); }
#[test]
fn match_via_with_tiles_round_trips_through_tile_match() {
let m = Match::new(EdgeRange::new(2, 3), EdgeRange::new(7, 3));
let tm = m.with_tiles(11, 13);
assert_eq!(tm.spec(), m);
}
#[test]
fn serde_round_trip() {
let tm = TileMatch::new(
Segment::new(2, EdgeRange::new(3, 4)),
Segment::new(5, EdgeRange::new(6, 4)),
);
let json = serde_json::to_string(&tm).unwrap();
let back: TileMatch = serde_json::from_str(&json).unwrap();
assert_eq!(back, tm);
}
}