ทดสอบว่ารายการมีค่าเฉพาะใน Clojure


163

เป็นวิธีที่ดีที่สุดในการทดสอบว่ารายการมีค่าที่กำหนดใน Clojure อะไร

โดยเฉพาะอย่างยิ่งพฤติกรรมของcontains?ในปัจจุบันทำให้ฉันสับสน:

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

เห็นได้ชัดว่าฉันสามารถเขียนฟังก์ชั่นง่าย ๆ เพื่อสำรวจรายการและทดสอบความเท่าเทียมกัน แต่ต้องมีวิธีมาตรฐานในการทำเช่นนี้หรือไม่?


7
แปลกจริง ๆ ประกอบด้วย? จะต้องเป็นฟังก์ชั่นที่มีชื่อที่ทำให้เข้าใจผิดมากที่สุดใน Clojure :) นี่คือความหวังว่า Clojure 1.3 จะเห็นมันเปลี่ยนชื่อเป็นมีคีย์? หรือคล้ายกัน
jg-faustus

4
ฉันคิดว่านี่เป็นสิ่งที่พูดถึงความตายหลายครั้งแล้ว มี? จะไม่เปลี่ยนแปลง ดูที่นี่: groups.google.com/group/clojure/msg/f2585c149cd0465dและgroups.google.com/group/clojure/msg/985478420223ecdf
kotarak

1
@kotarak ขอบคุณสำหรับลิงค์! ฉันเห็นด้วยกับที่นี่จริงๆในแง่ของการใช้งานของมี? ชื่อ แต่ผมคิดว่ามันควรจะมีการเปลี่ยนแปลงที่จะโยนความผิดพลาดเมื่อนำไปใช้กับรายการหรือลำดับ
mikera

คำตอบ:


204

อ่าcontains?... หนึ่งในห้าคำถามที่พบบ่อยครั้งหนึ่งน่าจะเป็น: Clojure

ไม่ได้ตรวจสอบว่าคอลเลกชันมีค่าหรือไม่ มันจะตรวจสอบว่ารายการสามารถเรียกคืนด้วยgetหรือในคำอื่น ๆ ไม่ว่าจะเป็นคอลเลกชันที่มีคีย์ สิ่งนี้สมเหตุสมผลสำหรับเซต (ซึ่งอาจคิดได้ว่าไม่มีความแตกต่างระหว่างคีย์และค่า) แผนที่ (เช่น(contains? {:foo 1} :foo)นั้นtrue) และเวกเตอร์ (แต่โปรดทราบว่านั่น(contains? [:foo :bar] 0)คือtrueเพราะคีย์ที่นี่คือดัชนีและเวกเตอร์ที่มีปัญหา "มี" ดัชนี0!)

