Skip to content

Instantly share code, notes, and snippets.

@ericnormand
Last active September 26, 2020 00:09
Show Gist options
  • Save ericnormand/9675d9ebea9f5e3b66474fdef9e3d1b6 to your computer and use it in GitHub Desktop.
Save ericnormand/9675d9ebea9f5e3b66474fdef9e3d1b6 to your computer and use it in GitHub Desktop.
396 - PurelyFunctional.tv Newsletter

Set Game

I used to play a game called Set. In it, you'd place 12 cards down on the table. Then the players would try to find "sets". If you found a set, you'd race to hit the cards with your hand to claim it. It was tons of fun and just mathy enough to appeal to someone like me.

In this task, you are going to take a function that judges whether three cards constitute a valid "set".

Each card has four properties:

  1. Color (red, purple, green)
  2. Number (1, 2, 3)
  3. Shading (empty, lined, full)
  4. Shape (squiggle, oval, diamond)

So one card might have 3 purple full diamonds. Search for the game and look at pictures if you want some examples.

We'll represent each card as a map, like this:

{:color :purple
 :number 3
 :shading :full
 :shape :diamond}

Three cards form a set if all of the properties are total matches or total mismatches. A property is a total match if all of the values are the same, for instance three red cards. They are a total mismatch if all the values are different, for instance a diamond, an oval, and a squiggle. If any of the properties don't match or mismatch, it's not a set.

Here's an example of a set:

[{:color :purple :number 3 :shape :diamond :shading :full}
 {:color :red    :number 3 :shape :diamond :shading :lines}
 {:color :green  :number 3 :shape :diamond :shading :empty}]

Colors are a total mismatch, numbers are a total match, shape is a total match, shading is a total mismatch.

Here's an example of a non-set:

[{:color :purple :number 3 :shape :diamond :shading :full}
 {:color :red    :number 3 :shape :diamond :shading :lines}
 {:color :purple :number 3 :shape :diamond :shading :empty}]

Above, the colors are two purples and a red. Not a total match and not a total mismatch.

Write a function that takes an array of cards and says whether they are a set.

(set? [{..} {..} {..}]) ;=> true/false

Thanks to this site for the challenge idea where it is considered Very Hard level in JavaScript.

Please submit your solutions as comments to this gist. Discussion is welcome.

@mchampine
Copy link

mchampine commented Sep 21, 2020

@steffan-westcott

