Set puzzle
Set Puzzles are created with a deck of cards from the Set Game(TM). 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 variation of the following four features: color, symbol, number and shading.
- there are three colors
- red, green, or purple
- there are three symbols
- oval, squiggle, or diamond
- there is a number of symbols on the card
- one, two, or three
- there are three shadings
- solid, open, or 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 4 sets; the advanced mode deals 12 cards that contain 6 sets. The task is to write code that deals the cards, 9 or 12, depending on selected mode, 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
D
<lang d>import std.stdio, std.traits, std.random, std.conv, std.exception,
std.array;
void main() {
auto dealer = new SetPuzzleDealer(); auto cards = dealer.deal();
writefln("\nDEALT %d CARDS:\n", cards.length); foreach (Unqual!(typeof(cards[0])) c; cards) writeln(c);
auto sets = dealer.findSets(cards); auto len = sets.length; writefln("\nFOUND %d %s:\n", len, len == 1 ? "SET" : "SETS"); foreach (set; sets) { foreach (Unqual!(typeof(set[0])) c; set) writeln(c); writeln(); }
}
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; }
Card[81] deck; }
this() nothrow { auto colors = [EnumMembers!Color]; auto numbers = [EnumMembers!Number]; auto symbols = [EnumMembers!Symbol]; auto fill = [EnumMembers!Fill];
foreach (immutable i; 0 .. deck.length) { auto c = colors[i / 27]; auto n = numbers[(i / 9) % 3]; auto s = symbols[(i / 3) % 3]; auto f = fill[i % 3]; deck[i] = Card(c, n, s, f); } }
// 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) { enforce(numCards < deck.length, "number of cards too large"); return assumeUnique(randomSample(deck[], 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) pure nothrow { 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) pure nothrow { auto len = cards.length; if (len >= 3) { typeof(return) sets; for (int i = 0; i < len - 2; i++) { for (int j = i + 1; j < len - 1; j++) for (int k = j + 1; k < len; k++) if (validSet(cards[i], cards[j], cards[k])) sets ~= [cards[i], cards[j], cards[k]]; } return assumeUnique(sets); } return null; }
}
class SetPuzzleDealer : SetDealer {
enum NumCards {basic = 9, advanced = 12}
override immutable(Card)[] deal(in uint numCards = NumCards.basic) { auto numSets = numCards / 2; typeof(return) cards; do { cards = super.deal(numCards); } while (findSets(cards).length != numSets);
return assumeUnique(cards); }
}</lang>
DEALT 9 CARDS: Card(green, one, oval, solid) Card(green, one, diamond, solid) Card(green, one, squiggle, solid) Card(green, two, squiggle, open) Card(green, three, oval, open) Card(green, three, diamond, solid) Card(green, three, squiggle, striped) Card(purple, two, squiggle, solid) Card(red, one, squiggle, open) FOUND 4 SETS: Card(green, one, oval, solid) Card(green, one, diamond, solid) Card(green, one, squiggle, solid) Card(green, one, squiggle, solid) Card(green, two, squiggle, open) Card(green, three, squiggle, striped) Card(green, three, oval, open) Card(green, three, diamond, solid) Card(green, three, squiggle, striped) Card(green, three, squiggle, striped) Card(purple, two, squiggle, solid) Card(red, one, squiggle, open)
Java
<lang java>package setpuzzle;
import java.util.Arrays; import java.util.Collections; import org.apache.commons.lang3.ArrayUtils;
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]; outer: while (true) { int cnt = 0; Collections.shuffle(Arrays.asList(deck)); cards = ArrayUtils.subarray(deck, 0, numCards);
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])) { sets[cnt] = new Card[]{cards[i], cards[j], cards[k]}; if (++cnt == target) { break outer; } } } } } } 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; }
}</lang>
GIVEN 12 CARDS: [Card: GREEN, ONE, OVAL, OPEN] [Card: GREEN, ONE, DIAMOND, STRIPED] [Card: GREEN, TWO, OVAL, OPEN] [Card: GREEN, THREE, SQUIGGLE, OPEN] [Card: GREEN, THREE, DIAMOND, STRIPED] [Card: GREEN, THREE, SQUIGGLE, SOLID] [Card: GREEN, THREE, SQUIGGLE, STRIPED] [Card: PURPLE, TWO, DIAMOND, STRIPED] [Card: RED, ONE, OVAL, STRIPED] [Card: RED, ONE, DIAMOND, STRIPED] [Card: RED, TWO, SQUIGGLE, SOLID] [Card: RED, THREE, DIAMOND, OPEN] FOUND 6 SETS: [Card: RED, ONE, OVAL, STRIPED] [Card: RED, TWO, SQUIGGLE, SOLID] [Card: RED, THREE, DIAMOND, OPEN] [Card: RED, ONE, OVAL, STRIPED] [Card: PURPLE, TWO, DIAMOND, STRIPED] [Card: GREEN, THREE, SQUIGGLE, STRIPED] [Card: RED, TWO, SQUIGGLE, SOLID] [Card: PURPLE, TWO, DIAMOND, STRIPED] [Card: GREEN, TWO, OVAL, OPEN] [Card: PURPLE, TWO, DIAMOND, STRIPED] [Card: GREEN, THREE, DIAMOND, STRIPED] [Card: RED, ONE, DIAMOND, STRIPED] [Card: GREEN, TWO, OVAL, OPEN] [Card: GREEN, THREE, SQUIGGLE, SOLID] [Card: GREEN, ONE, DIAMOND, STRIPED] [Card: GREEN, THREE, SQUIGGLE, OPEN] [Card: GREEN, THREE, SQUIGGLE, SOLID] [Card: GREEN, THREE, SQUIGGLE, STRIPED]
Python
<lang 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() ]
dealt = 9
- Functions
def printcard(card):
print(' '.join('%8s' % f[i] for f,i in zip(features, card)))
def getdeal(dealt=dealt):
deck = list(product(list(range(3)), repeat=4)) 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__':
deal = getdeal() sets = getsets(deal) printit(deal, sets)</lang>
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: red one diamond striped green one squiggle striped purple two squiggle solid red one squiggle striped red one oval striped red two oval solid red three squiggle open green two oval striped purple two oval striped Found 3 sets: red one diamond striped red one squiggle striped red one oval striped red one diamond striped red two oval solid red three squiggle open green one squiggle striped purple two squiggle solid red three squiggle open