Splitting a vector by its maximum value in Clojure is the best way?

Newbie question:

How to split a vector of numbers on and including the first instance of the maximum value in it?

So from this [1 2 3 4 5 4 3 2 1] , get [1 2 3 4 5] [4 3 2 1] .

The way I do this seems too complicated:

 (def up5 [1 2 3 4 5 4 3 2 1]) (split-at (inc (.indexOf up5 (apply max up5))) up5) ; => [1 2 3 4 5] [4 3 2 1] 

Does this seem a bit uncomfortable? For example, using a given vector three times. And do we need to use Java to get the index?

What would be better, more idiomatic or more productive?

Thanks.

+5
source share
7 answers
 (defn split-at-max [v] (when (seq v) (let [m (apply max v) point (inc (count (reduce (fn [ab] (if (> mb) (conj ab) (reduced a))) [] v)))] ((juxt #(take point %) #(drop point %)) v)))) (split-at-max [1 2 9 2 -7 33 3 4 53 1 22 4 -44 444 3 2 3 0 -21]) ;;=> [(1 2 9 2 -7 33 3 4 53 1 22 4 -44 444) (3 2 3 0 -21)] (split-at-max []) ;;=> nil (split-at-max [26 27 28 29 30 31 32 33]) ;;=> [(26 27 28 29 30 31 32 33) ()] (split-at-max [33 32 31 30 29 28 27 26]) ;;=> [(33) (32 31 30 29 28 27 26)] ;; works also with sets and lists: (split-at-max '(1 2 9 2 -7 33 3 4 53 1 22 4 -44 444 3 2 3 0 -21)) ;;=> [(1 2 9 2 -7 33 3 4 53 1 22 4 -44 444) (3 2 3 0 -21)] (split-at-max '()) ;;=> nil (split-at-max (hash-set)) ;;=> nil (split-at-max (sorted-set)) ;;=> nil (split-at-max (sorted-set 1 2 9 2 -7 33 3 4 53 1 22 4 -44 444 3 2 3 0 -21)) ;;=> [(-44 -21 -7 0 1 2 3 4 9 22 33 53 444) ()] (split-at-max (hash-set 1 2 9 2 -7 33 3 4 53 1 22 4 -44 444 3 2 3 0 -21)) ;;=> [(0 1 4 -21 33 22 -44 3 2 444) (-7 9 53)] 

another similar way to use split-with to split to the maximum point (you also need to first execute seq in the input if you can have empty collections):

 (let [v [1 2 9 2 -7 33 3 4 53 1 22 4 -44 444 3 2 3 0 -21] m (apply max v)] ((juxt #(concat (first %) [(first (second %))]) #(rest (second %))) (split-with (partial > m) v))) ;;=> [(1 2 9 2 -7 33 3 4 53 1 22 4 -44 444) (3 2 3 0 -21)] 
+1
source

alternative option (just for fun):

  • you create a sequence of tuples with a divided position (item index + 1) and the element itself
  • find the tuple with maximum value using max-key
  • divide your collection by the desired index (the first element in the tuple)

     (defn split-at-max [items] (->> items (map vector (rest (range))) (apply max-key second) first (#(split-at % items)))) user> (split-at-max [-1 20 3 4 1 3 5 101 4 2 6 4]) [(-1 20 3 4 1 3 5 101) (4 2 6 4)] 

In addition, you can easily change it for use with arbitrary criteria for evaluating a value.

 (defn split-at-max [items & {identity-fn :by :or {identity-fn identity}}] (->> items (map vector (rest (range))) (apply max-key (comp identity-fn second)) first (#(split-at % items)))) 

max by identity:

 user> (split-at-max [-1 20 3 4 1 3 5 101 4 2 6 4]) [(-1 20 3 4 1 3 5 101) (4 2 6 4)] 

max in size:

 user> (split-at-max ["i" "wanna" "rock'n'roll" "all" "night" "and" "party" "every" "day"] :by count) [("i" "wanna" "rock'n'roll") ("all" "night" "and" "party" "every" "day")] 

or some external value, for example:

 user> (split-at-max [:a :b :c :d] :by {:a 0 :b 121 :c 2 :d -100}) [(:a :b) (:c :d)] 

therefore, it seems to me more functional (and for this is more a β€œclojure way”), although probably not the most productive one.

+2
source

If the order that comes first doesn't matter, you can use this

 (def up5 [1 2 3 4 5 4 3 2 1 0]) (def up5max (apply max up5) (->> up5 reverse (split-with (partial > up5max)) (map reverse)) #=> ((4 3 2 1 0) (1 2 3 4 5)) 
+2
source

If performance is important, I would do it like this:

 (defn vec-split-at [idx v] (if (empty? v) [[] []] [(subvec v 0 idx) (subvec v idx)])) (defn split-at-max [xs] (let [m-el (reduce-kv (fn [max kv] (if (<= v (second max)) max [kv])) [0 (first xs)] xs)] (if (vector? xs) (vec-split-at (-> m-el first inc) xs) (split-at (-> m-el first inc) xs)))) (split-at-max [1 10 10 1]) 

This should be an N + C comparison for vectors. Where C is relatively small.

+2
source

You can replace the indexOf() method indexOf() combination of count and take-while if you want to avoid using the Java method in the Clojure world.

 user> (def up5 [1 2 3 4 5 4 3 2 1]) #< Var@20c4449f : [1 2 3 4 5 4 3 2 1]> user> (split-at (inc (count (take-while #(< % (apply max up5)) up5))) up5) [(1 2 3 4 5) (4 3 2 1)] 

However, I prefer the following solution to the first, although it is longer than the index-based solution.

 user> (let [x (apply max up5) [lhs rhs] (split-with #(< % x) up5)] [(conj (vec lhs) (first rhs)) (vec (next rhs))]) [[1 2 3 4 5] [4 3 2 1]] 
+1
source

Firstly, given that .indexOf is listed in the Clojure cheatsheet , I think it's idiomatic to use it.

Here are two more alternatives:

This seems like a second solution:

 (let [[abc] (partition-by #(< % (apply max up5) up5)] [(concat ab) c]) ;=> [(1 2 3 4 5) (4 3 2 1)] 

This next one looks more complicated, but it is more elegant in one respect: it delays the effect of < to include the = element, so there is no need to use conj or concat after that again insert the = element into the first sequence:

 (let [the-max (apply max up5)] (loop [the-start [] the-rest up5 continue? true] (if continue? (let [this-one (first the-rest)] (recur (conj the-start this-one) (rest the-rest) (< this-one the-max))) [the-start the-rest]))) ;=> [[1 2 3 4 5] (4 3 2 1)] 

Second element of the result: clojure.lang.PersistentVector$ChunkedSeq , btw. For most purposes, the sequence type should not matter, but you can apply vec to it if you really want a vector. Similarly for the results of my first example.

+1
source

I started with

 (defn split-at-max [v] (let [m (apply max v) n (count (take-while #(> m %) v))] (split-at (inc n) v))) 

This is awkward. I should use split-with instead of split-at , avoiding the need to evaluate n . However, we can change it to use vectors:

 (defn split-at-max [v] (let [m (apply max v) n (loop [i 0] (if (= (vi) m) i (recur (inc i)))) n (inc n)] [(subvec v 0 n) (subvec vn)])) 

This avoids the implementation of shared sequences, so it is faster to use.

loop finds the first occurrence of the maximum element. Using the @Mars hint, we could use the Java ArrayList indexOf :

 (defn split-at-max [v] (let [m (apply max v) n (inc (.indexOf vm))] [(subvec v 0 n) (subvec vn)])) 

It is fast, red and clear.

+1
source

Source: https://habr.com/ru/post/1236945/


All Articles