1
0
mirror of https://github.com/janet-lang/janet synced 2024-06-29 00:23:18 +00:00

Add utilities for contains? and contains-key?

This is significantly clearer than using (not (nil? (index-of col val)))

Most major programming languages offer some sort of contains function (Python, Java, C, Rust).
The only exception I know of is C.
This commit is contained in:
Techcable 2022-08-25 20:46:44 -07:00
parent 0817e627ee
commit 12a1849090
No known key found for this signature in database
GPG Key ID: D7B98ADFF827CD17

View File

@ -120,6 +120,9 @@
(defn indexed? "Check if x is an array or tuple." [x]
(def t (type x))
(if (= t :array) true (= t :tuple)))
(defn collection? "Check if x is an array, tuple, table, or struct" [x]
(def t (type x))
(if (= t :array) true (if (= t :tuple) true (if (= t :table) true (= t :struct)))))
(defn truthy? "Check if x is truthy." [x] (if x true false))
(defn true? "Check if x is true." [x] (= x true))
(defn false? "Check if x is false." [x] (= x false))
@ -1194,6 +1197,86 @@
(def kw (keyword prefix (slice alias 1 -2)))
~(def ,alias :dyn ,;more ,kw))
(defn- collection-type-error [val]
(errorf "Expected a collection (tuple|array|table|struct), but got %t" val))
(defn contains-value?
```Checks if a collection contains the specified value.
Semantically equivalent to `(contains? (values dict) val)`,
but implemented more efficiently.
Unlike contains-key?, this has worst-case O(n) performance.
Noe that tables or structs (dictionaries) never contain null keys```
[collection target-val]
# Avoid allocating intermediate array for dictionary
# This works for both dictionaries and sequences
(cond
(indexed? collection) (not (nil? (index-of target-val collection)))
(dictionary? collection)
(do
(var res false)
(var k (next collection nil))
(unless (or (nil? k) (nil? target-val))
(while true
(def val (in collection k))
(cond
# We found a result, this will break the loop
(= val target-val) (do
(set res true)
(break))
# Reached end of dictionary
(nil? k) (break))
(set k (next collection k))))
res)
(collection-type-error collection)))
(defn contains-key?
```Checks if a collection contains the specified key.
Functions the same as contains? for dictionaries (table/structs).
Arrays and tuples are indexed by integer keys, and this function simply
checks if the index is valid.
If this function succeeds, then a call to `(in collection key)` is guarenteed
to succeed as well.
For dictionaries, this should be (approximate) O(1) time due to the
guarentees of table/struct.
For arrays and tuples it should likewise be O(1) because it is simply a comparison.
Note that this intentionally excludes string (and buffer types), for the same reasons
as `contains?` does.
Noe that tables or structs (dictionaries) never contain null keys```
[collection key]
(assert (collection? collection) (collection-type-error collection))
(not (nil? (get collection key))))
(defn contains?
```Checks if a collection contains the specified value (or key).
For tables and structs, this only checks the keys,
and not the values.
For arrays and tuples this takes O(n) time,
while for tables and structs this takes (average) O(1) time.
This intentionally throws an error when strings are encountered. Technically,
strings are an iterable type, they will succeed with `next` and `index-of`.
Interpreting a string as an iterable type, one would expect this to check "contains byte".
However, the user would very probably expect "contains substring".
Therefore, we intentionally forbid strings (and other buffer types).
Note that dictionaries never contain null keys```
[collection val]
(cond
(indexed? collection) (not (nil? (index-of val collection)))
(dictionary? collection) (not (nil? (get collection val)))
(collection-type-error collection)))
(defdyn *defdyn-prefix* ``Optional namespace prefix to add to keywords declared with `defdyn`.
Use this to prevent keyword collisions between dynamic bindings.``)
(defdyn *out* "Where normal print functions print output to.")