在Clojure中以最大值分割矢量 - 更好的方法?

时间:2015-11-27 06:53:23

标签: clojure

新手问题:

如何在数字矢量中包含最大值的第一个实例并将其包括在内?

因此,从此[1 2 3 4 5 4 3 2 1]开始,获取[1 2 3 4 5] [4 3 2 1]

我这样做的方式似乎过于复杂:

(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]

这看起来有点尴尬吗?例如,使用定义的向量三次。我们是否需要使用Java来获取索引?

什么是更好,更惯用或更高效的方式?

感谢。

7 个答案:

答案 0 :(得分:2)

替代变体(仅为了好玩):

  • 生成具有分割位置(项目索引+ 1)和项目本身的元组序列
  • 使用max-key
  • 找到包含最大项目的元组
  • 将您的集合拆分为所需的索引(元组中的第一项)

    (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)]
    

此外,您可以轻松修改它以使用任意标准来估算值。

(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))))

最大身份:

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)]

最大尺寸:

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")]

或某些外部价值,例如:

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

所以对我来说它似乎更具功能性(并且更多的是“clojure方式”),尽管可能不是最有效率的。

答案 1 :(得分:2)

如果先下订单并不重要,您可以使用此

(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 :(得分:2)

如果表现很重要,我就这样做:

(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 k v]
                 (if (<= v (second max))
                   max
                   [k v])) [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])

对于向量应该是N + C比较。其中C相对较小。

答案 3 :(得分:1)

如果您希望避免将Java方法引入Clojure世界,可以将indexOf()方法替换为counttake-while的组合。

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)]

但是,我更喜欢以前的解决方案,虽然这比基于索引的解决方案更长。

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]]

答案 4 :(得分:1)

(defn split-at-max [v]
  (when (seq v)
    (let [m (apply max v)
          point (inc (count (reduce (fn [a b] (if (> m b) (conj a b) 
                                              (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)]

使用split-with在最大点进行拆分的另一种类似方式(如果有可能有空集合,则首先需要在输入上执行seq):

(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)]

答案 5 :(得分:1)

首先,鉴于Clojure cheatsheet中列出.indexOf,我认为使用它是惯用的。

以下是另外两种选择:

这个类似于tnoda的第二个解决方案:

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

下一个看起来更复杂,但在一个方面它更优雅:它延迟<的效果以包含=项,因此不需要使用{{1} }或conj之后将concat项目粘贴回第一个序列:

=

结果的第二个元素是(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)] ,顺便说一句。对于大多数目的而言,序列的类型无关紧要,但如果您真的需要向量,则可以对其应用clojure.lang.PersistentVector$ChunkedSeq。同样对于我的第一个例子的结果。

答案 6 :(得分:1)

我从

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

这很笨拙。我应该使用split-with代替split-at,而无需计算n。但是,我们可以修改它以使用整个矢量:

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

这避免了实现分割序列,因此使用起来更快。

loop找到最大元素的第一个匹配项。从@Mars获取提示,我们可以使用Java ArrayList的{​​{1}}方法:

indexOf

快速,简洁,清晰。