2015-11-27 3 views
5

Вопрос для новичков:Разделить вектор на максимальное значение в 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 для получения индекса?

Что было бы лучше, более идиоматичным или более совершенным?

Спасибо.

+4

Более результативным было бы с 'reduce-kv', чтобы избежать вызова' .indexOf'. Но это даже дольше, чем ваша версия. – ClojureMostly

+0

Является ли вход всегда вектором? Требуется ли вам результат seq/vector/whatever? – Davyzhu

+0

Я не думаю, что это будет иметь значение. Во всяком случае, я не использую этот код для каких-либо целей, кроме как пытаться улучшить ... –

ответ

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

Ваша первая версия выходит из строя, если элемент max является первым или последним. Также сбой для пустого вектора – ClojureMostly

+0

Большое спасибо за указание на то, что я изменил его, используя 'reduce', чтобы найти max-point вместо' partition-by' – amirteymuri

1

Вы можете заменить метод indexOf() комбинацией count и take-while, если вы хотите избежать использования метода Java в мире Clojure.

user> (def up5 [1 2 3 4 5 4 3 2 1]) 
#<[email protected]: [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

плохая идея! '(apply max up5)' будет вызываться каждый раз, когда вызывается предикат для take-while/split-with. И это означает огромные накладные расходы. Лучше сначала найти максимальное значение, а затем использовать его в предикате. – leetwinski

+0

Благодарим вас за определение проблемы производительности в моем решении. Я пересмотрел свой ответ. – tnoda

+0

@tnoda Я думаю, что проблема все еще существует в вашем первом примере, 'apply max up5' следует оценивать каждый раз, когда' take-while' хочет проверить свой результат со следующим элементом коллекции (если я не прав, правильно меня, пожалуйста ...). – amirteymuri

2

альтернативный вариант (просто для удовольствия):

  • вы генерировать последовательность кортежей с разделенным позиции (ITE Индекс т в + 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 way»), хотя, вероятно, не самый продуктивный.

+0

Очень привлекательно, но очень приятно! –

+0

Ну, вот почему я сказал, что это весело. Другие решения, безусловно, лучше – leetwinski

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

Во-первых, учитывая, что .indexOf перечислен в Clojure cheatsheet, я думаю, что это идиоматическое использовать его.

Вот еще две альтернативы:

Это один подобен второму решению tnoda в:

(let [[a b c] (partition-by #(< % (apply max up5) up5)] 
    [(concat a b) c]) 
;=> [(1 2 3 4 5) (4 3 2 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, кстати. Для большинства целей тип последовательности не должен иметь значения, но вы можете применить к нему vec, если вы действительно хотите вектор. Аналогично для результатов моего первого примера.

+0

Первая версия случайно квадратична и не должна использоваться. – ClojureMostly

+0

Спасибо @Andre. Очень жаль. Это так просто, канун, хотя он, очевидно, делает много ненужной работы. Может быть, для небольших последовательностей .... – Mars

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 относительно мало.

+0

Это совместимо только с векторами или картами, из-за использования 'reduce-kv' – amirteymuri

+0

Почему' (if (vector? Xs) ...) '? Вам говорят, что «xs» - это вектор. Кроме того, 'xs' должен быть ассоциативным набором с позиционными индексами для' reduce-kv' и 'split-at' для работы. Кроме того, вы можете нажать 'if' down:' ((if vector? Xs vec-split-at split-at) (-> m-el first inc) xs) ', но, возможно, мы не привыкли к чтению условностей в позиции оператора. Все еще самое лучшее предложение. – Thumbnail

+0

В вызове 'reduce-kv' появляются некоторые недостатки:' <'должно быть' <= 'придерживаться * первого экземпляра максимального значения *, как требует вопрос; '1' должно быть' 0', чтобы соответствовать 'first'; и окончательный 'xs' может также быть' (rest xs) ', чтобы не смотреть на первый элемент дважды. – Thumbnail

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 «s indexOf метод вместо:

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

Это быстро, сжато и ясно.

Смежные вопросы