From b9feba74a418725fa8af638aeeda92346c8bb3f6 Mon Sep 17 00:00:00 2001 From: asonix Date: Sun, 26 May 2024 13:38:08 -0500 Subject: [PATCH] Permutation and Choose iterators --- .gitignore | 3 + Cargo.lock | 7 ++ Cargo.toml | 8 ++ examples/four.rs | 25 +++++++ flake.lock | 61 ++++++++++++++++ flake.nix | 36 +++++++++ src/lib.rs | 185 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 325 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 examples/four.rs create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1cbd80d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/.direnv +/.envrc diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b076aa5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "permute" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0fb0b65 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "permute" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/examples/four.rs b/examples/four.rs new file mode 100644 index 0000000..459e979 --- /dev/null +++ b/examples/four.rs @@ -0,0 +1,25 @@ +fn main() { + let permute_input = [1, 2, 3, 4]; + + for (i, permutation) in permute::permute(permute_input.clone()).enumerate() { + println!("{i}: {permutation:?}"); + } + let (size, _) = permute::permute(permute_input).size_hint(); + println!("size {size}"); + println!(); + + let choose_input = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + for (i, chosen) in permute::choose::<_, 4>(&choose_input).enumerate() { + println!("{i}: {chosen:?}"); + } + let (size, _) = permute::choose::<_, 4>(&choose_input).size_hint(); + println!("size {size}"); + println!(); + + for (i, permutation) in permute::choose_permute::<_, 4>(&choose_input).enumerate() { + println!("{i}: {permutation:?}"); + } + let (size, _) = permute::choose_permute::<_, 4>(&choose_input).size_hint(); + println!("size {size}"); +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..0e2a6e8 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1716509168, + "narHash": "sha256-4zSIhSRRIoEBwjbPm3YiGtbd8HDWzFxJjw5DYSDy1n8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "bfb7a882678e518398ce9a31a881538679f6f092", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..4aa0c6b --- /dev/null +++ b/flake.nix @@ -0,0 +1,36 @@ +{ + description = "actix-http"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + }; + in + { + packages.default = pkgs.hello; + + devShell = with pkgs; mkShell { + nativeBuildInputs = [ + cargo + cargo-outdated + clippy + openssl + pkg-config + rust-analyzer + rustc + rustfmt + stdenv.cc + taplo + ]; + + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + }; + }); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3b7d18b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,185 @@ +pub fn choose_permute( + source: &[T], +) -> impl Iterator + '_ { + ChoosePermute { + choose: choose(source), + permute: None, + } +} + +pub fn permute(source: [T; SIZE]) -> Permute { + Permute { + source, + count: [0; SIZE], + closed: false, + } +} + +pub fn choose(source: &[T]) -> Choose<'_, T, SIZE> { + if source.len() < SIZE { + panic!("Source to small to choose {SIZE} elements"); + } + + Choose { + source, + count: [0; SIZE], + closed: false, + } +} + +pub struct ChoosePermute<'a, T, const SIZE: usize> { + choose: Choose<'a, T, SIZE>, + permute: Option>, +} + +impl<'a, T: Clone, const SIZE: usize> Iterator for ChoosePermute<'a, T, SIZE> { + type Item = [T; SIZE]; + + fn next(&mut self) -> Option { + loop { + if let Some(permute) = self.permute.as_mut() { + if let Some(item) = permute.next() { + return Some(item); + } + + self.permute.take(); + } + + let chosen = self.choose.next()?; + + self.permute = Some(permute(chosen)); + } + } + + fn size_hint(&self) -> (usize, Option) { + let size = self.choose.size_hint().0 * factorial(SIZE); + + (size, Some(size)) + } +} + +pub struct Choose<'a, T, const SIZE: usize> { + source: &'a [T], + count: [usize; SIZE], + closed: bool, +} + +pub struct Permute { + source: [T; SIZE], + count: [usize; SIZE], + closed: bool, +} + +impl<'a, T: Clone, const SIZE: usize> Choose<'a, T, SIZE> { + fn increment(&mut self) { + let mut any_updated = false; + + for i in (0..SIZE).rev() { + let total_offset = self.count.iter().copied().sum::() + SIZE; + + if total_offset < self.source.len() { + self.count[i] += 1; + any_updated = true; + break; + } else { + self.count[i] = 0; + } + } + + self.closed = !any_updated; + } + + fn choose(&self) -> [T; SIZE] { + let mut idx = 0; + let mut total_offset = 0; + + self.count.clone().map(|offset| { + total_offset += offset; + let i = idx + total_offset; + idx += 1; + + self.source[i].clone() + }) + } +} + +impl<'a, T: Clone, const SIZE: usize> Iterator for Choose<'a, T, SIZE> { + type Item = [T; SIZE]; + + fn next(&mut self) -> Option { + if self.closed { + None + } else { + self.increment(); + + Some(self.choose()) + } + } + + fn size_hint(&self) -> (usize, Option) { + let numerator = factorial(self.source.len()); + let denominator = factorial(SIZE) * factorial(self.source.len() - SIZE); + + let size = numerator / denominator; + + (size, Some(size)) + } +} + +impl Permute { + fn increment(&mut self) { + let mut any_updated = false; + + for (idx, count) in self.count.iter_mut().rev().enumerate() { + if *count < idx { + *count += 1; + any_updated = true; + break; + } else { + *count = 0; + } + } + + self.closed = !any_updated; + } + + fn permmute(&self) -> [T; SIZE] { + let mut out = self.source.clone(); + + for (idx, count) in self.count.iter().enumerate() { + if *count > 0 { + out.swap(idx, idx + *count); + } + } + + out + } +} + +fn factorial(num: usize) -> usize { + if num == 0 { + return 0; + } + + (1..=num).fold(1, |acc, item| acc * item) +} + +impl Iterator for Permute { + type Item = [T; SIZE]; + + fn next(&mut self) -> Option { + if self.closed { + None + } else { + self.increment(); + + Some(self.permmute()) + } + } + + fn size_hint(&self) -> (usize, Option) { + let size = factorial(SIZE); + + (size, Some(size)) + } +}