Kiểm tra xem danh sách có chứa giá trị cụ thể trong Clojure không


162

Cách tốt nhất để kiểm tra xem một danh sách có chứa một giá trị nhất định trong Clojure không?

Đặc biệt, hành vi của contains?hiện đang làm tôi bối rối:

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

Tôi rõ ràng có thể viết một hàm đơn giản để duyệt qua danh sách và kiểm tra sự bằng nhau, nhưng chắc chắn phải có một cách tiêu chuẩn để làm điều này?


7
Lạ thật, có chứa? phải là chức năng được đặt tên sai nhất trong Clojure :) Tôi hy vọng rằng Clojure 1.3 sẽ thấy nó được đổi tên thành khóa chứa? hoặc tương tự.
jg-faustus

4
Tôi nghĩ rằng điều này được nói đến cái chết nhiều lần bây giờ. chứa đựng? sẽ không thay đổi. Xem tại đây: Groups.google.com.vn/group/clojure/msg/f2585c149cd0465dGroups.google.com.vn/group/clojure/msg/985478420223ecdf
kotarak

1
@kotarak cảm ơn vì đường link! Tôi thực sự đồng ý với Rich ở đây về việc sử dụng có chứa? Tên mặc dù tôi nghĩ rằng nó nên được thay đổi để ném lỗi khi áp dụng vào danh sách hoặc chuỗi
mikera

Câu trả lời:


204

À, contains?... được cho là một trong năm câu hỏi thường gặp hàng đầu re: Clojure.

không kiểm tra xem một bộ sưu tập có chứa một giá trị hay không; nó kiểm tra xem một mục có thể được truy xuất bằng gethay nói cách khác là liệu một bộ sưu tập có chứa khóa hay không. Điều này làm cho ý nghĩa đối với bộ (trong đó có thể được coi như làm cho có sự phân biệt giữa các phím và các giá trị), bản đồ (như vậy (contains? {:foo 1} :foo)true) và vectơ (nhưng lưu ý rằng (contains? [:foo :bar] 0)true, bởi vì các phím ở đây là chỉ số và vector trong câu hỏi không "chứa" các chỉ số 0!).

