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.

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);
    });

}

Author: emporas

Created: 2025-09-28 Sun 02:38