Skip to content

Commit e7b40eb

Browse files
authored
More collection functions (#439)
* Collection functions * zipmap * Dedupe * More goodies * Clean up the interface hierarchy a bit * Reversible vector stuff * Fix MyPy warning * More interface cleaning
1 parent 1d23cbc commit e7b40eb

File tree

4 files changed

+416
-13
lines changed

4 files changed

+416
-13
lines changed

src/basilisp/core.lpy

Lines changed: 209 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,16 @@
337337
[coll i]
338338
(basilisp.lang.runtime/nthrest coll i))
339339

340+
(defn nfirst
341+
"Return the result of calling (next (first v))."
342+
[v]
343+
(next (first v)))
344+
345+
(defn nnext
346+
"Return the result of calling (next (next v))."
347+
[v]
348+
(next (next v)))
349+
340350
(defn last
341351
"Return the last item in a seq, or nil if the seq is empty."
342352
[s]
@@ -952,11 +962,23 @@
952962
[x]
953963
(instance? python/complex x))
954964

965+
(defn counted?
966+
"Return true if x can be counted in constant time."
967+
[x]
968+
(instance? basilisp.lang.interfaces/ICounted x))
969+
955970
(defn decimal?
956971
"Return true if x is a Decimal."
957972
[x]
958973
(instance? decimal/Decimal x))
959974

975+
(defn empty?
976+
"Return true if coll is empty (as by '(not (seq coll))).
977+
978+
Typically, you should prefer the idiom (seq coll) to (not (empty? coll))."
979+
[coll]
980+
(not (seq coll)))
981+
960982
(defn even?
961983
"Return true if x is even."
962984
[x]
@@ -1107,11 +1129,21 @@
11071129
[x]
11081130
(or (integer? x) (float? x)))
11091131

1132+
(defn reversible?
1133+
"Return true if x implements IReversible."
1134+
[x]
1135+
(instance? basilisp.lang.interfaces/IReversible x))
1136+
11101137
(defn seqable?
11111138
"Return true if an ISeq can be produced from x."
11121139
[x]
11131140
(instance? basilisp.lang.interfaces/ISeqable x))
11141141

1142+
(defn sequential?
1143+
"Return true if x implements ISequential."
1144+
[x]
1145+
(instance? basilisp.lang.interfaces/ISequential x))
1146+
11151147
(defn simple-keyword?
11161148
"Return true if x is a keyword with no namespace."
11171149
[x]
@@ -1275,6 +1307,92 @@
12751307
[x n]
12761308
(bit-and (bit-shift-right x n) 1))
12771309

