Set puzzle
You are encouraged to solve this task according to the task description, using any language you may know.
Set Puzzles are created with a deck of cards from the Set Game™. The object of the puzzle is to find sets of 3 cards in a rectangle of cards that have been dealt face up.
There are 81 cards in a deck.
Each card contains a unique variation of the following four features: color, symbol, number and shading.
- there are three colors:
red, green, purple
- there are three symbols:
oval, squiggle, diamond
- there is a number of symbols on the card:
one, two, three
- there are three shadings:
solid, open, striped
Three cards form a set if each feature is either the same on each card, or is different on each card. For instance: all 3 cards are red, all 3 cards have a different symbol, all 3 cards have a different number of symbols, all 3 cards are striped.
There are two degrees of difficulty: basic and advanced. The basic mode deals 9 cards, that contain exactly 4 sets; the advanced mode deals 12 cards that contain exactly 6 sets.
When creating sets you may use the same card more than once.
- Task
Write code that deals the cards (9 or 12, depending on selected mode) from a shuffled deck in which the total number of sets that could be found is 4 (or 6, respectively); and print the contents of the cards and the sets.
For instance:
DEALT 9 CARDS:
- green, one, oval, striped
- green, one, diamond, open
- green, one, diamond, striped
- green, one, diamond, solid
- purple, one, diamond, open
- purple, two, squiggle, open
- purple, three, oval, open
- red, three, oval, open
- red, three, diamond, solid
CONTAINING 4 SETS:
- green, one, oval, striped
- purple, two, squiggle, open
- red, three, diamond, solid
- green, one, diamond, open
- green, one, diamond, striped
- green, one, diamond, solid
- green, one, diamond, open
- purple, two, squiggle, open
- red, three, oval, open
- purple, one, diamond, open
- purple, two, squiggle, open
- purple, three, oval, open
Ada
We start with the specification of a package "Set_Puzzle.
package Set_Puzzle is
type Three is range 1..3;
type Card is array(1 .. 4) of Three;
type Cards is array(Positive range <>) of Card;
type Set is array(Three) of Positive;
procedure Deal_Cards(Dealt: out Cards);
-- ouputs an array with disjoint cards
function To_String(C: Card) return String;
generic
with procedure Do_something(C: Cards; S: Set);
procedure Find_Sets(Given: Cards);
-- calls Do_Something once for each set it finds.
end Set_Puzzle;
Now we implement the package "Set_Puzzle".
with Ada.Numerics.Discrete_Random;
package body Set_Puzzle is
package Rand is new Ada.Numerics.Discrete_Random(Three);
R: Rand.Generator;
function Locate(Some: Cards; C: Card) return Natural is
-- returns index of card C in Some, or 0 if not found
begin
for I in Some'Range loop
if C = Some(I) then
return I;
end if;
end loop;
return 0;
end Locate;
procedure Deal_Cards(Dealt: out Cards) is
function Random_Card return Card is
(Rand.Random(R), Rand.Random(R), Rand.Random(R), Rand.Random(R));
begin
for I in Dealt'Range loop
-- draw a random card until different from all card previously drawn
Dealt(I) := Random_Card; -- draw random card
while Locate(Dealt(Dealt'First .. I-1), Dealt(I)) /= 0 loop
-- Dealt(I) has been drawn before
Dealt(I) := Random_Card; -- draw another random card
end loop;
end loop;
end Deal_Cards;
procedure Find_Sets(Given: Cards) is
function To_Set(A, B: Card) return Card is
-- returns the unique card C, which would make a set with A and B
C: Card;
begin
for I in 1 .. 4 loop
if A(I) = B(I) then
C(I) := A(I); -- all three the same
else
C(I) := 6 - A(I) - B(I); -- all three different;
end if;
end loop;
return C;
end To_Set;
X: Natural;
begin
for I in Given'Range loop
for J in Given'First .. I-1 loop
X := Locate(Given, To_Set(Given(I), Given(J)));
if I < X then -- X=0 is no set, 0 < X < I is a duplicate
Do_Something(Given, (J, I, X));
end if;
end loop;
end loop;
end Find_Sets;
function To_String(C: Card) return String is
Col: constant array(Three) of String(1..6)
:= ("Red ", "Green ", "Purple");
Sym: constant array(Three) of String(1..8)
:= ("Oval ", "Squiggle", "Diamond ");
Num: constant array(Three) of String(1..5)
:= ("One ", "Two ", "Three");
Sha: constant array(Three) of String(1..7)
:= ("Solid ", "Open ", "Striped");
begin
return (Col(C(1)) & " " & Sym(C(2)) & " " & Num(C(3)) & " " & Sha(C(4)));
end To_String;
begin
Rand.Reset(R);
end Set_Puzzle;
Finally, we write the main program, using the above package. It reads two parameters from the command line. The first parameter describes the number of cards, the second one the number of sets. Thus, for the basic mode one has to call "puzzle 9 4", for the advanced mode "puzzle 12 6", but the program would support any other combination of parameters just as well.
with Ada.Text_IO, Set_Puzzle, Ada.Command_Line;
procedure Puzzle is
package TIO renames Ada.Text_IO;
Card_Count: Positive := Positive'Value(Ada.Command_Line.Argument(1));
Required_Sets: Positive := Positive'Value(Ada.Command_Line.Argument(2));
Cards: Set_Puzzle.Cards(1 .. Card_Count);
function Cnt_Sets(C: Set_Puzzle.Cards) return Natural is
Cnt: Natural := 0;
procedure Count_Sets(C: Set_Puzzle.Cards; S: Set_Puzzle.Set) is
begin
Cnt := Cnt + 1;
end Count_Sets;
procedure CS is new Set_Puzzle.Find_Sets(Count_Sets);
begin
CS(C);
return Cnt;
end Cnt_Sets;
procedure Print_Sets(C: Set_Puzzle.Cards) is
procedure Print_A_Set(C: Set_Puzzle.Cards; S: Set_Puzzle.Set) is
begin
TIO.Put("(" & Integer'Image(S(1)) & "," & Integer'Image(S(2))
& "," & Integer'Image(S(3)) & " ) ");
end Print_A_Set;
procedure PS is new Set_Puzzle.Find_Sets(Print_A_Set);
begin
PS(C);
TIO.New_Line;
end Print_Sets;
begin
loop -- deal random cards
Set_Puzzle.Deal_Cards(Cards);
exit when Cnt_Sets(Cards) = Required_Sets;
end loop; -- until number of sets is as required
for I in Cards'Range loop -- print the cards
if I < 10 then
TIO.Put(" ");
end if;
TIO.Put_Line(Integer'Image(I) & " " & Set_Puzzle.To_String(Cards(I)));
end loop;
Print_Sets(Cards); -- print the sets
end Puzzle;
- Output:
>./puzzle 9 4 1 Red Diamond One Striped 2 Green Squiggle Two Solid 3 Red Squiggle Three Open 4 Green Squiggle Three Solid 5 Purple Oval Two Open 6 Purple Squiggle One Striped 7 Green Squiggle One Solid 8 Purple Squiggle One Solid 9 Purple Diamond Three Solid ( 2, 3, 6 ) ( 1, 4, 5 ) ( 2, 4, 7 ) ( 5, 6, 9 ) >./puzzle 12 6 1 Purple Diamond One Solid 2 Red Diamond One Striped 3 Red Oval Three Striped 4 Green Oval Two Solid 5 Red Squiggle Three Solid 6 Green Squiggle Two Solid 7 Red Squiggle Three Striped 8 Red Squiggle Three Open 9 Purple Squiggle One Striped 10 Red Diamond Two Solid 11 Red Squiggle One Open 12 Red Oval One Solid ( 1, 4, 5 ) ( 5, 7, 8 ) ( 6, 8, 9 ) ( 3, 10, 11 ) ( 5, 10, 12 ) ( 2, 11, 12 )
AutoHotkey
; Generate deck; card encoding from Raku
Loop, 81
deck .= ToBase(A_Index-1, 3)+1111 ","
deck := RegExReplace(deck, "3", "4")
; Shuffle
deck := shuffle(deck)
msgbox % clipboard := allValidSets(9, 4, deck)
msgbox % clipboard := allValidSets(12, 6, deck)
; Render a hand (or any list) of cards
PrettyHand(hand) {
Color1:="red",Color2:="green",Color4:="purple"
,Symbl1:="oval",Symbl2:="squiggle",Symbl4:="diamond"
,Numbr1:="one",Numbr2:="two",Numbr4:="three"
,Shape1:="solid",Shape2:="open",Shape4:="striped"
Loop, Parse, hand, `,
{
StringSplit, i, A_LoopField
s .= "`t" Color%i1% "`t" Symbl%i2% "`t" Numbr%i3% "`t" Shape%i4% "`n"
}
Return s
}
; Get all unique valid sets of three cards in a hand.
allValidSets(n, m, deck) {
While j != m
{
j := 0
,hand := draw(n, deck)
,s := "Dealt " n " cards:`n" . prettyhand(hand)
StringSplit, set, hand, `,
comb := comb(n,3)
Loop, Parse, comb, `n
{
StringSplit, i, A_LoopField, %A_Space%
If isValidSet(set%i1%, set%i2%, set%i3%)
s .= "`nSet " ++j ":`n" . prettyhand(set%i1% "," set%i2% "," set%i3%)
}
}
Return s
}
; Convert n to arbitrary base using recursion
toBase(n,b) { ; n >= 0, 1 < b < StrLen(t), t = digits
Static t := "0123456789ABCDEF"
Return (n < b ? "" : ToBase(n//b,b)) . SubStr(t,mod(n,b)+1,1)
}
; Knuth shuffle from http://rosettacode.org/wiki/Knuth_Shuffle#AutoHotkey
shuffle(list) { ; shuffle comma separated list, converted to array
StringSplit a, list, `, ; make array (length = a0)
Loop % a0-1 {
Random i, A_Index, a0 ; swap item 1,2... with a random item to the right of it
t := a%i%, a%i% := a%A_Index%, a%A_Index% := t
}
Loop % a0 ; construct string from sorted array
s .= "," . a%A_Index%
Return SubStr(s,2) ; drop leading comma
}
; Randomly pick a hand of cards from the deck
draw(n, deck) {
Loop, % n
{
Random, i, 1, 81
cards := deck
Loop, Parse, cards, `,
(A_Index = i) ? (hand .= A_LoopField ",") : (cards .= A_LoopField ",")
deck := cards
}
Return SubStr(hand, 1, -1)
}
; Test if a particular group of three cards is a valid set
isValidSet(a, b, c) {
StringSplit, a, a
StringSplit, b, b
StringSplit, c, c
Return !((a1|b1|c1 ~= "[3,5,6]") + (a2|b2|c2 ~= "[3,5,6]") + (a3|b3|c3 ~= "[3,5,6]") + (a4|b4|c4 ~= "[3,5,6]"))
}
; Get all combinations, from http://rosettacode.org/wiki/Combinations#AutoHotkey
comb(n,t) { ; Generate all n choose t combinations of 1..n, lexicographically
IfLess n,%t%, Return
Loop %t%
c%A_Index% := A_Index
i := t+1, c%i% := n+1
Loop {
Loop %t%
i := t+1-A_Index, c .= c%i% " "
c .= "`n" ; combinations in new lines
j := 1, i := 2
Loop
If (c%j%+1 = c%i%)
c%j% := j, ++j, ++i
Else Break
If (j > t)
Return c
c%j% += 1
}
}
- Sample output:
Dealt 9 cards: purple diamond three striped green diamond two open green oval one striped red oval two solid purple squiggle two striped red diamond three open red diamond three open green oval one solid red oval two solid Set 1: purple squiggle two striped red oval two solid green diamond two open Set 2: green oval one solid red diamond three open purple squiggle two striped Set 3: green oval one solid red diamond three open purple squiggle two striped Set 4: red oval two solid purple squiggle two striped green diamond two open Dealt 12 cards: purple oval two open purple diamond three solid green squiggle three striped green squiggle one solid purple squiggle one striped purple squiggle one solid green diamond two solid purple squiggle one striped red diamond two striped green diamond one open green oval one open red squiggle one open Set 1: purple squiggle one striped purple diamond three solid purple oval two open Set 2: purple squiggle one striped purple diamond three solid purple oval two open Set 3: green diamond one open red diamond two striped purple diamond three solid Set 4: green oval one open green diamond two solid green squiggle three striped Set 5: red squiggle one open purple squiggle one striped green squiggle one solid Set 6: red squiggle one open purple squiggle one striped green squiggle one solid
C
Brute force. Each card is a unique number in the range of [0,81]. Randomly deal a hand of cards until exactly the required number of sets are found.
#include <stdio.h>
#include <stdlib.h>
char *names[4][3] = {
{ "red", "green", "purple" },
{ "oval", "squiggle", "diamond" },
{ "one", "two", "three" },
{ "solid", "open", "striped" }
};
int set[81][81];
void init_sets(void)
{
int i, j, t, a, b;
for (i = 0; i < 81; i++) {
for (j = 0; j < 81; j++) {
for (t = 27; t; t /= 3) {
a = (i / t) % 3;
b = (j / t) % 3;
set[i][j] += t * (a == b ? a : 3 - a - b);
}
}
}
}
void deal(int *out, int n)
{
int i, j, t, c[81];
for (i = 0; i < 81; i++) c[i] = i;
for (i = 0; i < n; i++) {
j = i + (rand() % (81 - i));
t = c[i], c[i] = out[i] = c[j], c[j] = t;
}
}
int get_sets(int *cards, int n, int sets[][3])
{
int i, j, k, s = 0;
for (i = 0; i < n; i++) {
for (j = i + 1; j < n; j++) {
for (k = j + 1; k < n; k++) {
if (set[cards[i]][cards[j]] == cards[k])
sets[s][0] = i,
sets[s][1] = j,
sets[s][2] = k,
s++;
}
}
}
return s;
}
void show_card(int c)
{
int i, t;
for (i = 0, t = 27; t; i++, t /= 3)
printf("%9s", names[i][(c/t)%3]);
putchar('\n');
}
void deal_sets(int ncard, int nset)
{
int c[81];
int csets[81][3]; // might not be enough for large ncard
int i, j, s;
do deal(c, ncard); while ((s = get_sets(c, ncard, csets)) != nset);
printf("dealt %d cards\n", ncard);
for (i = 0; i < ncard; i++) {
printf("%2d:", i);
show_card(c[i]);
}
printf("\nsets:\n");
for (i = 0; i < s; i++) {
for (j = 0; j < 3; j++) {
printf("%2d:", csets[i][j]);
show_card(c[csets[i][j]]);
}
putchar('\n');
}
}
int main(void)
{
init_sets();
deal_sets(9, 4);
while (1) deal_sets(12, 6);
return 0;
}
C#
using System;
using System.Collections.Generic;
using static System.Linq.Enumerable;
public static class SetPuzzle
{
static readonly Feature[] numbers = { (1, "One"), (2, "Two"), (3, "Three") };
static readonly Feature[] colors = { (1, "Red"), (2, "Green"), (3, "Purple") };
static readonly Feature[] shadings = { (1, "Open"), (2, "Striped"), (3, "Solid") };
static readonly Feature[] symbols = { (1, "Oval"), (2, "Squiggle"), (3, "Diamond") };
private readonly struct Feature
{
public Feature(int value, string name) => (Value, Name) = (value, name);
public int Value { get; }
public string Name { get; }
public static implicit operator int(Feature f) => f.Value;
public static implicit operator Feature((int value, string name) t) => new Feature(t.value, t.name);
public override string ToString() => Name;
}
private readonly struct Card : IEquatable<Card>
{
public Card(Feature number, Feature color, Feature shading, Feature symbol) =>
(Number, Color, Shading, Symbol) = (number, color, shading, symbol);
public Feature Number { get; }
public Feature Color { get; }
public Feature Shading { get; }
public Feature Symbol { get; }
public override string ToString() => $"{Number} {Color} {Shading} {Symbol}(s)";
public bool Equals(Card other) => Number == other.Number && Color == other.Color && Shading == other.Shading && Symbol == other.Symbol;
}
public static void Main() {
Card[] deck = (
from number in numbers
from color in colors
from shading in shadings
from symbol in symbols
select new Card(number, color, shading, symbol)
).ToArray();
var random = new Random();
Deal(deck, 9, 4, random);
Console.WriteLine();
Console.WriteLine();
Deal(deck, 12, 6, random);
}
static void Deal(Card[] deck, int size, int target, Random random) {
List<(Card a, Card b, Card c)> sets;
do {
Shuffle(deck, random.Next);
sets = (
from i in 0.To(size - 2)
from j in (i + 1).To(size - 1)
from k in (j + 1).To(size)
select (deck[i], deck[j], deck[k])
).Where(IsSet).ToList();
} while (sets.Count != target);
Console.WriteLine("The board:");
foreach (Card card in deck.Take(size)) Console.WriteLine(card);
Console.WriteLine();
Console.WriteLine("Sets:");
foreach (var s in sets) Console.WriteLine(s);
}
static void Shuffle<T>(T[] array, Func<int, int, int> rng) {
for (int i = 0; i < array.Length; i++) {
int r = rng(i, array.Length);
(array[r], array[i]) = (array[i], array[r]);
}
}
static bool IsSet((Card a, Card b, Card c) t) =>
AreSameOrDifferent(t.a.Number, t.b.Number, t.c.Number) &&
AreSameOrDifferent(t.a.Color, t.b.Color, t.c.Color) &&
AreSameOrDifferent(t.a.Shading, t.b.Shading, t.c.Shading) &&
AreSameOrDifferent(t.a.Symbol, t.b.Symbol, t.c.Symbol);
static bool AreSameOrDifferent(int a, int b, int c) => (a + b + c) % 3 == 0;
static IEnumerable<int> To(this int start, int end) => Range(start, end - start - 1);
}
- Output:
The board: Two Green Open Oval(s) Three Green Open Diamond(s) Two Red Open Oval(s) One Purple Solid Oval(s) Two Green Striped Squiggle(s) Two Purple Open Oval(s) Two Red Striped Squiggle(s) Two Purple Solid Diamond(s) Three Green Solid Diamond(s) Sets: (Two Green Open Oval(s), Two Red Open Oval(s), Two Purple Open Oval(s)) (Two Green Open Oval(s), Two Red Striped Squiggle(s), Two Purple Solid Diamond(s)) (Three Green Open Diamond(s), One Purple Solid Oval(s), Two Red Striped Squiggle(s)) (Two Red Open Oval(s), Two Green Striped Squiggle(s), Two Purple Solid Diamond(s)) The board: One Purple Open Squiggle(s) One Red Striped Diamond(s) One Purple Solid Oval(s) Three Purple Striped Squiggle(s) Three Green Open Oval(s) Two Purple Solid Squiggle(s) One Red Open Diamond(s) Two Purple Open Diamond(s) Three Red Solid Diamond(s) One Green Open Oval(s) One Purple Solid Squiggle(s) One Purple Solid Diamond(s) Sets: (One Purple Open Squiggle(s), Three Purple Striped Squiggle(s), Two Purple Solid Squiggle(s)) (One Purple Open Squiggle(s), One Red Open Diamond(s), One Green Open Oval(s)) (One Red Striped Diamond(s), Three Green Open Oval(s), Two Purple Solid Squiggle(s)) (One Red Striped Diamond(s), One Green Open Oval(s), One Purple Solid Squiggle(s)) (One Purple Solid Oval(s), Three Purple Striped Squiggle(s), Two Purple Open Diamond(s)) (Three Purple Striped Squiggle(s), Three Green Open Oval(s), Three Red Solid Diamond(s))
C++
#include <time.h>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
enum color {
red, green, purple
};
enum symbol {
oval, squiggle, diamond
};
enum number {
one, two, three
};
enum shading {
solid, open, striped
};
class card {
public:
card( color c, symbol s, number n, shading h ) {
clr = c; smb = s; nbr = n; shd = h;
}
color getColor() {
return clr;
}
symbol getSymbol() {
return smb;
}
number getNumber() {
return nbr;
}
shading getShading() {
return shd;
}
std::string toString() {
std::string str = "[";
str += clr == red ? "red " : clr == green ? "green " : "purple ";
str += nbr == one ? "one " : nbr == two ? "two " : "three ";
str += smb == oval ? "oval " : smb == squiggle ? "squiggle " : "diamond ";
str += shd == solid ? "solid" : shd == open ? "open" : "striped";
return str + "]";
}
private:
color clr;
symbol smb;
number nbr;
shading shd;
};
typedef struct {
std::vector<size_t> index;
} set;
class setPuzzle {
public:
setPuzzle() {
for( size_t c = red; c <= purple; c++ ) {
for( size_t s = oval; s <= diamond; s++ ) {
for( size_t n = one; n <= three; n++ ) {
for( size_t h = solid; h <= striped; h++ ) {
card crd( static_cast<color> ( c ),
static_cast<symbol> ( s ),
static_cast<number> ( n ),
static_cast<shading>( h ) );
_cards.push_back( crd );
}
}
}
}
}
void create( size_t countCards, size_t countSets, std::vector<card>& cards, std::vector<set>& sets ) {
while( true ) {
sets.clear();
cards.clear();
std::random_shuffle( _cards.begin(), _cards.end() );
for( size_t f = 0; f < countCards; f++ ) {
cards.push_back( _cards.at( f ) );
}
for( size_t c1 = 0; c1 < cards.size() - 2; c1++ ) {
for( size_t c2 = c1 + 1; c2 < cards.size() - 1; c2++ ) {
for( size_t c3 = c2 + 1; c3 < cards.size(); c3++ ) {
if( testSet( &cards.at( c1 ), &cards.at( c2 ), &cards.at( c3 ) ) ) {
set s;
s.index.push_back( c1 ); s.index.push_back( c2 ); s.index.push_back( c3 );
sets.push_back( s );
}
}
}
}
if( sets.size() == countSets ) return;
}
}
private:
bool testSet( card* c1, card* c2, card* c3 ) {
int
c = ( c1->getColor() + c2->getColor() + c3->getColor() ) % 3,
s = ( c1->getSymbol() + c2->getSymbol() + c3->getSymbol() ) % 3,
n = ( c1->getNumber() + c2->getNumber() + c3->getNumber() ) % 3,
h = ( c1->getShading() + c2->getShading() + c3->getShading() ) % 3;
return !( c + s + n + h );
}
std::vector<card> _cards;
};
void displayCardsSets( std::vector<card>& cards, std::vector<set>& sets ) {
size_t cnt = 1;
std::cout << " ** DEALT " << cards.size() << " CARDS: **\n";
for( std::vector<card>::iterator i = cards.begin(); i != cards.end(); i++ ) {
std::cout << std::setw( 2 ) << cnt++ << ": " << ( *i ).toString() << "\n";
}
std::cout << "\n ** CONTAINING " << sets.size() << " SETS: **\n";
for( std::vector<set>::iterator i = sets.begin(); i != sets.end(); i++ ) {
for( size_t j = 0; j < ( *i ).index.size(); j++ ) {
std::cout << " " << std::setiosflags( std::ios::left ) << std::setw( 34 )
<< cards.at( ( *i ).index.at( j ) ).toString() << " : "
<< std::resetiosflags( std::ios::left ) << std::setw( 2 ) << ( *i ).index.at( j ) + 1 << "\n";
}
std::cout << "\n";
}
std::cout << "\n\n";
}
int main( int argc, char* argv[] ) {
srand( static_cast<unsigned>( time( NULL ) ) );
setPuzzle p;
std::vector<card> v9, v12;
std::vector<set> s4, s6;
p.create( 9, 4, v9, s4 );
p.create( 12, 6, v12, s6 );
displayCardsSets( v9, s4 );
displayCardsSets( v12, s6 );
return 0;
}
- Output:
** DEALT 9 CARDS: ** 1: [red three squiggle solid] 2: [purple three squiggle solid] 3: [red two diamond open] 4: [purple three oval striped] 5: [green one squiggle solid] 6: [green two diamond open] 7: [red one oval striped] 8: [green one diamond striped] 9: [purple one diamond open] ** CONTAINING 4 SETS: ** [red three squiggle solid] : 1 [red two diamond open] : 3 [red one oval striped] : 7 [purple three squiggle solid] : 2 [green two diamond open] : 6 [red one oval striped] : 7 [red two diamond open] : 3 [purple three oval striped] : 4 [green one squiggle solid] : 5 [green one squiggle solid] : 5 [red one oval striped] : 7 [purple one diamond open] : 9 ** DEALT 12 CARDS: ** 1: [green one diamond striped] 2: [red two squiggle solid] 3: [red three oval striped] 4: [red two diamond open] 5: [green three squiggle striped] 6: [red three squiggle striped] 7: [green two squiggle solid] 8: [purple two squiggle striped] 9: [purple one squiggle open] 10: [green one squiggle striped] 11: [purple three squiggle solid] 12: [red three squiggle open] ** CONTAINING 6 SETS: ** [green one diamond striped] : 1 [red three oval striped] : 3 [purple two squiggle striped] : 8 [red two squiggle solid] : 2 [green three squiggle striped] : 5 [purple one squiggle open] : 9 [green three squiggle striped] : 5 [purple three squiggle solid] : 11 [red three squiggle open] : 12 [red three squiggle striped] : 6 [green two squiggle solid] : 7 [purple one squiggle open] : 9 [red three squiggle striped] : 6 [purple two squiggle striped] : 8 [green one squiggle striped] : 10 [purple two squiggle striped] : 8 [purple one squiggle open] : 9 [purple three squiggle solid] : 11
Ceylon
Add import ceylon.random "1.3.3" to your module.ceylon file
import ceylon.random {
Random,
DefaultRandom
}
abstract class Feature() of Color | Symbol | NumberOfSymbols | Shading {}
abstract class Color()
of red | green | purple
extends Feature() {}
object red extends Color() {
string => "red";
}
object green extends Color() {
string => "green";
}
object purple extends Color() {
string => "purple";
}
abstract class Symbol()
of oval | squiggle | diamond
extends Feature() {}
object oval extends Symbol() {
string => "oval";
}
object squiggle extends Symbol() {
string => "squiggle";
}
object diamond extends Symbol() {
string => "diamond";
}
abstract class NumberOfSymbols()
of one | two | three
extends Feature() {}
object one extends NumberOfSymbols() {
string => "one";
}
object two extends NumberOfSymbols() {
string => "two";
}
object three extends NumberOfSymbols() {
string => "three";
}
abstract class Shading()
of solid | open | striped
extends Feature() {}
object solid extends Shading() {
string => "solid";
}
object open extends Shading() {
string => "open";
}
object striped extends Shading() {
string => "striped";
}
class Card(color, symbol, number, shading) {
shared Color color;
shared Symbol symbol;
shared NumberOfSymbols number;
shared Shading shading;
value plural => number == one then "" else "s";
string => "``number`` ``shading`` ``color`` ``symbol````plural``";
}
{Card*} deck = {
for(color in `Color`.caseValues)
for(symbol in `Symbol`.caseValues)
for(number in `NumberOfSymbols`.caseValues)
for(shading in `Shading`.caseValues)
Card(color, symbol, number, shading)
};
alias CardSet => [Card+];
Boolean validSet(CardSet cards) {
function allOrOne({Feature*} features) =>
let(uniques = features.distinct.size)
uniques == 3 || uniques == 1;
return allOrOne(cards*.color) &&
allOrOne(cards*.number) &&
allOrOne(cards*.shading) &&
allOrOne(cards*.symbol);
}
{CardSet*} findSets(Card* cards) =>
cards
.sequence()
.combinations(3)
.filter(validSet);
Random random = DefaultRandom();
class Mode of basic | advanced {
shared Integer numberOfCards;
shared Integer numberOfSets;
shared new basic {
numberOfCards = 9;
numberOfSets = 4;
}
shared new advanced {
numberOfCards = 12;
numberOfSets = 6;
}
}
[{Card*}, {CardSet*}] deal(Mode mode) {
value randomStream = random.elements(deck);
while(true) {
value cards = randomStream.distinct.take(mode.numberOfCards).sequence();
value sets = findSets(*cards);
if(sets.size == mode.numberOfSets) {
return [cards, sets];
}
}
}
shared void run() {
value [cards, sets] = deal(Mode.basic);
print("The cards dealt are:
");
cards.each(print);
print("
Containing the sets:
");
for(cardSet in sets) {
cardSet.each(print);
print("");
}
}
D
Basic Version
import std.stdio, std.random, std.array, std.conv, std.traits,
std.exception, std.range, std.algorithm;
const class SetDealer {
protected {
enum Color: ubyte {green, purple, red}
enum Number: ubyte {one, two, three}
enum Symbol: ubyte {oval, diamond, squiggle}
enum Fill: ubyte {open, striped, solid}
static struct Card {
Color c;
Number n;
Symbol s;
Fill f;
}
static immutable Card[81] deck;
}
static this() pure nothrow @safe {
immutable colors = [EnumMembers!Color];
immutable numbers = [EnumMembers!Number];
immutable symbols = [EnumMembers!Symbol];
immutable fill = [EnumMembers!Fill];
deck = deck.length.iota.map!(i => Card(colors[i / 27],
numbers[(i / 9) % 3],
symbols[(i / 3) % 3],
fill[i % 3])).array;
}
// randomSample produces a sorted output that's convenient in our
// case because we're printing to stout. Normally you would want
// to shuffle.
immutable(Card)[] deal(in uint numCards) const {
enforce(numCards < deck.length, "Number of cards too large");
return deck[].randomSample(numCards).array;
}
// The summed enums of valid sets are always zero or a multiple
// of 3.
bool validSet(in ref Card c1, in ref Card c2, in ref Card c3)
const pure nothrow @safe @nogc {
return !((c1.c + c2.c + c3.c) % 3 ||
(c1.n + c2.n + c3.n) % 3 ||
(c1.s + c2.s + c3.s) % 3 ||
(c1.f + c2.f + c3.f) % 3);
}
immutable(Card)[3][] findSets(in Card[] cards, in uint target = 0)
const pure nothrow @safe {
immutable len = cards.length;
if (len < 3)
return null;
typeof(return) sets;
foreach (immutable i; 0 .. len - 2)
foreach (immutable j; i + 1 .. len - 1)
foreach (immutable k; j + 1 .. len)
if (validSet(cards[i], cards[j], cards[k])) {
sets ~= [cards[i], cards[j], cards[k]];
if (target != 0 && sets.length > target)
return null;
}
return sets;
}
}
const final class SetPuzzleDealer : SetDealer {
enum {basic = 9, advanced = 12}
override immutable(Card)[] deal(in uint numCards = basic) const {
immutable numSets = numCards / 2;
typeof(return) cards;
do {
cards = super.deal(numCards);
} while (findSets(cards, numSets).length != numSets);
return cards;
}
}
void main() {
const dealer = new SetPuzzleDealer;
const cards = dealer.deal;
writefln("DEALT %d CARDS:", cards.length);
writefln("%(%s\n%)", cards);
immutable sets = dealer.findSets(cards);
immutable len = sets.length;
writefln("\nFOUND %d SET%s:", len, len == 1 ? "" : "S");
writefln("%(%(%s\n%)\n\n%)", sets);
}
- Sample output:
DEALT 9 CARDS: immutable(Card)(green, one, diamond, open) immutable(Card)(green, two, diamond, open) immutable(Card)(purple, one, diamond, striped) immutable(Card)(purple, one, diamond, solid) immutable(Card)(purple, two, squiggle, solid) immutable(Card)(purple, three, oval, open) immutable(Card)(red, one, diamond, solid) immutable(Card)(red, one, squiggle, open) immutable(Card)(red, three, oval, striped) FOUND 4 SETS: immutable(Card)(green, one, diamond, open) immutable(Card)(purple, one, diamond, striped) immutable(Card)(red, one, diamond, solid) immutable(Card)(green, one, diamond, open) immutable(Card)(purple, two, squiggle, solid) immutable(Card)(red, three, oval, striped) immutable(Card)(green, two, diamond, open) immutable(Card)(purple, three, oval, open) immutable(Card)(red, one, squiggle, open) immutable(Card)(purple, one, diamond, striped) immutable(Card)(purple, two, squiggle, solid) immutable(Card)(purple, three, oval, open)
Short Version
This requires the third solution module of the Combinations Task.
void main() {
import std.stdio, std.algorithm, std.range, std.random, combinations3;
enum nDraw = 9, nGoal = nDraw / 2;
auto deck = cartesianProduct("red green purple".split,
"one two three".split,
"oval squiggle diamond".split,
"solid open striped".split).array;
retry:
auto draw = deck.randomSample(nDraw).map!(t => [t[]]).array;
const sets = draw.combinations(3).filter!(cs => cs.dup
.transposed.all!(t => t.array.sort().uniq.count % 2)).array;
if (sets.length != nGoal)
goto retry;
writefln("Dealt %d cards:\n%(%-(%8s %)\n%)\n", draw.length, draw);
writefln("Containing:\n%(%(%-(%8s %)\n%)\n\n%)", sets);
}
- Output:
Dealt 9 cards: purple one oval solid red three squiggle solid purple three diamond solid green one squiggle open green two squiggle open red two oval striped purple one squiggle striped purple two squiggle striped green three diamond striped Containing: purple three diamond solid green one squiggle open red two oval striped red three squiggle solid green two squiggle open purple one squiggle striped red three squiggle solid green one squiggle open purple two squiggle striped red two oval striped purple one squiggle striped green three diamond striped
EchoLisp
(require 'list)
;; a card is a vector [id color number symb shading], 0 <= id < 81
(define (make-deck (id -1))
(for*/vector(
[ color '(red green purple)]
[ number '(one two three)]
[ symb '( oval squiggle diamond)]
[ shading '(solid open striped)]) (++ id) (vector id color number symb shading)))
(define DECK (make-deck))
;; pre-generate 531441 ordered triples, among which 6561 are winners
(define TRIPLES (make-vector (* 81 81 81)))
(define (make-triples )
(for* ((i 81)(j 81)(k 81))
(vector-set! TRIPLES (+ i (* 81 j) (* 6561 k))
(check-set [DECK i] [DECK j] [DECK k]))))
;; a deal is a list of cards id's.
(define (show-deal deal)
(for ((card deal)) (writeln [DECK card]))
(for ((set (combinations deal 3)))
(when
(check-set [DECK (first set)] [DECK (second set)][DECK (third set)])
(writeln 'winner set))))
;; rules of game here
(define (check-set cards: a b c)
(for ((i (in-range 1 5))) ;; each feature
#:continue (and (= [a i] [b i]) (= [a i] [c i]))
#:continue (and (!= [a i] [b i]) (!= [a i] [c i]) (!= [b i][c i]))
#:break #t => #f ))
;; sets = list of triples (card-id card-id card-id)
(define (count-sets sets )
(for/sum ((s sets))
(if [TRIPLES ( + (first s) (* 81 (second s)) (* 6561 (third s)))]
1 0)))
;; task
(make-triples)
(define (play (n 9) (cmax 4) (sets) (deal))
(while #t
(set! deal (take (shuffle (iota 81)) n))
(set! sets (combinations deal 3))
#:break (= (count-sets sets) cmax) => (show-deal deal)
))
- Output:
(play) ;; The 9-4 game by default #( 13 red two squiggle open) #( 54 purple one oval solid) #( 2 red one oval striped) #( 15 red two diamond solid) #( 53 green three diamond striped) #( 48 green three squiggle solid) #( 41 green two squiggle striped) #( 66 purple two squiggle solid) #( 64 purple two oval open) winner (13 54 53) winner (13 41 66) winner (54 15 48) winner (15 41 64) ;; 10 deals (play 12 6) #( 43 green two diamond open) #( 16 red two diamond open) #( 79 purple three diamond open) #( 63 purple two oval solid) #( 60 purple one diamond solid) #( 75 purple three squiggle solid) #( 64 purple two oval open) #( 71 purple two diamond striped) #( 67 purple two squiggle open) #( 34 green one diamond open) #( 59 purple one squiggle striped) #( 54 purple one oval solid) winner (16 79 34) winner (79 63 59) winner (79 60 71) winner (63 60 75) winner (63 71 67) winner (75 67 59) ;; 31 deals ;; the (9 6) game is more difficult #( 11 red two oval striped) #( 9 red two oval solid) #( 26 red three diamond striped) #( 5 red one squiggle striped) #( 60 purple one diamond solid) #( 43 green two diamond open) #( 10 red two oval open) #( 67 purple two squiggle open) #( 48 green three squiggle solid) winner (11 9 10) winner (11 26 5) winner (9 60 48) winner (26 60 43) winner (5 67 48) winner (43 10 67) ;; 17200 deals
Elixir
defmodule RC do
def set_puzzle(deal, goal) do
{puzzle, sets} = get_puzzle_and_answer(deal, goal, produce_deck)
IO.puts "Dealt #{length(puzzle)} cards:"
print_cards(puzzle)
IO.puts "Containing #{length(sets)} sets:"
Enum.each(sets, fn set -> print_cards(set) end)
end
defp get_puzzle_and_answer(hand_size, num_sets_goal, deck) do
hand = Enum.take_random(deck, hand_size)
sets = get_all_sets(hand)
if length(sets) == num_sets_goal do
{hand, sets}
else
get_puzzle_and_answer(hand_size, num_sets_goal, deck)
end
end
defp get_all_sets(hand) do
Enum.filter(comb(hand, 3), fn candidate ->
List.flatten(candidate)
|> Enum.group_by(&(&1))
|> Map.values
|> Enum.all?(fn v -> length(v) != 2 end)
end)
end
defp print_cards(cards) do
Enum.each(cards, fn card ->
:io.format " ~-8s ~-8s ~-8s ~-8s~n", card
end)
IO.puts ""
end
@colors ~w(red green purple)a
@symbols ~w(oval squiggle diamond)a
@numbers ~w(one two three)a
@shadings ~w(solid open striped)a
defp produce_deck do
for color <- @colors, symbol <- @symbols, number <- @numbers, shading <- @shadings,
do: [color, symbol, number, shading]
end
defp comb(_, 0), do: [[]]
defp comb([], _), do: []
defp comb([h|t], m) do
(for l <- comb(t, m-1), do: [h|l]) ++ comb(t, m)
end
end
RC.set_puzzle(9, 4)
RC.set_puzzle(12, 6)
- Output:
Dealt 9 cards: green oval one open red oval one open red oval two open green diamond two striped green diamond three open green diamond one open purple squiggle one open red oval three solid red oval three open Containing 4 sets: red oval one open red oval two open red oval three open red oval one open green diamond one open purple squiggle one open red oval two open green diamond three open purple squiggle one open green diamond two striped purple squiggle one open red oval three solid Dealt 12 cards: purple oval one open purple diamond two open red oval three striped purple diamond three striped purple oval one solid red oval two open green diamond three open green squiggle one solid green oval three striped red diamond two solid red diamond one solid green squiggle three striped Containing 6 sets: purple oval one open red diamond two solid green squiggle three striped purple diamond two open red oval three striped green squiggle one solid red oval three striped purple diamond three striped green squiggle three striped purple diamond three striped red oval two open green squiggle one solid purple oval one solid red oval two open green oval three striped purple oval one solid green squiggle one solid red diamond one solid
Erlang
Until a better solution is found this is one.
-module( set ).
-export( [deck/0, is_set/3, shuffle_deck/1, task/0] ).
-record( card, {number, symbol, shading, colour} ).
deck() -> [#card{number=N, symbol=Sy, shading=Sh, colour=C} || N <- [1,2,3], Sy <- [diamond, squiggle, oval], Sh <- [solid, striped, open], C <- [red, green, purple]].
is_set( Card1, Card2, Card3 ) ->
is_colour_correct( Card1, Card2, Card3 )
andalso is_number_correct( Card1, Card2, Card3 )
andalso is_shading_correct( Card1, Card2, Card3 )
andalso is_symbol_correct( Card1, Card2, Card3 ).
shuffle_deck( Deck ) -> knuth_shuffle:list( Deck ).
task() ->
basic(),
advanced().
advanced() -> common( 6, 12 ).
basic() -> common( 4, 9 ).
common( X, Y ) ->
{Sets, Cards} = find_x_sets_in_y_cards( X, Y, deck() ),
io:fwrite( "Cards ~p~n", [Cards] ),
io:fwrite( "Gives sets:~n" ),
[io:fwrite( "~p~n", [S] ) || S <- Sets].
find_x_sets_in_y_cards( X, Y, Deck ) ->
{Cards, _T} = lists:split( Y, shuffle_deck(Deck) ),
find_x_sets_in_y_cards( X, Y, Cards, make_sets1(Cards, []) ).
find_x_sets_in_y_cards( X, _Y, _Deck, Cards, Sets ) when erlang:length(Sets) =:= X -> {Sets, Cards};
find_x_sets_in_y_cards( X, Y, Deck, _Cards, _Sets ) -> find_x_sets_in_y_cards( X, Y, Deck ).
is_colour_correct( Card1, Card2, Card3 ) -> is_colour_different( Card1, Card2, Card3 ) orelse is_colour_same( Card1, Card2, Card3 ).
is_colour_different( #card{colour=C1}, #card{colour=C2}, #card{colour=C3} ) when C1 =/= C2, C1 =/= C3, C2 =/= C3 -> true;
is_colour_different( _Card1, _Card2, _Card3 ) -> false.
is_colour_same( #card{colour=C}, #card{colour=C}, #card{colour=C} ) -> true;
is_colour_same( _Card1, _Card2, _Card3 ) -> false.
is_number_correct( Card1, Card2, Card3 ) -> is_number_different( Card1, Card2, Card3 ) orelse is_number_same( Card1, Card2, Card3 ).
is_number_different( #card{number=N1}, #card{number=N2}, #card{number=N3} ) when N1 =/= N2, N1 =/= N3, N2 =/= N3 -> true;
is_number_different( _Card1, _Card2, _Card3 ) -> false.
is_number_same( #card{number=N}, #card{number=N}, #card{number=N} ) -> true;
is_number_same( _Card1, _Card2, _Card3 ) -> false.
is_shading_correct( Card1, Card2, Card3 ) -> is_shading_different( Card1, Card2, Card3 ) orelse is_shading_same( Card1, Card2, Card3 ).
is_shading_different( #card{shading=S1}, #card{shading=S2}, #card{shading=S3} ) when S1 =/= S2, S1 =/= S3, S2 =/= S3 -> true;
is_shading_different( _Card1, _Card2, _Card3 ) -> false.
is_shading_same( #card{shading=S}, #card{shading=S}, #card{shading=S} ) -> true;
is_shading_same( _Card1, _Card2, _Card3 ) -> false.
is_symbol_correct( Card1, Card2, Card3 ) -> is_symbol_different( Card1, Card2, Card3 ) orelse is_symbol_same( Card1, Card2, Card3 ).
is_symbol_different( #card{symbol=S1}, #card{symbol=S2}, #card{symbol=S3} ) when S1 =/= S2, S1 =/= S3, S2 =/= S3 -> true;
is_symbol_different( _Card1, _Card2, _Card3 ) -> false.
is_symbol_same( #card{symbol=S}, #card{symbol=S}, #card{symbol=S} ) -> true;
is_symbol_same( _Card1, _Card2, _Card3 ) -> false.
%% Nested loops 1, 2 and 3
make_sets1( [_Second_to_last, _Last], Sets ) -> Sets;
make_sets1( [Card | T], Sets ) -> make_sets1( T, make_sets2(Card, T, Sets) ).
make_sets2( _Card, [_Last], Sets ) -> Sets;
make_sets2( Card1, [Card2 | T], Sets ) -> make_sets2( Card1, T, make_sets3( Card1, Card2, T, Sets) ).
make_sets3( _Card1, _Card2, [], Sets ) -> Sets;
make_sets3( Card1, Card2, [Card3 | T], Sets ) ->
make_sets3( Card1, Card2, T, make_sets_acc(is_set(Card1, Card2, Card3), {Card1, Card2, Card3}, Sets) ).
make_sets_acc( true, Set, Sets ) -> [Set | Sets];
make_sets_acc( false, _Set, Sets ) -> Sets.
- Output:
53> set:task(). Cards [{card,2,diamond,striped,purple}, {card,3,squiggle,solid,purple}, {card,2,squiggle,open,red}, {card,3,oval,solid,purple}, {card,1,diamond,striped,green}, {card,1,oval,open,purple}, {card,3,squiggle,striped,purple}, {card,2,diamond,solid,purple}, {card,1,oval,striped,purple}] Gives sets: {{card,1,oval,open,purple}, {card,3,squiggle,striped,purple}, {card,2,diamond,solid,purple}} {{card,2,squiggle,open,red}, {card,3,oval,solid,purple}, {card,1,diamond,striped,green}} {{card,2,diamond,striped,purple}, {card,3,squiggle,striped,purple}, {card,1,oval,striped,purple}} {{card,2,diamond,striped,purple}, {card,3,squiggle,solid,purple}, {card,1,oval,open,purple}} Cards [{card,1,diamond,striped,purple}, {card,3,diamond,solid,purple}, {card,2,diamond,solid,green}, {card,1,diamond,open,green}, {card,3,oval,striped,red}, {card,3,squiggle,striped,red}, {card,2,oval,solid,purple}, {card,1,squiggle,open,green}, {card,3,diamond,solid,green}, {card,2,diamond,striped,red}, {card,2,squiggle,solid,purple}, {card,3,oval,open,purple}] Gives sets: {{card,3,squiggle,striped,red}, {card,3,diamond,solid,green}, {card,3,oval,open,purple}} {{card,3,squiggle,striped,red}, {card,1,squiggle,open,green}, {card,2,squiggle,solid,purple}} {{card,1,diamond,open,green}, {card,3,squiggle,striped,red}, {card,2,oval,solid,purple}} {{card,1,diamond,open,green}, {card,3,oval,striped,red}, {card,2,squiggle,solid,purple}} {{card,3,diamond,solid,purple}, {card,1,diamond,open,green}, {card,2,diamond,striped,red}} {{card,1,diamond,striped,purple}, {card,2,squiggle,solid,purple}, {card,3,oval,open,purple}}
F#
open System
type Number = One | Two | Three
type Color = Red | Green | Purple
type Fill = Solid | Open | Striped
type Symbol = Oval | Squiggle | Diamond
type Card = { Number: Number; Color: Color; Fill: Fill; Symbol: Symbol }
// A 'Set' is 3 cards in which each individual feature is either all the SAME on each card, OR all DIFFERENT on each card.
let SetSize = 3
type CardsGenerator() =
let _rand = Random()
let shuffleInPlace data =
Array.sortInPlaceBy (fun _ -> (_rand.Next(0, Array.length data))) data
let createCards() =
[| for n in [One; Two; Three] do
for c in [Red; Green; Purple] do
for f in [Solid; Open; Striped] do
for s in [Oval; Squiggle; Diamond] do
yield { Number = n; Color = c; Fill = f; Symbol = s } |]
let _cards = createCards()
member x.GetHand cardCount =
shuffleInPlace _cards
Seq.take cardCount _cards |> Seq.toList
// Find all the combinations of n elements
let rec combinations n items =
match n, items with
| 0, _ -> [[]]
| _, [] -> []
| k, (x::xs) -> List.map ((@) [x]) (combinations (k-1) xs) @ combinations k xs
let validCardSet (cards: Card list) =
// Valid feature if all features are the same or different
let validFeature = function
| [a; b; c] -> (a = b && b = c) || (a <> b && a <> c && b <> c)
| _ -> false
// Build and validate the feature lists
let isValid = cards |> List.fold (fun (ns, cs, fs, ss) c ->
(c.Number::ns, c.Color::cs, c.Fill::fs, c.Symbol::ss)) ([], [], [], [])
|> fun (ns, cs, fs, ss) ->
(validFeature ns) && (validFeature cs) && (validFeature fs) && (validFeature ss)
if isValid then Some cards else None
let findSolution cardCount setCount =
let cardsGen = CardsGenerator()
let rec search () =
let hand = cardsGen.GetHand cardCount
let foundSets = combinations SetSize hand |> List.choose validCardSet
if foundSets.Length = setCount then (hand, foundSets) else search()
search()
let displaySolution (hand: Card list, sets: Card list list) =
let printCardDetails (c: Card) =
printfn " %A %A %A %A" c.Number c.Color c.Symbol c.Fill
printfn "Dealt %d cards:" hand.Length
List.iter printCardDetails hand
printf "\n"
printfn "Found %d sets:" sets.Length
sets |> List.iter (fun cards -> List.iter printCardDetails cards; printf "\n" )
let playGame() =
let solve cardCount setCount =
displaySolution (findSolution cardCount setCount)
solve 9 4
solve 12 6
playGame()
Output:
Dealt 9 cards: Three Red Diamond Solid Two Red Oval Solid Three Red Oval Striped Two Purple Oval Striped One Green Squiggle Open One Purple Diamond Solid One Green Oval Striped One Green Diamond Solid Three Purple Diamond Striped Found 4 sets: Three Red Diamond Solid Two Purple Oval Striped One Green Squiggle Open Two Red Oval Solid One Green Squiggle Open Three Purple Diamond Striped Three Red Oval Striped Two Purple Oval Striped One Green Oval Striped One Green Squiggle Open One Green Oval Striped One Green Diamond Solid Dealt 12 cards: One Green Diamond Open Two Red Diamond Striped Three Red Oval Striped One Red Diamond Open Three Green Oval Open Two Purple Squiggle Solid Two Red Oval Striped One Red Oval Striped Two Red Oval Open Three Purple Oval Striped One Purple Diamond Open Three Red Oval Solid Found 6 sets: One Green Diamond Open Three Red Oval Striped Two Purple Squiggle Solid One Green Diamond Open One Red Diamond Open One Purple Diamond Open Three Red Oval Striped Two Red Oval Striped One Red Oval Striped Three Green Oval Open Three Purple Oval Striped Three Red Oval Solid Two Purple Squiggle Solid Three Purple Oval Striped One Purple Diamond Open One Red Oval Striped Two Red Oval Open Three Red Oval Solid
Factor
USING: arrays backtrack combinators.short-circuit formatting
fry grouping io kernel literals math.combinatorics math.matrices
prettyprint qw random sequences sets ;
IN: rosetta-code.set-puzzle
CONSTANT: deck $[
[
qw{ red green purple } amb-lazy
qw{ one two three } amb-lazy
qw{ oval squiggle diamond } amb-lazy
qw{ solid open striped } amb-lazy 4array
] bag-of
]
: valid-category? ( seq -- ? )
{ [ all-equal? ] [ all-unique? ] } 1|| ;
: valid-set? ( seq -- ? )
[ valid-category? ] column-map t [ and ] reduce ;
: find-sets ( seq -- seq )
3 <combinations> [ valid-set? ] filter ;
: deal-hand ( m n -- seq valid? )
[ deck swap sample ] dip over find-sets length = ;
: find-valid-hand ( m n -- seq )
[ f ] 2dip '[ drop _ _ deal-hand not ] loop ;
: set-puzzle ( m n -- )
[ find-valid-hand ] 2keep
[ "Dealt %d cards:\n" printf simple-table. nl ]
[
"Containing %d sets:\n" printf find-sets
{ { " " " " " " " " } } join simple-table. nl
] bi-curry* bi ;
: main ( -- )
9 4 set-puzzle
12 6 set-puzzle ;
MAIN: main
- Output:
Dealt 9 cards: purple one diamond striped purple three squiggle open purple one oval solid green two squiggle striped red one oval striped green three oval solid purple three diamond striped red two oval striped purple two diamond striped Containing 4 sets: purple one diamond striped purple three diamond striped purple two diamond striped purple three squiggle open purple one oval solid purple two diamond striped green two squiggle striped red one oval striped purple three diamond striped green two squiggle striped red two oval striped purple two diamond striped Dealt 12 cards: green one oval striped red two squiggle striped red two diamond open purple two oval solid green three squiggle open purple one squiggle striped purple two squiggle open red two squiggle solid red three oval open purple one oval solid red one diamond striped red two oval striped Containing 6 sets: green one oval striped purple two oval solid red three oval open green one oval striped purple one squiggle striped red one diamond striped red two diamond open red two squiggle solid red two oval striped purple two oval solid green three squiggle open red one diamond striped green three squiggle open purple one squiggle striped red two squiggle solid red two squiggle solid red three oval open red one diamond striped
Go
package main
import (
"fmt"
"math/rand"
"time"
)
const (
number = [3]string{"1", "2", "3"}
color = [3]string{"red", "green", "purple"}
shade = [3]string{"solid", "open", "striped"}
shape = [3]string{"oval", "squiggle", "diamond"}
)
type card int
func (c card) String() string {
return fmt.Sprintf("%s %s %s %s",
number[c/27],
color[c/9%3],
shade[c/3%3],
shape[c%3])
}
func main() {
rand.Seed(time.Now().Unix())
game("Basic", 9, 4)
game("Advanced", 12, 6)
}
func game(level string, cards, sets int) {
// create deck
d := make([]card, 81)
for i := range d {
d[i] = card(i)
}
var found [][3]card
for len(found) != sets {
found = found[:0]
// deal
for i := 0; i < cards; i++ {
j := rand.Intn(81 - i)
d[i], d[j] = d[j], d[i]
}
// consider all triplets
for i := 2; i < cards; i++ {
c1 := d[i]
for j := 1; j < i; j++ {
c2 := d[j]
l3:
for _, c3 := range d[:j] {
for f := card(1); f < 81; f *= 3 {
if (c1/f%3 + c2/f%3 + c3/f%3) % 3 != 0 {
continue l3 // not a set
}
}
// it's a set
found = append(found, [3]card{c1, c2, c3})
}
}
}
}
// found the right number
fmt.Printf("%s game. %d cards, %d sets.\n", level, cards, sets)
fmt.Println("Cards:")
for _, c := range d[:cards] {
fmt.Println(" ", c)
}
fmt.Println("Sets:")
for _, s := range found {
fmt.Printf(" %s\n %s\n %s\n",s[0],s[1],s[2])
}
}
- Output:
Basic game. 9 cards, 4 sets. Cards: 3 red solid oval 3 red open oval 3 purple striped oval 2 green striped oval 2 red solid oval 1 purple open diamond 2 purple solid squiggle 1 green striped diamond 3 green striped squiggle Sets: 2 purple solid squiggle 1 purple open diamond 3 purple striped oval 1 green striped diamond 2 purple solid squiggle 3 red open oval 3 green striped squiggle 1 purple open diamond 2 red solid oval 3 green striped squiggle 1 green striped diamond 2 green striped oval Advanced game. 12 cards, 6 sets. Cards: 2 green solid squiggle 3 red solid oval 3 purple open oval 2 purple open squiggle 3 red striped oval 1 red open oval 1 purple open diamond 1 green striped squiggle 3 red open oval 3 red striped squiggle 2 red striped oval 1 purple solid diamond Sets: 1 purple open diamond 2 purple open squiggle 3 purple open oval 1 purple open diamond 3 red striped oval 2 green solid squiggle 3 red open oval 3 red striped oval 3 red solid oval 2 red striped oval 1 red open oval 3 red solid oval 1 purple solid diamond 3 red solid oval 2 green solid squiggle 1 purple solid diamond 1 green striped squiggle 1 red open oval
Haskell
import Control.Monad.State
(State, evalState, replicateM, runState, state)
import System.Random (StdGen, newStdGen, randomR)
import Data.List (find, nub, sort)
combinations :: Int -> [a] -> [[a]]
combinations 0 _ = [[]]
combinations _ [] = []
combinations k (y:ys) = map (y :) (combinations (k - 1) ys) ++ combinations k ys
data Color
= Red
| Green
| Purple
deriving (Show, Enum, Bounded, Ord, Eq)
data Symbol
= Oval
| Squiggle
| Diamond
deriving (Show, Enum, Bounded, Ord, Eq)
data Count
= One
| Two
| Three
deriving (Show, Enum, Bounded, Ord, Eq)
data Shading
= Solid
| Open
| Striped
deriving (Show, Enum, Bounded, Ord, Eq)
data Card = Card
{ color :: Color
, symbol :: Symbol
, count :: Count
, shading :: Shading
} deriving (Show)
-- Identify a set of three cards by counting all attribute types.
-- if each count is 3 or 1 ( not 2 ) the the cards compose a set.
isSet :: [Card] -> Bool
isSet cs =
let total = length . nub . sort . flip map cs
in notElem 2 [total color, total symbol, total count, total shading]
-- Get a random card from a deck. Returns the card and removes it from the deck.
getCard :: State (StdGen, [Card]) Card
getCard =
state $
\(gen, cs) ->
let (i, newGen) = randomR (0, length cs - 1) gen
(a, b) = splitAt i cs
in (head b, (newGen, a ++ tail b))
-- Get a hand of cards. Starts with new deck and then removes the
-- appropriate number of cards from that deck.
getHand :: Int -> State StdGen [Card]
getHand n =
state $
\gen ->
let az = [minBound .. maxBound]
deck =
[ Card co sy ct sh
| co <- az
, sy <- az
, ct <- az
, sh <- az ]
(a, (newGen, _)) = runState (replicateM n getCard) (gen, deck)
in (a, newGen)
-- Get an unbounded number of hands of the appropriate number of cards.
getManyHands :: Int -> State StdGen [[Card]]
getManyHands n = (sequence . repeat) (getHand n)
-- Deal out hands of the appropriate size until one with the desired number
-- of sets is found. then print the hand and the sets.
showSolutions :: Int -> Int -> IO ()
showSolutions cardCount solutionCount = do
putStrLn $
"Showing hand of " ++
show cardCount ++ " cards with " ++ show solutionCount ++ " solutions."
gen <- newStdGen
let Just z =
find ((solutionCount ==) . length . filter isSet . combinations 3) $
evalState (getManyHands cardCount) gen
mapM_ print z
putStrLn ""
putStrLn "Solutions:"
mapM_ putSet $ filter isSet $ combinations 3 z
where
putSet st = do
mapM_ print st
putStrLn ""
-- Show a hand of 9 cards with 4 solutions
-- and a hand of 12 cards with 6 solutions.
main :: IO ()
main = do
showSolutions 9 4
showSolutions 12 6
- Output:
Showing hand of 9 cards with 4 solutions. Card {color = Red, symbol = Diamond, count = Two, shading = Open} Card {color = Purple, symbol = Diamond, count = Two, shading = Open} Card {color = Red, symbol = Oval, count = Two, shading = Open} Card {color = Green, symbol = Squiggle, count = Two, shading = Striped} Card {color = Red, symbol = Squiggle, count = Two, shading = Open} Card {color = Red, symbol = Diamond, count = One, shading = Striped} Card {color = Green, symbol = Diamond, count = Three, shading = Solid} Card {color = Purple, symbol = Squiggle, count = One, shading = Solid} Card {color = Purple, symbol = Oval, count = Three, shading = Striped} Solutions: Card {color = Red, symbol = Diamond, count = Two, shading = Open} Card {color = Red, symbol = Oval, count = Two, shading = Open} Card {color = Red, symbol = Squiggle, count = Two, shading = Open} Card {color = Purple, symbol = Diamond, count = Two, shading = Open} Card {color = Red, symbol = Diamond, count = One, shading = Striped} Card {color = Green, symbol = Diamond, count = Three, shading = Solid} Card {color = Purple, symbol = Diamond, count = Two, shading = Open} Card {color = Purple, symbol = Squiggle, count = One, shading = Solid} Card {color = Purple, symbol = Oval, count = Three, shading = Striped} Card {color = Green, symbol = Squiggle, count = Two, shading = Striped} Card {color = Red, symbol = Diamond, count = One, shading = Striped} Card {color = Purple, symbol = Oval, count = Three, shading = Striped} Showing hand of 12 cards with 6 solutions. Card {color = Purple, symbol = Oval, count = Two, shading = Solid} Card {color = Green, symbol = Squiggle, count = Two, shading = Striped} Card {color = Purple, symbol = Diamond, count = Two, shading = Open} Card {color = Green, symbol = Squiggle, count = One, shading = Open} Card {color = Green, symbol = Oval, count = Two, shading = Open} Card {color = Green, symbol = Oval, count = One, shading = Open} Card {color = Green, symbol = Squiggle, count = Three, shading = Solid} Card {color = Red, symbol = Diamond, count = Two, shading = Open} Card {color = Green, symbol = Diamond, count = Two, shading = Open} Card {color = Green, symbol = Oval, count = One, shading = Solid} Card {color = Red, symbol = Squiggle, count = Two, shading = Open} Card {color = Green, symbol = Oval, count = Three, shading = Open} Solutions: Card {color = Purple, symbol = Oval, count = Two, shading = Solid} Card {color = Green, symbol = Squiggle, count = Two, shading = Striped} Card {color = Red, symbol = Diamond, count = Two, shading = Open} Card {color = Green, symbol = Squiggle, count = Two, shading = Striped} Card {color = Green, symbol = Squiggle, count = One, shading = Open} Card {color = Green, symbol = Squiggle, count = Three, shading = Solid} Card {color = Purple, symbol = Diamond, count = Two, shading = Open} Card {color = Green, symbol = Oval, count = Two, shading = Open} Card {color = Red, symbol = Squiggle, count = Two, shading = Open} Card {color = Purple, symbol = Diamond, count = Two, shading = Open} Card {color = Red, symbol = Diamond, count = Two, shading = Open} Card {color = Green, symbol = Diamond, count = Two, shading = Open} Card {color = Green, symbol = Squiggle, count = One, shading = Open} Card {color = Green, symbol = Diamond, count = Two, shading = Open} Card {color = Green, symbol = Oval, count = Three, shading = Open} Card {color = Green, symbol = Oval, count = Two, shading = Open} Card {color = Green, symbol = Oval, count = One, shading = Open} Card {color = Green, symbol = Oval, count = Three, shading = Open}
J
Solution:
require 'stats/base'
Number=: ;:'one two three'
Colour=: ;:'red green purple'
Fill=: ;:'solid open striped'
Symbol=: ;:'oval squiggle diamond'
Features=: Number ; Colour ; Fill ;< Symbol
Deck=: > ; <"1 { i.@#&.> Features
sayCards=: (', ' joinstring Features {&>~ ])"1
drawRandom=: ] {~ (? #)
isSet=: *./@:(1 3 e.~ [: #@~."1 |:)"2
getSets=: [: (] #~ isSet) ] {~ 3 comb #
countSets=: #@:getSets
set_puzzle=: verb define
target=. <. -: y
whilst. target ~: countSets Hand do.
Hand=. y drawRandom Deck
end.
echo 'Dealt ',(": y),' Cards:'
echo sayCards sort Hand
echo LF,'Found ',(":target),' Sets:'
echo sayCards sort"2 getSets Hand
)
Example:
set_puzzle 9
Dealt 9 Cards:
one, red, solid, oval
one, green, open, squiggle
two, purple, striped, squiggle
three, red, solid, squiggle
three, red, open, oval
three, green, solid, oval
three, green, open, diamond
three, purple, open, oval
three, purple, striped, oval
Found 4 Sets:
three, red, solid, squiggle
three, green, open, diamond
three, purple, striped, oval
one, red, solid, oval
two, purple, striped, squiggle
three, green, open, diamond
one, green, open, squiggle
two, purple, striped, squiggle
three, red, solid, squiggle
three, red, open, oval
three, green, solid, oval
three, purple, striped, oval
Java
import java.util.*;
public class SetPuzzle {
enum Color {
GREEN(0), PURPLE(1), RED(2);
private Color(int v) {
val = v;
}
public final int val;
}
enum Number {
ONE(0), TWO(1), THREE(2);
private Number(int v) {
val = v;
}
public final int val;
}
enum Symbol {
OVAL(0), DIAMOND(1), SQUIGGLE(2);
private Symbol(int v) {
val = v;
}
public final int val;
}
enum Fill {
OPEN(0), STRIPED(1), SOLID(2);
private Fill(int v) {
val = v;
}
public final int val;
}
private static class Card implements Comparable<Card> {
Color c;
Number n;
Symbol s;
Fill f;
@Override
public String toString() {
return String.format("[Card: %s, %s, %s, %s]", c, n, s, f);
}
@Override
public int compareTo(Card o) {
return (c.val - o.c.val) * 10 + (n.val - o.n.val);
}
}
private static Card[] deck;
public static void main(String[] args) {
deck = new Card[81];
Color[] colors = Color.values();
Number[] numbers = Number.values();
Symbol[] symbols = Symbol.values();
Fill[] fillmodes = Fill.values();
for (int i = 0; i < deck.length; i++) {
deck[i] = new Card();
deck[i].c = colors[i / 27];
deck[i].n = numbers[(i / 9) % 3];
deck[i].s = symbols[(i / 3) % 3];
deck[i].f = fillmodes[i % 3];
}
findSets(12);
}
private static void findSets(int numCards) {
int target = numCards / 2;
Card[] cards;
Card[][] sets = new Card[target][3];
int cnt;
do {
Collections.shuffle(Arrays.asList(deck));
cards = Arrays.copyOfRange(deck, 0, numCards);
cnt = 0;
outer:
for (int i = 0; i < cards.length - 2; i++) {
for (int j = i + 1; j < cards.length - 1; j++) {
for (int k = j + 1; k < cards.length; k++) {
if (validSet(cards[i], cards[j], cards[k])) {
if (cnt < target)
sets[cnt] = new Card[]{cards[i], cards[j], cards[k]};
if (++cnt > target) {
break outer;
}
}
}
}
}
} while (cnt != target);
Arrays.sort(cards);
System.out.printf("GIVEN %d CARDS:\n\n", numCards);
for (Card c : cards) {
System.out.println(c);
}
System.out.println();
System.out.println("FOUND " + target + " SETS:\n");
for (Card[] set : sets) {
for (Card c : set) {
System.out.println(c);
}
System.out.println();
}
}
private static boolean validSet(Card c1, Card c2, Card c3) {
int tot = 0;
tot += (c1.c.val + c2.c.val + c3.c.val) % 3;
tot += (c1.n.val + c2.n.val + c3.n.val) % 3;
tot += (c1.s.val + c2.s.val + c3.s.val) % 3;
tot += (c1.f.val + c2.f.val + c3.f.val) % 3;
return tot == 0;
}
}
GIVEN 12 CARDS: [Card: GREEN, ONE, DIAMOND, OPEN] [Card: GREEN, TWO, SQUIGGLE, OPEN] [Card: GREEN, THREE, DIAMOND, STRIPED] [Card: GREEN, THREE, DIAMOND, OPEN] [Card: PURPLE, ONE, DIAMOND, SOLID] [Card: PURPLE, ONE, SQUIGGLE, SOLID] [Card: PURPLE, TWO, SQUIGGLE, SOLID] [Card: PURPLE, THREE, DIAMOND, OPEN] [Card: RED, ONE, SQUIGGLE, STRIPED] [Card: RED, ONE, OVAL, STRIPED] [Card: RED, TWO, DIAMOND, STRIPED] [Card: RED, THREE, OVAL, STRIPED] FOUND 6 SETS: [Card: GREEN, TWO, SQUIGGLE, OPEN] [Card: PURPLE, ONE, DIAMOND, SOLID] [Card: RED, THREE, OVAL, STRIPED] [Card: GREEN, THREE, DIAMOND, OPEN] [Card: RED, ONE, OVAL, STRIPED] [Card: PURPLE, TWO, SQUIGGLE, SOLID] [Card: GREEN, THREE, DIAMOND, OPEN] [Card: PURPLE, ONE, DIAMOND, SOLID] [Card: RED, TWO, DIAMOND, STRIPED] [Card: RED, ONE, SQUIGGLE, STRIPED] [Card: RED, THREE, OVAL, STRIPED] [Card: RED, TWO, DIAMOND, STRIPED] [Card: RED, ONE, OVAL, STRIPED] [Card: PURPLE, ONE, SQUIGGLE, SOLID] [Card: GREEN, ONE, DIAMOND, OPEN] [Card: GREEN, ONE, DIAMOND, OPEN] [Card: RED, THREE, OVAL, STRIPED] [Card: PURPLE, TWO, SQUIGGLE, SOLID]
Julia
Plays one basic game and one advanced game.
using Random, IterTools, Combinatorics
function SetGameTM(basic = true)
drawsize = basic ? 9 : 12
setsneeded = div(drawsize, 2)
setsof3 = Vector{Vector{NTuple{4, String}}}()
draw = Vector{NTuple{4, String}}()
deck = collect(Iterators.product(["red", "green", "purple"], ["one", "two", "three"],
["oval", "squiggle", "diamond"], ["solid", "open", "striped"]))
while length(setsof3) != setsneeded
empty!(draw)
empty!(setsof3)
map(x -> push!(draw, x), shuffle(deck)[1:drawsize])
for threecards in combinations(draw, 3)
canuse = true
for i in 1:4
u = length(unique(map(x->x[i], threecards)))
if u != 3 && u != 1
canuse = false
end
end
if canuse
push!(setsof3, threecards)
end
end
end
println("Dealt $drawsize cards:")
for card in draw
println(" $card")
end
println("\nFormed these cards into $setsneeded sets:")
for set in setsof3
for card in set
println(" $card")
end
println()
end
end
SetGameTM()
SetGameTM(false)
- Output:
Dealt 9 cards: ("green", "one", "oval", "open") ("green", "three", "diamond", "open") ("purple", "one", "diamond", "striped") ("purple", "three", "oval", "solid") ("red", "two", "diamond", "open") ("red", "one", "oval", "striped") ("green", "one", "squiggle", "striped") ("green", "two", "oval", "solid") ("purple", "two", "squiggle", "open") Formed these cards into 4 sets: ("green", "three", "diamond", "open") ("green", "one", "squiggle", "striped") ("green", "two", "oval", "solid") ("purple", "one", "diamond", "striped") ("purple", "three", "oval", "solid") ("purple", "two", "squiggle", "open") ("purple", "one", "diamond", "striped") ("red", "one", "oval", "striped") ("green", "one", "squiggle", "striped") ("purple", "three", "oval", "solid") ("red", "two", "diamond", "open") ("green", "one", "squiggle", "striped") Dealt 12 cards: ("red", "one", "squiggle", "open") ("green", "one", "diamond", "striped") ("red", "two", "oval", "solid") ("green", "three", "squiggle", "striped") ("green", "three", "squiggle", "open") ("red", "one", "oval", "solid") ("purple", "two", "oval", "striped") ("green", "two", "oval", "striped") ("green", "three", "oval", "open") ("purple", "two", "diamond", "open") ("purple", "three", "diamond", "striped") ("purple", "two", "squiggle", "solid") Formed these cards into 6 sets: ("red", "one", "squiggle", "open") ("green", "three", "squiggle", "striped") ("purple", "two", "squiggle", "solid") ("red", "one", "squiggle", "open") ("green", "three", "oval", "open") ("purple", "two", "diamond", "open") ("green", "one", "diamond", "striped") ("green", "three", "squiggle", "striped") ("green", "two", "oval", "striped") ("green", "three", "squiggle", "striped") ("red", "one", "oval", "solid") ("purple", "two", "diamond", "open") ("red", "one", "oval", "solid") ("purple", "two", "oval", "striped") ("green", "three", "oval", "open") ("purple", "two", "oval", "striped") ("purple", "two", "diamond", "open") ("purple", "two", "squiggle", "solid")
Kotlin
// version 1.1.3
import java.util.Collections.shuffle
enum class Color { RED, GREEN, PURPLE }
enum class Symbol { OVAL, SQUIGGLE, DIAMOND }
enum class Number { ONE, TWO, THREE }
enum class Shading { SOLID, OPEN, STRIPED }
enum class Degree { BASIC, ADVANCED }
class Card(
val color: Color,
val symbol: Symbol,
val number: Number,
val shading: Shading
) : Comparable<Card> {
private val value =
color.ordinal * 27 + symbol.ordinal * 9 + number.ordinal * 3 + shading.ordinal
override fun compareTo(other: Card) = value.compareTo(other.value)
override fun toString() = (
color.name.padEnd(8) +
symbol.name.padEnd(10) +
number.name.padEnd(7) +
shading.name.padEnd(7)
).toLowerCase()
companion object {
val zero = Card(Color.RED, Symbol.OVAL, Number.ONE, Shading.SOLID)
}
}
fun createDeck() =
List<Card>(81) {
val col = Color.values() [it / 27]
val sym = Symbol.values() [it / 9 % 3]
val num = Number.values() [it / 3 % 3]
val shd = Shading.values()[it % 3]
Card(col, sym, num, shd)
}
fun playGame(degree: Degree) {
val deck = createDeck()
val nCards = if (degree == Degree.BASIC) 9 else 12
val nSets = nCards / 2
val sets = Array(nSets) { Array(3) { Card.zero } }
var hand: Array<Card>
outer@ while (true) {
shuffle(deck)
hand = deck.take(nCards).toTypedArray()
var count = 0
for (i in 0 until hand.size - 2) {
for (j in i + 1 until hand.size - 1) {
for (k in j + 1 until hand.size) {
val trio = arrayOf(hand[i], hand[j], hand[k])
if (isSet(trio)) {
sets[count++] = trio
if (count == nSets) break@outer
}
}
}
}
}
hand.sort()
println("DEALT $nCards CARDS:\n")
println(hand.joinToString("\n"))
println("\nCONTAINING $nSets SETS:\n")
for (s in sets) {
s.sort()
println(s.joinToString("\n"))
println()
}
}
fun isSet(trio: Array<Card>): Boolean {
val r1 = trio.sumBy { it.color.ordinal } % 3
val r2 = trio.sumBy { it.symbol.ordinal } % 3
val r3 = trio.sumBy { it.number.ordinal } % 3
val r4 = trio.sumBy { it.shading.ordinal } % 3
return (r1 + r2 + r3 + r4) == 0
}
fun main(args: Array<String>) {
playGame(Degree.BASIC)
println()
playGame(Degree.ADVANCED)
}
Sample output:
DEALT 9 CARDS: red oval three solid red diamond two solid green oval one open green oval three open green squiggle one open green diamond one open purple oval three striped purple squiggle three solid purple diamond two striped CONTAINING 4 SETS: red oval three solid green squiggle one open purple diamond two striped red oval three solid green oval three open purple oval three striped green oval one open green squiggle one open green diamond one open red diamond two solid green squiggle one open purple oval three striped DEALT 12 CARDS: red squiggle two solid red diamond two solid red diamond two open red diamond two striped green oval one open green oval three solid green oval three open green squiggle one solid green diamond one striped purple oval one solid purple oval three open purple squiggle one striped CONTAINING 6 SETS: red diamond two open green oval three solid purple squiggle one striped red diamond two solid red diamond two open red diamond two striped red diamond two solid green oval three open purple squiggle one striped red squiggle two solid green diamond one striped purple oval three open green oval one open green squiggle one solid green diamond one striped red diamond two striped green squiggle one solid purple oval three open
Mathematica /Wolfram Language
A simple brute force approach. This code highlights two things: 1) a few of Mathematica's "higher-level" functions such as Tuples and Subsets and 2) the straightforwardness enabled by the language's "dynamic typing" (more precisely, its symbolic semantics) and its usage of lists for everything (in this particular example, the fact that functions such as Tuples and Entropy can be used on lists with arbitrary content).
colors = {Red, Green, Purple};
symbols = {"0", "\[TildeTilde]", "\[Diamond]"};
numbers = {1, 2, 3};
shadings = {"\[FilledSquare]", "\[Square]", "\[DoublePrime]"};
validTripleQ[l_List] := Entropy[l] != Entropy[{1, 1, 2}];
validSetQ[cards_List] := And @@ (validTripleQ /@ Transpose[cards]);
allCards = Tuples[{colors, symbols, numbers, shadings}];
deal[{numDeal_, setNum_}] := Module[{cards, count = 0},
While[count != setNum,
cards = RandomSample[allCards, numDeal];
count = Count[Subsets[cards, {3}], _?validSetQ]];
cards];
Row[{Style[#2, #1], #3, #4}] & @@@ deal[{9, 4}]
Nim
import algorithm, math, random, sequtils, strformat, strutils
type
# Card features.
Number {.pure.} = enum One, Two, Three
Color {.pure.} = enum Red, Green, Purple
Symbol {.pure.} = enum Oval, Squiggle, Diamond
Shading {.pure.} = enum Solid, Open, Striped
# Cards and list of cards.
Card = tuple[number: Number; color: Color; symbol: Symbol; shading: Shading]
Triplet = array[3, Card]
Deck = array[81, Card]
# Game level.
Level {.pure.} = enum Basic = "basic", Advanced = "advanced"
proc `$`(card: Card): string =
## Return the string representation of a card.
toLowerAscii(&"{card.number:<5} {card.color:<6} {card.symbol:<8} {card.shading:<7}")
proc initDeck(): Deck =
## Create a new deck.
var i = 0
for num in Number.low..Number.high:
for col in Color.low..Color.high:
for sym in Symbol.low..Symbol.high:
for sh in Shading.low..Shading.high:
result[i] = (number: num, color: col, symbol: sym, shading: sh)
inc i
proc isSet(triplet: Triplet): bool =
## Check if a triplets of cards is a set.
sum(triplet.mapIt(ord(it.number))) mod 3 == 0 and
sum(triplet.mapIt(ord(it.color))) mod 3 == 0 and
sum(triplet.mapIt(ord(it.symbol))) mod 3 == 0 and
sum(triplet.mapIt(ord(it.shading))) mod 3 == 0
proc playGame(level: Level) =
## Play the game at given level.
var deck = initDeck()
let (nCards, nSets) = if level == Basic: (9, 4) else: (12, 6)
var sets: seq[Triplet]
var hand: seq[Card]
echo &"Playing {level} game: {nCards} cards, {nSets} sets."
block searchHand:
while true:
sets.setLen(0)
deck.shuffle()
hand = deck[0..<nCards]
block countSets:
for i in 0..(nCards - 3):
for j in (i + 1)..(nCards - 2):
for k in (j + 1)..(nCards - 1):
let triplet = [hand[i], hand[j], hand[k]]
if triplet.isSet():
sets.add triplet
if sets.len > nSets:
break countSets # Too much sets. Try with a new hand.
if sets.len == nSets:
break searchHand # Found: terminate search.
# Display the hand and the sets.
echo "\nCards:"
for card in sorted(hand): echo " ", card
echo "\nSets:"
for s in sets:
for card in sorted(s): echo " ", card
echo()
randomize()
playGame(Basic)
echo()
playGame(Advanced)
- Output:
Playing basic game: 9 cards, 4 sets. Cards: one purple oval solid one purple diamond open two red squiggle open two green diamond open two green diamond striped two purple oval open three red diamond solid three red diamond open three purple squiggle open Sets: one purple diamond open two purple oval open three purple squiggle open one purple diamond open two green diamond striped three red diamond solid two red squiggle open two green diamond open two purple oval open one purple diamond open two green diamond open three red diamond open Playing advanced game: 12 cards, 6 sets. Cards: one green diamond striped one purple diamond striped two red oval open two red diamond open two green oval solid two green oval striped three red squiggle striped three green oval solid three green squiggle solid three green squiggle open three green squiggle striped three purple diamond open Sets: one green diamond striped two green oval striped three green squiggle striped one purple diamond striped two green oval striped three red squiggle striped one green diamond striped two green oval solid three green squiggle open one purple diamond striped two red oval open three green squiggle solid three green squiggle solid three green squiggle open three green squiggle striped three red squiggle striped three green oval solid three purple diamond open
PARI/GP
dealraw(cards)=vector(cards,i,vector(4,j,1<<random(3)));
howmany(a,b,c)=hammingweight(bitor(a,bitor(b,c)));
name(v)=Str(["red","green",0,"purple"][v[1]],", ",["oval","squiggle",0,"diamond"][v[2]],", ",["one","two",0,"three"][v[3]],", ",["solid","open",0,"striped"][v[4]]);
check(D,sets)={
my(S=List());
for(i=1,#D-2,for(j=i+1,#D-1,for(k=j+1,#D,
for(x=1,4,
if(howmany(D[i][x],D[j][x],D[k][x])==2,next(2))
);
listput(S,[i,j,k]);
if(#S>sets,return(0))
)));
if(#S==sets,Vec(S),0)
};
deal(cards,sets)={
my(v,s);
until(s,
s=check(v=dealraw(cards),sets)
);
v=apply(name,v);
for(i=1,cards,print(v[i]));
for(i=1,sets,
print("Set #"i);
for(j=1,3,print(" "v[s[i][j]]))
)
};
deal(9,4)
deal(12,6)
- Output:
green, diamond, one, open purple, squiggle, three, solid green, squiggle, two, striped green, oval, one, striped purple, oval, two, striped purple, oval, one, open red, squiggle, one, open green, squiggle, one, solid red, diamond, three, solid Set #1 green, diamond, one, open green, oval, one, striped green, squiggle, one, solid Set #2 green, diamond, one, open purple, oval, one, open red, squiggle, one, open Set #3 purple, squiggle, three, solid green, squiggle, two, striped red, squiggle, one, open Set #4 green, squiggle, two, striped purple, oval, one, open red, diamond, three, solid purple, squiggle, three, open red, oval, two, open purple, oval, two, solid green, squiggle, two, solid purple, diamond, two, striped purple, squiggle, two, solid green, oval, two, striped red, oval, one, striped red, squiggle, two, striped green, diamond, three, solid green, diamond, two, open purple, oval, one, open Set #1 red, oval, two, open purple, oval, two, solid green, oval, two, striped Set #2 red, oval, two, open green, squiggle, two, solid purple, diamond, two, striped Set #3 purple, oval, two, solid red, squiggle, two, striped green, diamond, two, open Set #4 green, squiggle, two, solid green, oval, two, striped green, diamond, two, open Set #5 purple, diamond, two, striped green, oval, two, striped red, squiggle, two, striped Set #6 red, squiggle, two, striped green, diamond, three, solid purple, oval, one, open
Perl
It's actually slightly simplified, since generating Enum classes and objects would be overkill for this particular task.
#!perl
use strict;
use warnings;
# This code was adapted from the Raku solution for this task.
# Each element of the deck is an integer, which, when written
# in octal, has four digits, which are all either 1, 2, or 4.
my $fmt = '%4o';
my @deck = grep sprintf($fmt, $_) !~ tr/124//c, 01111 .. 04444;
# Given a feature digit (1, 2, or 4), produce the feature's name.
# Note that digits 0 and 3 are unused.
my @features = map [split ' '], split /\n/,<<'';
! red green ! purple
! one two ! three
! oval squiggle ! diamond
! solid open ! striped
81 == @deck or die "There are ".@deck." cards (should be 81)";
# By default, draw 9 cards, but if the user
# supplied a parameter, use that.
my $draw = shift(@ARGV) || 9;
my $goal = int($draw/2);
# Get the possible combinations of 3 indices into $draw elements.
my @combinations = combine(3, 0 .. $draw-1);
my @sets;
do {
# Shuffle the first $draw elements of @deck.
for my $i ( 0 .. $draw-1 ) {
my $j = $i + int rand(@deck - $i);
@deck[$i, $j] = @deck[$j, $i];
}
# Find all valid sets using the shuffled elements.
@sets = grep {
my $or = 0;
$or |= $_ for @deck[@$_];
# If all colors (or whatever) are the same, then
# a 1, 2, or 4 will result when we OR them together.
# If they're all different, then a 7 will result.
# If any other digit occurs, the set is invalid.
sprintf($fmt, $or) !~ tr/1247//c;
} @combinations;
# Continue until there are exactly $goal valid sets.
} until @sets == $goal;
print "Drew $draw cards:\n";
for my $i ( 0 .. $#sets ) {
print "Set ", $i+1, ":\n";
my @cards = @deck[ @{$sets[$i]} ];
for my $card ( @cards ) {
my @octal = split //, sprintf '%4o', $card;
my @f = map $features[$_][$octal[$_]], 0 .. 3;
printf " %-6s %-5s %-8s %s\n", @f;
}
}
exit;
# This function is adapted from the perl5i solution for the
# RosettaCode Combinations task.
sub combine {
my $n = shift;
return unless @_;
return map [$_], @_ if $n == 1;
my $head = shift;
my @result = combine( $n-1, @_ );
unshift @$_, $head for @result;
@result, combine( $n, @_ );
}
__END__
- Output:
Drew 12 cards: Set 1: red three oval striped green three diamond striped purple three squiggle striped Set 2: red three oval striped purple three squiggle open green three diamond solid Set 3: purple one diamond striped red three diamond striped green two diamond striped Set 4: green three diamond striped green three diamond open green three diamond solid Set 5: red three diamond striped green three oval solid purple three squiggle open Set 6: green two diamond striped purple three squiggle striped red one oval striped
Phix
Converts cards 1..81 (that idea from C) to 1/2/4 [/7] (that idea from Perl) but inverts the validation
with javascript_semantics function comb(sequence pool, integer needed, sequence res={}, integer done=0, sequence chosen={}) if needed=0 then -- got a full set sequence {a,b,c} = chosen if not find_any({3,5,6},flatten(sq_or_bits(sq_or_bits(a,b),c))) then res = append(res,chosen) end if elsif done+needed<=length(pool) then -- get all combinations with and without the next item: done += 1 res = comb(pool,needed-1,res,done,append(deep_copy(chosen),pool[done])) res = comb(pool,needed,res,done,chosen) end if return res end function constant m124 = {1,2,4} function card(integer n) --returns the nth card (n is 1..81, res is length 4 of 1/2/4) n -= 1 sequence res = repeat(0,4) for i=1 to 4 do res[i] = m124[remainder(n,3)+1] n = floor(n/3) end for return res end function constant colours = {"red", "green", 0, "purple"}, symbols = {"oval", "squiggle", 0, "diamond"}, numbers = {"one", "two", 0, "three"}, shades = {"solid", "open", 0, "striped"} procedure print_cards(sequence hand, sequence cards) for i=1 to length(cards) do integer {c,m,n,g} = cards[i], id = find(cards[i],hand) printf(1,"%3d: %-7s %-9s %-6s %s\n",{id,colours[c],symbols[m],numbers[n],shades[g]}) end for printf(1,"\n") end procedure procedure play(integer cards=9, integer sets=4) integer deals = 1 while 1 do sequence deck = shuffle(tagset(81)) sequence hand = deck[1..cards] for i=1 to length(hand) do hand[i] = card(hand[i]) end for sequence res = comb(hand,3) if length(res)=sets then printf(1,"dealt %d cards (%d deals)\n",{cards,deals}) print_cards(hand,hand) printf(1,"with %d sets\n",{sets}) for i=1 to sets do print_cards(hand,res[i]) end for exit end if deals += 1 end while end procedure play() --play(12,6) --play(9,6)
- Output:
dealt 9 cards (172 deals) 1: red oval two open 2: green oval one solid 3: purple diamond two striped 4: green diamond one striped 5: green oval one striped 6: purple squiggle three solid 7: green diamond two solid 8: red diamond two open 9: green squiggle one striped with 4 sets 1: red oval two open 4: green diamond one striped 6: purple squiggle three solid 3: purple diamond two striped 7: green diamond two solid 8: red diamond two open 4: green diamond one striped 5: green oval one striped 9: green squiggle one striped 5: green oval one striped 6: purple squiggle three solid 8: red diamond two open
Picat
The problem generator check that it problem has exactly one solution, so that step can take a little time (some seconds). fail/0
is used to check for unicity of the solution.
import util.
import cp.
%
% Solve the task in the description.
%
go ?=>
sets(1,Sets,SetLen,NumSets),
print_cards(Sets),
set_puzzle(Sets,SetLen,NumSets,X),
print_sol(Sets,X),
nl,
fail, % check for other solutions
nl.
go => true.
%
% Generate and solve a random instance with NumCards cards,
% giving exactly NumSets sets.
%
go2 =>
_ = random2(),
NumCards = 9, NumSets = 4, SetLen = 3,
generate_and_solve(NumCards,NumSets,SetLen),
fail, % prove unicity
nl.
go3 =>
_ = random2(),
NumCards = 12, NumSets = 6, SetLen = 3,
generate_and_solve(NumCards,NumSets,SetLen),
fail, % prove unicity)
nl.
%
% Solve a Set Puzzle.
%
set_puzzle(Cards,SetLen,NumWanted, X) =>
Len = Cards.length,
NumFeatures = Cards[1].length,
X = new_list(NumWanted),
foreach(I in 1..NumWanted)
Y = new_array(SetLen),
foreach(J in 1..SetLen)
member(Y[J], 1..Len)
end,
% unicity and symmetry breaking of Y
increasing2(Y),
% ensure unicity of the selected cards in X
if I > 1 then
foreach(J in 1..I-1) X[J] @< Y end
end,
foreach(F in 1..NumFeatures)
Z = [Cards[Y[J],F] : J in 1..SetLen],
(allequal(Z) ; alldiff(Z))
end,
X[I] = Y
end.
% (Strictly) increasing
increasing2(List) =>
foreach(I in 1..List.length-1)
List[I] @< List[I+1]
end.
% All elements must be equal
allequal(List) =>
foreach(I in 1..List.length-1)
List[I] = List[I+1]
end.
% All elements must be different
alldiff(List) =>
Len = List.length,
foreach(I in 1..Len, J in 1..I-1)
List[I] != List[J]
end.
% Print a solution
print_sol(Sets,X) =>
println("Solution:"),
println(x=X),
foreach(R in X)
println([Sets[R[I]] : I in 1..3])
end,
nl.
% Print the cards
print_cards(Cards) =>
println("Cards:"),
foreach({Card,I} in zip(Cards,1..Cards.len))
println([I,Card])
end,
nl.
%
% Generate a problem instance with NumSets sets (a unique solution).
%
% Note: not all random combinations of cards give a unique solution so
% it might generate a number of deals.
%
generate_instance(NumCards,NumSets,SetLen, Cards) =>
println([numCards=NumCards,numWantedSets=NumSets,setLen=SetLen]),
Found = false,
% Check that this instance has a unique solution.
while(Found = false)
if Cards = random_deal(NumCards),
count_all(set_puzzle(Cards,SetLen,NumSets,_X)) = 1
then
Found := true
end
end.
%
% Generate a random problem instance of N cards.
%
random_deal(N) = Deal.sort() =>
all_combinations(Combinations),
Deal = [],
foreach(_I in 1..N)
Len = Combinations.len,
Rand = random(1,Len),
Comb = Combinations[Rand],
Deal := Deal ++ [Comb],
Combinations := delete_all(Combinations, Comb)
end.
%
% Generate a random instance and solve it.
%
generate_and_solve(NumCards,NumSets,SetLen) =>
generate_instance(NumCards,NumSets,SetLen, Cards),
print_cards(Cards),
set_puzzle(Cards,SetLen,NumSets,X), % solve it
print_sol(Cards,X),
nl.
%
% All the 81 possible combinations (cards)
%
table
all_combinations(All) =>
Colors = [red, green, purple],
Symbols = [oval, squiggle, diamond],
Numbers = [one, two, three],
Shadings = [solid, open, striped],
All = findall([Color,Symbol,Number,Shading],
(member(Color,Colors),
member(Symbol,Symbols),
member(Number,Numbers),
member(Shading,Shadings))).
%
% From the task description.
%
% Solution: [[1,6,9],[2,3,4],[2,6,8],[5,6,7]]
%
sets(1,Sets,SetLen,Wanted) =>
Sets =
[
[green, one, oval, striped], % 1
[green, one, diamond, open], % 2
[green, one, diamond, striped], % 3
[green, one, diamond, solid], % 4
[purple, one, diamond, open], % 5
[purple, two, squiggle, open], % 6
[purple, three, oval, open], % 7
[red, three, oval, open], % 8
[red, three, diamond, solid] % 9
],
SetLen = 3,
Wanted = 4.
Solving the instance in the task description (go/0
):
- Output:
[1,[green,one,oval,striped]] [2,[green,one,diamond,open]] [3,[green,one,diamond,striped]] [4,[green,one,diamond,solid]] [5,[purple,one,diamond,open]] [6,[purple,two,squiggle,open]] [7,[purple,three,oval,open]] [8,[red,three,oval,open]] [9,[red,three,diamond,solid]] Solution: x = [{1,6,9},{2,3,4},{2,6,8},{5,6,7}] [[green,one,oval,striped],[purple,two,squiggle,open],[red,three,diamond,solid]] [[green,one,diamond,open],[green,one,diamond,striped],[green,one,diamond,solid]] [[green,one,diamond,open],[purple,two,squiggle,open],[red,three,oval,open]] [[purple,one,diamond,open],[purple,two,squiggle,open],[purple,three,oval,open]]
Solving the two random tasks (go2/0
) and go3/0
):
{{out}::
[numCards = 9,numWantedSets = 4,setLen = 3] Cards: [1,[green,squiggle,one,solid]] [2,[green,squiggle,two,solid]] [3,[purple,diamond,three,striped]] [4,[purple,oval,two,striped]] [5,[purple,squiggle,one,striped]] [6,[purple,squiggle,three,solid]] [7,[purple,squiggle,three,striped]] [8,[red,squiggle,one,open]] [9,[red,squiggle,three,open]] Solution: x = [{1,5,8},{2,5,9},{2,7,8},{3,4,5}] [[green,squiggle,one,solid],[purple,squiggle,one,striped],[red,squiggle,one,open]] [[green,squiggle,two,solid],[purple,squiggle,one,striped],[red,squiggle,three,open]] [[green,squiggle,two,solid],[purple,squiggle,three,striped],[red,squiggle,one,open]] [[purple,diamond,three,striped],[purple,oval,two,striped],[purple,squiggle,one,striped]] [numCards = 12,numWantedSets = 6,setLen = 3] Cards: [1,[green,diamond,one,solid]] [2,[green,diamond,two,solid]] [3,[green,oval,one,open]] [4,[purple,oval,one,solid]] [5,[purple,squiggle,one,open]] [6,[purple,squiggle,one,solid]] [7,[purple,squiggle,one,striped]] [8,[red,diamond,one,solid]] [9,[red,diamond,two,striped]] [10,[red,oval,one,striped]] [11,[red,squiggle,three,solid]] [12,[red,squiggle,three,striped]] Solution: x = [{1,5,10},{2,4,11},{3,4,10},{3,7,8},{5,6,7},{9,10,12}] [[green,diamond,one,solid],[purple,squiggle,one,open],[red,oval,one,striped]] [[green,diamond,two,solid],[purple,oval,one,solid],[red,squiggle,three,solid]] [[green,oval,one,open],[purple,oval,one,solid],[red,oval,one,striped]] [[green,oval,one,open],[purple,squiggle,one,striped],[red,diamond,one,solid]] [[purple,squiggle,one,open],[purple,squiggle,one,solid],[purple,squiggle,one,striped]] [[red,diamond,two,striped],[red,oval,one,striped],[red,squiggle,three,striped]]
Constraint model
Here is the additional code for a constraint model. Note that the constraint solver only handles integers so the features must be converted to integers. To simplify, the random instance generator does not check for unicity of the problem instance, so it can have (and often have) a lot of solutions.
go4 =>
NumCards = 18,
NumWanted = 9,
SetLen = 3,
time(generate_instance2(NumCards,NumWanted, SetLen,Sets)),
print_cards(Sets),
println(setLen=SetLen),
println(numWanted=NumWanted),
SetsConv = convert_sets_to_num(Sets),
set_puzzle_cp(SetsConv,SetLen,NumWanted, X),
println(x=X),
foreach(Row in X)
println([Sets[I] : I in Row])
end,
nl,
fail, % more solutions?
nl.
set_puzzle_cp(Cards,SetLen,NumWanted, X) =>
NumFeatures = Cards[1].len,
NumSets = Cards.len,
X = new_array(NumWanted,SetLen),
X :: 1..NumSets,
foreach(I in 1..NumWanted)
% ensure unicity of the selected sets
all_different(X[I]),
increasing_strict(X[I]), % unicity and symmetry breaking of Y
foreach(F in 1..NumFeatures)
Z = $[ S : J in 1..SetLen, matrix_element(Cards, X[I,J],F, S) ],
% all features are different or all equal
(
(sum([ Z[J] #!= Z[K] : J in 1..SetLen, K in 1..SetLen, J != K ])
#= SetLen*SetLen - SetLen)
#\/
(sum([ Z[J-1] #= Z[J] : J in 2..SetLen]) #= SetLen-1)
)
end
end,
% Symmetry breaking (lexicographic ordered rows)
lex2(X),
solve($[ff,split],X).
%
% Symmetry breaking
% Ensure that the rows in X are lexicographic ordered
%
lex2(X) =>
Len = X[1].length,
foreach(I in 2..X.length)
lex_lt([X[I-1,J] : J in 1..Len], [X[I,J] : J in 1..Len])
end.
%
% Convert sets of "verbose" instances to integer
% representations.
%
convert_sets_to_num(Sets) = NewSets =>
Maps = new_map([
red=1,green=2,purple=3,
1=1,2=2,3=3,
one=1,two=2,three=3,
oval=1,squiggle=2,squiggles=2,diamond=3,
solid=1,open=2,striped=3
]),
NewSets1 = [],
foreach(S in Sets)
NewSets1 := NewSets1 ++ [[Maps.get(T) : T in S]]
end,
NewSets = NewSets1.
%
% Plain random problem instance, no check of solvability.
%
generate_instance2(NumCards,_NumSets,_SetLen, Cards) =>
Cards = random_deal(NumCards).
- Output:
This problem instance happens to have 10 solutions.
Cards: [1,[green,diamond,one,open]] [2,[green,diamond,one,solid]] [3,[green,oval,one,open]] [4,[green,oval,three,solid]] [5,[green,oval,two,solid]] [6,[green,squiggle,three,striped]] [7,[green,squiggle,two,striped]] [8,[purple,diamond,one,solid]] [9,[purple,diamond,two,striped]] [10,[purple,oval,one,solid]] [11,[purple,oval,two,open]] [12,[purple,squiggle,two,open]] [13,[red,diamond,two,solid]] [14,[red,oval,one,open]] [15,[red,oval,three,solid]] [16,[red,oval,two,solid]] [17,[red,oval,two,striped]] [18,[red,squiggle,one,striped]] setLen = 3 numWanted = 9 x = {{1,4,7},{1,5,6},{1,10,18},{3,8,18},{4,10,16},{5,10,15},{5,11,17},{7,9,17},{7,11,13}} [[green,diamond,one,open],[green,oval,three,solid],[green,squiggle,two,striped]] [[green,diamond,one,open],[green,oval,two,solid],[green,squiggle,three,striped]] [[green,diamond,one,open],[purple,oval,one,solid],[red,squiggle,one,striped]] [[green,oval,one,open],[purple,diamond,one,solid],[red,squiggle,one,striped]] [[green,oval,three,solid],[purple,oval,one,solid],[red,oval,two,solid]] [[green,oval,two,solid],[purple,oval,one,solid],[red,oval,three,solid]] [[green,oval,two,solid],[purple,oval,two,open],[red,oval,two,striped]] [[green,squiggle,two,striped],[purple,diamond,two,striped],[red,oval,two,striped]] [[green,squiggle,two,striped],[purple,oval,two,open],[red,diamond,two,solid]] x = {{1,4,7},{1,5,6},{1,10,18},{3,8,18},{4,10,16},{5,10,15},{5,11,17},{7,9,17},{14,15,17}} [[green,diamond,one,open],[green,oval,three,solid],[green,squiggle,two,striped]] [[green,diamond,one,open],[green,oval,two,solid],[green,squiggle,three,striped]] [[green,diamond,one,open],[purple,oval,one,solid],[red,squiggle,one,striped]] [[green,oval,one,open],[purple,diamond,one,solid],[red,squiggle,one,striped]] [[green,oval,three,solid],[purple,oval,one,solid],[red,oval,two,solid]] [[green,oval,two,solid],[purple,oval,one,solid],[red,oval,three,solid]] [[green,oval,two,solid],[purple,oval,two,open],[red,oval,two,striped]] [[green,squiggle,two,striped],[purple,diamond,two,striped],[red,oval,two,striped]] [[red,oval,one,open],[red,oval,three,solid],[red,oval,two,striped]] ...
Prolog
do_it(N) :-
card_sets(N, Cards, Sets),
!,
format('Cards: ~n'),
maplist(print_card, Cards),
format('~nSets: ~n'),
maplist(print_set, Sets).
print_card(Card) :- format(' ~p ~p ~p ~p~n', Card).
print_set(Set) :- maplist(print_card, Set), nl.
n(9,4).
n(12,6).
card_sets(N, Cards, Sets) :-
n(N,L),
repeat,
random_deal(N, Cards),
setof(Set, is_card_set(Cards, Set), Sets),
length(Sets, L).
random_card([C,S,N,Sh]) :-
random_member(C, [red, green, purple]),
random_member(S, [oval, squiggle, diamond]),
random_member(N, [one, two, three]),
random_member(Sh, [solid, open, striped]).
random_deal(N, Cards) :-
length(Cards, N),
maplist(random_card, Cards).
is_card_set(Cards, Result) :-
select(C1, Cards, Rest),
select(C2, Rest, Rest2),
select(C3, Rest2, _),
match(C1, C2, C3),
sort([C1,C2,C3], Result).
match([],[],[]).
match([A|T1],[A|T2],[A|T3]) :-
match(T1,T2,T3).
match([A|T1],[B|T2],[C|T3]) :-
dif(A,B), dif(B,C), dif(A,C),
match(T1,T2,T3).
- Output:
?- do_it(12). Cards: red squiggle two solid red squiggle three open purple diamond two striped red oval two striped green oval one solid purple squiggle one open purple squiggle two solid green oval two striped purple squiggle three striped green diamond one solid purple diamond two open red diamond two open Sets: green oval one solid purple diamond two striped red squiggle three open green oval one solid purple squiggle three striped red diamond two open green oval two striped purple diamond two open red squiggle two solid green oval two striped purple squiggle two solid red diamond two open purple squiggle one open purple squiggle three striped purple squiggle two solid red diamond two open red oval two striped red squiggle two solid true. ?- do_it(9). Cards: purple squiggle two solid green diamond one striped red diamond two solid green oval two open red diamond two striped purple diamond three striped green diamond two open green diamond three solid purple oval one open Sets: green diamond one striped green diamond three solid green diamond two open green diamond one striped purple diamond three striped red diamond two striped green oval two open purple squiggle two solid red diamond two striped purple diamond three striped purple oval one open purple squiggle two solid true.
Python
#!/usr/bin/python
from itertools import product, combinations
from random import sample
## Major constants
features = [ 'green purple red'.split(),
'one two three'.split(),
'oval diamond squiggle'.split(),
'open striped solid'.split() ]
deck = list(product(list(range(3)), repeat=4))
dealt = 9
## Functions
def printcard(card):
print(' '.join('%8s' % f[i] for f,i in zip(features, card)))
def getdeal(dealt=dealt):
deal = sample(deck, dealt)
return deal
def getsets(deal):
good_feature_count = set([1, 3])
sets = [ comb for comb in combinations(deal, 3)
if all( [(len(set(feature)) in good_feature_count)
for feature in zip(*comb)]
) ]
return sets
def printit(deal, sets):
print('Dealt %i cards:' % len(deal))
for card in deal: printcard(card)
print('\nFound %i sets:' % len(sets))
for s in sets:
for card in s: printcard(card)
print('')
if __name__ == '__main__':
while True:
deal = getdeal()
sets = getsets(deal)
if len(sets) == dealt / 2:
break
printit(deal, sets)
Note: You could remove the inner square brackets of the 'if all( [...] )'
part of the sets computation in the getsets function. It is a remnant of Python 2.7 debugging which gives access to the name feature
. The code works on Python 3.X too, but not that access.
- Output:
Dealt 9 cards: green three squiggle solid green three squiggle open purple two squiggle solid green one diamond solid red three oval solid green two oval solid red two oval open purple one diamond striped red two oval solid Found 4 sets: green three squiggle solid green one diamond solid green two oval solid green three squiggle solid red two oval open purple one diamond striped green three squiggle open purple one diamond striped red two oval solid purple two squiggle solid green one diamond solid red three oval solid
Short Version
import random, pprint
from itertools import product, combinations
N_DRAW = 9
N_GOAL = N_DRAW // 2
deck = list(product("red green purple".split(),
"one two three".split(),
"oval squiggle diamond".split(),
"solid open striped".split()))
sets = []
while len(sets) != N_GOAL:
draw = random.sample(deck, N_DRAW)
sets = [cs for cs in combinations(draw, 3)
if all(len(set(t)) in [1, 3] for t in zip(*cs))]
print "Dealt %d cards:" % len(draw)
pprint.pprint(draw)
print "\nContaining %d sets:" % len(sets)
pprint.pprint(sets)
- Output:
Dealt 9 cards: [('purple', 'three', 'squiggle', 'striped'), ('red', 'one', 'squiggle', 'solid'), ('red', 'three', 'diamond', 'striped'), ('red', 'two', 'oval', 'open'), ('purple', 'three', 'squiggle', 'open'), ('green', 'three', 'oval', 'open'), ('purple', 'three', 'squiggle', 'solid'), ('green', 'two', 'squiggle', 'open'), ('purple', 'two', 'oval', 'open')] Containing 4 sets: [(('purple', 'three', 'squiggle', 'striped'), ('red', 'one', 'squiggle', 'solid'), ('green', 'two', 'squiggle', 'open')), (('purple', 'three', 'squiggle', 'striped'), ('purple', 'three', 'squiggle', 'open'), ('purple', 'three', 'squiggle', 'solid')), (('red', 'one', 'squiggle', 'solid'), ('red', 'three', 'diamond', 'striped'), ('red', 'two', 'oval', 'open')), (('red', 'three', 'diamond', 'striped'), ('green', 'three', 'oval', 'open'), ('purple', 'three', 'squiggle', 'solid'))]
Racket
#lang racket
(struct card [bits name])
(define cards
(for/list ([C '(red green purple )] [Ci '(#o0001 #o0002 #o0004)]
#:when #t
[S '(oval squiggle diamond)] [Si '(#o0010 #o0020 #o0040)]
#:when #t
[N '(one two three )] [Ni '(#o0100 #o0200 #o0400)]
#:when #t
[D '(solid open striped)] [Di '(#o1000 #o2000 #o4000)])
(card (bitwise-ior Ci Si Ni Di) (format "~a, ~a, ~a, ~a" C S N D))))
(define (nsubsets l n)
(cond [(zero? n) '(())] [(null? l) '()]
[else (append (for/list ([l2 (nsubsets (cdr l) (- n 1))])
(cons (car l) l2))
(nsubsets (cdr l) n))]))
(define (set? cards)
(regexp-match? #rx"^[1247]*$"
(number->string (apply bitwise-ior (map card-bits cards)) 8)))
(define (deal C S)
(define hand (take (shuffle cards) C))
(define 3sets (filter set? (nsubsets hand 3)))
(cond [(not (= S (length 3sets))) (deal C S)]
[else (printf "Dealt ~a cards:\n" C)
(for ([c hand]) (printf " ~a\n" (card-name c)))
(printf "\nContaining ~a sets:\n" S)
(for ([set 3sets])
(for ([c set]) (printf " ~a\n" (card-name c)))
(newline))]))
(deal 9 4)
(deal 12 6)
Raku
(formerly Perl 6)
The trick here is to allocate three different bits for each enum, with the result that the cards of a matching set OR together to produce a 4-digit octal number that contains only the digits 1, 2, 4, or 7. This OR is done by funny looking [+|], which is the reduction form of +|, which is the numeric bitwise OR. (Because Raku stole the bare | operator for composing junctions instead.)
enum Color (red => 0o1000, green => 0o2000, purple => 0o4000);
enum Count (one => 0o100, two => 0o200, three => 0o400);
enum Shape (oval => 0o10, squiggle => 0o20, diamond => 0o40);
enum Style (solid => 0o1, open => 0o2, striped => 0o4);
my @deck = Color.enums X Count.enums X Shape.enums X Style.enums;
sub MAIN($DRAW = 9, $GOAL = $DRAW div 2) {
sub show-cards(@c) { { printf "%9s%7s%10s%9s\n", @c[$_;*]».key } for ^@c }
my @combinations = [^$DRAW].combinations(3);
my @draw;
repeat until (my @sets) == $GOAL {
@draw = @deck.pick($DRAW);
my @bits = @draw.map: { [+] @^enums».value }
@sets = gather for @combinations -> @c {
take @draw[@c].item when /^ <[1247]>+ $/ given ( [+|] @bits[@c] ).base(8);
}
}
say "Drew $DRAW cards:";
show-cards @draw;
for @sets.kv -> $i, @cards {
say "\nSet {$i+1}:";
show-cards @cards;
}
}
- Output:
Drew 9 cards: purple two diamond open red two squiggle striped purple three squiggle open purple two squiggle striped red three oval striped red one diamond striped purple two oval solid green three diamond solid red two squiggle open Set 1: purple two diamond open purple two squiggle striped purple two oval solid Set 2: purple two diamond open red one diamond striped green three diamond solid Set 3: red two squiggle striped red three oval striped red one diamond striped Set 4: purple three squiggle open red three oval striped green three diamond solid
REXX
Language note: each REXX implementation has its own method of determining a starter seed for generating
pseudo-random numbers, and in addition, that starter seed may be dependent upon operating system factors,
hardware architecture, and other things like the (local) date and time-of-day, and other such variables.
The algorithm is also not the same for all REXX implementations.
The particular set of cards dealt (show below) used Regina 3.9.3 under a Windows/XP environment.
/*REXX program finds and displays "sets" (solutions) for the SET puzzle (game). */
parse arg game seed . /*get optional # cards to deal and seed*/
if game=='' | game=="," then game= 9 /*Not specified? Then use the default.*/
if seed=='' | seed=="," then seed= 77 /* " " " " " " */
call aGame 0 /*with tell=0: suppress the output. */
call aGame 1 /*with tell=1: display " " */
exit sets /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
aGame: parse arg tell; good= game % 2 /*enable/disable the showing of output.*/
/* [↑] the GOOD var is the right #sets*/
do seed=seed until good==sets /*generate deals until good # of sets.*/
call random ,,seed /*repeatability for the RANDOM invokes.*/
call genFeatures /*generate various card game features. */
call genDeck /* " a deck (with 81 "cards").*/
call dealer game /*deal a number of cards for the game. */
call findSets game%2 /*find # of sets from the dealt cards. */
end /*until*/ /* [↓] when leaving, SETS is right #.*/
return /*return to invoker of this subroutine.*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
dealer: call sey 'dealing' game "cards:", , . /*shuffle and deal the cards. */
do cards=1 until cards==game /*keep dealing until finished. */
_= random(1, words(##) ) /*pick a card. */
##= delword(##, _, 1) /*delete " " */
@.cards= deck._ /*add the card to the tableau. */
call sey right('card' cards, 30) " " @.cards /*display a card to terminal.*/
do j=1 for words(@.cards) /* [↓] define cells for cards. */
@.cards.j= word(@.cards, j) /*define a cell for a card. */
end /*j*/
end /*cards*/
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
defFeatures: parse arg what,v; _= words(v) /*obtain what is to be defined. */
if _\==values then do; call sey 'error,' what "features ¬=" values, ., .
exit -1
end /* [↑] check for typos and/or errors. */
do k=1 for words(values) /*define all the possible values. */
call value what'.'k, word(values, k) /*define a card feature. */
end /*k*/
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
findSets: parse arg n; call genPoss /*N: the number of sets to be found. */
call sey /*find any sets that were generated [↑]*/
do j=1 for p /*P: is the number of possible sets. */
do f=1 for features
do g=1 for groups; !!.j.f.g= word(!.j.f, g)
end /*g*/
end /*f*/
ok= 1 /*everything is peachy─kean (OK) so far*/
do g=1 for groups
_= !!.j.1.g /*build strings to hold possibilities. */
equ= 1 /* [↓] handles all the equal features.*/
do f=2 to features while equ; equ= equ & _==!!.j.f.g
end /*f*/
dif= 1
__= !!.j.1.g /* [↓] handles all unequal features.*/
do f=2 to features while \equ
dif= dif & (wordpos(!!.j.f.g, __)==0)
__= __ !!.j.f.g /*append to string for next test*/
end /*f*/
ok=ok & (equ | dif) /*now, see if all are equal or unequal.*/
end /*g*/
if \ok then iterate /*Is this set OK? Nope, then skip it.*/
sets= sets + 1 /*bump the number of the sets found. */
call sey right('set' sets": ", 15) !.j.1 sep !.j.2 sep !.j.3
end /*j*/
call sey sets 'sets found.', .
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
genDeck: #= 0; ##= /*#: cards in deck; ##: shuffle aid.*/
do num=1 for values; xnum = word(numbers, num)
do col=1 for values; xcol = word(colors, col)
do sym=1 for values; xsym = word(symbols, sym)
do sha=1 for values; xsha = word(shadings, sha)
#= # + 1; ##= ## #;
deck.#= xnum xcol xsym xsha /*create a card. */
end /*sha*/
end /*num*/
end /*sym*/
end /*col*/
return /*#: the number of cards in the deck. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
genFeatures: features= 3; groups= 4; values= 3 /*define # features, groups, values. */
numbers = 'one two three' ; call defFeatures 'number', numbers
colors = 'red green purple' ; call defFeatures 'color', colors
symbols = 'oval squiggle diamond' ; call defFeatures 'symbol', symbols
shadings= 'solid open striped' ; call defFeatures 'shading', shadings
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
genPoss: p= 0; sets= 0; sep=' ───── ' /*define some REXX variables. */
!.=
do i=1 for game /* [↓] the IFs eliminate duplicates.*/
do j=i+1 to game
do k=j+1 to game
p= p + 1; !.p.1= @.i; !.p.2= @.j; !.p.3= @.k
end /*k*/
end /*j*/
end /*i*/ /* [↑] generate the permutation list. */
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
sey: if \tell then return /*¬ tell? Then suppress the output. */
if arg(2)==. then say; say arg(1); if arg(3)==. then say; return
output when using the default input:
dealing 9 cards: card 1 one green oval open card 2 two purple squiggle striped card 3 one green diamond solid card 4 three red diamond open card 5 two purple squiggle striped card 6 two purple oval striped card 7 two purple diamond striped card 8 three red squiggle open card 9 two red oval solid set 1: two purple squiggle striped ───── two purple oval striped ───── two purple diamond striped set 2: one green diamond solid ───── three red diamond open ───── two purple diamond striped set 3: one green diamond solid ───── two purple oval striped ───── three red squiggle open set 4: two purple squiggle striped ───── two purple oval striped ───── two purple diamond striped 4 sets found.
- output when using the input of: 12
dealing 12 cards: card 1 one purple diamond striped card 2 one green diamond striped card 3 one purple squiggle solid card 4 one red oval solid card 5 two green oval open card 6 one green diamond open card 7 two green squiggle striped card 8 three green squiggle solid card 9 three green squiggle open card 10 one purple diamond open card 11 three green squiggle open card 12 two red oval open set 1: one purple diamond striped ───── three green squiggle solid ───── two red oval open set 2: one green diamond striped ───── two green oval open ───── three green squiggle solid set 3: two green oval open ───── one green diamond open ───── three green squiggle open set 4: two green oval open ───── one green diamond open ───── three green squiggle open set 5: three green squiggle open ───── one purple diamond open ───── two red oval open set 6: one purple diamond open ───── three green squiggle open ───── two red oval open 6 sets found.
Ruby
Brute force.
COLORS = %i(red green purple) #use [:red, :green, :purple] in Ruby < 2.0
SYMBOLS = %i(oval squiggle diamond)
NUMBERS = %i(one two three)
SHADINGS = %i(solid open striped)
DECK = COLORS.product(SYMBOLS, NUMBERS, SHADINGS)
def get_all_sets(hand)
hand.combination(3).select do |candidate|
grouped_features = candidate.flatten.group_by{|f| f}
grouped_features.values.none?{|v| v.size == 2}
end
end
def get_puzzle_and_answer(hand_size, num_sets_goal)
begin
hand = DECK.sample(hand_size)
sets = get_all_sets(hand)
end until sets.size == num_sets_goal
[hand, sets]
end
def print_cards(cards)
puts cards.map{|card| " %-8s" * 4 % card}
puts
end
def set_puzzle(deal, goal=deal/2)
puzzle, sets = get_puzzle_and_answer(deal, goal)
puts "Dealt #{puzzle.size} cards:"
print_cards(puzzle)
puts "Containing #{sets.size} sets:"
sets.each{|set| print_cards(set)}
end
set_puzzle(9)
set_puzzle(12)
- Output:
Dealt 9 cards: red diamond two open red squiggle three open red diamond two striped red diamond two solid red oval three striped green squiggle three open red oval three open red squiggle one striped red oval one open Containing 4 sets: red diamond two open red squiggle three open red oval one open red diamond two open red diamond two striped red diamond two solid red diamond two striped red oval three striped red squiggle one striped red diamond two solid red oval three open red squiggle one striped Dealt 12 cards: red diamond three solid red diamond three striped purple squiggle one striped purple oval two striped green diamond two open purple oval three open red diamond one striped green oval one solid purple squiggle two solid green oval two open red oval two striped red diamond two striped Containing 6 sets: red diamond three solid purple squiggle one striped green oval two open red diamond three solid green oval one solid purple squiggle two solid red diamond three striped red diamond one striped red diamond two striped green diamond two open purple squiggle two solid red oval two striped purple oval three open green oval one solid red oval two striped purple squiggle two solid green oval two open red diamond two striped
Rust
use itertools::Itertools;
use rand::Rng;
const DECK_SIZE: usize = 81;
const NUM_ATTRIBUTES: usize = 4;
const ATTRIBUTES: [&[&str]; NUM_ATTRIBUTES] = [
&["red", "green", "purple"],
&["one", "two", "three"],
&["oval", "squiggle", "diamond"],
&["solid", "open", "striped"],
];
fn get_random_card_indexes(num_of_cards: usize) -> Vec<usize> {
let mut selected_cards: Vec<usize> = Vec::with_capacity(num_of_cards);
let mut rng = rand::thread_rng();
loop {
let idx = rng.gen_range(0..DECK_SIZE);
if !selected_cards.contains(&idx) {
selected_cards.push(idx);
}
if selected_cards.len() == num_of_cards {
break;
}
}
selected_cards
}
fn run_game(num_of_cards: usize, minimum_number_of_sets: usize) {
println!(
"\nGAME: # of cards: {} # of sets: {}",
num_of_cards, minimum_number_of_sets
);
// generate the deck with 81 unique cards
let deck = (0..NUM_ATTRIBUTES)
.map(|_| (0..=2_usize))
.multi_cartesian_product()
.collect::<Vec<_>>();
// closure to return true if the three attributes are the same, or each of them is different
let valid_attribute =
|a: usize, b: usize, c: usize| -> bool { a == b && b == c || (a != b && b != c && a != c) };
// closure to test all attributes, each of them should be true to have a valid set
let valid_set = |t: &Vec<&Vec<usize>>| -> bool {
for attr in 0..NUM_ATTRIBUTES {
if !valid_attribute(t[0][attr], t[1][attr], t[2][attr]) {
return false;
}
}
true
};
loop {
// select the required # of cards from the deck randomly
let selected_cards = get_random_card_indexes(num_of_cards)
.iter()
.map(|idx| deck[*idx].clone())
.collect::<Vec<_>>();
// generate all combinations, and filter/keep only which are valid sets
let valid_sets = selected_cards
.iter()
.combinations(3)
.filter(|triplet| valid_set(triplet))
.collect::<Vec<_>>();
// if the # of the sets is matching the requirement, print it and finish
if valid_sets.len() == minimum_number_of_sets {
print!("SELECTED CARDS:");
for card in &selected_cards {
print!("\ncard: ");
for attr in 0..NUM_ATTRIBUTES {
print!("{}, ", ATTRIBUTES[attr][card[attr]]);
}
}
print!("\nSets:");
for triplet in &valid_sets {
print!("\nSet: ");
for card in triplet {
for attr in 0..NUM_ATTRIBUTES {
print!("{}, ", ATTRIBUTES[attr][card[attr]]);
}
print!(" | ");
}
}
break;
}
//otherwise generate again
}
}
fn main() {
run_game(9, 4);
run_game(12, 6);
}
- Output:
GAME: # of cards: 9 # of sets: 4 SELECTED CARDS: card: green, two, diamond, striped, card: green, two, oval, solid, card: red, one, oval, striped, card: red, three, oval, striped, card: purple, three, squiggle, striped, card: green, three, diamond, solid, card: red, three, oval, open, card: purple, two, squiggle, open, card: red, two, oval, striped, Sets: Set: green, two, diamond, striped, | red, one, oval, striped, | purple, three, squiggle, striped, | Set: red, one, oval, striped, | red, three, oval, striped, | red, two, oval, striped, | Set: red, one, oval, striped, | green, three, diamond, solid, | purple, two, squiggle, open, | Set: purple, three, squiggle, striped, | green, three, diamond, solid, | red, three, oval, open, | GAME: # of cards: 12 # of sets: 6 SELECTED CARDS: card: purple, three, squiggle, solid, card: purple, three, squiggle, striped, card: green, three, diamond, striped, card: purple, three, oval, solid, card: green, two, oval, open, card: green, one, diamond, solid, card: red, three, oval, open, card: green, one, squiggle, solid, card: red, three, oval, solid, card: purple, three, diamond, open, card: red, two, oval, open, card: red, three, oval, striped, Sets: Set: purple, three, squiggle, solid, | green, three, diamond, striped, | red, three, oval, open, | Set: purple, three, squiggle, striped, | green, three, diamond, striped, | red, three, oval, striped, | Set: purple, three, squiggle, striped, | purple, three, oval, solid, | purple, three, diamond, open, | Set: purple, three, squiggle, striped, | green, one, diamond, solid, | red, two, oval, open, | Set: green, three, diamond, striped, | green, two, oval, open, | green, one, squiggle, solid, | Set: red, three, oval, open, | red, three, oval, solid, | red, three, oval, striped, |
Tailspin
Dealing cards at random to the size of the desired hand, then trying again if the desired set count is not achieved.
def deck: [ { by 1..3 -> (colour: $),
by 1..3 -> (symbol: $),
by 1..3 -> (number: $),
by 1..3 -> (shading: $)}
];
templates deal
@: $deck;
[ 1..$ -> \($@deal::length -> SYS::randomInt -> ^@deal($ + 1) !\)] !
end deal
templates isSet
def set : $;
[ $(1).colour::raw + $(2).colour::raw + $(3).colour::raw, $(1).symbol::raw + $(2).symbol::raw + $(3).symbol::raw,
$(1).number::raw + $(2).number::raw + $(3).number::raw, $(1).shading::raw + $(2).shading::raw + $(3).shading::raw ] -> #
// if it is an array where all elements of 3, 6 or 9, it is a set
when <[<=3|=6|=9>+ VOID]> do $set !
end isSet
templates findSets
def hand: $;
[ 1..$hand::length - 2 -> \(def a: $;
$a+1..$hand::length - 1 -> \(def b: $;
$b+1..$hand::length -> $hand([$a, $b, $]) !
\) !
\) -> isSet ] !
end findSets
templates setPuzzle
def nCards: $(1);
def nSets: $(2);
{} -> #
when <{sets: <[]($nSets..)>}> do $ !
otherwise
def hand: $nCards -> deal;
{hand: $hand, sets: $hand -> findSets} -> #
end setPuzzle
templates formatCard
def colours: ['red', 'green', 'purple'];
def symbols: ['oval', 'squiggle', 'diamond'];
def numbers: ['one', 'two', 'three'];
def shadings: ['solid', 'open', 'striped'];
$ -> '$colours($.colour);-$symbols($.symbol);-$numbers($.number);-$shadings($.shading);' !
end formatCard
templates formatSets
$ -> 'hand:
$.hand... -> '$ -> formatCard;
';
sets:
$.sets... -> '[$... -> ' $ -> formatCard; ';]
';' !
end formatSets
[9,4] -> setPuzzle -> formatSets -> !OUT::write
- Output:
hand: green-squiggle-three-open green-oval-three-striped red-diamond-three-striped red-oval-one-open purple-squiggle-three-striped red-oval-two-striped purple-diamond-one-solid red-squiggle-three-solid purple-diamond-two-open sets: [ green-squiggle-three-open red-oval-one-open purple-diamond-two-open ] [ green-squiggle-three-open purple-squiggle-three-striped red-squiggle-three-solid ] [ green-squiggle-three-open red-oval-two-striped purple-diamond-one-solid ] [ green-oval-three-striped red-diamond-three-striped purple-squiggle-three-striped ]
Twelve cards with six sets
[12,6] -> setPuzzle -> formatSets -> !OUT::write
- Output:
hand: red-oval-one-striped red-squiggle-one-open purple-diamond-two-striped purple-oval-two-open red-diamond-one-solid green-oval-three-solid green-diamond-one-open green-diamond-three-open red-diamond-three-solid green-diamond-three-solid green-squiggle-one-open red-oval-one-open sets: [ red-oval-one-striped red-squiggle-one-open red-diamond-one-solid ] [ red-oval-one-striped purple-oval-two-open green-oval-three-solid ] [ red-squiggle-one-open purple-diamond-two-striped green-oval-three-solid ] [ red-squiggle-one-open purple-oval-two-open green-diamond-three-open ] [ purple-diamond-two-striped red-diamond-one-solid green-diamond-three-open ] [ purple-diamond-two-striped green-diamond-one-open red-diamond-three-solid ]
Tcl
The principle behind this code is that the space of possible solutions is a substantial proportion of the space of possible hands, so picking a random hand and verifying that it is a solution, repeating until that verification succeeds, is a much quicker way to find a solution than a systematic search. It also makes the code substantially simpler.
# Generate random integer uniformly on range [0..$n-1]
proc random n {expr {int(rand() * $n)}}
# Generate a shuffled deck of all cards; the card encoding was stolen from the
# Perl6 solution. This is done once and then used as a constant. Note that the
# rest of the code assumes that all cards in the deck are unique.
set ::AllCards [apply {{} {
set cards {}
foreach color {1 2 4} {
foreach symbol {1 2 4} {
foreach number {1 2 4} {
foreach shading {1 2 4} {
lappend cards [list $color $symbol $number $shading]
}
}
}
}
# Knuth-Morris-Pratt shuffle (not that it matters)
for {set i [llength $cards]} {$i > 0} {} {
set j [random $i]
set tmp [lindex $cards [incr i -1]]
lset cards $i [lindex $cards $j]
lset cards $j $tmp
}
return $cards
}}]
# Randomly pick a hand of cards from the deck (itself in a global for
# convenience).
proc drawCards n {
set cards $::AllCards; # Copies...
for {set i 0} {$i < $n} {incr i} {
set idx [random [llength $cards]]
lappend hand [lindex $cards $idx]
set cards [lreplace $cards $idx $idx]
}
return $hand
}
# Test if a particular group of three cards is a valid set
proc isValidSet {a b c} {
expr {
([lindex $a 0]|[lindex $b 0]|[lindex $c 0]) in {1 2 4 7} &&
([lindex $a 1]|[lindex $b 1]|[lindex $c 1]) in {1 2 4 7} &&
([lindex $a 2]|[lindex $b 2]|[lindex $c 2]) in {1 2 4 7} &&
([lindex $a 3]|[lindex $b 3]|[lindex $c 3]) in {1 2 4 7}
}
}
# Get all unique valid sets of three cards in a hand.
proc allValidSets {hand} {
set sets {}
for {set i 0} {$i < [llength $hand]} {incr i} {
set a [lindex $hand $i]
set hand [set cards2 [lreplace $hand $i $i]]
for {set j 0} {$j < [llength $cards2]} {incr j} {
set b [lindex $cards2 $j]
set cards2 [set cards3 [lreplace $cards2 $j $j]]
foreach c $cards3 {
if {[isValidSet $a $b $c]} {
lappend sets [list $a $b $c]
}
}
}
}
return $sets
}
# Solve a particular version of the set puzzle, by picking random hands until
# one is found that satisfies the constraints. This is usually much faster
# than a systematic search. On success, returns the hand found and the card
# sets within that hand.
proc SetPuzzle {numCards numSets} {
while 1 {
set hand [drawCards $numCards]
set sets [allValidSets $hand]
if {[llength $sets] == $numSets} {
break
}
}
return [list $hand $sets]
}
Demonstrating:
# Render a hand (or any list) of cards (the "."s are just placeholders).
proc PrettyHand {hand {separator \n}} {
set Co {. red green . purple}
set Sy {. oval squiggle . diamond}
set Nu {. one two . three}
set Sh {. solid open . striped}
foreach card $hand {
lassign $card co s n sh
lappend result [format "(%s,%s,%s,%s)" \
[lindex $Co $co] [lindex $Sy $s] [lindex $Nu $n] [lindex $Sh $sh]]
}
return $separator[join $result $separator]
}
# Render the output of the Set Puzzle solver.
proc PrettyOutput {setResult} {
lassign $setResult hand sets
set sep "\n "
puts "Hand (with [llength $hand] cards) was:[PrettyHand $hand $sep]"
foreach s $sets {
puts "Found set [incr n]:[PrettyHand $s $sep]"
}
}
# Demonstrate on the two cases
puts "=== BASIC PUZZLE ========="
PrettyOutput [SetPuzzle 9 4]
puts "=== ADVANCED PUZZLE ======"
PrettyOutput [SetPuzzle 12 6]
- Sample output:
=== BASIC PUZZLE ========= Hand (with 9 cards) was: (purple,squiggle,one,solid) (green,diamond,two,striped) (green,oval,two,striped) (purple,diamond,three,striped) (red,oval,three,open) (green,squiggle,three,solid) (red,squiggle,one,solid) (red,oval,one,solid) (purple,oval,three,open) Found set 1: (purple,squiggle,one,solid) (green,diamond,two,striped) (red,oval,three,open) Found set 2: (green,oval,two,striped) (purple,oval,three,open) (red,oval,one,solid) Found set 3: (red,oval,three,open) (green,squiggle,three,solid) (purple,diamond,three,striped) Found set 4: (red,squiggle,one,solid) (green,diamond,two,striped) (purple,oval,three,open) === ADVANCED PUZZLE ====== Hand (with 12 cards) was: (green,diamond,two,open) (red,diamond,one,solid) (purple,diamond,one,solid) (red,squiggle,two,open) (green,diamond,three,open) (red,oval,two,striped) (red,diamond,two,solid) (purple,diamond,two,striped) (purple,diamond,three,open) (purple,diamond,three,striped) (purple,oval,three,open) (purple,squiggle,two,striped) Found set 1: (green,diamond,two,open) (red,diamond,one,solid) (purple,diamond,three,striped) Found set 2: (green,diamond,two,open) (purple,diamond,two,striped) (red,diamond,two,solid) Found set 3: (purple,diamond,one,solid) (purple,diamond,three,open) (purple,diamond,two,striped) Found set 4: (purple,diamond,one,solid) (purple,oval,three,open) (purple,squiggle,two,striped) Found set 5: (green,diamond,three,open) (red,diamond,one,solid) (purple,diamond,two,striped) Found set 6: (red,diamond,two,solid) (red,oval,two,striped) (red,squiggle,two,open)
Wren
import "/dynamic" for Enum
import "/trait" for Comparable
import "/fmt" for Fmt
import "/str" for Str
import "/math" for Nums
import "/sort" for Sort
import "random" for Random
var Color = Enum.create("Color", ["RED", "GREEN", "PURPLE"])
var Symbol = Enum.create("Symbol", ["OVAL", "SQUIGGLE", "DIAMOND"])
var Number = Enum.create("Number", ["ONE", "TWO", "THREE"])
var Shading = Enum.create("Shading", ["SOLID", "OPEN", "STRIPED"])
var Degree = Enum.create("Degree", ["BASIC", "ADVANCED"])
class Card is Comparable {
static zero { Card.new(Color.RED, Symbol.OVAL, Number.ONE, Shading.SOLID) }
construct new(color, symbol, number, shading) {
_color = color
_symbol = symbol
_number = number
_shading = shading
_value = color * 27 + symbol * 9 + number * 3 + shading
}
color { _color }
symbol { _symbol }
number { _number }
shading { _shading }
value { _value }
compare(other) { (_value - other.value).sign }
toString {
return Str.lower(Fmt.swrite("$-8s$-10s$-7s$-7s",
Color.members [_color],
Symbol.members [_symbol],
Number.members [_number],
Shading.members[_shading]
))
}
}
var createDeck = Fn.new {
var deck = List.filled(81, null)
for (i in 0...81) {
var col = (i/27).floor
var sym = (i/ 9).floor % 3
var num = (i/ 3).floor % 3
var shd = i % 3
deck[i] = Card.new(col, sym, num, shd)
}
return deck
}
var rand = Random.new()
var isSet = Fn.new { |trio|
var r1 = Nums.sum(trio.map { |c| c.color }) % 3
var r2 = Nums.sum(trio.map { |c| c.symbol }) % 3
var r3 = Nums.sum(trio.map { |c| c.number }) % 3
var r4 = Nums.sum(trio.map { |c| c.shading }) % 3
return r1 + r2 + r3 + r4 == 0
}
var playGame = Fn.new { |degree|
var deck = createDeck.call()
var nCards = (degree == Degree.BASIC) ? 9 : 12
var nSets = (nCards/2).floor
var sets = List.filled(nSets, null)
for (i in 0...nSets) sets[i] = [Card.zero, Card.zero, Card.zero]
var hand = []
while (true) {
rand.shuffle(deck)
hand = deck.take(nCards).toList
var count = 0
var hSize = hand.count
var outer = false
for (i in 0...hSize-2) {
for (j in i+1...hSize-1) {
for (k in j+1...hSize) {
var trio = [hand[i], hand[j], hand[k]]
if (isSet.call(trio)) {
sets[count] = trio
count = count + 1
if (count == nSets) {
outer = true
break
}
}
}
if (outer) break
}
if (outer) break
}
if (outer) break
}
Sort.quick(hand)
System.print("DEALT %(nCards) CARDS:\n")
System.print(hand.join("\n"))
System.print("\nCONTAINING %(nSets) SETS:\n")
for (s in sets) {
Sort.quick(s)
System.print(s.join("\n"))
System.print()
}
}
playGame.call(Degree.BASIC)
System.print()
playGame.call(Degree.ADVANCED)
- Output:
Sample output:
DEALT 9 CARDS: red oval one open red oval two open green oval three striped green squiggle one open green squiggle two solid green diamond one open purple oval one solid purple diamond three open purple diamond three striped CONTAINING 4 SETS: red oval one open green squiggle two solid purple diamond three striped red oval two open green squiggle one open purple diamond three open red oval two open green oval three striped purple oval one solid green oval three striped green squiggle two solid green diamond one open DEALT 12 CARDS: red oval one solid red squiggle two solid red squiggle three open red diamond two solid red diamond two striped green oval two solid green squiggle two striped green diamond two open green diamond two striped purple oval two striped purple diamond one solid purple diamond two striped CONTAINING 6 SETS: red diamond two solid green diamond two open purple diamond two striped red diamond two striped green diamond two striped purple diamond two striped green oval two solid green squiggle two striped green diamond two open red diamond two striped green squiggle two striped purple oval two striped red oval one solid red squiggle three open red diamond two striped red squiggle two solid green diamond two open purple oval two striped
zkl
const nDraw=9, nGoal=(nDraw/2); // Basic
var [const] UH=Utils.Helpers; // baked in stash of goodies
deck:=Walker.cproduct("red green purple".split(), // Cartesian product of 4 lists of lists
"one two three".split(), // T(1,2,3) (ie numbers) also works
"oval squiggle diamond".split(),
"solid open striped".split()).walk();
reg draw,sets,N=0;
do{ N+=1;
draw=deck.shuffle()[0,nDraw]; // one draw per shuffle
sets=UH.pickNFrom(3,draw) // 84 sets of 3 cards (each with 4 features)
.filter(fcn(set){ // list of 12 items (== 3 cards)
set[0,4].zip(set[4,4],set[8,4]) // -->4 tuples of 3 features
.pump(List,UH.listUnique,"len", // 1,3 (good) or 2 (bad)
'==(2)) // (F,F,F,F)==good
.sum(0) == 0 // all 4 feature sets good
});
}while(sets.len()!=nGoal);
println("Dealt %d cards %d times:".fmt(draw.len(),N));
draw.pump(Void,fcn(card){ println(("%8s "*4).fmt(card.xplode())) });
println("\nContaining:");
sets.pump(Void,fcn(card){ println((("%8s "*4 + "\n")*3).fmt(card.xplode())) });
- Output:
Dealt 9 cards 271 times: red one oval solid green one diamond striped red two oval open purple two squiggle striped green three diamond open purple three squiggle solid purple one diamond striped green three squiggle solid green one squiggle open Containing: red one oval solid purple two squiggle striped green three diamond open red one oval solid purple one diamond striped green one squiggle open green one diamond striped red two oval open purple three squiggle solid red two oval open purple one diamond striped green three squiggle solid