Подтвердить что ты не робот

Вернуть первый элемент в карте/списке/последовательности, который удовлетворяет предикату

Я ищу функцию, которая возвращает первый элемент в последовательности, для которой fn оценивается как true. Например:

(first-map (fn [x] (= x 1)) '(3 4 1))

Вышеуказанная поддельная функция должна возвращать 1 (последний элемент в списке). Есть ли что-то подобное в Clojure?

4b9b3361

Ответ 1

user=> (defn find-first
         [f coll]
         (first (filter f coll)))
#'user/find-first
user=> (find-first #(= % 1) [3 4 1])
1

Изменить: A concurrency.:) Нет. Это не относится ко всему списку f. Только для элементов до первого совпадения из-за лень filter.

Ответ 2

В вашем случае идиома

(some #{1} [1 2 3 4])

Как это работает: # {1} - это набор литералов. Набор также является функцией, вычисляющей его arg, если arg присутствует в наборе и в противном случае равен нулю. Любой заданный элемент является "правдивым" значением (ну, кроме логического ложного, но это редкость в наборе). some возвращает возвращаемое значение предиката, оцениваемое по отношению к первому элементу коллекции, для которого результат был правным.

Ответ 3

Я попробовал несколько методов, упомянутых в этом потоке (JDK 8 и Clojure 1.7), и провел несколько эталонных тестов:

repl> (defn find-first
         [f coll]
         (first (filter f coll)))
#'cenx.parker.strategies.vzw.repl/find-first

repl> (time (find-first #(= % 50000000) (range)))
"Elapsed time: 5799.41122 msecs"
50000000

repl> (time (some #{50000000} (range)))
"Elapsed time: 4386.256124 msecs"
50000000

repl> (time (reduce #(when (= %2 50000000) (reduced %2)) nil (range)))
"Elapsed time: 993.267553 msecs"
50000000

Результаты показывают, что способ reduce может быть наиболее эффективным решением, как в Clojure 1.7.

Ответ 4

Я думаю, что some - лучший инструмент для работы:

(some #(if (= % 1) %) '(3 4 1))

Ответ 5

Использование drop-while вместо filter должно адресовать "чрезмерное приложение" f для фрагментированных последовательностей:

(defn find-first [f coll]
  (first (drop-while (complement f) coll)))
;;=> #'user/find-first

(find-first #(= % 1) [3 4 1])
;;=> 1

Ответ 6

В 2016 году был добавлен патч для ядра clojure, в котором был добавлен эффективный ярлык для (first (filter pred coll)) идиомы, он назывался seek.

Реализация избежала проблем в herent с альтернативами (first (filter)) и (some #(when (pred))). То есть он эффективно работает с чанковыми последовательностями и хорошо играет с nil? и false? предикаты.

Patch:

(defn seek
  "Returns first item from coll for which (pred item) returns true.
   Returns nil if no such item is present, or the not-found value if supplied."
  {:added  "1.9" ; note, this was never accepted into clojure core
   :static true}
  ([pred coll] (seek pred coll nil))
  ([pred coll not-found]
   (reduce (fn [_ x]
             (if (pred x)
               (reduced x)
               not-found))
           not-found coll)))

Примеры:

(seek odd? (range)) => 1
(seek pos? [-1 1]) => 1
(seek pos? [-1 -2] ::not-found) => ::not-found
(seek nil? [1 2 nil 3] ::not-found) => nil

В итоге патч был отклонен:

После проверки мы решили, что не хотим включать это. Использование линейного поиска (и, в частности, вложенного линейного поиска) приводит к низкой производительности - часто лучше использовать другие типы структур данных, и поэтому эта функциональность не была включена в прошлом. ~ Алекс Миллер 12 мая /17.34