1310+
;;;;;;;;;;;;;;;;;;;;;;;;;;
1311+
;; Collection Functions ;;
1312+
;;;;;;;;;;;;;;;;;;;;;;;;;;
1313+
1314+
(defn bounded-count
1315+
[n coll]
1316+
(if (counted? coll)
1317+
(count coll)
1318+
(let [counter (fn counter
1319+
[coll cur]
1320+
(cond
1321+
(>= cur n) n
1322+
(not (seq coll)) cur
1323+
:else (recur (rest coll) (inc cur))))]
1324+
(counter coll 0))))
1325+
1326+
(defn empty
1327+
"Return an empty collection of the same interface type as coll, or nil."
1328+
[coll]
1329+
(when (coll? coll)
1330+
(.empty coll)))
1331+
1332+
(defn not-empty
1333+
"Return coll when coll is not empty, otherwise return nil."
1334+
[coll]
1335+
(when (seq coll)
1336+
coll))
1337+
1338+
(defn peek
1339+
"For a list or a queue, return the first element.
1340+
1341+
For a vector, return the last element (more efficiently than by last).
1342+
1343+
For empty collections, returns nil."
1344+
[coll]
1345+
(.peek coll))
1346+
1347+
(defn pop
1348+
"For a list or a queue, return a new list without the first element.
1349+
1350+
For a vector, return a new vector without the last element.
1351+
1352+
If coll is empty, throw an exception."
1353+
[coll]
1354+
(.pop coll))
1355+
1356+
(defn reverse
1357+
"Return a seq containing the elements in coll in reverse order. The
1358+
returned sequence is not lazy."
1359+
[coll]
1360+
(let [do-reverse (fn do-reverse
1361+
[in out]
1362+
(if (seq in)
1363+
(recur (rest in) (cons (first in) out))
1364+
out))]
1365+
(do-reverse coll '())))
1366+
1367+
(defn rseq
1368+
"Return a sequence of the elements of coll in reverse order in constant
1369+
time. Only Vectors support this operation."
1370+
[coll]
1371+
(.rseq coll))
1372+
1373+
(defn sequence
1374+
"Coerces coll to a possibly empty sequence."
1375+
[coll]
1376+
(if (seq? coll)
1377+
coll
1378+
(or (seq coll) '())))
1379+
1380+
(defn subvec
1381+
"Return a vector of elements consisting of the elements of v from the index 'start'
1382+
(inclusive) to index 'end' exclusive, or the end of the vector if no 'end' is
1383+
supplied."
1384+
([v start]
1385+
(subvec v start nil))
1386+
([v start end]
1387+
(when (> start (count v))
1388+
(throw (python/IndexError "Start index out of range")))
1389+
(when-not (nil? end)
1390+
(when (> end (count v))
1391+
(throw (python/IndexError "End index out of range")))
1392+
(when (> start end)
1393+
(throw (python/IndexError "Start index must be less than or equal to end index"))))
1394+
(operator/getitem v (python/slice start end))))
1395+
12781396
;;;;;;;;;;;;;;;;;;;;;;;;;;;
12791397
;; Associative Functions ;;
12801398
;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1298,12 +1416,6 @@
12981416
[m & ks]
12991417
(apply (.-dissoc m) ks))
13001418

1301-
(defn empty
1302-
"Return an empty collection of the same interface type as coll, or nil."
1303-
[coll]
1304-
(when (coll? coll)
1305-
(.empty coll)))
1306-
13071419
(defn get
13081420
"Return the entry of m corresponding to k if it exists or nil/default otherwise."
13091421
([m k]
@@ -1399,9 +1511,9 @@
13991511
[m]
14001512
(seq (.values m)))
14011513

1402-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1403-
;; Higher Order Functions ;;
1404-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1514+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1515+
;; Higher Order and Collection Functions ;;
1516+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
14051517

14061518
(defmacro lazy-seq
14071519
"Takes a body of expressions which will produce a seq or nil. When
@@ -1534,6 +1646,14 @@
15341646
(f assoc-coll k v))))
15351647
init))
15361648

1649+
(defn into
1650+
"Return a new collection created by adding all of the elements of from
1651+
to the existing to collection, as by conj."
1652+
([] [])
1653+
([to] to)
1654+
([to from]
1655+
(reduce conj to from)))
1656+
15371657
(defn comp
15381658
"Return a function which is the composition of all the functions
15391659
given as arguments. Note that, as in mathematical function composition,
@@ -1751,6 +1871,11 @@
17511871
([n coll]
17521872
(map (comp first vector) coll (drop n coll))))
17531873

1874+
(defn butlast
1875+
"Return all but the last element in a sequence in linear time."
1876+
[coll]
1877+
(drop-last coll))
1878+
17541879
(defn split-at
17551880
"Split a collection at the nth item. Returns a vector of
17561881
[(take n coll) (drop n coll)]."
@@ -1763,6 +1888,19 @@
17631888
[pred coll]
17641889
[(take-while pred coll) (drop-while pred coll)])
17651890

1891+
(defn frequencies
1892+
"Return a map whose keys are the elements of coll and whose values are
1893+
the counts for the number of times the key appears in coll."
1894+
[coll]
1895+
(if-not (seq coll)
1896+
{}
1897+
(reduce (fn [m v]
1898+
(if (contains? m v)
1899+
(update m v inc)
1900+
(assoc m v 1)))
1901+
{}
1902+
coll)))
1903+
17661904
(defn group-by
17671905
"Return a map whose keys are the result of calling f on each element
17681906
in coll and whose values are vectors of the values which produced the
@@ -1896,6 +2034,47 @@
18962034
run (cons elem (take-while #(= felem (f %)) (next coll)))]
18972035
(cons run (partition-by f (seq (drop (count run) coll))))))))
18982036

2037+
(defn distinct
2038+
"Return a lazy sequence of the elements of coll, removing duplicates."
2039+
[coll]
2040+
(let [coll-distinct (fn coll-distinct
2041+
[coll found]
2042+
(lazy-seq
2043+
(when (seq coll)
2044+
(let [e (first coll)]
2045+
(if-not (contains? found e)
2046+
(cons e (coll-distinct (rest coll) (conj found e)))
2047+
(coll-distinct (rest coll) found))))))]
2048+
(coll-distinct coll #{})))
2049+
2050+
(defn dedupe
2051+
"Return a lazy sequence of the elements of coll, removing consecutive duplicates."
2052+
[coll]
2053+
(let [coll-dedupe (fn coll-dedupe
2054+
[coll prev]
2055+
(lazy-seq
2056+
(when (seq coll)
2057+
(let [e (first coll)]
2058+
(if-not (= e prev)
2059+
(cons e (coll-dedupe (rest coll) e))
2060+
(coll-dedupe (rest coll) prev))))))]
2061+
(lazy-seq
2062+
(when-let [e (first coll)]
2063+
(cons e (coll-dedupe (rest coll) e))))))
2064+
2065+
(defn flatten
2066+
"Flatten any combination of nested sequences (such as lists or vectors)
2067+
into a single lazy sequence. Calling flattening on non-sequential values
2068+
returns an empty sequence."
2069+
[v]
2070+
(lazy-seq
2071+
(when (and (or (seq? v) (seqable? v)) (seq v))
2072+
(let [e (first v)
2073+
r (rest v)]
2074+
(if (or (seq? e) (seqable? e))
2075+
(concat (flatten e) (flatten r))
2076+
(cons e (flatten r)))))))
2077+
18992078
(defn min-key
19002079
"Return the arg for which (k arg) is the smallest number.
19012080
If multiple values return the same number, return the last."
@@ -1925,6 +2104,12 @@
19252104
([keyfn cmp coll]
19262105
(basilisp.lang.runtime/sort-by keyfn coll cmp)))
19272106

2107+
(defn zipmap
2108+
"Return a map with the keys mapped to their corresponding indexed value
2109+
in vals."
2110+
[keys vals]
2111+
(apply hash-map (interleave keys vals)))
2112+
19282113
(defn merge
19292114
"Merge maps together from left to right as by conj. If a duplicate key
19302115
appears in a map, the rightmost map's value for that key will be taken."
@@ -2488,6 +2673,21 @@
24882673
(operator/mod fmt arg)))
24892674
(operator/mod fmt (python/tuple args))))
24902675

2676+
(defn subs
2677+
"Return a substring of s from the index 'start' (inclusive) to index 'end'
2678+
exclusive, or the end of the string if no 'end' is supplied."
2679+
([s start]
2680+
(subs s start nil))
2681+
([s start end]
2682+
(when (> start (count s))
2683+
(throw (python/IndexError "Start index out of range")))
2684+
(when-not (nil? end)
2685+
(when (> end (count s))
2686+
(throw (python/IndexError "End index out of range")))
2687+
(when (> start end)
2688+
(throw (python/IndexError "Start index must be less than or equal to end index"))))
2689+
(operator/getitem s (python/slice start end))))
2690+
24912691
;;;;;;;;;;;;;;;;;;;;;;
24922692
;; Output Utilities ;;
24932693
;;;;;;;;;;;;;;;;;;;;;;

src/basilisp/lang/interfaces.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ def deref(
2525
raise NotImplementedError()
2626

2727

28+
class ICounted(ABC):
29+
__slots__ = ()
30+
31+
2832
# Making this interface Generic causes the __repr__ to differ between
2933
# Python 3.6 and 3.7, which affects a few simple test assertions.
3034
# Since there is little benefit to this type being Generic, I'm leaving
@@ -72,6 +76,14 @@ def with_meta(self, meta: "IPersistentMap") -> "IMeta":
7276
ILispObject = _LispObject
7377

7478

79+
class IReversible(Generic[T]):
80+
__slots__ = ()
81+
82+
@abstractmethod
83+
def rseq(self) -> "ISeq[T]":
84+
raise NotImplementedError()
85+
86+
7587
class ISeqable(Iterable[T]):
7688
__slots__ = ()
7789

@@ -80,6 +92,10 @@ def seq(self) -> "ISeq[T]":
8092
raise NotImplementedError()
8193

8294

95+
class ISequential(ABC):
96+
__slots__ = ()
97+
98+
8399
class ILookup(Generic[K, V], ABC):
84100
__slots__ = ()
85101

@@ -131,27 +147,34 @@ def pop(self) -> "IPersistentStack[T]":
131147
raise NotImplementedError()
132148

133149

134-
class IPersistentList(IPersistentStack[T]):
150+
class IPersistentList(ISequential, IPersistentStack[T]):
135151
__slots__ = ()
136152

137153

138-
class IPersistentMap(IAssociative[K, V]):
154+
class IPersistentMap(ICounted, IAssociative[K, V]):
139155
__slots__ = ()
140156

141157
@abstractmethod
142158
def dissoc(self, *ks: K) -> "IPersistentMap[K, V]":
143159
raise NotImplementedError()
144160

145161

146-
class IPersistentSet(AbstractSet[T], IPersistentCollection[T]):
162+
class IPersistentSet(AbstractSet[T], ICounted, IPersistentCollection[T]):
147163
__slots__ = ()
148164

149165
@abstractmethod
150166
def disj(self, *elems: T) -> "IPersistentSet[T]":
151167
raise NotImplementedError()
152168

153169

154-
class IPersistentVector(Sequence[T], IAssociative[int, T], IPersistentStack[T]):
170+
class IPersistentVector(
171+
Sequence[T],
172+
IAssociative[int, T],
173+
ICounted,
174+
IReversible[T],
175+
ISequential,
176+
IPersistentStack[T],
177+
):
155178
__slots__ = ()
156179

157180
@abstractmethod

0 commit comments

Comments
 (0)