From 68da7a906023749603e5bfc0d970f6f3106eb623 Mon Sep 17 00:00:00 2001 From: Mats Fangohr Date: Tue, 31 Oct 2023 20:55:42 +0100 Subject: [PATCH] implement grambulation in Rust --- .gitignore | 2 + Cargo.toml | 10 + LICENSE | 21 ++ README.md | 84 ++++++ src/lib.rs | 553 +++++++++++++++++++++++++++++++++++++++ tests/test_grambulate.rs | 27 ++ 6 files changed, 697 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 tests/test_grambulate.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9c83bc4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "grambulate" +version = "0.1.0" +edition = "2021" +description = "An implementation of grambulation for positive integers in Rust." +license = "MIT" +repository = "https://github.com/MatsFangohr/grambulate-rust" + +[dependencies] +integer-sqrt = "0.1.5" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d87b0d8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-present Mats Fangohr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..14a7f3f --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# grambulate-rust + +An implementation of grambulation in Rust, as initially explained [on Reddit](https://www.reddit.com/r/mathmemes/comments/tvn2gj/the_solution_to_the_april_fools_math/) and more precisely defined for [Code Golf](https://codegolf.stackexchange.com/questions/259698/implement-grambulation). + +## Implementation + +To grambulate two numbers $a$ and $b$ to a solution $c$, we must find the coordinates $c'$ of the solution, for which we must first determine the coordinates of $a$ and $b$ ($a'$ and $b'$) on the spiral. + +To do so, we can calculate the 'rings' $r_a$ and $r_b$ the numbers are in with the formula +$$r(x)=\left\lfloor{\frac{\left\lceil{\sqrt{x}}\right\rceil}{2}}\right\rfloor$$ +Then, the formula +$$v_d(r)=4r^2-(5-d)\cdot{}r+1$$ +can be used to to calculate the four values on the four main diagonals for ring $x$, where $d$ specifies which diagonal to use. To do this, we specify the number on a given diagonal in ring 1 as $d$: + +| Diagonal | $d$ | +| ------------ | --- | +| top-right | 3 | +| top-left | 5 | +| bottom-left | 7 | +| bottom-right | 9 | + +This gives us 4 values near our desired value (either $a$ or $b$). If we find the value closest to but larger than or equal to our target value, we can calculate the difference and apply it as either an x or y offset, giving us the coordinates of our desired value. + +Now that we have both pairs of coordinates, we can calculate the connecting vector $\vec{v}$ and apply to it to $b'$ to find $c'$. + +Once we know $c'$, we know that the ring is the larger of the absolute x and y values. + +We now determine which diagonal we need to calculate, retrieve the value using the above formula and subtract the difference of the relevant coordinate. + +## Example + +Let $a=5$ and $b=11$. + +We can use the ring formula to determine $r_a=1$ and $r_b=2$. + +Starting with $a$, we can calculate the 4 diagonal values on ring $r_a$: +$v_3=3$ +$v_5=5$ +$v_7=7$ +$v_9=9$ + +The closest of these values that is $\le{}a$ is $v_5=5$. The coordinates of that value are $v_5'=(-1\cdot{}r_a~|+1\cdot{}r_a) = (-1~|~1)$. As $v_5=a$, we don't need any further offsets. + +Moving on to $b$: +$v_3=13$ +$v_5=17$ +$v_7=21$ +$v_9=25$ + +The closest of these values that is $\le{}b$ is $v_3=13$. The coordinates of that value are $v_3'=(+1\cdot{}r_b~|+1\cdot{}r_b)=(2~|~2)$. As $v_3\neq{}b$, we still need to do more. + +As we determined the closest value _ahead_ of $b$ to be the top-right diagonal, we need to decrease the y coordinate of $v_3'$ by $v_3-b=13-11=2$. That leaves us with $\vec{c'}=\vec{v_3'}-\begin{pmatrix}0\\2\end{pmatrix}=\begin{pmatrix}2-0\\2-2\end{pmatrix}=\begin{pmatrix}2\\0\end{pmatrix}$. + +Now we know: $$a'=(-1~|~1)~~~~~~~b'=(2~|~0)$$ +The connecting vector is $\vec{v}=\vec{b'}-\vec{a'}=\begin{pmatrix}2-(-1)\\0-1\end{pmatrix}=\begin{pmatrix}3\\-1\end{pmatrix}$. Applying this to $b'$ gives us the position vector of $c'$. +$$\vec{c'}=\vec{b'}+\vec{v}=\begin{pmatrix}2+3\\0+(-1)\end{pmatrix}=\begin{pmatrix}5\\-1\end{pmatrix}$$ +As the value is not directly on a diagonal ($|x|\neq{}|y|$), we can use the following table to determine the diagonal _ahead_ of our target value: + +| condition | diagonal | +| ---------- | ------------ | +| $\|x\|< y$ | top-left | +| $x>\|y\|$ | top-right | +| $x<-\|y\|$ | bottom-left | +| $-\|x\|>y$ | bottom-right | + +We need the top-left diagonal. We also know that our value is on ring $r_c=\max(|5|, |-1|)=5$. The value of the top-left diagonal at ring 5 is, using the formula above, $v_3(5)=91$. +By definition, $c$ is smaller than that value. As the straight in front of the top-left diagonal is vertical and upwards, we need to subtract the difference between the y-value of 91 and the y-value of $c'$ from 91 and we should find $c$. $$c=91-((y_{91})-(y_{c'}))=91-((+1\cdot{}5)-(-1))=91-6=85$$ + +That's it! $5~\lozenge{}~11=85$. + +## Speed + +This approach may seem confusing, but it avoids any loops that change with the inputs; the only loops in my code iterate over the 4 types of `Diagonal`, no more. + +This allows very low execution time regardless of input; results from a rough test on my computer: + +| `value_a` | `value_b` | time per iteration, average of 100'000'000 | +| --------------- | ---------------- | ------------------------------------------ | +| 1 | 2 | 529ns | +| 10 | 25 | 395ns | +| 100000 | 2500000 | 601ns | +| 100000000000000 | 2500000000000000 | 364ns | + +These were just the first numbers I chose and this is in no way a proper test, but it'll do for an impression. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..788e973 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,553 @@ +use std::{cmp, collections::HashMap}; + +use integer_sqrt::IntegerSquareRoot; + +#[derive(Eq, PartialEq, Default, Debug)] +struct Coordinate { + x: i64, + y: i64, +} + +#[derive(Default, Eq, PartialEq, Debug)] +struct GridValue { + coord: Coordinate, + value: u64, +} + +#[derive(Eq, PartialEq, Hash, Debug)] +enum Diagonal { + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} + +#[derive(Copy, Clone)] +struct GeneratorAttributes { + base_value: u8, + x_incr: i8, + y_incr: i8, +} + +fn get_diagonal_ahead(coord: &Coordinate) -> Diagonal { + let (x, y) = (coord.x, coord.y); + if x.abs() == y.abs() { + if x > 0 && y > 0 { + Diagonal::TopRight + } else if x > 0 && y < 0 { + Diagonal::BottomRight + } else if x < 0 && y > 0 { + Diagonal::TopLeft + } else if x < 0 && y < 0 { + Diagonal::BottomLeft + } else { + unreachable!() + } + } else if x > y.abs() { + // right quadrant + Diagonal::TopRight + } else if x < -y.abs() { + // left quadrant + Diagonal::BottomLeft + } else if x.abs() < y { + // top quadrant + Diagonal::TopLeft + } else if -x.abs() > y { + // bottom quadrant + Diagonal::BottomRight + } else { + unreachable!() + } +} + +fn get_diagonal_value(x_incr: i8, y_incr: i8, base_value: u8, ring: u64) -> GridValue { + GridValue { + coord: Coordinate { + x: x_incr as i64 * ring as i64, + y: y_incr as i64 * ring as i64, + }, + value: { + (4 * ring.pow(2) as i128 - ring as i128 * (5 - base_value as i8) as i128 + 1) as u64 + }, // math magic + } +} + +fn ceil_sqrt(n: u64) -> u64 { + if n == 1 { + 1 + } else { + (n - 1).integer_sqrt() + 1 + } +} + +fn get_number_ring(target: u64) -> u64 { + ceil_sqrt(target) / 2 +} // integer divison + +fn get_coords( + target: u64, + diagonal_properties: &HashMap, +) -> Coordinate { + let ring = get_number_ring(target); + + let mut diffs: HashMap<&Diagonal, (GridValue, u64)> = HashMap::new(); + for (diag, props) in diagonal_properties { + let value = get_diagonal_value(props.x_incr, props.y_incr, props.base_value, ring); + if value.value >= target { + let diff = value.value - target; + diffs.insert(diag, (value, diff)); + } + } + + let mut smallest: (&Diagonal, u64, GridValue) = + (&Diagonal::TopLeft, u64::MAX, GridValue::default()); // placeholder value + for (diag, (gridvalue, diff)) in diffs { + if diff < smallest.1 { + smallest = (diag, diff, gridvalue) + } + } + + let diagonal_ahead = smallest.0; + let value_ahead = smallest.2; + + let mut x_offset = 0; + let mut y_offset = 0; + + match diagonal_ahead { + Diagonal::TopLeft => x_offset = -(target as i128 - value_ahead.value as i128) as i64, + Diagonal::TopRight => y_offset = (target as i128 - value_ahead.value as i128) as i64, + Diagonal::BottomLeft => y_offset = -(target as i128 - value_ahead.value as i128) as i64, + Diagonal::BottomRight => x_offset = (target as i128 - value_ahead.value as i128) as i64, + } + + Coordinate { + x: value_ahead.coord.x + x_offset, + y: value_ahead.coord.y + y_offset, + } +} + +fn get_value_at_coords( + coord: &Coordinate, + diagonal_properties: &HashMap, +) -> u64 { + if coord == (&Coordinate { x: 0, y: 0 }) { + return 1; + } + let ring = cmp::max(coord.x.abs(), coord.y.abs()) as u64; + + let diagonal_ahead = get_diagonal_ahead(coord); + + let props = diagonal_properties[&diagonal_ahead]; + + match diagonal_ahead { + Diagonal::TopRight | Diagonal::BottomLeft => { + let gridvalue = get_diagonal_value(props.x_incr, props.y_incr, props.base_value, ring); + gridvalue.value - (gridvalue.coord.y - coord.y).unsigned_abs() + } + Diagonal::BottomRight | Diagonal::TopLeft => { + let gridvalue = get_diagonal_value(props.x_incr, props.y_incr, props.base_value, ring); + gridvalue.value - (gridvalue.coord.x - coord.x).unsigned_abs() + } + } +} + +fn get_diagonal_properties() -> HashMap { + HashMap::from([ + ( + Diagonal::TopLeft, + GeneratorAttributes { + base_value: 5, + x_incr: -1, + y_incr: 1, + }, + ), + ( + Diagonal::TopRight, + GeneratorAttributes { + base_value: 3, + x_incr: 1, + y_incr: 1, + }, + ), + ( + Diagonal::BottomLeft, + GeneratorAttributes { + base_value: 7, + x_incr: -1, + y_incr: -1, + }, + ), + ( + Diagonal::BottomRight, + GeneratorAttributes { + base_value: 9, + x_incr: 1, + y_incr: -1, + }, + ), + ]) +} + +/// Perform grambulation upon two given positive integers. +/// +/// # Grambulation +/// +/// Using the following spiral of positive integers: +/// +/// | | | | | | | | +/// |---|---|---|---|---|---|---| +/// | 37 | 36 | 35 | 34 | 33 | 32 | 31 | +/// | 38 | 17 | 16 | 15 | 14 | 13 | 30 | +/// | 39 | 18 | 5 | 4 | 3 | 12 | 29 | +/// | 40 | 19 | 6 | 1 | 2 | 11 | 28 | +/// | 41 | 20 | 7 | 8 | 9 | 10 | 27 | +/// | 42 | 21 | 22 | 23 | 24 |25 | 26 | +/// | 43 | 44 | 45 | 46 | 47 | 48 | ... | +/// +/// We can define grambulation as an operation ◊ : N×N → N. +/// The spiral is not limited to any specific size. +/// +/// To grambulate two numbers `a` and `b`, find their coordinates `a'` and `b'` on the grid. +/// Define a vector from `a'` to `b'` and calculate the coordinates `c'` of the solution `c` by applying it to the point `b'`. +/// +/// ## Example of grambulation +/// +/// To give an example: Let `a` be 5 and `b` be 15. +/// That would mean `a' = (-1, 1)` and `b' = (0, 2)`. +/// The connecting vector is `b' - a'`, so `(1, 1)`. +/// Applying that vector to `b'` gives us `(1, 3)`, so `c'`. +/// The value at those coordinates (33) is our result: 5 ◊ 15 = 33. +/// +/// This can be seen especially well graphically using the above grid. +/// +/// # Errors +/// +/// If either of the provided inputs is zero, an error will be returned. Succeeds otherwise. +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), String> { +/// assert_eq!(grambulate::grambulate(1, 2)?, 11); +/// assert_eq!(grambulate::grambulate(5, 15)?, 33); +/// assert_eq!(grambulate::grambulate(7, 21)?, 43); +/// assert_eq!(grambulate::grambulate(21, 7)?, 1); +/// # Ok(()) +/// # } +/// ``` +/// ``` +/// assert!(grambulate::grambulate(0, 1).is_err()); +/// ``` +/// +pub fn grambulate(value_a: u64, value_b: u64) -> Result { + if value_a == 0 || value_b == 0 { + return Err("inputs values may not be zero".to_owned()); + } + + let diagonal_properties = get_diagonal_properties(); + + let a_coords = get_coords(value_a, &diagonal_properties); + let b_coords = get_coords(value_b, &diagonal_properties); + + let diff = (b_coords.x - a_coords.x, b_coords.y - a_coords.y); + let result_coords = Coordinate { + x: (b_coords.x + diff.0), + y: (b_coords.y + diff.1), + }; + Ok(get_value_at_coords(&result_coords, &diagonal_properties)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ceil_sqrt() { + assert_eq!(ceil_sqrt(1), 1); + assert_eq!(ceil_sqrt(2), 2); + assert_eq!(ceil_sqrt(3), 2); + assert_eq!(ceil_sqrt(4), 2); + assert_eq!(ceil_sqrt(5), 3); + + assert_eq!(ceil_sqrt(8), 3); + assert_eq!(ceil_sqrt(9), 3); + assert_eq!(ceil_sqrt(10), 4); + + assert_eq!(ceil_sqrt(4611686018427387904), 2147483648); + assert_eq!(ceil_sqrt(4611686018427387904 + 1), 2147483648 + 1); + } + + #[test] + fn test_get_number_ring() { + assert_eq!(get_number_ring(1), 0); + + // with pytest.raises(ValueError): + // get_number_ring(0) + // with pytest.raises(ValueError): + // get_number_ring(-3457834857) + + assert_eq!(get_number_ring(2), 1); + assert_eq!(get_number_ring(9), 1); + assert_eq!(get_number_ring(10), 2); + assert_eq!(get_number_ring(25), 2); + assert_eq!(get_number_ring(26), 3); + + assert_eq!(get_number_ring(289), 8); + } + + #[test] + fn test_get_diagonal_value() { + let diagonal_properties = get_diagonal_properties(); + + let mut diag = &Diagonal::TopLeft; + let mut x_incr = diagonal_properties[diag].x_incr; + let mut y_incr = diagonal_properties[diag].y_incr; + let mut base_value = diagonal_properties[diag].base_value; + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 0), + GridValue { + coord: Coordinate { x: 0, y: 0 }, + value: 1 + } + ); + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 1), + GridValue { + coord: Coordinate { x: -1, y: 1 }, + value: 5 + } + ); + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 2), + GridValue { + coord: Coordinate { x: -2, y: 2 }, + value: 17 + } + ); + + diag = &Diagonal::TopRight; + x_incr = diagonal_properties[diag].x_incr; + y_incr = diagonal_properties[diag].y_incr; + base_value = diagonal_properties[diag].base_value; + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 0), + GridValue { + coord: Coordinate { x: 0, y: 0 }, + value: 1 + } + ); + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 1), + GridValue { + coord: Coordinate { x: 1, y: 1 }, + value: 3 + } + ); + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 2), + GridValue { + coord: Coordinate { x: 2, y: 2 }, + value: 13 + } + ); + + diag = &Diagonal::BottomLeft; + x_incr = diagonal_properties[diag].x_incr; + y_incr = diagonal_properties[diag].y_incr; + base_value = diagonal_properties[diag].base_value; + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 0), + GridValue { + coord: Coordinate { x: 0, y: 0 }, + value: 1 + } + ); + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 1), + GridValue { + coord: Coordinate { x: -1, y: -1 }, + value: 7 + } + ); + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 2), + GridValue { + coord: Coordinate { x: -2, y: -2 }, + value: 21 + } + ); + diag = &Diagonal::BottomRight; + x_incr = diagonal_properties[diag].x_incr; + y_incr = diagonal_properties[diag].y_incr; + base_value = diagonal_properties[diag].base_value; + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 0), + GridValue { + coord: Coordinate { x: 0, y: 0 }, + value: 1 + } + ); + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 1), + GridValue { + coord: Coordinate { x: 1, y: -1 }, + value: 9 + } + ); + assert_eq!( + get_diagonal_value(x_incr, y_incr, base_value, 2), + GridValue { + coord: Coordinate { x: 2, y: -2 }, + value: 25 + } + ); + } + + #[test] + fn test_get_coords() { + let diagonal_props = &get_diagonal_properties(); + + assert_eq!(get_coords(1, diagonal_props), Coordinate { x: 0, y: 0 },); + assert_eq!(get_coords(2, diagonal_props), Coordinate { x: 1, y: 0 },); + assert_eq!(get_coords(3, diagonal_props), Coordinate { x: 1, y: 1 },); + assert_eq!(get_coords(7, diagonal_props), Coordinate { x: -1, y: -1 },); + assert_eq!(get_coords(9, diagonal_props), Coordinate { x: 1, y: -1 },); + assert_eq!(get_coords(25, diagonal_props), Coordinate { x: 2, y: -2 },); + assert_eq!(get_coords(49, diagonal_props), Coordinate { x: 3, y: -3 },); + assert_eq!(get_coords(281, diagonal_props), Coordinate { x: 0, y: -8 },); + assert_eq!(get_coords(100, diagonal_props), Coordinate { x: -4, y: 5 },); + } + + #[test] + fn test_get_diagonal_ahead() { + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 0, y: 1 }), + Diagonal::TopLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -1, y: 1 }), + Diagonal::TopLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 1, y: 2 }), + Diagonal::TopLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -1, y: 2 }), + Diagonal::TopLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -2, y: 2 }), + Diagonal::TopLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 9999, y: 10000 }), + Diagonal::TopLeft + ); + + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 1, y: 0 }), + Diagonal::TopRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 1, y: 1 }), + Diagonal::TopRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 2, y: 1 }), + Diagonal::TopRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 2, y: -1 }), + Diagonal::TopRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 2, y: 2 }), + Diagonal::TopRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 10000, y: -9999 }), + Diagonal::TopRight + ); + + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -1, y: 0 }), + Diagonal::BottomLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -1, y: -1 }), + Diagonal::BottomLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -2, y: 1 }), + Diagonal::BottomLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -2, y: -2 }), + Diagonal::BottomLeft + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -10000, y: 9999 }), + Diagonal::BottomLeft + ); + + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 0, y: -1 }), + Diagonal::BottomRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: 1, y: -2 }), + Diagonal::BottomRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -1, y: -2 }), + Diagonal::BottomRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { x: -2, y: -3 }), + Diagonal::BottomRight + ); + assert_eq!( + get_diagonal_ahead(&Coordinate { + x: -9999, + y: -10000 + }), + Diagonal::BottomRight + ); + } + + #[test] + fn test_get_value_at_coords() { + let diagonal_props = &get_diagonal_properties(); + + assert_eq!( + get_value_at_coords(&Coordinate { x: 0, y: 0 }, diagonal_props), + 1, + ); + assert_eq!( + get_value_at_coords(&Coordinate { x: 1, y: 0 }, diagonal_props), + 2, + ); + assert_eq!( + get_value_at_coords(&Coordinate { x: 1, y: 1 }, diagonal_props), + 3, + ); + assert_eq!( + get_value_at_coords(&Coordinate { x: 0, y: 1 }, diagonal_props), + 4, + ); + + assert_eq!( + get_value_at_coords(&Coordinate { x: 1, y: -1 }, diagonal_props), + 9, + ); + assert_eq!( + get_value_at_coords(&Coordinate { x: 2, y: -1 }, diagonal_props), + 10, + ); + assert_eq!( + get_value_at_coords(&Coordinate { x: 2, y: 0 }, diagonal_props), + 11, + ); + } +} diff --git a/tests/test_grambulate.rs b/tests/test_grambulate.rs new file mode 100644 index 0000000..56c9fe9 --- /dev/null +++ b/tests/test_grambulate.rs @@ -0,0 +1,27 @@ +use grambulate::grambulate; + +#[test] +fn test_grambulate() -> Result<(), String> { + assert_eq!(grambulate(1, 2)?, 11); + assert_eq!(grambulate(1, 9)?, 25); + assert_eq!(grambulate(1, 7)?, 21); + assert_eq!(grambulate(7, 1)?, 3); + assert_eq!(grambulate(9, 5)?, 37); + assert_eq!(grambulate(114, 116)?, 118); + + assert_eq!(grambulate(120842908, 765255103)?, 2562110898); + assert_eq!(grambulate(283311628, 982590465)?, 3030985010); + assert_eq!(grambulate(841055110, 210288268)?, 1373718672); + assert_eq!(grambulate(435594887, 106289809)?, 1721403763); + assert_eq!(grambulate(239517836, 742207561)?, 4894885652); + assert_eq!(grambulate(128398453, 764847139)?, 2794172097); + assert_eq!(grambulate(751271524, 945230955)?, 4979352842); + + // precision + assert_eq!( + grambulate(9223372036854775807, 9223372036854775808)?, + 9223372036854775809 + ); + + Ok(()) +}