2

What is the idiomatic way to get something like that?

(fn [coll] (function body) [1 0 -1 -1 -1 0 0 0 0 1 1 1 1 1 0 1 -1])
-> [1 1 3 2 1 4 3 2 1 5 4 3 2 1 1 1 1]

I can only do this in this way.

(fn [coll] 
  (loop [i 0 r []]
    (if (< i (count coll))
      (let [elem (nth coll i)
            cc (loop [j 1]
                 (if (< (+ i j) (dec (count coll)))
                   (let [nelem (nth coll (+ i j))]
                     (if (= elem nelem)
                       (recur (inc j))
                       j))
                   j))]
        (recur (inc i) (conj r cc)))
      r)))
Ribelo
  • 205
  • 2
  • 8
  • 1
    The answer should surely be `(1 1 3 4 5 1 1 1)`. No? – Thumbnail May 17 '14 at 15:44
  • My English is definitely not perfect, but my code does exactly what I expect from him. Perhaps my description is bad. I want to compare only the elements after item. – Ribelo May 17 '14 at 15:57
  • Suggested opening: *How do you map a sequence to - at each point - a count of the run of equal items ahead of the item, including the item itself?* – Thumbnail May 17 '14 at 16:36
  • Correction: omit the `dec` in line 6. It causes on-off errors when the sequence ends on a repeated item: on `[1 1]`, it produces `[1 1]`. – Thumbnail May 18 '14 at 08:08

4 Answers4

4

Modifying @noisesmith's neat solution to what we thought the problem was:

(defn countdown-runs [s]
  (->> s
       (partition-by identity)
       (map count)
       (mapcat #(range % 0 -1))))

For example,

(countdown-runs [1 0 -1 -1 -1 0 0 0 0 1 1 1 1 1 0 1 -1])
; (1 1 3 2 1 4 3 2 1 5 4 3 2 1 1 1 1)
Thumbnail
  • 13,293
  • 2
  • 29
  • 37
2

Supposing your output is what you want, the answer should be

(mapcat #(reverse (range 1 (inc %)))
      (map count (partition-by identity [1 0 -1 -1 -1 0 0 0 0 1 1 1 1 1 0 1 -1])))

First do

(map count (partition-by identity [1 0 -1 -1 -1 0 0 0 0 1 1 1 1 1 0 1 -1]))

to get a the count of each consecutive elements, and then for each of the count, supply a list from the number counting down to 1. Then mapcat the nested sequence together.

albusshin
  • 3,930
  • 3
  • 29
  • 57
  • 2
    please use `(mapcat #(reverse ...) ...)` instead of `(flatten (map #(reverse ...) ...))`, flatten is almost always the wrong thing because it can easily destroy nested structure of an input. It can usually (as in this case) be replaced with mapcat. – noisesmith May 17 '14 at 16:04
  • 2
    Another hint: (style rather than accuracy here) - to be more idiomatic you can replace `(+ 1 %)` with `(inc %)` - same number of characters but the usage is more clear. – noisesmith May 17 '14 at 16:40
2

Although this doesn't provide exactly what was requested by OP, you may end up on this page when looking for a way of counting consecutive elements in a collection.

With transducers in Clojure 1.8 we can do something like this:

(defn runs
  ([]
   (runs identity))
  ([f]
   (comp (partition-by f)
         (map (juxt (comp f first) count))))
  ([f xs]
   (into [] (runs f) xs)))

That will allow us to create a transducer with a given partitioning function, or when called with both the partitioning function and a collection return a vector of realised values.

An example of its use:

(runs :name [{:name "John" :age 22}
             {:name "John" :age 44}
             {:name "Mary" :age 33}
             {:name "Lee" :age 99}
             {:name "Lee" :age 55}])

;; => [["John" 2] ["Mary" 1] ["Lee" 2]]
0

We can tidy up the offered solution in several ways:

  • Instead of index i, use the sub-vector that starts at i as an argument in the outer loop. This makes the code easier to understand.
  • For brevity, use (v j) instead of (nth v j) to get the jth element of v.
  • Use and to simplify the control structure of the inner loop.
  • (And - while we are at it - remove the dec that causes one-off errors for sequences ending on a repeated value.)

This gives us

(defn op-s [v] 
  (loop [v v, r []]
    (if (seq v)
      (let [elem (v 0)
            cc (loop [j 1]
                 (if (and (< j (count v)) (= elem (v j)))
                   (recur (inc j))
                   j))]
        (recur (subvec v 1) (conj r cc)))
      r)))

This can still be slow. For a run of length n, it performs the inner loop about n2/2 times. We can speed this up. Once we have found a run, we can count down from its length, as the lazy versions do.

(defn op-s [v] 
  (loop [v v, r []]
    (if (seq v)
      (let [elem (v 0)
            cc (loop [j 1]
                 (if (and (< j (count v)) (= elem (v j)))
                   (recur (inc j))
                   j))
            rr (loop [r r, i cc] (case i, 0 r, (recur (conj r i) (dec i))))]
        (recur (subvec v cc) rr))
      r)))
Thumbnail
  • 13,293
  • 2
  • 29
  • 37