The Lowest Level PL
HOME
Table of Contents
Let’s compare the different implementations of a simple problem in three PLs. All three have some common characteristics.
- Exhaustive pattern matching
- No null/nil
- Algeraic data types
The goal is to identify which PL of all is the lowest level.
Rust’s implementation isn’t ideal, but I kept it that way to be as similar as possible to the other implementations. Also Rust doesn’t have recursion.
The math notation at some places cannot be copied, the renderer doesn’t recognize it as a math tex notation. I cannot figure out why, but first time I wrote math using tex.
Problem Statement
6 colored cubes into 9 holes
We place six colored cubes — two red (R), two green (G), two blue (B) — into nine labeled holes (positions in a row). The remaining three holes are empty (we denote empty by E or “.”).
Constraint: no two adjacent colored cubes of the same color may occur. Empty holes may be adjacent to anything (including empties and same-colored neighbors).
How many distinct arrangements (sequences of length 9 of the multiset \(\{R^2,G^2,B^2,E^3\}\)) satisfy the adjacency constraint?
Math
Enumeration of placements
Let the multiset be \(\{R,R,G,G,B,B,E,E,E\}\). Without adjacency restrictions, the number of distinct sequences is the multinomial \[ N_{\text{total}}=\frac{9!}{2!\,2!\,2!\,3!}. \]
Let for a colour \(X\in\{R,G,B\}\) the event \(A_X\) be ``the two \(X\)-cubes are adjacent somewhere in the sequence.’’ Because each colour appears exactly twice, \(A_X\) is equivalent to the two \(X\)-cubes forming a single adjacent block \(XX\).
We want sequences avoiding all three events \(A_R,A_G,A_B\). We will count them by inclusion–exclusion.
Inclusion–exclusion setup
By inclusion–exclusion,
Ngood = Ntotal - ∑X N(AX) + ∑X<Y N(AX∩ AY) - N(AR∩ AG∩ AB)
Ngood = Ntotal - ∑X∈\{R,G,B\} N(AX) + ∑X<Y N(AX∩ AY) - N(AR∩ AG∩ AB)
We compute each term by replacing each adjacent pair \(XX\) by a single block and counting permutations of the resulting multiset.
Total (no restrictions)
\[ N_{\text{total}}=\frac{9!}{2!\,2!\,2!\,3!}. \]
Compute the factorials and denominators explicitly:
\begin{align*} 9!&=362\,880,\\ 2!\,2!\,2!\,3!&=2\cdot2\cdot2\cdot6=48,\\ N_{\text{total}}&=\frac{362\,880}{48}=7\,560. \end{align*}One colour adjacent (e.g. \(A_R\))
Treat the two \(R\)-cubes as a single block \(RR\). The remaining multiset is \(\{RR, G,G, B,B, E,E,E\}\) of size 8 with multiplicities \(G:2, B:2, E:3\). Thus \[ N(A_R)=\frac{8!}{2!\,2!\,3!}. \] Compute:
\begin{align*} 8!&=40\,320,\\ 2!\,2!\,3!&=2\cdot2\cdot6=24,\\ N(A_R)&=\frac{40\,320}{24}=1\,680. \end{align*}By symmetry the same value holds for \(N(A_G)\) and \(N(A_B)\). Hence \[ \sum_X N(A_X)=3\cdot1\,680=5\,040. \]
Two colours adjacent (e.g. \(A_R\cap A_G\))
Treat \(RR\) and \(GG\) as two blocks. The remaining multiset is \(\{RR,GG,B,B,E,E,E\}\) of size 7 with multiplicities \(B:2, E:3\). Thus \[ N(A_R\cap A_G)=\frac{7!}{2!\,3!}. \] Compute:
\begin{align*} 7!&=5\,040,\\ 2!\,3!&=2\cdot6=12,\\ N(A_R\cap A_G)&=\frac{5\,040}{12}=420. \end{align*}There are \(\binom{3}{2}=3\) unordered pairs of colours, so \[ \sum_{X\lt Y} N(A_X\cap A_Y) = 3\cdot420 = 1260. \]
All three colours adjacent (\(A_R\cap A_G\cap A_B\))
Treat \(RR,GG,BB\) as three blocks. The remaining multiset is \(\{RR,GG,BB,E,E,E\}\) of size 6 with multiplicity \(E:3\). Thus \[ N(A_R\cap A_G\cap A_B)=\frac{6!}{3!}. \] Compute:
\begin{align*} 6!&=720,\\ 3!&=6,\\ N(A_R\cap A_G\cap A_B)&=\frac{720}{6}=120. \end{align*}Put everything together
Apply inclusion–exclusion:
Ngood = Ntotal - ∑X N(AX) + ∑X<Y N(AX∩ AY) - N(AR∩ AG∩ AB) = 7560 - 5040 + 1260 - 120 = 3660.
Therefore there are \(\boxed{3\,660}\) admissible arrangements.
Lean
inductive Color where | R | G | B | E deriving Repr, DecidableEq structure Counts where r : Nat g : Nat b : Nat e : Nat deriving Repr def countsZero (c : Counts) : Bool := c.r == 0 && c.g == 0 && c.b == 0 && c.e == 0 def decCount (c : Counts) (col : Color) : Counts := match col with | Color.R => { c with r := c.r - 1 } | Color.G => { c with g := c.g - 1 } | Color.B => { c with b := c.b - 1 } | Color.E => { c with e := c.e - 1 } -- allowed choices given remaining counts and previous placed color def allowed (counts : Counts) (prev : Option Color) : List Color := let ok (col : Color) (avail : Nat) : Bool := avail > 0 && match prev with | none => true | some p => (p != col) || (col == Color.E) -- empties may repeat; colored must differ ([Color.R, Color.G, Color.B, Color.E]).filter fun col => match col with | Color.R => ok Color.R counts.r | Color.G => ok Color.G counts.g | Color.B => ok Color.B counts.b | Color.E => ok Color.E counts.e partial def build (rem : Nat) (prev : Option Color) (counts : Counts) : List (List Color) := if rem == 0 then if countsZero counts then [ [] ] else [] else let choices := allowed counts prev -- avoid using `List.bind` in case of name-resolution differences; -- build result by folding over choices and appending mapped tails choices.foldl (fun acc c => let counts' := decCount counts c let tails := build (rem - 1) (some c) counts' acc ++ (tails.map fun t => c :: t) ) [] def showColor : Color -> String | Color.R => "R" | Color.G => "G" | Color.B => "B" | Color.E => "." def main : IO Unit := do let initial : Counts := { r := 2, g := 2, b := 2, e := 3 } let solutions := build 9 none initial IO.println s!"Found {solutions.length} solutions (each printed as a 9-length sequence; '.' = empty)" for s in solutions do IO.println (String.intercalate " " (s.map showColor))
Haskell
module Main where data Color = R | G | B | E deriving (Eq, Show) data Counts = Counts { cr :: Int , cg :: Int , cb :: Int , ce :: Int } deriving (Show) countsZero :: Counts -> Bool countsZero (Counts r g b e) = r == 0 && g == 0 && b == 0 && e == 0 decCount :: Counts -> Color -> Counts decCount c col = case col of R -> c { cr = cr c - 1 } G -> c { cg = cg c - 1 } B -> c { cb = cb c - 1 } E -> c { ce = ce c - 1 } allowed :: Counts -> Maybe Color -> [Color] allowed counts prev = filter ok [R, G, B, E] where avail c = case c of R -> cr counts G -> cg counts B -> cb counts E -> ce counts ok c = avail c > 0 && case prev of Nothing -> True Just p -> (p /= c) || (c == E) -- empties may repeat; colored must differ -- build positions: rem = remaining slots to fill build :: Int -> Maybe Color -> Counts -> [[Color]] build 0 _ counts = if countsZero counts then [[]] else [] build rem prev counts = concatMap extend (allowed counts prev) where extend c = map (c :) (build (rem - 1) (Just c) (decCount counts c)) showColor :: Color -> String showColor R = "R" showColor G = "G" showColor B = "B" showColor E = "." main :: IO () main = do let initial = Counts { cr = 2, cg = 2, cb = 2, ce = 3 } solutions = build 9 Nothing initial putStrLn $ "Found " ++ show (length solutions) ++ " solutions ('.' = empty)" mapM_ (putStrLn . unwords . map showColor) solutions
Rust
use std::fmt; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Color { R, G, B, E, } impl fmt::Display for Color { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Color::R => "R", Color::G => "G", Color::B => "B", Color::E => ".", }; write!(f, "{}", s) } } #[derive(Clone, Copy, Debug)] struct Counts { r: u32, g: u32, b: u32, e: u32, } impl Counts { fn zero(&self) -> bool { self.r == 0 && self.g == 0 && self.b == 0 && self.e == 0 } } /// decrement one count of `col` (caller must ensure availability > 0) fn dec_count(mut c: Counts, col: Color) -> Counts { match col { Color::R => c.r -= 1, Color::G => c.g -= 1, Color::B => c.b -= 1, Color::E => c.e -= 1, } c } /// allowed choices given remaining counts and previous placed color fn allowed(counts: Counts, prev: Option<Color>) -> Vec<Color> { let mut v = Vec::new(); let ok = |col: Color, avail: u32| -> bool { if avail == 0 { return false; } match prev { None => true, Some(p) => p != col || col == Color::E, // empties may repeat; colored must differ } }; if ok(Color::R, counts.r) { v.push(Color::R); } if ok(Color::G, counts.g) { v.push(Color::G); } if ok(Color::B, counts.b) { v.push(Color::B); } if ok(Color::E, counts.e) { v.push(Color::E); } v } /// backtracking builder fn backtrack( rem: usize, prev: Option<Color>, counts: Counts, current: &mut Vec<Color>, solutions: &mut Vec<Vec<Color>>, ) { if rem == 0 { if counts.zero() { solutions.push(current.clone()); } return; } for col in allowed(counts, prev) { let counts_next = dec_count(counts, col); current.push(col); backtrack(rem - 1, Some(col), counts_next, current, solutions); current.pop(); } } fn main() { let initial = Counts { r: 2, g: 2, b: 2, e: 3, }; let mut solutions: Vec<Vec<Color>> = Vec::new(); let mut current: Vec<Color> = Vec::with_capacity(9); backtrack(9, None, initial, &mut current, &mut solutions); println!( "Found {} solutions (each printed as a 9-length sequence; '.' = empty)", solutions.len() ); solutions.iter().for_each(|s| { let line = s.iter() .map(|c| c.to_string()) .collect::<Vec<_>>() .join(" "); println!("{}", line); }); }