🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of pastafari

pastafari's solution

to Anagram in the Clojure Track

Published at Jul 13 2018 · 7 comments
Instructions
Test suite
Solution

Given a word and a list of possible anagrams, select the correct sublist.

Given "listen" and a list of candidates like "enlists" "google" "inlets" "banana" the program should return a list containing "inlets".

Source

Inspired by the Extreme Startup game https://github.com/rchatley/extreme_startup

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

anagram_test.clj

(ns anagram-test
  (:require [clojure.test :refer [deftest is]]
            anagram))

(deftest no-matches
  (is (= []
         (anagram/anagrams-for "diaper" ["hello" "world" "zombies" "pants"]))))

(deftest detect-simple-anagram
  (is (= ["tan"] (anagram/anagrams-for "ant" ["tan" "stand" "at"]))))

(deftest does-not-confuse-different-duplicates
  (is (= [] (anagram/anagrams-for "galea" ["eagle"]))))

(deftest eliminate-anagram-subsets
  (is (= [] (anagram/anagrams-for "good" ["dog" "goody"]))))

(deftest detect-anagram
  (is (= ["inlets"]
         (let [coll ["enlists" "google" "inlets" "banana"]]
           (anagram/anagrams-for "listen" coll)))))

(deftest multiple-anagrams
  (is (= ["gallery" "regally" "largely"]
         (let [coll ["gallery" "ballerina" "regally"
                     "clergy"  "largely"   "leading"]]
           (anagram/anagrams-for "allergy" coll)))))

(deftest case-insensitive-anagrams
  (is (= ["Carthorse"]
         (let [coll ["cashregister" "Carthorse" "radishes"]]
           (anagram/anagrams-for "Orchestra" coll)))))

(deftest word-is-not-own-anagram
  (is (= [] (anagram/anagrams-for "banana" ["banana"]))))

(deftest capital-word-is-not-own-anagram
  (is (= [] (anagram/anagrams-for "BANANA" ["banana"]))))
(ns anagram
  (:require [clojure.string :as str]))

(defn- alphagram
  [word]
  (frequencies (str/lower-case word)))

(defn anagram?
  [a b]
  (and
   (not= a b)
   (= (alphagram a)
      (alphagram b))))

(defn anagrams-for
  [word other-words]
  (filter #(anagram? word %) other-words))

Community comments

Find this solution interesting? Ask the author a question to learn more.
Avatar of pastafari
pastafari
Solution Author
commented about 7 years ago

Extracted alphagram into a named function Moved equality check to anagram?

Avatar of rsslldnphy

Looks nice and clean. Can you avoid the alphagram of the original word being calculated again for each of the other-words?

Also, I'm not sure how appropriate it is to use the name "alphagram" for your frequency map, as the word has a specific meaning related to the sorted letters.

Avatar of pastafari
pastafari
Solution Author
commented about 7 years ago

@rsslldnphy oops, i've actually introduced a bug here. the alphagram method is not really an alphagram, its a frequency map :)) will fix in next iteration.

and point taken about pre-creating the alphagram for word.

Avatar of mdub

Nice.

I agree with @rsslldnphy about avoiding re-computation of (alphagram a)

Avatar of pastafari
pastafari
Solution Author
commented over 6 years ago

Thanks @mdub for the comments. Will refactor this.

Avatar of 7enderhead

Regarding performance, memoize should do the trick, no? Because I like the overall solution structure as is...

Avatar of mascip

According to your code "BAnaNA" and "banana" are anagrams. It still passes all the tests; but what if after a bug report someone decided that you need a new test for that:

(is (= [] (anagram/anagrams-for "BAnaNA" ["banana"]))))

How would you refactor this code to get this to work, while keeping the code elegant (readable and concise)? I'm curious what solution you will come up with :) (Just add this test in your test suite if you want to try)

I think your code is very readable, nice! Apart from too many newlines for me: anagram? doesn't need to be 6 lines long. One line per idea is enough: (defn anagram? [a b] "Same unordered characters, but not the same word" (and (not= a b) (= (alphagram a) (alphagram b))))

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?