(defn set? [cards]
  (every? #(-> (map % cards) set count odd?) [:color :number :shading :shape]))

Nice! Or with a small tweak you get a slightly smaller and more general version:

(defn set? [cards]
  (every? (comp odd? count set) (apply mapv vector (map vals cards))))

@steffan-westcott
Copy link

@mchampine Thank you! About the suggested 'transpose' tweak (also suggested by @zugnush), I don't think this is strictly correct since equal maps may have differently ordered entries. In my REPL I get:

   (vals {:a 1 :b 2})
=> (1 2)
   (vals {:b 2 :a 1})
=> (2 1)

I believe you'd need sorted-map or similar to make assumptions on entry order.

@mchampine
Copy link

@mchampine Thank you! About the suggested 'transpose' tweak (also suggested by @zugnush), I don't think this is strictly correct
I believe you'd need sorted-map or similar to make assumptions on entry order.

Good point! Transposing does not account for that, and a correct solution shouldn't require sorted maps. How about

(defn aset? [cards]
  (every? #(-> (map % cards) set count odd?) (keys (first cards))))

which is order agnostic but preserves the generality?

@ndonolli
Copy link

(defn is-set? [cards]
 (let [set-count (count cards)]
  (->> cards
   (map vals)
   (apply interleave)
   (partition set-count)
   (map (comp count set))
   (every? (partial #{1 set-count})))))

@daveschoutens
Copy link

daveschoutens commented Sep 22, 2020

Inspired by @steffan-westcott, @mchampine, @ndonolli, (EDIT: And @albertzak) and my own prior solution, here is a version that attempts to optimize for readability/understandability, while being general along card properties and set size:

(defn is-set? [cards]
  (let [properties (keys (first cards))
        is-valid-set? #(or (apply distinct? %) (apply = %))
        is-property-set? #(->> cards (map %) is-valid-set?)]
    (every? is-property-set? properties)))

Test:

(is-set? [{:color :red :number 1 :shading :full :shape :square :material :paper}
          {:color :blue :number 1 :shading :full :shape :triangle :material :metal}
          {:color :green :number 1 :shading :full :shape :circle :material :plastic}
          {:color :purple :number 1 :shading :full :shape :pentagon :material :wood}]) ;; true

@sztamas
Copy link

sztamas commented Sep 22, 2020

Could be made shorter, but was trying to make it a bit more readable.

We collect the distinct values for each key, we count the distinct values and then we compare to 1 (total match) or number of cards (total mismatch) at the end.

(defn set? [cards]
  (let [
        init-empty-sets        (zipmap (keys (first cards)) (repeatedly hash-set))
        distinct-values        (reduce (partial merge-with conj) init-empty-sets cards)
        distinct-values-counts (map count (vals distinct-values))]
    (every? #(or
               (= 1 %)               ; total match
               (= (count cards) %))  ; total mismatch
            distinct-values-counts)))

@tugh
Copy link

tugh commented Sep 22, 2020

(defn- total-match? [xs]
  (apply = xs))

(defn- total-mismatch? [xs]
  (apply distinct? xs))

(defn- property-set? [xs]
  (or (total-match? xs)
      (total-mismatch? xs)))

(defn set? [cards]
  (letfn [(extract [k] (map k cards))]
    (->> [:color :number :shape :shading]
         (map extract)
         (every? property-set?))))

@ndonolli
Copy link

@daveschoutens very readable, I like this solution.

@proush42
Copy link

(defn is-set? [cards]
  (let [val-grps (for [p [:color :number :shading :shape]]
                   (map p cards))
        mixed?   (fn [vs]
                   (< 1 (count (set vs)) (count vs)))]
    (not (some mixed? val-grps))))

@albertzak
Copy link

(defn all-distinct-or-same? [& xs]
  (or (apply distinct? xs)
      (apply = xs)))

(defn set? [xs]
  (every? true? 
          (apply mapv all-distinct-or-same? 
                 (map vals xs))))

@kolstae
Copy link

kolstae commented Sep 23, 2020

(defn set? [cs]
    (every? #{1 3}
            (for [k [:color :number :shape :shading]]
              (count (distinct (map k cs))))))

@jeroenvanwijgerden
Copy link

jeroenvanwijgerden commented Sep 23, 2020

(defn update-all
  "Similar to clojure.core/update but for all values in m."
  [m f]
  (reduce-kv (fn [acc k v]
               (assoc acc k (f v)))
             {}
             m))

(defn properties
  [cards]
  ;; sometimes let bindings are much more understandable than a threading macro,
  ;; typically when the intermediate result changes type significantly.
  (let [name+value       (apply concat cards)
        name->name+value (group-by first name+value)
        name->values     (update-all name->name+value
                                     #(map second %))]
    name->values))

(defn total-match?
  [values]
  (apply = values))

(defn total-mismatch?
  [values]
  (apply distinct? values))

(defn set?
  [cards]
  (->> (properties cards)
       (every? (fn [[_name values]]
                 (or (total-match?    values)
                     (total-mismatch? values))))))

(set? [{:color :purple :number 3 :shape :diamond :shading :full}
       {:color :red    :number 3 :shape :diamond :shading :lines}
       {:color :green  :number 3 :shape :diamond :shading :empty}]) ; => true

(set? [{:color :purple :number 3 :shape :diamond :shading :full}
       {:color :red    :number 3 :shape :diamond :shading :lines}
       {:color :purple :number 3 :shape :diamond :shading :empty}]) ; => false

@KingCode
Copy link

(defn prop-success? [prop cards]
  (let [vs (map prop cards)]
    (or (apply = vs)
        (= (count vs) (count (set vs))))))

(def props [:color :number :shape :shading])

(defn set? [cards]
  (every? #(prop-success? % cards) props))

@JonathanHarford
Copy link

(defn is-set? [cards]
  (->> cards
       first
       keys
       (every? (fn [property]
                 (let [values (mapv #(get % property) cards)]
                   (or (apply = values)
                       (apply distinct? values)))))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment