0

I have written the same function twice, each using a list and a vector respectively. The function finds an element and returns the next one in the collection and wraps around if the found element is at the end. nil is returned if the element cannot be found.

List version

(def syms '(\< \^ \> \v))

(defn next-elem- [coll elem]
  (loop [coll-rest coll]
    (cond
      (empty? coll-rest) nil
      (= (first coll-rest) elem) (nth coll-rest 1 (first coll))
      :else (recur (rest coll-rest)))))

(defn rotate-left [sym]
  (next-elem- syms sym))

Vector version

(def syms [\< \^ \> \v])

(defn next-index- [coll i]
  (let [elem-count (count coll)]
    (cond
      (or (< i 0) (> i elem-count)) -1
      (= i (dec elem-count)) 0
      :else (inc i))))

(defn rotate-left [sym]
  (let [i (.indexOf syms sym)]
    (get syms (next-index- syms i) nil)))

Tests

(assert (= \< (rotate-left \v)))
(assert (= nil (rotate-left \o)))

Which version is better? I have read that lists are generally preferred in functional programming and at least in F# vectors (there arrays) are mutable, which is not what I need. Handling indices also feels awkward but is easier to wrap your head around as a non-functional programmer.

PS: This is one of the first functional programs I have written, so it might not be optimal. PPS: Am I using the backslashes correctly or should I use something else in its place?

Post Self
  • 1,471
  • 2
  • 14
  • 34
  • Clojure vectors are immutable. Also note that clojure lists are actually 64-ary trees under the hood. As for lists being better than vectors, it depends. – Jared Smith Jan 27 '19 at 18:50
  • Aren't lists singly-linked lists? – Post Self Jan 27 '19 at 18:51
  • look here: https://stackoverflow.com/questions/1147975/in-clojure-when-should-i-use-a-vector-over-a-list-and-the-other-way-around – Alex Ott Jan 27 '19 at 19:13
  • @Carcigenicate I see, interesting. Though, that link from before mentions that lists are the equivalent of Java `LinkedList` – Post Self Jan 27 '19 at 20:06
  • Clojure lists are indeed singly-linked lists. No trees are involved at all; they are very simple. Vectors are implemented as trees with a branching factor of 32. – amalloy Jan 27 '19 at 21:09
  • @amalloy Really? I don't use plain lists that often, but I've been under the impression for awhile that they were trees. I'll have to look into the source. My bad. Deleting. – Carcigenicate Jan 27 '19 at 21:35
  • You can see for yourself just by looking at [the implementation](https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentList.java): the only fields are `first`, `rest`, and `count` – amalloy Jan 27 '19 at 21:38

1 Answers1

1

First version of next-elem- is better because it would work with any sequence. Second version relies on sequence to have an efficient index access, which is really easy to accidentally fail and get a low-performing code.

An advice: change rest to next. Rest is sort of deprecated, next is better in all cases.

Also avoid using lists. They are very specific data structure, rarely needed. Whenever you need a linear sequence, consider vector first.

A functional version of your code:

(defn next-elem- [coll elem]
  (->> (concat coll [(first coll)])
       (drop-while #(not= % elem))
       (second)))
Niki Tonsky
  • 1,327
  • 11
  • 19
  • Thank you for the help! I am still thinking quite traditionally as evidenced by this cool implementation – Post Self Jan 28 '19 at 21:51