Listenin Clojure'da belirli bir değer içerip içermediğini test edin


163

Bir listenin Clojure'da belirli bir değer içerip içermediğini test etmenin en iyi yolu nedir?

Özellikle, contains?şu anda beni karıştırıyor:

(contains? '(100 101 102) 101) => false

Açıkçası listede dolaşmak ve eşitliği test etmek için basit bir işlev yazabilirim, ancak bunu yapmak için mutlaka standart bir yol olmalı?


7
Gerçekten garip, içerir? Clojure'daki en yanıltıcı isimlendirilmiş işlev olmalı :) İşte Clojure 1.3'ün şunu içerecek şekilde key-key olarak yeniden adlandırıldığını görmesini umuyoruz? veya benzeri.
jg-faustus

4
Sanırım bu birkaç kez ölümle konuşuldu. içeriyor? değişmeyecek. Buraya bakın: groups.google.com/group/clojure/msg/f2585c149cd0465d ve groups.google.com/group/clojure/msg/985478420223ecdf
kotarak

1
@kotarak bağlantı için teşekkürler! Aslında burada içerdiği kullanım açısından Rich ile katılıyorum? isim olsa da bir liste veya sıraya uygulandığında bir hata atmak için değiştirilmesi gerektiğini düşünüyorum
mikera

Yanıtlar:


204

Ah, contains?... sözde ilk beş SSS'den biri: Clojure.

O mu değil bir koleksiyon değeri içerip içermediğini kontrol edin; bir öğenin alınıp alınamayacağını getveya başka bir deyişle bir koleksiyonun anahtar içerip içermediğini kontrol eder. Bu (anahtarlar ve değerler arasında ayrım yapmadan olarak düşünülebilir) kümeleri, haritalar (şimdiye için mantıklı (contains? {:foo 1} :foo)olan true) ve vektörlerin (ama not (contains? [:foo :bar] 0)olan truetuşlar burada endeksleri ve söz konusu vektör "içeren" çünkü, endeksi 0!).

Karışıklığa eklemek için, aramanın mantıklı olmadığı durumlarda contains?, sadece geri döner false; ve bunun içinde olan (contains? :foo 1) da budur (contains? '(100 101 102) 101) . Güncelleme: In Clojure ≥ 1.5 contains?, amaçlanan "anahtar üyelik" testini desteklemeyen tipte bir nesne verildiğinde atar.

Yapmaya çalıştığınız şeyi yapmanın doğru yolu aşağıdaki gibidir:

; most of the time this works
(some #{101} '(100 101 102))

Bir grup öğeden birini ararken daha büyük bir set kullanabilirsiniz; ararken false/ nilkullanabileceğiniz false?/ nil?- çünkü (#{x} x)getiriler x, böylece (#{nil} nil)olduğu nil; Birden öğelerden birini ararken bazıları olabilir falseveya nilkullanabileceğiniz

(some (zipmap [...the items...] (repeat true)) the-collection)

(Öğelerin zipmapherhangi bir koleksiyonda aktarılabileceğini unutmayın .)


Teşekkürler Michal - Clojure bilgeliğinin her zamanki gibi bir fontsun! Bu durumda kendi fonksiyonumu yazacağım gibi görünüyor ... çekirdek dilde bir tane olmadığı beni biraz şaşırtıyor.
mikera

4
Michal'in dediği gibi - özünde zaten arzu ettiğiniz şeyi yapan bir işlev var: bazıları.
kotarak

2
Yukarıda, Michal (some #{101} '(100 101 102))"çoğu zaman bu işe yaradığını" söyledi. Her zaman işe yaradığını söylemek adil değil mi? Clojure 1.4 kullanıyorum ve belgeler bu tür bir örnek kullanır. Benim için çalışıyor ve mantıklı. İşe yaramayacağı bir tür özel durum var mı?
David J.

7
@DavidJames: Varlığını kontrol ediyorsanız falseveya çalışmaz nil- aşağıdaki paragrafa bakın. Ayrı bir notta, Clojure 1.5-RC1'de contains?anahtarsız bir koleksiyon argüman olarak verildiğinde bir istisna atar. Sanırım son sürüm çıktığında bu cevabı düzenleyeceğim.
Michał Marczyk

1
Bu aptalca! Bir koleksiyonun ana ayrımı üyelik ilişkisidir. Koleksiyonlar için en önemli işlev olmalıydı. en.wikipedia.org/wiki/Set_(mathematics)#Üyelik
jgomo3

132

İşte aynı amaç için benim standart util:

(defn in? 
  "true if coll contains elm"
  [coll elm]  
  (some #(= elm %) coll))

36
O da falsy değerler gibi kolları gibi bu en basit ve en güvenli çözümdür nilve false. Şimdi bu neden clojure / core'un bir parçası değil?
Stian Soiland-Reyes

2
seqcollfonksiyonu ile karışıklığı önlemek için , belki de yeniden adlandırılabilir seq?
nha

3
@nha Bunu yapabilirsin, evet. Burada önemli değil: Fonksiyonu seqvücudun içinde kullanmadığımızdan , aynı addaki parametreyle bir çelişki yok. Ancak, yeniden adlandırma işleminin anlaşılmasını kolaylaştıracağını düşünüyorsanız, yanıtı düzenlemekten çekinmeyin.
jg-faustus

1
Bunun (boolean (some #{elm} coll))endişelenmenize gerek yok nilya da 3-4 kat daha yavaş olabileceğini belirtmek gerekir false.
neverfox

2
@AviFlax ben düşünüyordum clojure.org/guides/threading_macros o "Geleneksel olarak, diziler üzerinde işlem çekirdek fonksiyonları son argüman olarak diziyi bekliyoruz diyor. Buna göre, harita, filtre, çıkarmak, azaltmak içeren boru hatlarını, içine, vb genellikle - >> makrosunu çağırır. " Ama sanırım bu kural diziler ve dönüş dizileri üzerinde çalışan işlevler hakkında.
John Wiseman

18

Java yöntemlerini her zaman .methodName sözdizimi ile çağırabilirsiniz.

(.contains [100 101 102] 101) => true

5
IMHO bu en iyi cevap. Çok kötü clojure içeriyor mu? çok kafa karıştırıcı bir şekilde adlandırılmıştır.
mikkom

1
Saygıdeğer usta Qc Na, öğrencisi Anton ile birlikte yürüyordu. Anton ona yeni başlayanlarla ilgili bir sorun yaşadığını söylediğinde contains?, Qc Na ona bir Bô ile vurdu ve “Aptal öğrenci! Kaşık olmadığını fark etmelisiniz. Hepsi sadece Java altında! Nokta gösterimini kullanın.”. O anda Anton aydınlandı.
David Tonhofer

17

Biraz geç kaldığımı biliyorum, ama ne olacak:

(contains? (set '(101 102 103)) 102)

Sonunda clojure 1.4 çıktıları doğru :)


3
(set '(101 102 103))ile aynıdır %{101 102 103}. Böylece cevabınız şöyle yazılabilir (contains? #{101 102 103} 102).
David J.

4
Bu, orijinal listenin '(101 102 103)bir kümeye dönüştürülmesini gerektirme dezavantajına sahiptir .
David J.

12
(not= -1 (.indexOf '(101 102 103) 102))

Çalışır, ancak aşağıda daha iyidir:

(some #(= 102 %) '(101 102 103)) 

7

Ne için değer, bu benim listeleri içeren bir işlev basit uygulama:

(defn list-contains? [coll value]
  (let [s (seq coll)]
    (if s
      (if (= (first s) value) true (recur (rest s) value))
      false)))

Yüklem kısmını bir argüman olarak isteyebilir miyiz? Gibi bir şey almak için:(defn list-contains? [pred coll value] (let [s (seq coll)] (if s (if (pred (first s) value) true (recur (rest s) value)) false)))
Rafi Panoyan

6

Bir vektörünüz veya listeniz varsa ve içinde bir değerin bulunup bulunmadığını kontrol etmek istiyorsanız contains?, bunun işe yaramadığını göreceksiniz . Michał zaten nedenini açıkladı .

; does not work as you might expect
(contains? [:a :b :c] :b) ; = false

Bu durumda deneyebileceğiniz dört şey vardır:

  1. Gerçekten bir vektör veya listeye ihtiyacınız olup olmadığını düşünün. Eğer varsa yerine kümesini kullanmak , contains?çalışacaktır.

    (contains? #{:a :b :c} :b) ; = true
  2. someHedefi aşağıdaki gibi bir kümeye sararak kullanın :

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
  3. Bir falsy değeri ( falseveya nil) arıyorsanız işlev olarak ayarla kısayolu çalışmaz .

    ; will not work
    (some #{false} [true false true]) ; = nil

    Bu durumlarda, bu değer için yerleşik yüklem işlevini kullanmalısınız false?veya nil?:

    (some false? [true false true]) ; = true
  4. Bu tür bir aramayı çok yapmanız gerekiyorsa, bunun için bir işlev yazın :

    (defn seq-contains? [coll target] (some #(= target %) coll))
    (seq-contains? [true false true] false) ; = true

Ayrıca, birden fazla hedeften herhangi birinin bir sırada bulunup bulunmadığını kontrol etmenin yolları için Michał'nın cevabına bakınız .


5

İşte bu amaçla kullandığım standart yardımcı programlarımdan hızlı bir işlev:

(defn seq-contains?
  "Determine whether a sequence contains a given item"
  [sequence item]
  (if (empty? sequence)
    false
    (reduce #(or %1 %2) (map #(= %1 item) sequence))))

Evet, sizinki avantajı, tüm diziyi eşlemeye devam etmek yerine bir eşleşme bulur bulmaz durmasıdır.
G__

5

İşte klasik Lisp çözümü:

(defn member? [list elt]
    "True if list contains at least one instance of elt"
    (cond 
        (empty? list) false
        (= (first list) elt) true
        true (recur (rest list) elt)))

4
Tamam, Clojure'da kötü bir çözüm olmasının nedeni, yığını bir işlemci üzerinde toplamasıdır. Daha iyi bir Clojure çözümü <pre> (defn üye? [Elt col] (bazı # (= elt%) col)) </pre> Bunun nedeni somemevcut çekirdekler arasında potansiyel olarak paralel olmasıdır.
Simon Brooke

4

"Liste-içerir?" Jg-faustus sürümü üzerine inşa ettik . Şimdi herhangi bir sayıda argüman alıyor.

(defn list-contains?
([collection value]
    (let [sequence (seq collection)]
        (if sequence (some #(= value %) sequence))))
([collection value & next]
    (if (list-contains? collection value) (apply list-contains? collection next))))

2

Bir set kullanmak kadar basittir - haritalara benzer şekilde, onu işlev konumuna bırakabilirsiniz. Sette (doğrulukta) veya nil( falseyde) değeri değerlendirir :

(#{100 101 102} 101) ; 101
(#{100 101 102} 99) ; nil

Çalışma zamanına kadar sahip olmayacağınız makul büyüklükte bir vektör / listeye karşı kontrol ediyorsanız, setişlevi de kullanabilirsiniz :

; (def nums '(100 101 102))
((set nums) 101) ; 101

1

Önerilen yol somebir set ile kullanmaktır - için belgelere bakın clojure.core/some.

Daha sonra somegerçek bir doğru / yanlış yüklem içinde kullanabilirsiniz , örn.

(defn in? [coll x] (if (some #{x} coll) true false))

neden if trueve false? somezaten true-ish ve false-ish değerlerini döndürür.
subsub

ne hakkında (bazı # {nil} [nil])? Yanlış dönüştürülecek nil dönecekti.
Wei Qiu

1
(defn in?
  [needle coll]
  (when (seq coll)
    (or (= needle (first coll))
        (recur needle (next coll)))))

(defn first-index
  [needle coll]
  (loop [index 0
         needle needle
         coll coll]
    (when (seq coll)
      (if (= needle (first coll))
        index
        (recur (inc index) needle (next coll))))))

1
(defn which?
 "Checks if any of elements is included in coll and says which one
  was found as first. Coll can be map, list, vector and set"
 [ coll & rest ]
 (let [ncoll (if (map? coll) (keys coll) coll)]
    (reduce
     #(or %1  (first (filter (fn[a] (= a %2))
                           ncoll))) nil rest )))

örnek kullanım (hangisi [1 2 3] 3) veya (hangisi # # 1 1 1 3} 4 5 3)


bunun için hala dil çekirdeği sağlanmıyor mu?
matanster

1

Clojure Java üzerine kurulduğundan, .indexOf Java işlevini . Bu işlev, bir koleksiyondaki herhangi bir öğenin dizinini döndürür ve bu öğeyi bulamazsa, -1 değerini döndürür.

Bunu kullanarak şunu söyleyebiliriz:

(not= (.indexOf [1 2 3 4] 3) -1)
=> true

0

'Önerilen' çözümün sorunu, aradığınız değer 'sıfır' olduğunda kesilmesidir. Bu çözümü tercih ederim:

(defn member?
  "I'm still amazed that Clojure does not provide a simple member function.
   Returns true if `item` is a member of `series`, else nil."
  [item series]
  (and (some #(= item %) series) true))

0

Tupelo kütüphanesinde bu amaç için uygun fonksiyonlar vardır . Özellikle, fonksiyonlar contains-elem?, contains-key?ve contains-val?çok faydalıdır. API dokümanlarında tam dokümantasyon mevcuttur .

contains-elem?en genel olanıdır ve vektörler veya başka herhangi bir renk için tasarlanmıştır seq:

  (testing "vecs"
    (let [coll (range 3)]
      (isnt (contains-elem? coll -1))
      (is   (contains-elem? coll  0))
      (is   (contains-elem? coll  1))
      (is   (contains-elem? coll  2))
      (isnt (contains-elem? coll  3))
      (isnt (contains-elem? coll  nil)))

    (let [coll [ 1 :two "three" \4]]
      (isnt (contains-elem? coll  :no-way))
      (isnt (contains-elem? coll  nil))
      (is   (contains-elem? coll  1))
      (is   (contains-elem? coll  :two))
      (is   (contains-elem? coll  "three"))
      (is   (contains-elem? coll  \4)))

    (let [coll [:yes nil 3]]
      (isnt (contains-elem? coll  :no-way))
      (is   (contains-elem? coll  :yes))
      (is   (contains-elem? coll  nil))))

Burada bir tamsayı aralığı veya karışık bir vektör contains-elem?için koleksiyondaki hem var olan hem de var olmayan elemanlar için beklendiği gibi çalıştığını görüyoruz . Haritalar için, herhangi bir anahtar / değer çiftini de arayabiliriz (len-2 vektörü olarak ifade edilir):

 (testing "maps"
    (let [coll {1 :two "three" \4}]
      (isnt (contains-elem? coll nil ))
      (isnt (contains-elem? coll [1 :no-way] ))
      (is   (contains-elem? coll [1 :two]))
      (is   (contains-elem? coll ["three" \4])))
    (let [coll {1 nil "three" \4}]
      (isnt (contains-elem? coll [nil 1] ))
      (is   (contains-elem? coll [1 nil] )))
    (let [coll {nil 2 "three" \4}]
      (isnt (contains-elem? coll [1 nil] ))
      (is   (contains-elem? coll [nil 2] ))))

Bir seti aramak da basittir:

  (testing "sets"
    (let [coll #{1 :two "three" \4}]
      (isnt (contains-elem? coll  :no-way))
      (is   (contains-elem? coll  1))
      (is   (contains-elem? coll  :two))
      (is   (contains-elem? coll  "three"))
      (is   (contains-elem? coll  \4)))

    (let [coll #{:yes nil}]
      (isnt (contains-elem? coll  :no-way))
      (is   (contains-elem? coll  :yes))
      (is   (contains-elem? coll  nil)))))

Haritalar ve kümeler için, contains-key?bir harita girişi veya bir küme öğesi bulmak için kullanmak daha basit (ve daha verimli) :

(deftest t-contains-key?
  (is   (contains-key?  {:a 1 :b 2} :a))
  (is   (contains-key?  {:a 1 :b 2} :b))
  (isnt (contains-key?  {:a 1 :b 2} :x))
  (isnt (contains-key?  {:a 1 :b 2} :c))
  (isnt (contains-key?  {:a 1 :b 2}  1))
  (isnt (contains-key?  {:a 1 :b 2}  2))

  (is   (contains-key?  {:a 1 nil   2} nil))
  (isnt (contains-key?  {:a 1 :b  nil} nil))
  (isnt (contains-key?  {:a 1 :b    2} nil))

  (is   (contains-key? #{:a 1 :b 2} :a))
  (is   (contains-key? #{:a 1 :b 2} :b))
  (is   (contains-key? #{:a 1 :b 2}  1))
  (is   (contains-key? #{:a 1 :b 2}  2))
  (isnt (contains-key? #{:a 1 :b 2} :x))
  (isnt (contains-key? #{:a 1 :b 2} :c))

  (is   (contains-key? #{:a 5 nil   "hello"} nil))
  (isnt (contains-key? #{:a 5 :doh! "hello"} nil))

  (throws? (contains-key? [:a 1 :b 2] :a))
  (throws? (contains-key? [:a 1 :b 2]  1)))

Ayrıca, haritalar için aşağıdaki değerlerle de arama yapabilirsiniz contains-val?:

(deftest t-contains-val?
  (is   (contains-val? {:a 1 :b 2} 1))
  (is   (contains-val? {:a 1 :b 2} 2))
  (isnt (contains-val? {:a 1 :b 2} 0))
  (isnt (contains-val? {:a 1 :b 2} 3))
  (isnt (contains-val? {:a 1 :b 2} :a))
  (isnt (contains-val? {:a 1 :b 2} :b))

  (is   (contains-val? {:a 1 :b nil} nil))
  (isnt (contains-val? {:a 1 nil  2} nil))
  (isnt (contains-val? {:a 1 :b   2} nil))

  (throws? (contains-val?  [:a 1 :b 2] 1))
  (throws? (contains-val? #{:a 1 :b 2} 1)))

Testte görüldüğü gibi, nildeğerleri ararken bu işlevlerin her biri doğru şekilde çalışır .


0

Başka seçenek:

((set '(100 101 102)) 101)

Java.util.Collection # include () öğesini kullanın:

(.contains '(100 101 102) 101)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.