1
\$\begingroup\$

I'm going to try brute-forcing the solving of a Magic Square. To simplify the code later on, I wanted a function similar to =, but one that returns the common element if all the elements are equal and nil otherwise. This is unlike = which always returns either true or false. I'm calling it =? for lack of a better name.

Example:

(=? 1 2 1)
nil ; nil because they're not all the same

(=? 1 1 1)
1 ; Returns the common element of 1

Obviously, this function won't work well when nil may be the sole element in a collection, since it will return falsey even if all the elements are the same, but nil.

I thought that there must be some kind of core operation I could use here, but after a few minutes of thinking, I couldn't think of any. I ended up rolling my own function using loop. I'd like general advice on the function that I wrote, or potentially a more idiomatic way to write it that I'm missing. It looks exceedingly clunky to me; especially after I realized the need for the last-arg accumulator. A recursive solution would be nice, as I fear I'm missing something obvious here:

(defn =?
  "Checks if every supplied argument =s every other.
  Returns the common element if they're all the same, nil otherwise."
  [& args]
  (loop [[f-arg & r-args] (rest args)
         last-arg (first args)]
    (cond
      (nil? f-arg) last-arg
      (= f-arg last-arg) (recur r-args f-arg)
      :else nil)))
\$\endgroup\$
2
\$\begingroup\$

You don't need the single argument case. You can abbreviate to

(defn =?5
  ([] nil)
  ([arg & args]
   (reduce #(if (= % %2) % (reduced nil))
           arg
           args)))

However, the following is simpler still:

(defn =?
  ([] nil)
  ([x & xs]
   (when (every? #(= x %) xs) x)))

We can even fold in the zero-argument case in by destructuring, as you do in =?4:

(defn =? [& [x & xs]]
  (when (every? #(= x %) xs) x))

But this is too obscure for me.

\$\endgroup\$
2
\$\begingroup\$

Soon after I posted this, I went for a walk and realized that this is the perfect opportunity to use reduce. I'm kind of embarrassed that I didn't see it originally, but this was my train of thought that lead me to what I'm much happier with.

First, I tried a simple reduction. This was pretty simple, although still a little verbose:

(defn =?2 [& args]
  (reduce (fn [acc n]
              (if (= n acc)
                n
                (reduced nil)))
          (first args)
          (rest args)))

I decided that this was probably simple enough to use a function macro for. Normally I don't like using them for reductions, but there's not much going on here:

(defn =?3 [& args]
  (reduce #(if (= % %2) % (reduced nil))
          (first args)
          (rest args)))

I don't like using explicit calls to first and rest, as I find they're usually neater when they're implicit in a deconstruction. I decided to deconstruct the arguments instead:

(defn =?4 [& [arg & rest-args]]
  (reduce #(if (= % %2) % (reduced nil))
          arg
          rest-args))

Then I checked what = uses, and found that it just uses multiple argument lists, so I tried that. This strikes me as the more idiomatic solution:

(defn =?5
  ([] nil)
  ([arg] arg)
  ([arg & args]
   (reduce #(if (= % %2) % (reduced nil))
           arg
           args)))

I'm torn between 4 and 5, but I think that both are a significant improvement.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.