Để thêm vào sự nhầm lẫn, trong trường hợp không có ý nghĩa để gọi contains?, nó chỉ đơn giản trở lại false; đây là những gì xảy ra trong (contains? :foo 1) và cũng có (contains? '(100 101 102) 101) . Cập nhật: Trong Clojure ≥ 1,5 lần contains?ném khi trao một đối tượng thuộc loại không hỗ trợ kiểm tra "tư cách thành viên chính" dự định.

Cách chính xác để làm những gì bạn đang cố gắng làm như sau:

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

Khi tìm kiếm một trong số các mặt hàng, bạn có thể sử dụng một bộ lớn hơn; khi tìm kiếm false/ nil, bạn có thể sử dụng false?/ nil?- bởi vì (#{x} x)trả về x, do đó, (#{nil} nil)nil; khi tìm kiếm một trong nhiều mục, một số trong đó có thể falsehoặc nil, bạn có thể sử dụng

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

(Lưu ý rằng các mục có thể được chuyển đến zipmaptrong bất kỳ loại bộ sưu tập nào.)


Cảm ơn Michal - bạn luôn là một phông chữ của trí tuệ Clojure! Có vẻ như tôi sẽ viết chức năng của riêng mình trong trường hợp này ... điều đó hơi làm tôi ngạc nhiên rằng không có ngôn ngữ cốt lõi nào.
mikera

4
Như Michal đã nói - đã có một chức năng trong cốt lõi thực hiện những gì bạn mong muốn: một số.
kotarak

2
Ở trên, Michal bình luận về việc (some #{101} '(100 101 102))nói rằng "hầu hết thời gian điều này hoạt động". Có công bằng không khi nói rằng nó luôn hoạt động? Tôi đang sử dụng Clojure 1.4 và tài liệu sử dụng loại ví dụ này. Nó làm việc cho tôi và có ý nghĩa. Có một số trường hợp đặc biệt khi nó không hoạt động?
David J.

7
@DavidJames: Sẽ không hoạt động nếu bạn đang kiểm tra sự hiện diện của falsehoặc nil- xem đoạn văn sau. Trên một lưu ý riêng, trong Clojure 1.5-RC1 contains?sẽ đưa ra một ngoại lệ khi đưa ra một bộ sưu tập không khóa làm đối số. Tôi cho rằng tôi sẽ chỉnh sửa câu trả lời này khi bản phát hành cuối cùng ra mắt.
Michał Marc:

1
Điều này thật ngu ngốc! Sự khác biệt chính của một bộ sưu tập là mối quan hệ thành viên. Nó nên là chức năng quan trọng nhất cho các bộ sưu tập. vi.wikipedia.org/wiki/set_(mathatures)#Membership
jgomo3

132

Đây là tiêu chuẩn của tôi cho cùng một mục đích:

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

36
Đây là giải pháp đơn giản và an toàn nhất, vì nó cũng xử lý các giá trị giả như nilfalse. Bây giờ tại sao đây không phải là một phần của clojure / core?
Stian Soiland-Reyes

2
seqcó thể được đổi tên thành coll, để tránh nhầm lẫn với chức năng seq?
nha

3
@nha Bạn có thể làm điều đó, vâng. Không thành vấn đề ở đây: Vì chúng ta không sử dụng chức năng seqbên trong cơ thể, nên không có xung đột với tham số cùng tên. Nhưng hãy thoải mái chỉnh sửa câu trả lời nếu bạn nghĩ việc đổi tên sẽ giúp dễ hiểu hơn.
jg-faustus

1
Điều đáng chú ý là điều này có thể chậm hơn 3-4 lần so với (boolean (some #{elm} coll))nếu bạn không phải lo lắng về nilhoặc false.
neverfox

2
@AviFlax Tôi đã suy nghĩ về clojure.org/guides/threading_macros , trong đó có ghi "Theo quy ước, các hàm cốt lõi hoạt động theo trình tự mong đợi chuỗi là đối số cuối cùng của chúng. Theo đó, các đường ống chứa bản đồ, bộ lọc, loại bỏ, giảm, vào, v.v. thường gọi cho - >> macro. " Nhưng tôi đoán quy ước là nhiều hơn về các chức năng hoạt động trên các chuỗi và trả về các chuỗi.
John Wiseman

18

Bạn luôn có thể gọi các phương thức java bằng cú pháp .methodName.

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

5
IMHO đây là câu trả lời tốt nhất. Clojure quá xấu chứa? được đặt tên rất khó hiểu.
mikkom

1
Ông chủ đáng kính Qc Na đang đi dạo cùng với học trò của mình, Anton. Khi Anton nói với anh ta về vấn đề của người mới bắt đầu contains?, Qc Na đã đánh anh ta bằng một Bô và nói: "Học sinh ngu ngốc! Bạn phải nhận ra không có thìa. Tất cả chỉ là Java bên dưới! Sử dụng ký hiệu dấu chấm.". Ngay lúc đó, Anton trở nên chứng ngộ.
David Tonhofer

17

Tôi biết rằng tôi hơi muộn một chút, nhưng về:

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

Cuối cùng trong clojure 1.4 đầu ra đúng :)


3
(set '(101 102 103))cũng giống như %{101 102 103}. Vì vậy, câu trả lời của bạn có thể được viết là (contains? #{101 102 103} 102).
David J.

4
Điều này có nhược điểm là yêu cầu chuyển đổi danh sách gốc '(101 102 103)thành một tập hợp.
David J.

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

Hoạt động, nhưng dưới đây là tốt hơn:

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

7

Đối với những gì nó có giá trị, đây là cách thực hiện đơn giản của tôi về một hàm chứa cho các danh sách:

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

Chúng ta có thể yêu cầu phần vị ngữ làm đối số không? Để có được một cái gì đó như:(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

Nếu bạn có một vectơ hoặc danh sách và muốn kiểm tra xem một giá trị có được chứa trong đó hay không, bạn sẽ thấy contains?nó không hoạt động. Michał đã giải thích lý do tại sao .

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

Có bốn điều bạn có thể thử trong trường hợp này:

  1. Xem xét liệu bạn thực sự cần một vector hoặc danh sách. Nếu bạn sử dụng một bộ thay thế , contains?sẽ làm việc.

    (contains? #{:a :b :c} :b) ; = true
  2. Sử dụngsome , gói mục tiêu trong một bộ, như sau:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
  3. Phím tắt set-as-function sẽ không hoạt động nếu bạn đang tìm kiếm một giá trị giả ( falsehoặc nil).

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

    Trong những trường hợp này, bạn nên sử dụng hàm vị ngữ tích hợp cho giá trị đó false?hoặc nil?:

    (some false? [true false true]) ; = true
  4. Nếu bạn sẽ cần thực hiện loại tìm kiếm này rất nhiều, hãy viết một hàm cho nó :

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

Ngoài ra, hãy xem câu trả lời của Michał để biết cách kiểm tra xem có bất kỳ mục tiêu nào trong số nhiều mục tiêu được chứa trong một chuỗi hay không.


5

Đây là một chức năng nhanh chóng trong số các tiện ích tiêu chuẩn của tôi mà tôi sử dụng cho mục đích này:

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

Vâng, bạn có lợi thế là nó sẽ dừng lại ngay khi tìm thấy trận đấu thay vì tiếp tục ánh xạ toàn bộ chuỗi.
G__

5

Đây là giải pháp Lisp cổ điển:

(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
OK, lý do là một giải pháp kém trong Clojure là vì nó đệ quy ngăn xếp trên một bộ xử lý. Một giải pháp Clojure tốt hơn là <pre> (thành viên defn? [Elt col] (some # (= elt%) col)) </ pre> Điều này là do somecó khả năng song song trên các lõi có sẵn.
Simon Brooke

4

Tôi đã xây dựng dựa trên phiên bản jg-faustus của "list-chứa?". Bây giờ có bất kỳ số lượng các đối số.

(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

Nó đơn giản như sử dụng một bộ - tương tự như bản đồ, bạn chỉ cần thả nó vào vị trí chức năng. Nó ước tính giá trị nếu trong tập hợp (đó là sự thật) hoặc nil(đó là falsey):

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

Nếu bạn đang kiểm tra đối với một vectơ / danh sách có kích thước hợp lý mà bạn sẽ không có cho đến khi chạy, bạn cũng có thể sử dụng sethàm:

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

1

Cách được đề xuất là sử dụng somevới một bộ - xem tài liệu cho clojure.core/some.

Sau đó, bạn có thể sử dụng sometrong một vị từ đúng / sai, vd

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

Tại sao if truefalse? someđã trả về giá trị true-ish và false-ish.
subub

còn (một số # {nil} [nil]) thì sao? Nó sẽ trả về nil sẽ được chuyển thành false.
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 )))

sử dụng ví dụ (which? [1 2 3] 3) hoặc (which? # {1 2 3} 4 5 3)


vẫn không có chức năng cung cấp ngôn ngữ cốt lõi cho nó?
matanster

1

Vì Clojure được xây dựng trên Java, bạn có thể dễ dàng gọi .indexOfhàm Java. Hàm này trả về chỉ mục của bất kỳ phần tử nào trong bộ sưu tập và nếu không thể tìm thấy phần tử này, trả về -1.

Sử dụng điều này chúng ta có thể nói một cách đơn giản:

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

0

Vấn đề với giải pháp 'được đề xuất' là nó bị phá vỡ khi giá trị bạn đang tìm kiếm là 'không'. Tôi thích giải pháp này:

(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

Có các chức năng thuận tiện cho mục đích này trong thư viện Tupelo . Đặc biệt, chức năng contains-elem?, contains-key?contains-val?rất hữu ích. Tài liệu đầy đủ có mặt trong các tài liệu API .

contains-elem?là chung nhất và được dành cho các vectơ hoặc bất kỳ clojure nào khác 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))))

Ở đây chúng ta thấy rằng đối với một phạm vi số nguyên hoặc một vectơ hỗn hợp, contains-elem?hoạt động như mong đợi cho cả các phần tử hiện có và không tồn tại trong bộ sưu tập. Đối với bản đồ, chúng tôi cũng có thể tìm kiếm bất kỳ cặp khóa-giá trị nào (được biểu thị dưới dạng vectơ len-2):

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

Nó cũng đơn giản để tìm kiếm một bộ:

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

Đối với bản đồ & bộ, sử dụng đơn giản hơn (& hiệu quả hơn) contains-key?để tìm mục nhập bản đồ hoặc phần tử được đặt:

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

Và, đối với bản đồ, bạn cũng có thể tìm kiếm các giá trị với 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)))

Như đã thấy trong thử nghiệm, mỗi hàm này hoạt động chính xác khi tìm kiếm nilgiá trị.


0

Một lựa chọn khác:

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

Sử dụng java.util.Collection # chứa ():

(.contains '(100 101 102) 101)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.