From e0f922104b09d99560d109ecf4ca6ff950c0360a Mon Sep 17 00:00:00 2001 From: ia7ck <23146842+ia7ck@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:51:03 +0900 Subject: [PATCH 1/2] doubling --- libs/doubling/Cargo.toml | 13 ++ libs/doubling/src/lib.rs | 260 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 libs/doubling/Cargo.toml create mode 100644 libs/doubling/src/lib.rs diff --git a/libs/doubling/Cargo.toml b/libs/doubling/Cargo.toml new file mode 100644 index 00000000..ef95b3e5 --- /dev/null +++ b/libs/doubling/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "doubling" +version = "0.1.0" +authors = ["ia7ck <23146842+ia7ck@users.noreply.github.com>"] +edition = "2024" +license = "CC0-1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[dev-dependencies] +proptest = "1.9.0" diff --git a/libs/doubling/src/lib.rs b/libs/doubling/src/lib.rs new file mode 100644 index 00000000..4f2ff1f7 --- /dev/null +++ b/libs/doubling/src/lib.rs @@ -0,0 +1,260 @@ +/// ダブリング +/// +/// # Examples +/// +/// ``` +/// use doubling::{Doubling, Transition, Value}; +/// +/// #[derive(Debug, PartialEq)] +/// struct Sum(i64); +/// +/// impl Value for Sum { +/// fn op(&self, other: &Self) -> Self { +/// Sum(self.0 + other.0) +/// } +/// } +/// +/// struct E { +/// to: usize, +/// value: i64, +/// } +/// +/// // 0, 1, 2, 0, 1, 2, ... +/// let n = 3; +/// let to = vec![ +/// E { to: 1, value: 1 }, +/// E { to: 2, value: 10 }, +/// E { to: 0, value: 100 }, +/// ]; +/// let doubling = Doubling::new(n, 100, |i| { +/// let e = &to[i]; +/// Transition::new(e.to, Sum(e.value)) +/// }); +/// +/// assert_eq!( +/// doubling.fold(0, 4, Sum(0), |acc, t| Sum(acc.0 + t.value.0)), +/// // 0 -> 1 -> 2 -> 0 -> 1 +/// Sum(1 + 10 + 100 + 1) +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct Doubling { + transitions: Vec>, + n_state: usize, + max_steps: usize, + log2_max_steps: usize, +} + +#[derive(Debug, Clone)] +pub struct Transition { + pub next: usize, + pub value: V, +} + +impl Transition { + pub fn new(next: usize, value: V) -> Self { + Self { next, value } + } +} + +pub trait Value { + fn op(&self, other: &Self) -> Self; +} + +impl Doubling +where + V: Value, +{ + /// ダブリングのテーブルを構築します。 + /// + /// `step1(i)`は状態`i`から1回の遷移における + /// + /// - 遷移先の状態 + /// - その遷移にともなう値 + /// + /// を返す関数。 + pub fn new(n_state: usize, max_steps: usize, step1: F) -> Self + where + F: Fn(usize) -> Transition, + { + let log2_max_steps = if max_steps == 0 { + 0 + } else { + max_steps.ilog2() as usize + }; + + let mut transitions = Vec::with_capacity(n_state * (log2_max_steps + 1)); + for i in 0..n_state { + let t = step1(i); + + assert!(t.next < n_state); + + transitions.push(t); + } + + for k in 1..=log2_max_steps { + let offset = n_state * (k - 1); + for i in 0..n_state { + let t1 = &transitions[offset + i]; + let t2 = &transitions[offset + t1.next]; + transitions.push(Transition { + next: t2.next, + value: t1.value.op(&t2.value), + }); + } + } + + Self { + transitions, + n_state, + max_steps, + log2_max_steps, + } + } + + /// 状態`start`から`step`回の遷移、初期値`init`から始めて`f`で畳みこんだ結果を返します。 + pub fn fold(&self, start: usize, step: usize, init: A, f: F) -> A + where + F: Fn(A, &Transition) -> A, + { + assert!(start < self.n_state); + assert!(step <= self.max_steps); + + let mut i = start; + let mut acc = init; + for k in 0..=self.log2_max_steps { + if step >> k & 1 == 1 { + let offset = self.n_state * k; + let t = &self.transitions[offset + i]; + (i, acc) = (t.next, f(acc, t)); + } + } + + acc + } +} + +#[cfg(test)] +mod tests { + use ::proptest::{collection, prelude::*}; + + use super::*; + + #[derive(Debug, PartialEq)] + struct Sum(i64); + + impl Value for Sum { + fn op(&self, other: &Self) -> Self { + Sum(self.0 + other.0) + } + } + + #[test] + fn test_cycle() { + struct E { + to: usize, + value: i64, + } + + // 0, 1, 2, 0, 1, 2, ... + let n = 3; + let to = vec![ + E { to: 1, value: 1 }, + E { to: 2, value: 10 }, + E { to: 0, value: 100 }, + ]; + let doubling = Doubling::new(n, 100, |i| { + let e = &to[i]; + Transition::new(e.to, Sum(e.value)) + }); + + assert_eq!( + doubling.fold(0, 0, Sum(0), |acc, t| Sum(acc.0 + t.value.0)), + Sum(0) + ); + assert_eq!( + doubling.fold(0, 1, Sum(0), |acc, t| Sum(acc.0 + t.value.0)), + Sum(1) + ); + assert_eq!( + doubling.fold(0, 2, Sum(0), |acc, t| Sum(acc.0 + t.value.0)), + Sum(1 + 10) + ); + assert_eq!( + doubling.fold(0, 3, Sum(0), |acc, t| Sum(acc.0 + t.value.0)), + Sum(1 + 10 + 100) + ); + assert_eq!( + doubling.fold(0, 4, Sum(0), |acc, t| Sum(acc.0 + t.value.0)), + Sum(1 + 10 + 100 + 1) + ); + } + + impl Value for String { + fn op(&self, other: &Self) -> Self { + format!("{}{}", self, other) + } + } + + proptest! { + #[test] + fn test_fold_associativity( + (n_state, max_steps, nexts, values, start, step1, step2) in (1_usize..=10, 0_usize..=100) + .prop_flat_map(|(n_state, max_steps)| { + ( + Just(n_state), + Just(max_steps), + collection::vec(0..n_state, n_state), + collection::vec(proptest::char::range('a', 'z'), n_state), + ) + }) + .prop_flat_map(|(n_state, max_steps, nexts, values)| { + ( + Just(n_state), + Just(max_steps), + Just(nexts), + Just(values), + 0..n_state, + 0..=max_steps, + ) + }) + .prop_flat_map(|(n_state, max_steps, nexts, values, start, step1)| { + ( + Just(n_state), + Just(max_steps), + Just(nexts), + Just(values), + Just(start), + Just(step1), + 0..=(max_steps - step1), + ) + }) + ) { + let doubling = Doubling::new(n_state, max_steps, |i| { + Transition::new(nexts[i], values[i].to_string()) + }); + + #[derive(Debug, Clone, PartialEq)] + struct Acc { + value: String, + state: usize, + } + + let init = Acc { + value: String::new(), + state: start, + }; + let f = |acc: Acc, t: &Transition| Acc { + value: format!("{}{}", acc.value, t.value), + state: t.next, + }; + + let combined = doubling.fold(start, step1 + step2, init.clone(), f); + + let intermediate = doubling.fold(start, step1, init.clone(), f); + let split = doubling.fold(intermediate.state, step2, intermediate.clone(), f); + + prop_assert_eq!(combined.value, split.value); + } + } +} From a19e2690715dd4894e1b26e96db3a08d35ed9ebd Mon Sep 17 00:00:00 2001 From: ikd <23146842+ia7ck@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:27:43 +0900 Subject: [PATCH 2/2] fn mut Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- libs/doubling/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/doubling/src/lib.rs b/libs/doubling/src/lib.rs index 4f2ff1f7..98f14377 100644 --- a/libs/doubling/src/lib.rs +++ b/libs/doubling/src/lib.rs @@ -113,9 +113,9 @@ where } /// 状態`start`から`step`回の遷移、初期値`init`から始めて`f`で畳みこんだ結果を返します。 - pub fn fold(&self, start: usize, step: usize, init: A, f: F) -> A + pub fn fold(&self, start: usize, step: usize, init: A, mut f: F) -> A where - F: Fn(A, &Transition) -> A, + F: FnMut(A, &Transition) -> A, { assert!(start < self.n_state); assert!(step <= self.max_steps);