List utilities

To use the bindings from this module:

(import :std/misc/list)

length=?, length<? ... length>=?

(length=?  lst1 lst2) -> boolean
(length<?  lst1 lst2) -> boolean
(length<=? lst1 lst2) -> boolean
(length>?  lst1 lst2) -> boolean
(length>=? lst1 lst2) -> boolean

  lst1, lst2 := lists to compare

Compares the list lengths of both lst1 and lst2, and returns a truth value (#t or #f).

function less efficient variant
(length=? x y) (= (length x) (length y))
(length<? x y) (< (length x) (length y))
(length<=? x y) (<= (length x) (length y))
(length>? x y) (> (length x) (length y))
(length>=? x y) (>= (length x) (length y))

These functions are potentially more efficient because they only need to compare the lists up until the point where they start to differ from one another. They will short-circuit once they encounter a difference instead of calculating both lengths up-front.

Also, either of these two lists is allowed to be circular, but not both.

Examples:

> (import :std/srfi/1)
> (def small (iota 10))   ; => (0 1 ... 9)
> (def large (iota 100))  ; => (0 1 ... 99)

> (length=? small large)
#f    ; comparison stops as soon as small runs out of elements

> (length<? large (circular-list 1 2 3))
#t    ; circular list never runs out of elements

> (length>=? (circular-list 0 1) [])
#t

length=n?, length<n? ... length>=n?

(length=n?  lst n) -> boolean | error
(length<n?  lst n) -> boolean | error
(length<=n? lst n) -> boolean | error
(length>n?  lst n) -> boolean | error
(length>=n? lst n) -> boolean | error

  lst := list to compare
  n   := number

Checks how the length of lst compares to n and returns a truth value result (#t or #f). Signals an error when n isn't a valid number.

function less efficient variant
(length=n? x n) (= (length x) n)
(length<n? x n) (< (length x) n)
(length<=n? x n) (<= (length x) n)
(length>n? x n) (> (length x) n)
(length>=n? x n) (>= (length x) n)

These functions are potentially more efficient because they only need to check the list for up to n elements instead of calculating lst's length up-front.

Also, lst is allowed to be circular.

Examples:

> (length=n? [#\a #\b #\c] 3)
#t

> (import :std/srfi/1)
> (length>=n? (circular-list 0 1) 5)
#t

> (length<n? (circular-list 1 2 3) 10000)
#f    ; circular list never runs out of elements

snoc, append1

(snoc elem lst) -> list | error

  elem := element to append to lst
  lst  := proper list

snoc is similar to cons, but appends elem at the end of lst instead of putting it at the front.

Different from cons: snoc will signal an error when lst is not a proper list. cons, in contrast, constructs a pair out of these two input values.

Examples:

> (cons 4 [1 2 3])
(4 1 2 3)

> (snoc 4 [1 2 3])
(1 2 3 4)

> (cons 1 2)
(1 . 2)

> (snoc 1 2)
error    ; expects a list as second argument

> (snoc '(a b c) '())
((a b c))

append1

(append1 lst elem) -> list | error

  lst  := proper list
  elem := element to append to lst

append1 is similar to append, but is constructing a proper list whereas the latter returns an improper list when appending a non-list elem to lst. append also joins two or more input lists while append1 simply adds the second list as-is.

Signals an error when lst is not a list.

Note that snoc and append1 have the same function body, just with their two arguments swapped.

Examples:

> (append [1 2 3] 4)
(1 2 3 . 4)

> (append1 [1 2 3] 4)
(1 2 3 4)

> (append [1 2 3] [4 5 6])
(1 2 3 4 5 6)

> (append1 [1 2 3] [4 5 6])
(1 2 3 (4 5 6))

> (append1 "raise" "error")
error    ; expects a list as first argument

for-each!

(for-each! lst proc) -> void

  lst  := proper or even improper list
  proc := procedure called for side-effects

for-each! is similar to for-each, but the arguments lst and proc are swapped which allows better nesting. Another slight difference: for-each! even accepts improper lists.

Examples:

> (def exprs [[2 + 0] [2 - 0] [0 * 2] [2 / 0] [0 / 2]])

> (for-each (match <>
              ([x (eq? /) (? zero? y)]
               (displayln "div by zero!"))
              ([x op y]
               (displayln (op x y))))
            exprs)

> (for-each! exprs
    (match <>
      ([x (eq? /) (? zero? y)]
       (displayln "div by zero!"))
      ([x op y]
       (displayln (op x y)))))

;; both print:
2
2
0
div by zero!
0

> (for-each displayln '(1 2 . 3))
error    ; list expected

> (for-each! '(1 2 . 3) displayln)
1
2        ; dotted list ending not included

push!

(push! elem lst) -> unspecified | error

  elem := element to cons onto lst
  lst  := list

Macro that conses elem onto lst and set!s lst accordingly. lst needs to be bound beforehand or it signals an error. It's unspecified what push! returns otherwise. Note that the argument order is element first and list second, as is traditional with the original Lisp PUSH macro.

Examples:

> (def lst [])
> (push! 10 lst)
> (push! 20 lst)
> (push! 30 lst)
> lst
(30 20 10)

> (def pair [#\b . #\a])
> (push! #\c pair)
> pair
(#\c #\b . #\a)

> (push! 1 [2 3])
error    ; uses set! internally, requires valid binding

pop!

(pop! lst) -> #f | elem

  elem := first element from lst
  lst  := list, from which the first element will be removed

Macro that checks whether the lst is a cons cell, if so returns the car of that cons cell, and sets lst to the cdr of that cons cell, otherwise returns #f.

Examples:

> (def lst [])
> (pop! lst)
#f
> (push! 10 lst)
> (push! 20 lst)
> (pop! lst)
20
> (pop! lst)
10
> (pop! lst)
#f

flatten

(flatten tree) -> list

  tree := nested list of lists

Flatten a tree into a list. The argument tree can be a list of lists, where pairs are branching nodes and the empty list is an empty tree, and the elements that are neither pairs nor empty lists are values to be accumulated left to right into a list, that is returned.

Examples:

> (flatten [1 [2 3] [[4 5]]])
(1 2 3 4 5)

> (flatten [1 [] [2 [3 [] 4] 5]])
(1 2 3 4 5)  ; ignores empty sublists

> (flatten '((a 1 . b) (2 c . 3) d . 4)))
(a 1 b 2 c 3 d 4) ; improper list terminators are tree elements like others

flatten1

(flatten1 lst) -> list | error

  lst := proper nested list-of-lists

flatten1 is a special variant of flatten which will not flatten the whole nested list-of-lists (or tree structure), but instead removes only a single layer of nesting from lst.

Note: lst is expected to be a list of proper lists, association lists will signal an error.

Examples:

> (flatten1 [1 [2 3] [[4 5]]])
(1 2 3 (4 5))

> (flatten1 [1 [] [2 [3 [] 4] 5]])
(1 2 (3 () 4) 5)

> (import :std/srfi/1)
> (map (cut iota <>) [1 2 3 4]
((0) (0 1) (0 1 2) (0 1 2 3))
> (flatten (map (cut iota <>) [1 2 3 4]))
(0 0 1 0 1 2 0 1 2 3)

> (flatten1 '((a . 1) (b . 2) (c . 3)))
error    ; expects proper non-dotted list-of-lists

unique, unique!

(unique lst [test = equal?]) -> list

  lst  := proper list
  test := test procedure which takes two arguments, defaults to equal?

Alias for delete-duplicates and delete-duplicates! (SRFI 1).

Examples:

> (unique [1 2 3 2])
(1 2 3)

> (let (lst [1 2])
    (unique [lst lst] ev?))
((1 2))

delete-duplicates/hash

(delete-duplicates/hash lst
  [table: (make-hash-table)]
  [key: identity]
  [from-end?: #f]) -> list

  lst       := list from which to delete elements
  table     := a hash-table with suitable test function and initial elements to detect duplicates
  key       := optional unary procedure to get the to compare item out of a list element
  from-end? := optional boolean to delete from the end rather than from the start

delete-duplicates/hash returns a copy of the list from which duplicates are removed, using an algorithm in O(n) backed by the optional hash-table table.

If no table is specified, the default is to create a new one that uses the equal? predicate. By overriding this default, the user may select a different equality predicate, pre-fill a number of already seen entries to remove, and/or keep a set of entries seen for later use or reuse.

The key function, which defaults to identity, specifies which part of each element must be seen only once. The from-end?: flag if true specifies that latter elements will be deleted, instead or earlier elements (if the flag is false, the default). Thus, for instance, keyword arguments key: car from-end?: #t can be used to remove redundant entries in an alist.

delete-duplicates/hash is more efficient than unique, unique!, delete-duplicates, delete-duplicates! that have quadratic cost, in exchange for which it only works for the builtin equality predicates equal?, eqv?, eq?.

Examples:

> (delete-duplicates/hash '(a b c d a e f c))
(b d a e f c)
> (delete-duplicates/hash '(a b c d a e f c) from-end?: #t)
(a b c d e f)
> (delete-duplicates/hash '(a b c d a e f c) table: (hash (a #f)))
(b d e f c)
> (delete-duplicates/hash '((a 1) (b 2) (c 3) (a 4) (d 5) (c 6))
                          from-end?: #t key: car)
((a 1) (b 2) (c 3) (d 5))

duplicates

(duplicates lst [test = equal?] [key: #f]) -> list

  lst  := proper list
  test := test procedure which takes two arguments, defaults to equal?
  key  := optional unary procedure to get the to compare item out of a list element

duplicates returns a cons cells (item . count) for every element that occurs more than once in lst. If key: is not #f the unary procedure is applied to every element before comparison.

Examples:

> (duplicates ['a 1 'a])
((a . 2))

> (duplicates [1 2 3])
()

> (duplicates '((a . 10) (b . 10)) key: cdr)
((10 . 2))

rassoc

(rassoc elem alist [pred = eqv?]) -> pair | #f

  elem  := element to search for in alist
  alist := association list
  pred  := comparison predicate, optional

rassoc is similar to assoc, but instead of comparing elem with the first element of each pair in alist the optional predicate pred (which defaults to eqv?) will compare with the pair's second element.

Returns the first pair in alist whose cdr satisfies the predicate pred, or #f otherwise.

Examples:

> (rassoc 2 '((a . 1) (b . 2) (c . 2) (d . 1)))
(b . 2)

> (rassoc "a" '((1 . "a") (2 . "b")))
#f       ; eqv? is used by default

> (rassoc "a" '((1 . "a") (2 . "b")) string=?)
(1 . "a")

> (rassoc '(5 6) '((a 1 2) (b 3 4) (c 5 6)) equal?)
(c 5 6)  ; equivalent to '(c . (5 6))

when/list

(when/list cond lst) -> list  | []

  cond := expression that evaluates to a generalized boolean
  lst := expression evaluated and returned if cond was true

when/list is like when but returns [] instead of (void) when the condition is false. It is meant to be used when a list is expected, for instance, a list of options or keyword arguments, that are only defined when some feature is enabled or other condition is true.

Examples:

> (when/list #t [mykw: 42])
(mykw: 42)
> (when/list #f [mykw: 42])
()
> (when/list #f (error "foo"))
()

when-list-or-empty

(when-list-or-empty lst body ...) -> body ... | []

  lst := value or list on which expansion depends

Macro which expands to body expressions only if lst is a non-empty list, otherwise an empty list is returned.

Examples:

> (let (nums [1 2 3])
    (when-list-or-empty nums
      (cdr nums)))
(2 3)

> (when-list-or-empty []
    (cons "never" "expanded"))
()

listify

(listify x) -> list

  x := expression that is returned if a list, or else [] is returned

listify always returns a list: when the argument x is a list, it is returned, otherwise return the empty list [].

Examples:

> (listify 42)
()
> (listify [])
()
> (listify ["proper" "list"])
("proper" "list")
> (listify ["improper" . "list"])
("improper" . "list")

keyword-when

(keyword-when cond [value]) -> list

  keyword := arbitrary value, usually a keyword
  cond := expression that if true, triggers the keyword-value list to be returned
  value := expression evaluated if cond is true, returned as the value in the list

keyword-when always returns a list: when the cond is true, the list [keyword value] is returned, otherwise the empty list []. If value is not specified, the non-false value returned by cond is used (but cond is only evaluated once, in case it has side-effects). keyword doesn't actually have to be a keyword.

Examples:

> (keyword-when mykw: #t 42)
(mykw: 42)
> (keyword-when mykw: #f 42)
()
> (keyword-when mykw: "true")
(mykw: "true")
> (keyword-when mykw: #f)
()
> (keyword-when mykw: #f (error "foo"))
()
> (keyword-when "not a keyword" #t 42)
("not a keyword" 42)

slice

(slice lst start [limit = #f]) -> list

  lst   := proper list
  start := start index
  limit := number of items to take from lst

Returns a list from lst, starting from the left at start, containing limit elements.

Examples:

> (slice [1 2 3 4] 2)
(3 4)

> (slice [1 2 3 4] 2 1)
(3)

slice-right

(slice-right lst start [limit = #f]) -> list

  lst   := proper list
  start := start index from the right of lst
  limit := number of items to take from lst

Returns a list from lst, starting from the right at start, containing limit elements.

Examples:

> (slice-right [1 2 3 4] 2)
(1 2)

> (slice-right [1 2 3 4] 2 1)
(2)

slice!

(slice! lst start [limit = #f]) -> list

  lst   := proper list
  start := start index
  limit := number of items to take from lst

Returns a sublist by potentially updating the input list lst. Starting from the left at start, containing limit elements.

Examples:

> (def lst [1 2 3 4 5])
> (slice! lst 2 2)
(3 4)

slice-right!

(slice-right! lst start [limit = #f]) -> list

  lst   := proper list
  start := start index from the right of lst
  limit := number of items to take from lst

Returns a sublist by potentially updating the input list lst. Starting from the right at start, containing limit elements.

Examples:

> (def lst [1 2 3 4 5])
> (slice-right! lst 2 2)
(2 3)

butlast

(butlast lst) -> list

  lst   := proper list

butlast returns a copy of the proper list lst, except the last element. When lst is empty, lst is returned as it is.

Examples:

> (butlast [1 2 3])
(1 2)

> (butlast [])
()

split

(split lst proc [limit = #f]) -> list

  lst   := proper list
  stop  := value or unary procedure
  limit := optional, split the list only limit times

split the list lst into a list-of-lists using the value or unary procedure stop. If limit is set, split the list only limit times.

Examples:

(split '(1 2 "hi" 3 4) string?)
> ((1 2) (3 4))

(split [1 2 0 3 4 0 5 6] 0 1)
> ((1 2) (3 4 0 5 6))

(split [] number?)
> ()

take-until

take-until!

(take-until pred lst) -> list

  pred := predicate
  lst  := proper or circular list

take-until returns a list with all elements before pred returns #t. take-until! is the linear-update variant of take-until.

Examples:

> (take-until number? ['a "hi" 1 'c])
(a "hi")

drop-until

(drop-until pred lst) -> list

  pred := predicate
  lst  := proper or circular list

drop-until returns a list with all elements from the point on pred returns #t.

Examples:

> (drop-until number? ['a [] "hi" 1 'c])
(1 c)

group-consecutive

(group-consecutive lst [test = equal?]) -> list

  lst  := proper list
  test := optional, binary predicate

group consecutive elements of the list lst into a list-of-lists wherein elements that satisfy the predicate with the previous element are put in the same list, but those that don't are put in the next list.

Examples:

> (group-consecutive [1 2 2 3 1 1])
((1) (2 2) (3) (1 1))

> (import :std/sort)
> (group-consecutive (sort [1 2 2 3 1 1] <) eqv?)
((1 1 1) (2 2) (3))

> (group-consecutive [1 2 3 2 2 3 4 0 3 5] <)
((1 2 3) (2) (2 3 4) (0 3 5))

group-n-consecutive

(group-n-consecutive n lst) -> list-of-list

  n    := a positive integer
  lst  := list

group elements of the list lst into clusters of n elements, resulting in a a list-of-lists that when concatenated return a list equal to lst. each element of the result list is a non-empty list, and the last element and that element only may have fewer than n elements, if n does not divide the length of the original list. If lst is an improper list, the last element of the result will be an improper list with the same terminator.

Examples:

> (group-n-consecutive 2 [1 2 2 3 1 1])
((1 2) (2 3) (1 1))

> (group-n-consecutive 1 [1 2 3 4 5 6])
((1) (2) (3) (4) (5) (6))

> (group-n-consecutive 2 [1 2 3 4 5 6])
((1 2) (3 4) (5 6))

> (group-n-consecutive 2 [1 2 3 4 5 6 7])
((1 2) (3 4) (5 6) (7))

> (group-n-consecutive 3 [1 2 3 4 5 6 . 7])
((1 2 3) (4 5 6 . 7))

group-same

(group-same lst key: [key] table: [table]) -> list-of-list

  lst   := list
  key   := 1-argument function (default: identity)
  table := a hash-table, hash-table-eq or hash-table-eqv (defaults: empty hash-table)

group elements of the list lst that are have the same key value (default: that are the same), using the provided table to store the similar ones, with "same" meaning equal according to the table's equality predicate (which is equal? by default, but the user may provide a different hash-table). If the user provides a hash-table, it will be populated with the list of elements for each key, reversed. The elements in the resulting list appear in the order that their first element appear in the original list, and are each lists containing elements with the same key in the same order as their appear in the original list.

Examples:

> (group-same [1 2 2 3 1])
((1 1) (2 2) (3))

> (group-same '("abc" "b" "c" "ef" "gh" "ijk") key: string-length)
(("abc" "ijk") ("b" "c") ("ef" "gh"))

map/car

(map/car f pair)

  f    := function acting on the car of the pair
  pair := pair

returns a new pair where the first element (car) has been transformed by the function f.

Examples:

> (map/car (cut * 10 <>) (cons 3 4))
(30 . 4)

every-consecutive?

(every-consecutive? pred lst)

returns a boolean that is true if any two consecutive terms in the list satisfy the predicate. In particular, if the predicate is a partial order predicate (respectively a strict partial order predicate), then the list is totally ordered (respectively strictly totally ordered) according to the predicate.

Examples:

> (every-consecutive? <= [1 2 2 3 10 100])
#t

> (every-consecutive? < [5 1 8 9])
#f

separate-keyword-arguments

(separate-keyword-arguments args [positionals-only]) -> (values positional-args keyword-args)

Given a list of arguments passed to a function, return two values, the first one the list of positional arguments, in order, in the first list, the second one the plist of keyword arguments with each provided keyword followed by the corresponding argument value.

If positionals-only is false (the default), then the #!key and #!rest markers are kept into the list in the first value, so the list can be used in a call to apply to another function to keep passing those values, without the keyword arguments (or with the keyword arguments prepended in front).

If positionals-only is true, then the #!key and #!rest markers are filtered off so the first value is ready to be matched positionally against a list of variables of the same length (and/or with a rest argument).

Examples:

> (separate-keyword-arguments '(x a: 1 y b: 2 c: 3 z))
(x y z)
(a: 1 b: 2 c: 3)

> (separate-keyword-arguments '(x a: 1 y #!key b: 2 c: 3 z #!rest t d: 4) #t)
(x y b: 2 z t d: 4)
(a: 1 c: 3)

> (separate-keyword-arguments '(x a: 1 y #!key b: 2 c: 3 z #!rest t d: 4) #f)
(x y #!key b: 2 z #!rest t d: 4)
(a: 1 c: 3)

first-and-only

(first-and-only lst)

returns the first and only value of a singleton list, or raises an error if the argument wasn't a singleton list.

Examples:

> (first-and-only '(42))
42

> (first-and-only '())
*** ERROR IN std/misc/list#first-and-only, -515899390@648.11 -- Assertion failed (and (pair? x) (null? (cdr x)))

with-list-builder, call-with-list-builder

This macro and this function are re-exported from :std/misc/list-builder