Skip to content

Commit

Permalink
Add dominoes exercise (#64)
Browse files Browse the repository at this point in the history
* Add dominoes exercise

* Simplify 'new'

* Refactor
  • Loading branch information
Nenad Misić authored Jul 5, 2024
1 parent e75f503 commit e772322
Show file tree
Hide file tree
Showing 8 changed files with 453 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@
],
"prerequisites": [],
"difficulty": 3
},
{
"slug": "dominoes",
"name": "Dominoes",
"uuid": "4bf12182-491e-4da7-a51a-3d96a2649d8a",
"practices": [],
"prerequisites": [],
"difficulty": 10
}
],
"foregone": [
Expand Down
13 changes: 13 additions & 0 deletions exercises/practice/dominoes/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Instructions

Make a chain of dominoes.

Compute a way to order a given set of dominoes in such a way that they form a correct domino chain (the dots on one half of a stone match the dots on the neighboring half of an adjacent stone) and that dots on the halves of the stones which don't have a neighbor (the first and last stone) match each other.

For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something
like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same.

For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same.
4 != 3

Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used.
18 changes: 18 additions & 0 deletions exercises/practice/dominoes/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"authors": [
"misicnenad"
],
"files": {
"solution": [
"src/lib.cairo",
"Scarb.toml"
],
"test": [
"src/tests.cairo"
],
"example": [
".meta/example.cairo"
]
},
"blurb": "Make a chain of dominoes."
}
145 changes: 145 additions & 0 deletions exercises/practice/dominoes/.meta/example.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use core::dict::Felt252DictTrait;

type Domino = (u8, u8);

/// A table keeping track of available dominoes.
///
/// Effectively a 6x6 matrix represented as a dynamic array. Each position denotes whether a domino
/// is available with that column dots and row dots. Positions are mirrored ((3,4) == (4,3)), except
/// for positions with equal row and column numbers.
#[derive(Destruct)]
struct AvailabilityTable {
d: Felt252Dict<u8>,
len: usize
}

fn index(x: u8, y: u8) -> u8 {
(x - 1) * 6 + (y - 1)
}

#[generate_trait]
impl AvailabilityTableImpl of AvailabilityTableTrait {
fn new() -> AvailabilityTable {
AvailabilityTable { d: Default::default(), len: 36 }
}

fn get(ref self: AvailabilityTable, x: u8, y: u8) -> u8 {
let i = index(x, y);
assert!(i.into() < self.len, "Index out of bounds");
self.d.get(i.into())
}

fn set(ref self: AvailabilityTable, x: u8, y: u8, c: u8) {
self.d.insert(index(x, y).into(), c);
}

fn add(ref self: AvailabilityTable, x: u8, y: u8) {
let c = self.get(x, y);
self.set(x, y, c + 1);
if x != y { // Not the diagonal
let c = self.get(y, x);
self.set(y, x, c + 1);
}
}

fn remove(ref self: AvailabilityTable, x: u8, y: u8) {
// For this toy code hard explicit fail is best
assert!(self.get(x, y) > 0, "no stones to remove: ({:?}, {:?})", x, y);

let c = self.get(x, y);
self.set(x, y, c - 1);
if x != y { // Not the diagonal
let c = self.get(y, x);
self.set(y, x, c - 1);
}
}

fn pop_first(ref self: AvailabilityTable, x: u8) -> Option<u8> {
// the "double" has precedence, otherwise an invalid chain might occur
if self.get(x, x) > 0 {
self.remove(x, x);
return Option::Some(x);
}

let mut y = 1;
loop {
if y == 7 {
break Option::None;
}
if self.get(x, y) > 0 {
self.remove(x, y);
break Option::Some(y);
}
y += 1;
}
}
}

fn chain(dominoes: @Array<Domino>) -> Option<Array<Domino>> {
match dominoes.len() {
0 => Option::Some(array![]),
1 => {
let domino: Domino = *dominoes[0];
let (x, y) = domino;
if x == y {
Option::Some(array![domino])
} else {
Option::None
}
},
_ => {
// First check if the total number of each amount of dots is even, if not it's not
// possible to complete a cycle. This follows from that it's an Eulerian path.
let mut d: Felt252Dict<u8> = Default::default();
let mut i = 0;
while i < dominoes
.len() {
let (x, y): Domino = *dominoes[i];
d.insert(x.into(), d.get(x.into()) + 1);
d.insert(y.into(), d.get(y.into()) + 1);
i += 1;
};
let mut i = 0;
let even_dot_types = loop {
if i == 6 {
break true;
}
if d.get(i.into()) % 2 != 0 {
break false;
}
i += 1;
};
if !even_dot_types {
return Option::None;
}
let chain = chain_worker(dominoes);
if chain.len() == dominoes.len() {
Option::Some(chain)
} else {
Option::None
}
}
}
}

fn chain_worker(dominoes: @Array<Domino>) -> Array<Domino> {
let mut t = AvailabilityTableTrait::new();
let mut i = dominoes.len() - 1;
let first = *dominoes[i];
while i != 0 {
i -= 1;
let (x, y): Domino = *dominoes[i];
t.add(x, y);
};
let mut chain: Array<Domino> = array![];
chain.append(first);
let (_, mut x) = first;
while let Option::Some(y) = t.pop_first(x) {
chain.append((x, y));
x = y;
};
chain
}

#[cfg(test)]
mod tests;
49 changes: 49 additions & 0 deletions exercises/practice/dominoes/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[31a673f2-5e54-49fe-bd79-1c1dae476c9c]
description = "empty input = empty output"

[4f99b933-367b-404b-8c6d-36d5923ee476]
description = "singleton input = singleton output"

[91122d10-5ec7-47cb-b759-033756375869]
description = "singleton that can't be chained"

[be8bc26b-fd3d-440b-8e9f-d698a0623be3]
description = "three elements"

[99e615c6-c059-401c-9e87-ad7af11fea5c]
description = "can reverse dominoes"

[51f0c291-5d43-40c5-b316-0429069528c9]
description = "can't be chained"

[9a75e078-a025-4c23-8c3a-238553657f39]
description = "disconnected - simple"

[0da0c7fe-d492-445d-b9ef-1f111f07a301]
description = "disconnected - double loop"

[b6087ff0-f555-4ea0-a71c-f9d707c5994a]
description = "disconnected - single isolated"

[2174fbdc-8b48-4bac-9914-8090d06ef978]
description = "need backtrack"

[167bb480-dfd1-4318-a20d-4f90adb4a09f]
description = "separate loops"

[cd061538-6046-45a7-ace9-6708fe8f6504]
description = "nine elements"

[44704c7c-3adb-4d98-bd30-f45527cf8b49]
description = "separate three-domino loops"
9 changes: 9 additions & 0 deletions exercises/practice/dominoes/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "dominoes"
version = "0.1.0"
edition = "2023_11"

# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html

[dependencies]
alexandria_sorting = { git = "https://github.com/keep-starknet-strange/alexandria.git" }
5 changes: 5 additions & 0 deletions exercises/practice/dominoes/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn chain(dominoes: @Array<(u8, u8)>) -> Option<Array<(u8, u8)>> {
panic!(
"From the given dominoes '{dominoes:?}' construct a proper dominoes chain or return None if it is not possible."
)
}
Loading

0 comments on commit e772322

Please sign in to comment.