Η Γλώσσα Κατώτερου Επιπέδου
Αρχική Σελίδα
Πίνακας Περιεχομένων
Ας συγκρίνουμε τις διαφορετικές υλοποιήσεις ενός απλού προβλήματος σε τρεις γλώσσες προγραμματισμού. Και οι τρεις έχουν μερικά κοινά χαρακτηριστικά.
- Εξαντλητική απαρίθμηση τύπων
- Χωρίς null/nil
- Αλγεβρικοί τύποι δεδομένων
Η υλοποίηση σε Rust δεν είναι ιδανική, αλλά την άφησα έτσι ώστε να μοιάζει όσο το δυνατόν περισσότερο με τις άλλες υλοποιήσεις. Επίσης, η Rust δεν έχει κανονική αναδρομή.
Οι μαθηματικοί τύποι σε μερικά σημεία δεν μπορούν να αντιγραφούν, δεν αναγνωρίζονται ως μαθηματική TeX σύνταξη. H πρώτη φορά που έγραψα μαθηματικά χρησιμοποιώντας TeX.
Μαθηματικά και διατύπωση του προβλήματος
Καταμέτρηση τοποθετήσεων — 6 έγχρωμοι κύβοι σε 9 θέσεις
Τοποθετούμε έξι έγχρωμους κύβους — δύο κόκκινους (R), δύο πράσινους (G), δύο μπλε (B) — σε εννέα επισημασμένες θέσεις (θέσεις σε σειρά). Οι υπόλοιπες τρεις θέσεις είναι κενές (συμβολίζουμε το κενό με E ή “.”).
Περιορισμός: Δεν επιτρέπεται να εμφανιστούν δύο διπλανοί έγχρωμοι κύβοι του ίδιου χρώματος. Οι κενές θέσεις μπορούν να είναι διπλανές με οτιδήποτε (συμπεριλαμβανομένων άλλων κενών και γειτόνων ίδιου χρώματος).
Πόσες διακριτές διατάξεις (ακολουθίες μήκους 9 του πολυσυνόλου ({R2,G2,B2,E3})) ικανοποιούν τον περιορισμό της γειτνίασης;
Σημειογραφία και ιδέα
Έστω το πολυσύνολο ({R,R,G,G,B,B,E,E,E}). Χωρίς περιορισμούς γειτνίασης, ο αριθμός των διακριτών ακολουθιών δίνεται από τον πολυωνυμικό (multinomial) τύπο
\[ N_{\text{total}}=\frac{9!}{2!,2!,2!,3!}. \]
Έστω για χρώμα \(X\in{R,G,B}\) το γεγονός \(A_X\) ότι «οι δύο κύβοι \(X\) είναι γειτονικοί κάπου στην ακολουθία». Εφόσον κάθε χρώμα εμφανίζεται ακριβώς δύο φορές, το \(A_X\) είναι ισοδύναμο με το ότι οι δύο κύβοι \(X\) σχηματίζουν ένα ενιαίο μπλοκ \(XX\).
Θέλουμε ακολουθίες που αποφεύγουν τα τρία γεγονότα \(A_R,A_G,A_B\). Θα τα μετρήσουμε χρησιμοποιώντας τη μέθοδο συμπερίληψης–αποκλεισμού.
Διάταξη συμπερίληψης–αποκλεισμού
Με συμπερίληψη–αποκλεισμό,
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).
Υπολογίζουμε κάθε όρο αντικαθιστώντας κάθε διπλό ζεύγος \(XX\) με ένα ενιαίο μπλοκ και μετρώντας τις διατάξεις του προκύπτοντος πολυσυνόλου.
Συνολικό (χωρίς περιορισμούς)
\[ N_{\text{total}}=\frac{9!}{2!\,2!\,2!\,3!}. \]
Υπολογίζουμε ρητά τις παραγοντικές και τους παρονομαστές:
\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*}Ένα χρώμα σε γειτνίαση (π.χ. (AR))
Θεωρούμε τους δύο κύβους (R) ως ένα ενιαίο μπλοκ (RR). Το υπόλοιπο πολυσύνολο είναι \(\{RR, G,G, B,B, E,E,E\}\) μεγέθους 8 με πολλαπλότητες \(G:2, B:2, E:3\). Συνεπώς \[ N(A_R)=\frac{8!}{2!,2!,3!}. \] Υπολογίζουμε:
\begin{align*} 8!&=40,320,\ 2!,2!,3!&=2\cdot2\cdot6=24,\\ N(A_R)&=\frac{40,320}{24}=1,680. \end{align*}Κατά συμμετρία η ίδια τιμή ισχύει για \(N(A_G)\) και \(N(A_B)\). Επομένως \[ \sum_X N(A_X)=3\cdot1,680=5,040. \]
Δύο χρώματα σε γειτνίαση (π.χ. (AR∩ AG))
Θεωρούμε (RR) και (GG) ως δύο μπλοκ. Το υπόλοιπο πολυσύνολο είναι ({RR,GG,B,B,E,E,E}) μεγέθους 7 με πολλαπλότητες \(B:2, E:3\). Έτσι \[ N(A_R\cap A_G)=\frac{7!}{2!,3!}. \] Υπολογίζουμε:
\begin{align*} 7!&=5,040,\ 2!,3!&=2\cdot6=12,\ N(A_R\cap A_G)&=\frac{5,040}{12}=420. \end{align*}Υπάρχουν \(\binom{3}{2}=3\) ανεξάρτητα ζεύγη χρωμάτων, οπότε \[ \sum_{X\lt Y} N(A_X\cap A_Y) = 3\cdot420 = 1260. \]
Και τα τρία χρώματα σε γειτνίαση ((AR∩ AG∩ AB))
Θεωρούμε (RR,GG,BB) ως τρία μπλοκ. Το υπόλοιπο πολυσύνολο είναι ({RR,GG,BB,E,E,E}) μεγέθους 6 με πολλαπλότητα (E:3). Επομένως
\[ N(A_R\cap A_G\cap A_B)=\frac{6!}{3!}. \] Υπολογίζουμε:
\begin{align*} 6!&=720,\\ 3!&=6,\\ N(A_R\cap A_G\cap A_B)&=\frac{720}{6}=120. \end{align*}Συνδυάζοντας τα παραπάνω
Εφαρμόζοντας τη μέθοδο συμπερίληψης–αποκλεισμού:
Ngood = Ntotal - ∑X N(AX) + ∑X<Y N(AX∩ AY) - N(AR∩ AG∩ AB) = 7560 - 5040 + 1260 - 120 = 3660.
Επομένως υπάρχουν \(\boxed{3,660}\) επιτρεπτές διατάξεις.
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); }); }