;;; A collection of functions that operate on lists.
(export (
drop drop-while range repeat take take-while zip zip-with
all any count each filter find foldl foldr index map))
;; Drop the first `n` elements from `li`, returning the remaining elements.
;; If the list is shorter than `n` elements, `()` is returned.
(define (drop n li)
(let ((length (len li)))
(if (< n length)
(slice li n length))))
;; Drop elements while a predicate is satisfied, returning the remaining elements.
(define (drop-while fn li)
(cond
((null li) ())
((fn (first li)) (drop-while fn (tail li)))
(else li)))
;; Returns a list representing the range [`start`, `end`).
;;
;; If `start` is omitted, the range begins at `0`.
;;
;; `step` if given, is the interval between values. It must be non-zero.
(define (range a :optional b (step 1))
(cond
((null b) (range 0 a))
((> step 0) (range-pos a b step ()))
((< step 0) (range-neg a b step ()))
(else (panic "`range` got 0 step"))))
(define (range-pos start end step out)
(if (>= start end)
out
(range-pos (+ start step) end step (append out start))))
(define (range-neg start end step out)
(if (<= start end)
out
(range-neg (+ start step) end step (append out start))))
;; Returns a list containing `n` instances of `elem`.
(define (repeat n elem) (repeat-into (int n) elem ()))
(define (repeat-into n elem out)
(if (<= n 0)
out
(repeat-into (- n 1) elem (append out elem))))
;; Take the first `n` elements from `li`.
;; If the list is shorter than `n` elements, the whole list is returned.
(define (take n li)
(slice li 0 (min n (len li))))
;; Take elements of a list while a predicate is satisfied.
(define (take-while fn li) (take-while-inner fn li li 0))
(define (take-while-inner fn li orig idx)
(cond
((null li) orig)
((fn (first li)) (take-while-inner fn (tail li) orig (+ idx 1)))
(else (slice orig 0 idx))))
;; Returns a list of lists containing corresponding elements from each input list.
;; The result will be as long as the shortest list.
(define (zip li :rest rest)
(zip-into () (concat (list li) rest)))
;; Calls a function with each successive set of elements from the given lists.
;; The resulting list will be as long as the shortest list.
(define (zip-with fn li :rest rest)
(map (lambda (li) (apply fn li)) (zip-into () (concat (list li) rest))))
(define (zip-into li inputs)
(if (any null inputs)
li
(zip-into
(append li (map first inputs))
(map tail inputs))))
;; Returns whether all elements satisfy a predicate.
(define (all fn li)
(or (null li)
(and (fn (first li)) (all fn (tail li)))))
;; Returns whether any element satisfies a predicate.
(define (any fn li)
(and (not (null li))
(or (fn (first li)) (any fn (tail li)))))
;; Returns the number of elements satisfying a predicate.
(define (count fn li) (count-inner fn li 0))
(define (count-inner fn li n)
(if (null li)
n
(count-inner fn (tail li)
(if (fn (first li)) (+ n 1) n))))
;; Calls a function for each element, discarding the result.
(define (each fn li)
(if (not (null li))
(do
(fn (first li))
(each fn (tail li)))))
;; Returns a list containing all elements satisfying a predicate.
(define (filter fn li) (filter-into fn li ()))
(define (filter-into fn li out)
(if (null li)
out
(filter-into fn (tail li)
(if (fn (first li))
(append out (first li))
out))))
;; Returns the first element satisfying a predicate.
(define (find fn li)
(cond
((null li) ())
((fn (first li)) (first li))
(else (find fn (tail li)))))
;; Returns the given list, left-folded.
(define (foldl fn ini li)
(if (null li)
ini
(foldl fn (fn ini (first li)) (tail li))))
;; Returns the given list, right-folded.
(define (foldr fn ini li)
(if (null li)
ini
(foldr fn (fn (last li) ini) (init li))))
;; Returns the index of the first element satisfying a predicate, or `()`.
(define (index fn li) (index-inner fn li 0))
(define (index-inner fn li n)
(cond
((null li) ())
((fn (first li)) n)
(else (index-inner fn (tail li) (+ n 1)))))
;; Returns each element mapped with `fn`.
(define (map fn li) (map-into fn li ()))
(define (map-into fn li out)
(if (null li)
out
(map-into fn (tail li) (append out (fn (first li))))))