เพื่อเพิ่มความสับสนในกรณีที่มันไม่เหมาะสมที่จะโทรcontains?มันก็กลับมาfalse; นี่คือสิ่งที่เกิดขึ้นในและ(contains? :foo 1) (contains? '(100 101 102) 101) อัปเดต:ใน Clojure ≥ 1.5 contains?พ่นเมื่อส่งมอบวัตถุประเภทที่ไม่สนับสนุนการทดสอบ "การเป็นสมาชิกคีย์" ที่ต้องการ

วิธีที่ถูกต้องในการทำสิ่งที่คุณพยายามทำมีดังนี้:

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

เมื่อค้นหารายการใดรายการหนึ่งคุณสามารถใช้ชุดที่มีขนาดใหญ่กว่าได้ เมื่อค้นหาfalse/ nilคุณสามารถใช้false?/ nil?- เพราะ(#{x} x)ผลตอบแทนxจึง(#{nil} nil)เป็นnil; เมื่อค้นหาหนึ่งในหลายรายการซึ่งบางอย่างอาจเป็นfalseหรือnilคุณสามารถใช้

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

(โปรดทราบว่ารายการสามารถส่งผ่านไปยังzipmapคอลเลกชันประเภทใดก็ได้)


ขอบคุณ Michal - คุณเป็นตัวอักษรของภูมิปัญญา Clojure เช่นเคย! ดูเหมือนว่าฉันจะเขียนฟังก์ชั่นของตัวเองในกรณีนี้ ... มันทำให้ฉันประหลาดใจเล็กน้อยที่ไม่มีภาษาหลักอยู่แล้ว
mikera

4
ดังที่มิชาลกล่าว - มีฟังก์ชั่นหลักอยู่แล้วซึ่งทำสิ่งที่คุณต้องการ: บางฟังก์ชั่น
kotarak

2
ด้านบนมีคาลให้ความเห็นเกี่ยวกับการ(some #{101} '(100 101 102))พูดว่า "ส่วนใหญ่เวลานี้ใช้งานได้" มันไม่ยุติธรรมที่จะบอกว่ามันใช้งานได้เสมอหรือ ฉันกำลังใช้ Clojure 1.4 และเอกสารใช้ตัวอย่างประเภทนี้ มันใช้งานได้สำหรับฉันและเข้าท่า มีกรณีพิเศษบางอย่างที่มันไม่ทำงานหรือไม่?
David J.

7
@DavidJames: มันไม่ทำงานหากคุณกำลังตรวจสอบสถานะfalseหรือnil- ดูย่อหน้าต่อไปนี้ ในบันทึกย่อที่แยกต่างหากใน Clojure 1.5-RC1 จะcontains?แสดงข้อยกเว้นเมื่อได้รับการรวบรวมที่ไม่มีคีย์เป็นอาร์กิวเมนต์ ฉันคิดว่าฉันจะแก้ไขคำตอบนี้เมื่อรุ่นสุดท้ายออกมา
Michał Marczyk

1
นี่โง่! ความแตกต่างที่สำคัญของคอลเลกชันคือความสัมพันธ์ของสมาชิก มันควรจะเป็นฟังก์ชั่นที่สำคัญที่สุดสำหรับคอลเลกชัน en.wikipedia.org/wiki/Set_(mathematics)#Membership
jgomo3

132

นี่คือประโยชน์มาตรฐานของฉันเพื่อจุดประสงค์เดียวกัน:

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

36
นี่คือวิธีที่ง่ายที่สุดและปลอดภัยที่สุดในขณะที่มันยังจับค่า falsy ชอบและnil falseทีนี้ทำไมนี่ไม่ได้เป็นส่วนหนึ่งของ clojure / core?
Stian Soiland-Reyes

2
seqอาจถูกเปลี่ยนชื่อเป็นcollเพื่อหลีกเลี่ยงความสับสนกับฟังก์ชั่น seq?
nha

3
@nha คุณสามารถทำได้ใช่ ไม่สำคัญที่นี่: เนื่องจากเราไม่ได้ใช้ฟังก์ชั่นseqภายในร่างกายจึงไม่ขัดแย้งกับพารามิเตอร์ที่มีชื่อเดียวกัน แต่อย่าลังเลที่จะแก้ไขคำตอบหากคุณคิดว่าการเปลี่ยนชื่อจะทำให้เข้าใจง่ายขึ้น
JG-เฟาสตุส

1
มันเป็นมูลค่า noting ว่านี้สามารถ 3-4x ช้ากว่า(boolean (some #{elm} coll))ถ้าคุณไม่ต้องกังวลเกี่ยวกับหรือnil false
neverfox

2
@AviFlax ฉันกำลังคิดเกี่ยวกับclojure.org/guides/threading_macrosซึ่งมันบอกว่า "ตามแบบแผนฟังก์ชันหลักที่ทำงานกับลำดับนั้นคาดว่าลำดับนั้นเป็นอาร์กิวเมนต์สุดท้ายของพวกเขาดังนั้นท่อที่บรรจุแผนที่ตัวกรองลบลดลง ฯลฯ มักจะเรียกใช้แมโคร - >> แต่ฉันคิดว่าการประชุมนั้นเกี่ยวกับฟังก์ชั่นที่ทำงานกับลำดับและส่งคืนลำดับมากขึ้น
John Wiseman

18

คุณสามารถเรียกเมธอด java ด้วยไวยากรณ์. method ชื่อได้ตลอดเวลา

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

5
IMHO นี่คือคำตอบที่ดีที่สุด Clojure เลวเกินไปมี? เป็นชื่อที่สับสน
mikkom

1
อาจารย์ Qc Na ผู้น่านับถือกำลังเดินไปกับแอนตันของนักเรียน เมื่อแอนตันบอกเขาเกี่ยวกับการมีปัญหาบางอย่างเริ่มต้นของcontains?, QC นาตีเขาด้วย BO และกล่าวว่า "!. นักเรียนโง่คุณต้องตระหนักไม่มีช้อนมันทั้งหมดเพียง Java ใต้!ใช้สัญกรณ์ดอท." ในขณะนั้นแอนตันก็รู้แจ้ง
David Tonhofer

17

ฉันรู้ว่าฉันมาสายนิดหน่อย แต่เกี่ยวกับ:

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

ในที่สุด clojure 1.4 เอาต์พุตจริง :)


3
(set '(101 102 103))%{101 102 103}เป็นเช่นเดียวกับ (contains? #{101 102 103} 102)ดังนั้นคำตอบของคุณสามารถเขียนเป็น
David J.

4
สิ่งนี้มีข้อเสียของการแปลงรายการต้นฉบับ'(101 102 103)เป็นชุด
David J.

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

ใช้งานได้ แต่ด้านล่างดีกว่า:

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

7

สำหรับสิ่งที่คุ้มค่านี่คือการใช้งานฟังก์ชั่นแบบเรียบง่ายสำหรับรายการ

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

เราขอส่วนเพรดิเคตเป็นข้อโต้แย้งได้หรือไม่? เพื่อรับสิ่งที่ชอบ:(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

หากคุณมีเวกเตอร์หรือรายการและต้องการตรวจสอบว่ามีค่าอยู่ในนั้นหรือไม่คุณจะพบว่าcontains?มันไม่ทำงาน Michałยังแล้วอธิบายว่าทำไม

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

ในกรณีนี้มีสี่สิ่งที่คุณสามารถลองได้:

  1. พิจารณาว่าคุณต้องการเวกเตอร์หรือรายการจริงๆ หากคุณใช้ชุดแทน , contains?จะทำงาน

    (contains? #{:a :b :c} :b) ; = true
  2. ใช้someห่อเป้าหมายในชุดดังต่อไปนี้:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
  3. ทางลัด set-as-function จะไม่ทำงานหากคุณค้นหาค่าที่ผิดพลาด ( falseหรือnil)

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

    ในกรณีเหล่านี้คุณควรใช้ฟังก์ชันเพรดิเคตในตัวสำหรับค่านั้นfalse?หรือnil?:

    (some false? [true false true]) ; = true
  4. หากคุณจะต้องทำการค้นหาแบบนี้มากเขียนฟังก์ชัน :

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

โปรดดูคำตอบของMichałสำหรับวิธีการตรวจสอบว่ามีหลายเป้าหมายในลำดับหรือไม่


5

นี่คือฟังก์ชั่นอย่างรวดเร็วจากสาธารณูปโภคมาตรฐานของฉันที่ฉันใช้เพื่อจุดประสงค์นี้:

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

ใช่คุณมีข้อได้เปรียบที่จะหยุดทันทีที่พบการแข่งขันมากกว่าที่จะทำการแมปลำดับทั้งหมดต่อไป
G__

5

นี่คือทางออก Lisp คลาสสิก:

(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
ตกลงเหตุผลที่เป็นโซลูชันที่ไม่ดีใน Clojure ก็คือมันเกิดขึ้นซ้ำสแต็คในโปรเซสเซอร์เดียว โซลูชัน Clojure ที่ดีกว่าคือ <pre> (ป้องกันสมาชิก? [elt col] (บาง # (= elt%) col)) </pre> เนื่องจากsomeนี่อาจขนานกันระหว่างแกนที่มีอยู่
Simon Brooke

4

ฉันได้สร้างขึ้นเมื่อ JG-เฟาสตุสรุ่นของ "รายการมี?" ตอนนี้รับอาร์กิวเมนต์จำนวนเท่าใดก็ได้

(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

มันง่ายเหมือนการใช้ชุด - คล้ายกับแผนที่คุณสามารถวางไว้ในตำแหน่งฟังก์ชั่น มันประเมินค่าหากอยู่ในชุด (ซึ่งเป็นความจริง) หรือnil(ซึ่งเป็นเท็จ):

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

หากคุณตรวจสอบกับเวกเตอร์ / รายการที่มีขนาดพอสมควรซึ่งคุณจะไม่มีจนกระทั่งถึงรันไทม์คุณยังสามารถใช้setฟังก์ชัน:

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

1

วิธีที่แนะนำคือการใช้someกับชุด - clojure.core/someดูเอกสาร

จากนั้นคุณสามารถใช้someภายในเพรดิเคตจริง / เท็จเช่น

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

ทำไมif trueและfalse? someส่งคืนค่า true-ish และ false-ish แล้ว
subsub

เกี่ยวกับ (บาง # {nil} [nil])? มันจะคืนค่าศูนย์ซึ่งจะถูกแปลงเป็นเท็จ
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 )))

ตัวอย่างการใช้งาน (อันไหน [1 2 3] 3) หรือ (อัน? # {1 2 3} 4 5 3)


ยังไม่มีฟังก์ชั่นภาษาหลักที่ให้มาหรือไม่
matanster

1

เนื่องจาก Clojure สร้างขึ้นบน Java คุณสามารถโทรได้อย่างง่ายดาย .indexOfฟังก์ชัน Javaฟังก์ชันนี้ส่งคืนดัชนีขององค์ประกอบใด ๆ ในคอลเลกชันและหากไม่พบองค์ประกอบนี้ให้ส่งคืน -1

การใช้ประโยชน์จากสิ่งนี้เราสามารถพูดได้ว่า:

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

0

ปัญหาของการแก้ปัญหา 'แนะนำ' คือมันจะแตกเมื่อค่าที่คุณต้องการคือ 'ไม่มี' ฉันชอบวิธีนี้:

(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

มีฟังก์ชั่นอำนวยความสะดวกสำหรับวัตถุประสงค์นี้ในห้องสมุดเพอ โดยเฉพาะอย่างยิ่งฟังก์ชั่นcontains-elem?, contains-key?และcontains-val?มีประโยชน์มาก เอกสารเต็มรูปแบบที่มีอยู่ในเอกสารของ API

contains-elem?เป็นสามัญที่สุดและมีไว้สำหรับเวกเตอร์หรือ clojure อื่น ๆ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))))

ที่นี่เราเห็นว่าสำหรับช่วงจำนวนเต็มหรือเวกเตอร์ผสมcontains-elem?ทำงานตามที่คาดไว้สำหรับองค์ประกอบที่มีอยู่และไม่มีอยู่ในคอลเลกชัน สำหรับแผนที่เราสามารถค้นหาคู่ค่าคีย์ใด ๆ (แสดงเป็นเวกเตอร์ 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] ))))

นอกจากนี้ยังตรงไปตรงมาเพื่อค้นหาชุด:

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

สำหรับแผนที่และชุดมันง่ายกว่า (& มีประสิทธิภาพมากขึ้น) เพื่อใช้contains-key?ในการค้นหารายการแผนที่หรือองค์ประกอบชุด:

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

และสำหรับแผนที่คุณยังสามารถค้นหาค่าด้วย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)))

เท่าที่เห็นในการทดสอบแต่ละฟังก์ชั่นเหล่านี้ทำงานอย่างถูกต้องเมื่อค้นหาnilค่า


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.