From 9367dc0caff7ee2113b59854b7fb90c23eb17014 Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Wed, 10 Dec 2025 23:33:11 +0100 Subject: [PATCH] impl using pure rust --- .envrc | 1 + .gitignore | 4 + Cargo.lock | 16 +++ Cargo.toml | 18 +++ flake.lock | 137 +++++++++++++++++++++++ flake.nix | 94 ++++++++++++++++ src/lib.rs | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 25 +++++ 8 files changed, 605 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..c3f8276 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake . -L --impure diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f815133 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.direnv +shuffles/target +target +result diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..77aecf7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "shuffles" +version = "0.1.0" +dependencies = [ + "bit-vec", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a41707a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "shuffles" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "shuffles" +crate-type = ["rlib"] + +[[bin]] +name = "shuffles" + +[dependencies] +bit-vec = "*" + +[profile.release] +debug = true diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7e56e6c --- /dev/null +++ b/flake.lock @@ -0,0 +1,137 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1765145449, + "narHash": "sha256-aBVHGWWRzSpfL++LubA0CwOOQ64WNLegrYHwsVuVN7A=", + "owner": "ipetkov", + "repo": "crane", + "rev": "69f538cdce5955fcd47abfed4395dc6d5194c1c5", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1765252472, + "narHash": "sha256-byMt/uMi7DJ8tRniFopDFZMO3leSjGp6GS4zWOFT+uQ=", + "owner": "nix-community", + "repo": "fenix", + "rev": "8456b985f6652e3eef0632ee9992b439735c5544", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1765270179, + "narHash": "sha256-g2a4MhRKu4ymR4xwo+I+auTknXt/+j37Lnf0Mvfl1rE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "677fbe97984e7af3175b6c121f3c39ee5c8d62c9", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "fenix": "fenix", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1765120009, + "narHash": "sha256-nG76b87rkaDzibWbnB5bYDm6a52b78A+fpm+03pqYIw=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "5e3e9c4e61bba8a5e72134b9ffefbef8f531d008", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1765334520, + "narHash": "sha256-jTof2+ir9UPmv4lWksYO6WbaXCC0nsDExrB9KZj7Dz4=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "db61f666aea93b28f644861fbddd37f235cc5983", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "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..91d7d85 --- /dev/null +++ b/flake.nix @@ -0,0 +1,94 @@ +{ + # Build Pyo3 package + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + crane.url = "github:ipetkov/crane"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + inputs: + inputs.flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ inputs.rust-overlay.overlays.default ]; + config = { + allowUnfree = true; + cudaSupport = false; + }; + }; + lib = pkgs.lib; + + python_version = pkgs.python313; + wheel_tail = "cp313-cp313-linux_x86_64"; # Change if python_version changes + + # Get a custom rust toolchain + craneLib = (inputs.crane.mkLib pkgs).overrideToolchain ( + p: inputs.fenix.packages.${system}.complete.toolchain + ); + + project_name = (craneLib.crateNameFromCargoToml { cargoToml = ./shuffles/Cargo.toml; }).pname; + project_version = (craneLib.crateNameFromCargoToml { cargoToml = ./shuffles/Cargo.toml; }).version; + + crate_cfg = { + src = + let + fs = lib.fileset; + in + fs.toSource { + root = ./shuffles; + fileset = fs.unions [ + ./shuffles/src/lib.rs + ./shuffles/Cargo.lock + ./shuffles/Cargo.toml + ./shuffles/pyproject.toml + ]; + }; + nativeBuildInputs = [ python_version ]; + # doCheck = true; + # buildInputs = []; + }; + + crate_artifacts = craneLib.buildDepsOnly ( + crate_cfg + // { + pname = "${project_name}-artifacts"; + version = project_version; + } + ); + + # Build the library, then re-use the target dir to generate the wheel file with maturin + crate_pkg = ( + craneLib.buildPackage ( + crate_cfg + // { + pname = project_name; + version = project_version; + cargoArtifacts = crate_artifacts; + } + ) + ); + in + rec { + devShells.default = craneLib.devShell { + packages = [ + pkgs.cargo + pkgs.cargo-flamegraph + ]; + shellHook = '' + export CUDA_PATH=${pkgs.cudatoolkit} + ''; + }; + } + ); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fd73ed3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,310 @@ +#![feature(coroutines, coroutine_trait, stmt_expr_attributes)] +use bit_vec::BitVec; +use std::{ + fmt::{Display, Error, Formatter}, + ops::Coroutine, +}; + +type Person = usize; + +#[derive(Debug, Clone, Copy)] +enum Side { + Top, + Bot, +} + +#[derive(Clone)] +pub struct Seating { + amount: usize, + people_top: Vec>, + people_bot: Vec>, +} + +#[derive(Clone)] +pub struct Adjacencies { + amap: Vec, +} + +impl Adjacencies { + pub fn new(size: usize) -> Self { + Adjacencies { + amap: (0..size).map(|_| BitVec::from_elem(size, false)).collect(), + } + } + + pub fn reset(&mut self) { + self.amap.iter_mut().for_each(|bitvec| { + bitvec.clear(); + }) + } + + fn is_disjoint(&mut self, other: &Adjacencies) -> bool { + self.amap.iter_mut().enumerate().all(|(key, bitvec)| { + bitvec.and(&other.amap[key]); + !bitvec.any() + }) + } + + fn intersect(&self, other: &Adjacencies) -> Adjacencies { + Adjacencies { + amap: self + .amap + .iter() + .enumerate() + .map(|(key, bitset)| { + let mut bc = bitset.clone(); + bc.and(&other.amap[key]); + bc + }) + .collect(), + } + } +} + +impl Display for Adjacencies { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { + for (key, val) in self.amap.iter().enumerate() { + write!(fmt, "{}:", key)?; + for other in val { + write!(fmt, " {},", other)?; + } + write!(fmt, "\n")?; + } + Ok(()) + } +} + +impl Display for Seating { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { + let width = if self.amount == 0 { + 1 + } else { + (self.amount.ilog10() + 2) as usize + }; + for peep in self.people_top.iter() { + if let Some(perp) = peep { + write!(fmt, "{:width$}", perp)?; + } else { + write!(fmt, "{:>1$}", "X", width)?; + } + } + write!(fmt, "\n")?; + for peep in self.people_bot.iter() { + if let Some(perp) = peep { + write!(fmt, "{:width$}", perp)?; + } else { + write!(fmt, "{:>1$}", "X", width)?; + } + } + Ok(()) + } +} + +impl Seating { + pub fn calc_alternatives( + &self, + size: usize, + ) -> impl Coroutine { + let mut old_adjacencies = Adjacencies::new(size); + self.get_adjacent(&mut old_adjacencies); + + #[coroutine] + move || { + let mut checked_locations = 0; + let mut new_adjacencies = Adjacencies::new(size); + let mut tree = vec![((0..size).collect::>(), Seating::make_empty(size))]; + while let Some((to_place, start)) = tree.pop() { + checked_locations += 1; + if to_place.len() == 0 { + yield start + } else { + for pos in start.empty_table_coords() { + let mut dup = start.clone(); + dup.seat_person(to_place[0], pos); + new_adjacencies.reset(); + dup.get_adjacent(&mut new_adjacencies); + if new_adjacencies.is_disjoint(&old_adjacencies) { + tree.push((to_place[1..].to_vec(), dup)); + } + } + } + } + checked_locations + } + } +} + +impl Seating { + pub fn make_seating(size: usize) -> Self { + let top_amt = size / 2; + let bot_amt = size - top_amt; + Seating { + amount: size, + people_top: (0..top_amt).map(|i| Some(i * 2 + 1)).collect(), + people_bot: (0..bot_amt).map(|i| Some(i * 2)).collect(), + } + } + + pub fn make_empty(size: usize) -> Self { + let top_amt = size / 2; + Seating { + amount: size, + people_top: (0..top_amt).map(|_| None).collect(), + people_bot: (top_amt..size).map(|_| None).collect(), + } + } + + fn make_clone(&self) -> Self { + self.clone() + } + + fn empty_table_coords(&self) -> Vec<(usize, Side)> { + self.people_top + .iter() + .enumerate() + .filter_map(|(coord, val)| { + if val.is_none() { + Some((coord, Side::Top)) + } else { + None + } + }) + .chain( + self.people_bot + .iter() + .enumerate() + .filter_map(|(coord, val)| { + if val.is_none() { + Some((coord, Side::Bot)) + } else { + None + } + }), + ) + .collect() + } + + fn seat_person(&mut self, person: usize, position: (usize, Side)) { + let (x, y) = position; + assert!( + self.index(x, y) == None, + "Can't seat person {} where {} is already sitting", + person, + self.index(x, y).expect("there was someone sitting there") + ); + let side = match y { + Side::Top => &mut self.people_top, + Side::Bot => &mut self.people_bot, + }; + side[x] = Some(person); + } + + fn get_adjacent(&self, prev_adj: &mut Adjacencies) { + for (i, person) in self.people_top.iter().enumerate() { + if let &Some(person) = person { + assert!( + prev_adj.amap.len() > person, + "adjacencies must contain all possible people" + ); + + if i > 0 { + // to their right: + if let Some(adjacent) = self.index(i - 1, Side::Top) { + prev_adj.amap[person].set(adjacent, true); + } + // across right: + if let Some(adjacent) = self.index(i - 1, Side::Bot) { + prev_adj.amap[person].set(adjacent, true) + } + } + // to their left: + if let Some(adjacent) = self.index(i + 1, Side::Top) { + prev_adj.amap[person].set(adjacent, true); + } + // across: + if let Some(adjacent) = self.index(i, Side::Bot) { + prev_adj.amap[person].set(adjacent, true); + } + // across left: + if let Some(adjacent) = self.index(i + 1, Side::Bot) { + prev_adj.amap[person].set(adjacent, true); + } + } + } + + for (i, person) in self.people_bot.iter().enumerate() { + if let &Some(person) = person { + assert!( + prev_adj.amap.len() > person, + "adjacencies must contain all possible people" + ); + + // Layout I think I want to use? + // [0, 1, 2, 3, 4, 5] + // [6, 7, 8, 9,10,11] + + if i > 0 { + // to their left: + if let Some(adjacent) = self.index(i - 1, Side::Bot) { + prev_adj + .amap + .get_mut(person) + .expect("bitset was created above") + .set(adjacent, true); + } + // across left: + if let Some(adjacent) = self.index(i - 1, Side::Top) { + prev_adj + .amap + .get_mut(person) + .expect("bitset was created above") + .set(adjacent, true); + } + } + // to their right: + if let Some(adjacent) = self.index(i + 1, Side::Bot) { + prev_adj + .amap + .get_mut(person) + .expect("bitset was created above") + .set(adjacent, true); + } + // across: + if let Some(adjacent) = self.index(i, Side::Top) { + prev_adj + .amap + .get_mut(person) + .expect("bitset was created above") + .set(adjacent, true); + } + // across right: + if let Some(adjacent) = self.index(i + 1, Side::Top) { + prev_adj + .amap + .get_mut(person) + .expect("bitset was created above") + .set(adjacent, true); + } + } + } + } + + fn index(&self, x: usize, y: Side) -> Option { + match y { + Side::Top => { + if x >= self.people_top.len() { + None + } else { + Some(self.people_top[x]?) + } + } + Side::Bot => { + if x >= self.people_bot.len() { + None + } else { + Some(self.people_bot[x]?) + } + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..04d8514 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,25 @@ +#![feature(coroutine_trait)] +use std::{ + ops::{Coroutine, CoroutineState}, + pin::Pin, +}; + +use shuffles::*; + +fn main() { + for size in 0..17 { + let table = Seating::make_seating(size); + println!("We have a table\n{table}"); + let mut corot = table.calc_alternatives(size); + println!("searching size {size}"); + loop { + match Pin::new(&mut corot).resume(()) { + CoroutineState::Yielded(val) => println!("found setting\n{val}"), + CoroutineState::Complete(total_checked) => { + println!("checked {total_checked} in total"); + break; + } + } + } + } +}