Search code examples
clojureclojure-java-interop

Why doesn't clojure core library support an index-of function?


Recently, I've been starting out with clojure programming and functional programming in general. One thing I noticed, solving some basic programming puzzles, is that there is no index-of function for vectors, eg. no reverse for the core function nth. I've seen people implement it themselves or use some java substitute implementation.

My question isn't as much if there's a workaround (but if you know of a particularly elegant one, share it), because I know there is. What I'm wondering is: why is this the case, eg. why did the clojure developers decide against implementing what seems like such a basic operation. Is doing a position lookup in a vector somehow non-idiomatic in clojure and would ideally be done in other ways? How?


Solution

  • There is a standard Clojure function that almost does what the problem requires.

    If we look at the skeleton source, what we need to do is invert a sequence such as ...

    (def ranks [2 3 4 5 6 7 8 9 10 :jack :queen :king :ace])
    

    ... into a map (or other function) that gives us the index of every value:

    {7 5, :king 11, 4 2, :queen 10, :ace 12, 6 4, 3 1, 2 0, :jack 9, 9 7, 5 3, 10 8, 8 6}
    

    We can do this once and for all.

    The standard function that nearly does this is clojure.set/map-invert.

    • But it works on maps, not on sequences.
    • But it treats them as sequences of pairs.

    So a simple function to invert a vector (or other sequence) into an index function is ...

    (defn indexes-of [v]
      (->> v
           (map-indexed vector)
           (clojure.set/map-invert)))
    

    For example,

       (indexes-of ranks)
    => {7 5, :king 11, 4 2, :queen 10, :ace 12, 6 4, 3 1, 2 0, :jack 9, 9 7, 5 3, 10 8, 8 6}
    

    So the folks who say that you've got the wrong end of the stick are correct. However, it is surely better to perform a domain-free computation like this within Clojure itself, however smoothly Clojure docks with whatever VM it happens to be running on.

    What we need is a cloak to throw over a vector to make it into a map: just as rseq throws a cloak over a vector, making it appear as a reversed sequence. Then we need not assume that map-invert will accept a sequence of pairs. (To be safe now, we could feed the sequence of pairs into a map to feed to map-invert).