diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 007afadb..0004f954 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1197,45 +1197,15 @@ ~(def ,alias :dyn ,;more ,kw)) -(defn contains-key? - ```Checks if a collection contains the specified key. - - Semantically equivalent to `(not (nil? (get collection key)))`. - - Arrays, tuples, and buffer types (string, buffer, keyword, symbol) are all indexed by integer keys. - For those types, this function simply checks if the index is less than the length. - - If this function succeeds, then a call to `(in collection key)` is guarenteed - to succeed as well. - - Note that tables or structs (dictionaries) never contain null keys``` - [collection key] - (not (nil? (get collection key)))) - -(defn contains? - ```Checks if a collection contains the specified value. - - This supports any iterable type by way of the `next` function. - This includes buffers, dictionaries, arrays, fibers, and possibly abstract types. - - For tables and structs, this checks the values, not the keys. - For arrays, tuples (and any other iterable type), this simply checks if any of the values are equal. - - For buffer types (strings, buffers, keywords), this checks if the specified byte is present. - This is because, buffer types (strings, keywords, symbols) are simply sequences, with byte values. - This means they will also work with `next` and `index-of`. - - However, it also means this function will not check for substrings, only integer bytes (which could be unexpected). - In other words is `(contains? "foo bar" "foo")` is always false, because "foo" is not an integer byte - If you want to check for a substring in a buffer, then use `(truthy? (string/find substr buffer))`, - or just `(if (string/find substr buffer) then else)` - - In general this function has O(n) performance, since it requires iterating over all the values. - - Note that tables or structs (dictionaries) never contain null values``` - [collection val] - (not (nil? (index-of val collection)))) +(defn has-key? + "Check if a data structure `ds` contains the key `key`." + [ds key] + (not= nil (get ds key))) +(defn has-value? + "Check if a data structure `ds` contains the value `value`. Will run in time proportional to the size of `ds`." + [ds value] + (not= nil (index-of value ds))) (defdyn *defdyn-prefix* ``Optional namespace prefix to add to keywords declared with `defdyn`. Use this to prevent keyword collisions between dynamic bindings.``) diff --git a/test/suite0010.janet b/test/suite0010.janet index d8cbe3f8..d17ecdd9 100644 --- a/test/suite0010.janet +++ b/test/suite0010.janet @@ -33,7 +33,7 @@ (assert (= nil (index-of (chr "a") "")) "index-of 9") (assert (= nil (index-of 10 @[])) "index-of 10") (assert (= nil (index-of 10 @[1 2 3])) "index-of 11") -# NOTE: These is a motivation for the contains? and contains-key? functions below +# NOTE: These is a motivation for the has-value? and has-key? functions below # returns false despite key present (assert (= false (index-of 8 {true 7 false 8})) "index-of corner key (false) 1") @@ -41,36 +41,36 @@ # still returns null (assert (= nil (index-of 7 {false 8})) "index-of corner key (false) 3") -# contains? -(assert (= false (contains? [] "foo")) "contains? 1") -(assert (= true (contains? [4 7 1 3] 4)) "contains? 2") -(assert (= false (contains? [4 7 1 3] 22)) "contains? 3") -(assert (= false (contains? @[1 2 3] 4)) "contains? 4") -(assert (= true (contains? @[:a :b :c] :a)) "contains? 5") -(assert (= false (contains? {} :foo)) "contains? 6") -(assert (= true (contains? {:a :A :b :B} :A)) "contains? 7") -(assert (= true (contains? {:a :A :b :B} :A)) "contains? 7") -(assert (= true (contains? @{:a :A :b :B} :A)) "contains? 8") -(assert (= true (contains? "abc" (chr "a"))) "contains? 9") -(assert (= false (contains? "abc" "1")) "contains? 10") +# has-value? +(assert (= false (has-value? [] "foo")) "has-value? 1") +(assert (= true (has-value? [4 7 1 3] 4)) "has-value? 2") +(assert (= false (has-value? [4 7 1 3] 22)) "has-value? 3") +(assert (= false (has-value? @[1 2 3] 4)) "has-value? 4") +(assert (= true (has-value? @[:a :b :c] :a)) "has-value? 5") +(assert (= false (has-value? {} :foo)) "has-value? 6") +(assert (= true (has-value? {:a :A :b :B} :A)) "has-value? 7") +(assert (= true (has-value? {:a :A :b :B} :A)) "has-value? 7") +(assert (= true (has-value? @{:a :A :b :B} :A)) "has-value? 8") +(assert (= true (has-value? "abc" (chr "a"))) "has-value? 9") +(assert (= false (has-value? "abc" "1")) "has-value? 10") # weird true/false corner cases, should align with "index-of corner key {k}" cases -(assert (= true (contains? {true 7 false 8} 8)) "contains? corner key (false) 1") -(assert (= true (contains? @{false 8} 8)) "contains? corner key (false) 2") -(assert (= false (contains? {false 8} 7)) "contains? corner key (false) 3") +(assert (= true (has-value? {true 7 false 8} 8)) "has-value? corner key (false) 1") +(assert (= true (has-value? @{false 8} 8)) "has-value? corner key (false) 2") +(assert (= false (has-value? {false 8} 7)) "has-value? corner key (false) 3") -# contains-key? +# has-key? (do - (var test-contains-key-auto 0) - (defn test-contains-key [col key expected &keys {:name name}] - ``Test that contains-key has the outcome `expected`, and that if + (var test-has-key-auto 0) + (defn test-has-key [col key expected &keys {:name name}] + ``Test that has-key has the outcome `expected`, and that if the result is true, then ensure (in key) does not fail either`` (assert (boolean? expected)) - (default name (string "contains-key? " (++ test-contains-key-auto))) - (assert (= expected (contains-key? col key)) name) + (default name (string "has-key? " (++ test-has-key-auto))) + (assert (= expected (has-key? col key)) name) (if - # guarenteed by `contains-key?` to never fail + # guarenteed by `has-key?` to never fail expected (in col key) - # if `contains-key?` is false, then `in` should fail (for indexed types) + # if `has-key?` is false, then `in` should fail (for indexed types) # # For dictionary types, it should return nil (let [[success retval] (protect (in col key))] @@ -81,26 +81,26 @@ "%s: expected (in col key) to %s, but got %q" name (if expected "succeed" "fail") retval))))) - (test-contains-key [] 0 false) # 1 - (test-contains-key [4 7 1 3] 2 true) # 2 - (test-contains-key [4 7 1 3] 22 false) # 3 - (test-contains-key @[1 2 3] 4 false) # 4 - (test-contains-key @[:a :b :c] 2 true) # 5 - (test-contains-key {} :foo false) # 6 - (test-contains-key {:a :A :b :B} :a true) # 7 - (test-contains-key {:a :A :b :B} :A false) # 8 - (test-contains-key @{:a :A :b :B} :a true) # 9 - (test-contains-key "abc" 1 true) # 10 - (test-contains-key "abc" 4 false) # 11 + (test-has-key [] 0 false) # 1 + (test-has-key [4 7 1 3] 2 true) # 2 + (test-has-key [4 7 1 3] 22 false) # 3 + (test-has-key @[1 2 3] 4 false) # 4 + (test-has-key @[:a :b :c] 2 true) # 5 + (test-has-key {} :foo false) # 6 + (test-has-key {:a :A :b :B} :a true) # 7 + (test-has-key {:a :A :b :B} :A false) # 8 + (test-has-key @{:a :A :b :B} :a true) # 9 + (test-has-key "abc" 1 true) # 10 + (test-has-key "abc" 4 false) # 11 # weird true/false corner cases # - # Tries to mimic the corresponding corner cases in contains? and index-of, + # Tries to mimic the corresponding corner cases in has-value? and index-of, # but with keys/values inverted # # in the first two cases (truthy? (get val col)) would have given false negatives - (test-contains-key {7 true 8 false} 8 true :name "contains-key? corner value (false) 1") - (test-contains-key @{8 false} 8 true :name "contains-key? corner value (false) 2") - (test-contains-key @{8 false} 7 false :name "contains-key? corner value (false) 3")) + (test-has-key {7 true 8 false} 8 true :name "has-key? corner value (false) 1") + (test-has-key @{8 false} 8 true :name "has-key? corner value (false) 2") + (test-has-key @{8 false} 7 false :name "has-key? corner value (false) 3")) # Regression (assert (= {:x 10} (|(let [x $] ~{:x ,x}) 10)) "issue 463")