2012-04-17 10 views
40

Szukam funkcji, która zwraca pierwszy element w sekwencji, która evalutes fn na true. Na przykład:Zwraca pierwszy element na mapie/liście/sekwencji spełniającej funkcję zwracającą true

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

Powyższa fałszywa funkcja powinna zwrócić 1 (ostatni element na liście). Czy jest coś takiego w Clojure?

+7

dlaczego nie tylko '(najpierw (filtr # (=% 1) '(3 4 1))?? – 4e6

+0

@ 4e6 Ponieważ to zastosowałoby funkcję do każdego elementu na liście, co może być niepożądane na dużej liście – Matthew

+10

Mapa jest leniwy, więc nie sądzę, że by tak było, ale powinieneś to przetestować – Bill

Odpowiedz

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

Edit: współbieżności. :) Nie. Nie ma zastosowania f do całej listy. Tylko do elementów aż do pierwszego pasującego ze względu na lenistwo filter.

+3

Dzięki @kotarak, moim zmartwieniem jest to, że przetwarzałoby to wszystkie elementy w kolekcji, co jest niepożądane na dużej liście. Być może potrzebuję utworzyć funkcję, która powtarza się, dopóki nie znajdzie lement, który spełnia warunek – Matthew

+1

Widzę twoją edycję Awesome! Dziękuję bardzo – Matthew

+4

@Matthew modulo chunked sekwencji.There 'f' może być stosowane do większej liczby elementów w zależności od rozmiar porcji: – kotarak

8

myślę some jest najlepszym narzędziem do pracy:

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

Nie ma to wpływu na efekt sekwencji porcji. Ale nie działa w przypadkach, gdy '1' jest' nil' lub 'false'. YMMV. – kotarak

+0

Czekaj ... jak 1 może być równy zeru? :) W każdym razie, Mateusz stwierdził, że elementy powinny "oceniać na prawdę", więc uważam, że zachowanie jest pożądane. – vemv

+3

Rozważ "f" jako "# (zawiera? # {0" false "false}%)'. I powiedział "ocenia fn na true". – kotarak

40

W twoim przypadku, idiom jest

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

Jak to działa: # {1} jest zbiorem dosłowne. Zbiór jest także funkcją oceniającą jego arg, jeśli arg jest obecny w zbiorze, aw żadnym wypadku nie. Każdy ustawiony element jest wartością "prawdy" (no, z wyjątkiem fałszu logicznego, ale jest to rzadkość w zbiorze). some zwraca zwracaną wartość predykatu ocenioną względem pierwszego elementu kolekcji, dla którego wynik był zgodny z prawdą.

3

Stosując drop-while zamiast filter powinna obejmować „na zgłoszeniu” z f w podzielonych na kawałki sekwencji:

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

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

że próbowano kilku metod wymienionych w tym temacie (JDK 8 i Clojure 1.7), i czy niektóre porównawczych testów :

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 

Wyniki pokazują, że metoda reduce może być najbardziej wydajnym rozwiązaniem, jak w clojure 1.7.

+1

Bardzo interesujące. Dzięki za testowanie tych, Xando. Gdyby lepiej było mieć numery z [Criterium] (https://github.com/hugoduncan/criterium), ale domyślam się, że szukając elementu, który jest tak daleko w sekwencji, JVM miała szansę zoptymalizuj kod. – Mars

Powiązane problemy