Set, the card game
You are encouraged to solve this task according to the task description, using any language you may know.
The card game, Set, is played with a pack of 81 cards, each of which depicts either one, two, or three diamonds, ovals, or squiggles. The symbols are coloured red, green, or purple, and the shading is either solid, striped, or open. No two cards are identical.
In the game a number of cards are layed out face up and the players try to identify "sets" within the cards.
A set is three cards where either the symbols on the cards are all the same or they are all different, the number of symbols on the cards are all the same or all different, the colours are all the same or all different, and the shadings are all the same or all different.
For example, this is a set:
two solid green ovals one open green squiggle three striped green diamonds
because each card depicts a different symbol, the number of symbols on each card is different, the colours are all the same, and the shadings are all different.
This is not a set:
two solid purple ovals one open green squiggle three striped green diamonds
because two of the cards are green and one is purple, so the colours are neither all the same nor all different.
- task
- Create a representation of a pack of Set cards, shuffle it, select a specified number of cards from the pack and list them in the output.
- Identify the sets in the selected cards and list them.
- Also see
- The Wikipedia article, Set (card game)
Acornsoft Lisp
The Set puzzle task is so similar that the same solution can be used. Just redefine play
from
(defun play ((n-cards . 9))
(find-enough-sets n-cards (quotient n-cards 2)))
to
(defun play ((n-cards . 9))
(find-enough-sets n-cards 0))
C++
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <numeric>
#include <random>
#include <string>
#include <unordered_set>
#include <vector>
const std::vector<std::string> numbers { "ONE", "TWO", "THREE" };
const std::vector<std::string> colours { "GREEN", "RED", "PURPLE" };
const std::vector<std::string> shadings { "OPEN", "SOLID", "SRIPED" };
const std::vector<std::string> shapes { "DIAMOND", "OVAL", "SQUIGGLE" };
typedef std::vector<std::string> Card;
std::vector<Card> create_pack_of_cards() {
std::vector<Card> pack;
for ( std::string number : numbers ) {
for ( std::string colour : colours ) {
for ( std::string shading : shadings ) {
for ( std::string shape : shapes ) {
Card card = { number, colour, shading, shape };
pack.emplace_back(card);
}
}
}
}
return pack;
}
bool all_same_or_all_different(const std::vector<Card>& triple, const int32_t& index) {
std::unordered_set<std::string> triple_set;
for ( const Card& card : triple ) {
triple_set.insert(card[index]);
}
return triple_set.size() == 1 || triple_set.size() == 3;
}
bool is_game_set(const std::vector<Card>& triple) {
return all_same_or_all_different(triple, 0) &&
all_same_or_all_different(triple, 1) &&
all_same_or_all_different(triple, 2) &&
all_same_or_all_different(triple, 3);
}
template <typename T>
std::vector<std::vector<T>> combinations(const std::vector<T>& list, const int32_t& choose) {
std::vector<std::vector<T>> combinations;
std::vector<uint64_t> combination(choose);
std::iota(combination.begin(), combination.end(), 0);
while ( combination[choose - 1] < list.size() ) {
std::vector<T> entry;
for ( const uint64_t& value : combination ) {
entry.emplace_back(list[value]);
}
combinations.emplace_back(entry);
int32_t temp = choose - 1;
while ( temp != 0 && combination[temp] == list.size() - choose + temp ) {
temp--;
}
combination[temp]++;
for ( int32_t i = temp + 1; i < choose; ++i ) {
combination[i] = combination[i - 1] + 1;
}
}
return combinations;
}
int main() {
std::random_device rand;
std::mt19937 mersenne_twister(rand());
std::vector<Card> pack = create_pack_of_cards();
for ( const int32_t& card_count : { 4, 8, 12 } ) {
std::shuffle(pack.begin(), pack.end(), mersenne_twister);
std::vector<Card> deal(pack.begin(), pack.begin() + card_count);
std::cout << "Cards dealt: " << card_count << std::endl;
for ( const Card& card : deal ) {
std::cout << "[" << card[0] << " " << card[1] << " " << card[2] << " " << card[3] << "]" << std::endl;
}
std::cout << std::endl;
std::cout << "Sets found: " << std::endl;
for ( const std::vector<Card>& combination : combinations(deal, 3) ) {
if ( is_game_set(combination) ) {
for ( const Card& card : combination ) {
std::cout << "[" << card[0] << " " << card[1] << " " << card[2] << " " << card[3] << "] ";
}
std::cout << std::endl;
}
}
std::cout << "-------------------------" << std::endl << std::endl;
}
}
- Output:
Cards dealt: 4 [TWO GREEN OPEN SQUIGGLE] [THREE GREEN SRIPED OVAL] [TWO RED SOLID SQUIGGLE] [ONE GREEN SRIPED OVAL] Sets found: ------------------------- Cards dealt: 8 [ONE PURPLE OPEN SQUIGGLE] [ONE GREEN OPEN OVAL] [ONE RED SOLID DIAMOND] [TWO RED SOLID OVAL] [TWO PURPLE OPEN DIAMOND] [THREE PURPLE SOLID OVAL] [TWO GREEN SOLID OVAL] [THREE RED OPEN SQUIGGLE] Sets found: [ONE GREEN OPEN OVAL] [TWO PURPLE OPEN DIAMOND] [THREE RED OPEN SQUIGGLE] ------------------------- Cards dealt: 12 [ONE RED SRIPED DIAMOND] [THREE GREEN OPEN OVAL] [ONE GREEN SRIPED DIAMOND] [THREE GREEN SRIPED DIAMOND] [TWO GREEN SRIPED SQUIGGLE] [ONE PURPLE OPEN SQUIGGLE] [TWO RED SOLID OVAL] [ONE RED SOLID SQUIGGLE] [THREE GREEN OPEN DIAMOND] [THREE RED SRIPED DIAMOND] [TWO RED OPEN OVAL] [TWO PURPLE SRIPED SQUIGGLE] Sets found: [THREE GREEN SRIPED DIAMOND] [ONE PURPLE OPEN SQUIGGLE] [TWO RED SOLID OVAL] [ONE PURPLE OPEN SQUIGGLE] [THREE GREEN OPEN DIAMOND] [TWO RED OPEN OVAL] [ONE RED SOLID SQUIGGLE] [THREE RED SRIPED DIAMOND] [TWO RED OPEN OVAL] -------------------------
Common Lisp
The Set puzzle task is so similar that the Common Lisp solution there could be used with only slight modification. Here we take a somewhat more different approach by creating the deck as a vector, so that it can be shuffled more efficiently, rather than taking a random sample from the deck represented as a list.
Compare Acornsoft Lisp above.
(defparameter numbers '(one two three))
(defparameter shadings '(solid open striped))
(defparameter colours '(red green purple))
(defparameter symbols '(oval squiggle diamond))
(defun play (&optional (n-cards 9))
(let* ((deck (make-deck))
(deal (take n-cards (shuffle deck)))
(sets (find-sets deal)))
(show-cards deal)
(show-sets sets)))
(defun show-cards (cards)
(format t "~D cards~%~{~(~{~10S~}~)~%~}~%"
(length cards) cards))
(defun show-sets (sets)
(format t "~D sets~2%~:{~(~@{~{~8S~}~%~}~)~%~}"
(length sets) sets))
(defun find-sets (deal)
(remove-if-not #'is-set (combinations 3 deal)))
(defun is-set (cards)
(every #'feature-makes-set (transpose cards)))
(defun feature-makes-set (feature-values)
(or (all-same feature-values)
(all-different feature-values)))
(defun combinations (n items)
(cond
((zerop n) '(()))
((null items) '())
(t (append
(mapcar (lambda (c) (cons (car items) c))
(combinations (1- n) (cdr items)))
(combinations n (cdr items))))))
;;; Making a deck
(defun make-deck ()
(let ((deck (make-array (list (expt 3 4))))
(i -1))
(dolist (n numbers deck)
(dolist (sh shadings)
(dolist (c colours)
(dolist (sy symbols)
(setf (svref deck (incf i))
(list n sh c sy))))))))
;;; Utilities
(defun shuffle (deck)
(loop for i from (1- (length deck)) downto 0
do (rotatef (elt deck i)
(elt deck (random (1+ i))))
finally (return deck)))
(defun take (n seq) ; returns a list
(loop for i from 0 below n
collect (elt seq i)))
(defun all-same (values)
(every #'eql values (rest values)))
(defun all-different (values)
(every (lambda (v) (= (count v values) 1))
values))
(defun transpose (list-of-rows)
(apply #'mapcar #'list list-of-rows))
- Output:
Depending on which Common Lisp you use, calling (play)
might output:
12 cards three solid purple diamond one striped green squiggle two striped purple diamond three open purple diamond three striped red squiggle one solid green squiggle three open purple oval two open green squiggle two solid red diamond three open red squiggle three solid red oval two solid green squiggle 1 sets one striped green squiggle three open purple oval two solid red diamond
EasyLang
attr$[][] &= [ "one " "two " "three" ]
attr$[][] &= [ "solid " "striped" "open " ]
attr$[][] &= [ "red " "green " "purple" ]
attr$[][] &= [ "diamond " "oval " "squiggle" ]
#
for card = 0 to 80
pack[] &= card
.
proc card2attr card . attr[] .
attr[] = [ ]
for i to 4
attr[] &= card mod 3 + 1
card = card div 3
.
.
proc prcards cards[] . .
for card in cards[]
card2attr card attr[]
for i to 4
write attr$[i][attr[i]] & " "
.
print ""
.
print ""
.
ncards = random 5 + 7
print "Take " & ncards & " cards:"
for i to ncards
ind = random len pack[]
cards[] &= pack[ind]
pack[ind] = pack[len pack[]]
len pack[] -1
.
prcards cards[]
#
for i to len cards[]
card2attr cards[i] a[]
for j = i + 1 to len cards[]
card2attr cards[j] b[]
for k = j + 1 to len cards[]
card2attr cards[k] c[]
ok = 1
for at to 4
s = a[at] + b[at] + c[at]
if s <> 3 and s <> 6 and s <> 9
# 1,1,1 2,2,2 3,3,3 1,2,3
ok = 0
.
.
if ok = 1
print "Set:"
prcards [ cards[i] cards[j] cards[k] ]
.
.
.
.
- Output:
Take 10 cards: one striped purple oval two open red oval one open purple oval three open purple diamond one open purple diamond three open red oval one striped green oval two striped purple squiggle one open red oval two solid green oval Set: one striped purple oval three open red oval two solid green oval Set: two open red oval three open red oval one open red oval
Factor
USING: grouping io kernel literals math.combinatorics
prettyprint qw random sequences sequences.product sets ;
CONSTANT: cards $[
qw{
one two three
solid open striped
red green purple
diamond oval squiggle
} 3 group <product-sequence>
]
: deal ( n -- seq ) cards swap sample ;
: set? ( seq -- ? ) cardinality { 1 3 } member? ;
: sets ( seq -- newseq )
3 [ flip [ set? ] all? ] filter-combinations ;
: .length ( seq str -- ) write bl length . nl ;
: .cards ( seq -- )
[ " " join dup "o" head? "" "s" ? append print ] each nl ;
: .sets ( seq -- )
dup "Sets present:" .length [ .cards ] each ;
: play ( n -- )
deal [ "Cards dealt:" .length ]
[ .cards ]
[ sets .sets ] tri ;
4 8 12 [ play ] tri@
- Output:
Cards dealt: 4 two solid purple ovals three open green diamonds two striped purple ovals three solid purple diamonds Sets present: 0 Cards dealt: 8 two open red squiggles one open red oval two striped purple diamonds one striped green oval one striped red squiggle three solid purple ovals one solid green diamond three striped purple ovals Sets present: 1 two open red squiggles one solid green diamond three striped purple ovals Cards dealt: 12 two striped purple diamonds two open purple ovals three striped green squiggles one striped red diamond three open green diamonds three open green squiggles two open green ovals two solid red diamonds three open purple squiggles one open purple squiggle two solid green ovals two striped green ovals Sets present: 2 one striped red diamond three open purple squiggles two solid green ovals two open green ovals two solid green ovals two striped green ovals
FreeBASIC
Dim Shared As String*5 nums(2) = {"one", "two", "three"}
Dim Shared As String*7 shades(2) = {"solid", "striped", "open"}
Dim Shared As String*6 colours(2) = {"red", "green", "purple"}
Dim Shared As String*8 symbols(2) = {" diamond", " oval", "squiggle"}
Sub showcard(card As Integer)
Dim As Integer n, s, c, m
n = card Mod 3
card \= 3
s = card Mod 3
card \= 3
c = card Mod 3
card \= 3
m = card Mod 3
Print Trim(nums(n)); " "; Trim(shades(s)); " "; Trim(colours(c)); " "; _
Trim(symbols(m)); Iif(n = 0, "", "s")
End Sub
Sub showsets(hand() As Integer)
Dim As Integer i, j, k
Dim As Integer uh = Ubound(hand) + 1
Color 14: Print "Cards dealt: "; uh
Color 7: Print
If uh <> 81 Then
For i = 0 To uh - 1
showcard(hand(i))
Next
End If
Dim As Integer sets = 0
For i = 0 To uh - 3
For j = i + 1 To uh - 2
For k = j + 1 To uh - 1
If (hand(i) + hand(j) + hand(k)) Mod 3 = 0 Then sets += 1
Next
Next
Next
Print
Color 11: Print "Sets present: "; sets
Color 7: Print
If uh <> 81 Then
For i = 0 To uh - 3
For j = i + 1 To uh - 2
For k = j + 1 To uh - 1
If (hand(i) + hand(j) + hand(k)) Mod 3 = 0 Then
showcard(hand(i))
showcard(hand(j))
showcard(hand(k))
Print
End If
Next
Next
Next
End If
End Sub
Randomize Timer
Dim As Integer i, deal, j
Dim As Integer pack(80)
For i = 0 To 80
pack(i) = i
Next
For deal = 4 To 81 Step 4
For i = 80 To 1 Step -1
j = Int(Rnd * (i + 1))
Swap pack(i), pack(j)
Next
Dim As Integer hand(deal - 1)
For i = 0 To deal - 1
hand(i) = pack(i)
Next
showsets(hand())
Next
Sleep
J
Implementation:
deck=: >,{;:each'one two three';'red green purple';'solid striped open';'diamond oval squiggle'
deal=: (?#) { ]
sets=: {{ >;sets0\y }}
sets0=: {{ <;({:y) sets1\}:y }}
sets1=: {{ <~.;({:y) sets2 m\}:y }}
sets2=: {{ <(m,:n)<@,"2 1 y#~m isset n"1 y }}
isset=: {{ 1=#~.(m=n),(m=y),:n=y }}
disp=: <@(;:inv)"1
Task examples:
four=: 4 deal deck
eight=: 8 deal deck
twelve=: 12 deal deck
>disp four
one purple striped oval
one purple striped diamond
three green solid oval
two red solid oval
>disp eight
three green striped diamond
two green open squiggle
three purple striped squiggle
one purple solid squiggle
two green solid oval
three purple solid squiggle
two red solid oval
three purple solid diamond
>disp twelve
two red solid squiggle
three purple open squiggle
two purple open oval
one purple open oval
one green solid oval
three green striped oval
one green open oval
three green open squiggle
one red open squiggle
one purple striped squiggle
two purple striped diamond
two red open diamond
disp sets four
┌┐
││
└┘
disp sets eight
┌┐
││
└┘
disp sets twelve
┌─────────────────────────┬───────────────────────────┬──────────────────────────┐
│three green open squiggle│one purple striped squiggle│two red solid squiggle │
├─────────────────────────┼───────────────────────────┼──────────────────────────┤
│one green open oval │two red open diamond │three purple open squiggle│
├─────────────────────────┼───────────────────────────┼──────────────────────────┤
│three green open squiggle│two red open diamond │one purple open oval │
└─────────────────────────┴───────────────────────────┴──────────────────────────┘
Java
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public final class SetTheCardGame {
public static void main(String[] args) {
List<Card> pack = createPackOfCards();
for ( int cardCount : List.of( 4, 8, 12 ) ) {
Collections.shuffle(pack);
List<Card> deal = pack.subList(0, cardCount);
System.out.println("Cards dealt: " + cardCount);
for ( Card card : deal ) {
System.out.println(card);
}
System.out.println();
System.out.println("Sets found: ");
for ( List<Card> combination : combinations(deal, 3) ) {
if ( isGameSet(combination) ) {
for ( Card card : combination ) {
System.out.print(card + " ");
}
System.out.println();
}
}
System.out.println("-------------------------" + System.lineSeparator());
}
}
private static interface Feature {}
private static enum Number implements Feature { ONE, TWO, THREE }
private static enum Colour implements Feature { GREEN, RED, PURPLE }
private static enum Shading implements Feature { OPEN, SOLID, STRIPED }
private static enum Shape implements Feature { DIAMOND, OVAL, SQUIGGLE }
private static record Card(Number number, Colour colour, Shading shading, Shape shape) {
public String toString() {
return "[" + number + " " + colour + " " + shading + " " + shape + "]";
}
}
private static List<Card> createPackOfCards() {
List<Card> pack = new ArrayList<Card>(81);
for ( Number number : Number.values() ) {
for ( Colour colour : Colour.values() ) {
for ( Shading shading : Shading.values() ) {
for ( Shape shape : Shape.values() ) {
pack.add( new Card(number, colour, shading, shape) );
}
}
}
}
return pack;
}
private static boolean isGameSet(List<Card> triple) {
return allSameOrAllDifferent(triple.stream().map( c -> (Feature) c.number ).toList()) &&
allSameOrAllDifferent(triple.stream().map( c -> (Feature) c.colour ).toList()) &&
allSameOrAllDifferent(triple.stream().map( c -> (Feature) c.shading ).toList()) &&
allSameOrAllDifferent(triple.stream().map( c -> (Feature) c.shape ).toList());
}
private static boolean allSameOrAllDifferent(List<Feature> features) {
Set<Feature> featureSet = new HashSet<Feature>(features);
return featureSet.size() == 1 || featureSet.size() == 3;
}
private static <T> List<List<T>> combinations(List<T> list, int choose) {
List<List<T>> combinations = new ArrayList<List<T>>();
List<Integer> combination = IntStream.range(0, choose).boxed().collect(Collectors.toList());
while ( combination.get(choose - 1) < list.size() ) {
combinations.add(combination.stream().map( i -> list.get(i) ).toList());
int temp = choose - 1;
while ( temp != 0 && combination.get(temp) == list.size() - choose + temp ) {
temp -= 1;
}
combination.set(temp, combination.get(temp) + 1);
for ( int i = temp + 1; i < choose; i++ ) {
combination.set(i, combination.get(i - 1) + 1);
}
}
return combinations;
}
}
- Output:
Cards dealt: 4 [TWO RED OPEN SQUIGGLE] [THREE PURPLE OPEN DIAMOND] [TWO PURPLE STRIPED DIAMOND] [THREE RED STRIPED DIAMOND] Sets found: ------------------------- Cards dealt: 8 [ONE RED STRIPED SQUIGGLE] [TWO RED SOLID OVAL] [ONE PURPLE STRIPED OVAL] [TWO GREEN OPEN SQUIGGLE] [TWO GREEN STRIPED DIAMOND] [THREE GREEN OPEN OVAL] [TWO RED OPEN SQUIGGLE] [ONE PURPLE SOLID SQUIGGLE] Sets found: [TWO RED SOLID OVAL] [ONE PURPLE STRIPED OVAL] [THREE GREEN OPEN OVAL] ------------------------- Cards dealt: 12 [TWO PURPLE SOLID SQUIGGLE] [TWO GREEN SOLID SQUIGGLE] [THREE PURPLE OPEN DIAMOND] [ONE RED SOLID DIAMOND] [ONE PURPLE STRIPED OVAL] [ONE PURPLE OPEN SQUIGGLE] [TWO RED OPEN DIAMOND] [THREE RED SOLID OVAL] [THREE PURPLE SOLID SQUIGGLE] [ONE GREEN STRIPED OVAL] [ONE RED OPEN SQUIGGLE] [ONE PURPLE OPEN OVAL] Sets found: [TWO PURPLE SOLID SQUIGGLE] [THREE PURPLE OPEN DIAMOND] [ONE PURPLE STRIPED OVAL] [ONE RED SOLID DIAMOND] [ONE PURPLE OPEN SQUIGGLE] [ONE GREEN STRIPED OVAL] [TWO RED OPEN DIAMOND] [THREE PURPLE SOLID SQUIGGLE] [ONE GREEN STRIPED OVAL] -----------------------
jq
Works with jq, the C implementation of jq
Works with gojq, the Go implementation of jq
The following solution adapts the program given on the RosettaCode.org companion page at Set_puzzle#jq. There, the foundations for a generic version of the Set game are laid, with arbitrarily many attributes and values.
To avoid duplication, the functions given there are not repeated here. Instead, here is a guide for adapting the code:
(1) After the `include` directive for including the MRG32k3a module, add the following code:
def values: { "Number": ["one", "two", "three"], "Shading": ["solid", "striped", "open"], "Color": ["red", "green", "purple"], "Symbol": ["diamond", "oval", "squiggle"] };
(2) In place of the `task` definition, insert the following code:
# pretty-print a single card, interpreting the attribute values
def pp:
[attributes, .] | transpose
| map( values[.[0]][.[1]]|lpad(8) )
| join(" ");
# Pretty-print up to 1000 trios
def findSets:
[combinations(3) | select(isSet)] as $sets
| "Sets present: \($sets|length)",
($sets[:1000][] | (.[] | pp), ""),
if ($sets|length) > 1000 then "... etc ..." else empty end ;
def task:
def prompt: "Enter number of cards to deal: 3 to 81, or q to quit: ";
createDeck(number_of_values)
| shuffle # shuffle for each deal
| . as $pack
| prompt,
(try input catch halt
| if . == "q" then halt end
| try tonumber catch halt
| if . < 3 then task
else $pack[0: 1 + tonumber]
| findSets
end),
task;
- Output:
Enter number of cards to deal: 3 to 81, or q to quit: 6 Sets present: 0 Enter number of cards to deal: 3 to 81, or q to quit: 6 Sets present: 6 purple oval two open red diamond three open green squiggle one open purple oval two open green squiggle one open red diamond three open red diamond three open purple oval two open green squiggle one open red diamond three open green squiggle one open purple oval two open green squiggle one open purple oval two open red diamond three open green squiggle one open red diamond three open purple oval two open Enter number of cards to deal: 3 to 81, or q to quit: q
Julia
import Random: shuffle
import Combinatorics: combinations
const NUMBERS = ["one", "two", "three"]
const SHADINGS = ["solid", "striped", "open"]
const COLORS = ["red", "green", "purple"]
const SYMBOLS = ["diamond", "oval", "squiggle"]
struct SetCard
t::Tuple{UInt8, UInt8, UInt8, UInt8}
function SetCard(num, sha, col, sym)
@assert all(i -> 1 <= i <= 3, (num, sha, col, sym))
return new(tuple(num, sha, col, sym))
end
end
function Base.string(s::SetCard)
return "(" *
join([NUMBERS[s.t[1]], SHADINGS[s.t[2]], COLORS[s.t[3]], SYMBOLS[s.t[4]]], " ") *
(s.t[1] == 1 ? "" : "s") * ")"
end
Base.print(io:: IO, sc::SetCard) = print(io, string(sc))
Base.print(io:: IO, vsc::Vector{SetCard}) = print(io, "[" * join(string.(vsc), ", ") * "]")
""" Return an iterator for a vector of the sets found in the dealt `cards` """
function process_deal(cards::Vector{SetCard})
return Iterators.filter(combinations(cards, 3)) do c
return all(i -> (c[1].t[i] + c[2].t[i] + c[3].t[i]) % 3 == 0, eachindex(c[1].t))
end
end
function testcardsets()
pack = vec([SetCard(n, sh, c, sy) for n in 1:3, sh in 1:3, c in 1:3, sy in 1:3])
numcards = 81
while !isnothing(numcards)
print("\n\nEnter number of cards to deal (3 to 81, or just a space to exit) => ")
numcards = tryparse(Int, readline())
if !isnothing(numcards) && 3 <= numcards <= 81
deal = shuffle(pack)[begin:numcards]
sets = collect(process_deal(deal))
println("\nThe deal is:\n$deal\n\nThere are $(length(sets)) sets.")
foreach(println, sets)
end
end
end
testcardsets()
- Output:
Enter number of cards to deal (3 to 81, or just a space to exit) => 4 The deal is: [(one striped red squiggle), (one striped purple diamond), (three solid purple diamonds), (three open red ovals)] There are 0 sets. Enter number of cards to deal (3 to 81, or just a space to exit) => 12 The deal is: [(one striped green squiggle), (one solid green oval), (one open green oval), (one striped red diamond), (one open purple oval), (two open purple squiggles), (one solid red diamond), (three open purple diamonds), (three open green diamonds), (three striped red ovals), (three open green ovals), (two open red ovals)] There are 3 sets. [(one striped green squiggle), (one open purple oval), (one solid red diamond)] [(one open purple oval), (two open purple squiggles), (three open purple diamonds)] [(one open purple oval), (three open green ovals), (two open red ovals)] Enter number of cards to deal (3 to 81, or just a space to exit) => 16 The deal is: [(three open purple squiggles), (one open red diamond), (three striped purple diamonds), (three solid red diamonds), (one solid purple diamond), (one solid green oval), (three solid red ovals), (three solid purple ovals), (one solid green diamond), (two solid green diamonds), (two open purple squiggles), (one open purple squiggle), (three striped purple squiggles), (two solid green squiggles), (three solid red squiggles), (one open purple diamond)] There are 6 sets. [(three open purple squiggles), (three striped purple diamonds), (three solid purple ovals)] [(three open purple squiggles), (two open purple squiggles), (one open purple squiggle)] [(one open red diamond), (three striped purple diamonds), (two solid green diamonds)] [(three solid red diamonds), (one solid purple diamond), (two solid green diamonds)] [(three solid red diamonds), (three solid red ovals), (three solid red squiggles)] [(one solid purple diamond), (three solid red ovals), (two solid green squiggles)] Enter number of cards to deal (3 to 81, or just a space to exit) =>
Phix
Cards are 0..80 in decimal, which is 0000..2222 in base 3, and we can just subtract '/' from each digit to get indexes 1..3 to the constants.
with javascript_semantics constant nums = {"one", "two", "three"}, shades = {"solid", "striped", "open"}, colours = {"red", "green", "purple"}, symbols = {"diamond", "oval", "squiggle"} procedure showcard(integer card) -- aside: &-1 prevents "JS does not support string subscript destructuring" integer {n,s,c,m} = sq_sub(sprintf("%04a",{{3,card}}),'/') & -1 printf(1,"%s %s %s %s%s\n",{nums[n],shades[s],colours[c],symbols[m],iff(n=1?"":"s")}) end procedure procedure showsets(sequence hand) integer lh = length(hand) printf(1,"Cards dealt: %d\n%n",{lh,lh!=81}) if lh!=81 then papply(hand,showcard) end if sequence sets = {} for t in combinations(hand,3) do integer r3 = 0 for r in {1,3,9,27} do r3 += rmdr(sum(sq_rmdr(sq_floor_div(t,r),3)),3) end for if r3=0 then sets = append(sets,t) end if end for printf(1,"\nSets present: %d\n\n",length(sets)) if lh!=81 then for s in sets do papply(s,showcard) printf(1,"\n") end for end if end procedure sequence pack = tagstart(0,81) for deal in {4,8,12,81} do pack = shuffle(pack) showsets(pack[1..deal]) end for
- Output:
Cards dealt: 4 three open purple ovals two solid green ovals three solid red squiggles three striped purple diamonds Sets present: 0 Cards dealt: 8 two striped purple squiggles three striped red squiggles one striped green squiggle two open purple diamonds three solid green squiggles two solid green squiggles one striped purple oval two solid purple squiggles Sets present: 1 three striped red squiggles one striped green squiggle two striped purple squiggles Cards dealt: 12 two open green diamonds two striped purple diamonds two open purple ovals two solid red ovals three solid purple squiggles three striped green ovals three solid green diamonds one striped purple diamond three solid green ovals one open purple oval three solid red diamonds three solid purple ovals Sets present: 5 three solid red diamonds two open green diamonds one striped purple diamond three solid red diamonds three solid green ovals three solid purple squiggles one striped purple diamond two open purple ovals three solid purple squiggles two striped purple diamonds one open purple oval three solid purple squiggles two solid red ovals three striped green ovals one open purple oval Cards dealt: 81 Sets present: 1080
Python
from itertools import combinations
from itertools import product
from random import shuffle
from typing import Iterable
from typing import List
from typing import NamedTuple
from typing import Tuple
NUMBERS = ("one", "two", "three")
SHAPES = ("diamond", "squiggle", "oval")
SHADING = ("solid", "striped", "open")
COLORS = ("red", "green", "purple")
class Card(NamedTuple):
number: str
shading: str
color: str
shape: str
def __str__(self) -> str:
s = " ".join(self)
if self.number != "one":
s += "s"
return s
Cards = List[Card]
def new_deck() -> Cards:
"""Return a new shuffled deck of 81 unique cards."""
deck = [Card(*features) for features in product(NUMBERS, SHADING, COLORS, SHAPES)]
shuffle(deck)
return deck
def deal(deck: Cards, n: int) -> Tuple[Cards, Cards]:
"""Return _n_ cards from the top of the deck and what remains of the deck."""
return deck[:n], deck[n:]
def is_set(cards: Tuple[Card, Card, Card]) -> bool:
"""Return _True_ if _cards_ forms a set."""
return (
same_or_different(c.number for c in cards)
and same_or_different(c.shape for c in cards)
and same_or_different(c.shading for c in cards)
and same_or_different(c.color for c in cards)
)
def same_or_different(features: Iterable[str]) -> bool:
"""Return _True_ if _features_ are all the same or all different."""
return len(set(features)) in (1, 3)
def print_sets_from_new_deck(n: int) -> None:
"""Display sets found in _n_ cards dealt from a new shuffled deck."""
table, _ = deal(new_deck(), n)
print(f"Cards dealt: {n}\n")
print("\n".join(str(card) for card in table), end="\n\n")
sets = [comb for comb in combinations(table, 3) if is_set(comb)]
print(f"Sets present: {len(sets)}\n")
for _set in sets:
print("\n".join(str(card) for card in _set), end="\n\n")
print("----")
if __name__ == "__main__":
for n in (4, 8, 12):
print_sets_from_new_deck(n)
- Output:
Cards dealt: 4 two open green diamonds three striped green ovals two open purple ovals two open red squiggles Sets present: 1 two open green diamonds two open purple ovals two open red squiggles ---- Cards dealt: 8 three striped purple diamonds one solid purple oval two open purple diamonds three solid purple diamonds one solid green squiggle three open green squiggles three open purple squiggles three solid purple ovals Sets present: 1 three striped purple diamonds three open purple squiggles three solid purple ovals ---- Cards dealt: 12 two open green squiggles three solid purple ovals three open red diamonds two open red squiggles three open purple ovals three open red squiggles three striped red squiggles two open purple diamonds three solid red squiggles one solid red squiggle two striped purple diamonds one solid red diamond Sets present: 2 two open red squiggles three striped red squiggles one solid red squiggle three open red squiggles three striped red squiggles three solid red squiggles ----
Quackery
Why does isset
, the word that tests if three cards constitute a set, use +
and mod
?
If we map any of the properties, say colour, onto the numbers 0, 1 and 2, then the sum of three colours mod 3 is 0 if and only if all the colours are different or all the colours are the same. This can be confirmed exhaustively, or for the underlying mathematics see the first two paragraphs of the section "A Mathematical Perspective" (pages 7 and 8) in this paper:
transpose
is defined at Matrix transposition#Quackery.
comb
and arrange
are defined at Combinations#Quackery.
[ true swap transpose witheach
[ 0 swap witheach +
3 mod if [ not conclude ] ] ] is isset ( [ --> b )
[ [ [] 81 times
[ i 4 times [ 3 /mod swap ]
drop 3 times join
nested join ] ] constant
shuffle swap split drop ] is cards ( n --> [ )
[ [] swap dup size swap 3 rot comb
witheach
[ dip dup arrange
dup isset iff
[ nested rot join swap ]
else drop ] drop ] is sets ( [ --> [ )
[ unpack dup dip
[ [ table
$ "one" $ "two" $ "three" ]
do echo$ sp
[ table
$ "solid" $ "striped" $ "open" ]
do echo$ sp
[ table
$ "red" $ "green" $ "purple" ]
do echo$ sp
[ table
$ "diamond" $ "squiggle" $ "oval" ]
do echo$ ]
if [ say "s" ] cr ] is echocard ( [ --> )
[ dup cards swap
cr say "Cards dealt: " echo cr cr
dup witheach echocard cr
sets dup size
say "Sets present: " echo cr cr
witheach [ witheach echocard cr ] ] is play ( n --> )
' [ 4 8 12 ] witheach [ play say "-----" ]
- Output:
Cards dealt: 4 two striped green squiggles one open purple oval one solid purple diamond three open red diamonds Sets present: 0 ----- Cards dealt: 8 three open purple squiggles two open purple ovals three solid purple ovals three solid red squiggles two striped purple diamonds two solid green squiggles one striped green oval one open purple diamond Sets present: 1 three open purple squiggles two open purple ovals one open purple diamond ----- Cards dealt: 12 one solid green diamond one striped red diamond one open purple squiggle two solid green diamonds two striped green squiggles two solid red ovals two solid green squiggles one open green squiggle two solid green ovals two solid red diamonds one open purple diamond three striped purple diamonds Sets present: 3 two solid red ovals one open green squiggle three striped purple diamonds two solid green diamonds two solid green squiggles two solid green ovals one solid green diamond one striped red diamond one open purple diamond -----
Raku
my @attributes = <one two three>, <solid striped open>, <red green purple>, <diamond oval squiggle>;
sub face ($_) { .polymod(3 xx 3).kv.map({ @attributes[$^k;$^v] }) ~ ('s' if $_%3) }
sub sets (@cards) { @cards.combinations(3).race.grep: { !(sum ([Z+] $_».polymod(3 xx 3)) »%» 3) } }
for 4,8,12 -> $deal {
my @cards = (^81).pick($deal);
my @sets = @cards.&sets;
say "\nCards dealt: $deal";
for @cards { put .&face };
say "\nSets found: {+@sets}";
for @sets { put .map(&face).join("\n"), "\n" };
}
say "\nIn the whole deck, there are {+(^81).&sets} sets.";
- Sample output:
Cards dealt: 4 one open purple squiggle one striped red squiggle three striped green diamonds one open green diamond Sets found: 0 Cards dealt: 8 three striped purple squiggles three open green diamonds one striped purple oval three open red squiggles two striped red diamonds one solid purple diamond one solid red oval one solid green diamond Sets found: 2 three open green diamonds two striped red diamonds one solid purple diamond three open red squiggles two striped red diamonds one solid red oval Cards dealt: 12 three open purple squiggles one striped purple diamond two striped red squiggles two striped green squiggles one solid green oval three open red squiggles two striped purple diamonds three striped purple squiggles one open red diamond two striped red diamonds two striped green ovals one open green oval Sets found: 3 three open purple squiggles one solid green oval two striped red diamonds two striped red squiggles two striped purple diamonds two striped green ovals one solid green oval three open red squiggles two striped purple diamonds In the whole deck, there are 1080 sets.
Ruby
ATTRIBUTES = [:number, :shading, :colour, :symbol]
Card = Struct.new(*ATTRIBUTES){ def to_s = values.join(" ") }
combis = %i[one two three].product(%i[solid striped open], %i[red green purple], %i[diamond oval squiggle])
PACK = combis.map{|combi| Card.new(*combi) }
def set?(trio) = ATTRIBUTES.none?{|attr| trio.map(&attr).uniq.size == 2 }
[4, 8, 12].each do |hand_size|
puts "#{"_"*40}\n\nCards dealt: #{hand_size}"
puts hand = PACK.sample(hand_size)
sets = hand.combination(3).select{|h| set? h }
puts "\n#{sets.size} sets found"
sets.each{|set| puts set, ""}
end
- Sample output:
________________________________________ Cards dealt: 4 one striped green squiggle one open red squiggle two striped green oval three solid green diamond 0 sets found ________________________________________ Cards dealt: 8 three open green squiggle three striped green diamond three striped red oval three open red oval three solid red diamond one solid purple diamond two open red diamond one striped red diamond 2 sets found three striped green diamond one solid purple diamond two open red diamond three solid red diamond two open red diamond one striped red diamond ________________________________________ Cards dealt: 12 one solid purple oval one striped purple oval one open red diamond three striped purple squiggle three striped purple oval three solid green squiggle three solid purple diamond two solid green squiggle two open green squiggle three open green diamond two open purple squiggle one striped red squiggle 3 sets found one striped purple oval three solid purple diamond two open purple squiggle one open red diamond three striped purple oval two solid green squiggle three solid green squiggle two open purple squiggle one striped red squiggle
Wren
Note that entering 81 for the number of cards to deal confirms that there are 1080 possible sets.
import "random" for Random
import "./ioutil" for Input
import "./fmt" for Fmt
import "./perm" for Comb
var nums = ["one", "two", "three"]
var shas = ["solid", "striped", "open"]
var cols = ["red", "green", "purple"]
var syms = ["diamond", "oval", "squiggle"]
var pack = List.filled(81, null)
var i = 0
for (num in 0..2) {
for (sha in 0..2) {
for (col in 0..2) {
for (sym in 0..2) {
pack[i] = [nums[num], shas[sha], cols[col], syms[sym]]
i = i + 1
}
}
}
}
var printCards = Fn.new { |cards|
for (card in cards) {
var pl = card[0] != "one" ? "s" : ""
Fmt.print("$s $s $s $s$s", card[0], card[1], card[2], card[3], pl)
}
}
var findSets = Fn.new { |cards|
var sets = []
var trios = Comb.list(cards, 3)
for (trio in trios) {
var t1 = trio[0]
var t2 = trio[1]
var t3 = trio[2]
var found = true
for (i in 0..3) {
if (t1[i] == t2[i] && t2[i] == t3[i]) continue
if (t1[i] != t2[i] && t2[i] != t3[i] && t1[i] != t3[i]) continue
found = false
break
}
if (found) sets.add(trio)
}
Fmt.print("Sets present: $d\n", sets.count)
if (sets.count > 0) {
for (set in sets) {
printCards.call(set)
System.print()
}
}
}
var prompt = "Enter number of cards to deal - 3 to 81 or q to quit: "
Input.quit = "q"
while(true) {
Random.new().shuffle(pack) // shuffle for each deal
var i = Input.integer(prompt, 3, 81)
if (i == Input.quit) return
var dealt = pack[0...i]
System.print()
printCards.call(dealt)
System.print()
findSets.call(dealt)
}
- Output:
Sample run:
Enter number of cards to deal - 3 to 81 or q to quit: 4 three solid green diamonds one solid red diamond one solid green oval three striped purple squiggles Sets present: 0 Enter number of cards to deal - 3 to 81 or q to quit: 8 one open green squiggle one open purple squiggle one solid green squiggle three solid purple squiggles three open green squiggles one striped red diamond one striped green oval one striped green squiggle Sets present: 1 one open green squiggle one solid green squiggle one striped green squiggle Enter number of cards to deal - 3 to 81 or q to quit: 12 three open green ovals three striped green diamonds one solid purple oval one striped purple diamond two open green diamonds three solid red diamonds three solid red ovals three solid green diamonds three striped red ovals three striped red squiggles two open red squiggles one solid green oval Sets present: 3 three striped green diamonds one solid purple oval two open red squiggles one solid purple oval two open green diamonds three striped red squiggles one striped purple diamond two open green diamonds three solid red diamonds Enter number of cards to deal - 3 to 81 or q to quit: q