2020-09-01 11:17:43 +02:00
|
|
|
;;; list.el --- Functions for working with lists -*- lexical-binding: t -*-
|
|
|
|
|
2019-10-09 13:13:56 +02:00
|
|
|
;; Author: William Carroll <wpcarro@gmail.com>
|
2020-09-01 11:17:43 +02:00
|
|
|
;; Version: 0.0.1
|
|
|
|
;; Package-Requires: ((emacs "24"))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
|
|
|
;;; Commentary:
|
2020-09-01 11:17:43 +02:00
|
|
|
;; Since I prefer having the `list-' namespace, I wrote this module to wrap many
|
2022-08-05 21:35:22 +02:00
|
|
|
;; of the functions that are defined in the the global namespace in Elisp. I
|
2019-10-09 13:13:56 +02:00
|
|
|
;; sometimes forget the names of these functions, so it's nice for them to be
|
|
|
|
;; organized like this.
|
|
|
|
;;
|
|
|
|
;; Motivation:
|
2022-08-05 21:35:22 +02:00
|
|
|
;; Here are some examples of function names where I prefer more modern
|
|
|
|
;; alternatives:
|
2019-10-09 13:13:56 +02:00
|
|
|
;; - `car': Return the first element (i.e. "head") of a linked list
|
|
|
|
;; - `cdr': Return the tail of a linked list
|
|
|
|
|
2022-08-05 21:35:22 +02:00
|
|
|
;; As are most APIs for standard libraries that I write, this is influenced by
|
|
|
|
;; Elixir's standard library.
|
2019-10-09 13:13:56 +02:00
|
|
|
;;
|
|
|
|
;; Similar libraries:
|
2022-08-05 21:35:22 +02:00
|
|
|
;; - dash.el: Excellent and widely adopted library for working with lists.
|
2019-10-09 13:13:56 +02:00
|
|
|
;; - list-utils.el: Utility library that covers things that dash.el may not
|
|
|
|
;; cover.
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Dependencies
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2022-07-30 05:56:29 +02:00
|
|
|
(require 'maybe)
|
2022-07-29 05:29:07 +02:00
|
|
|
(require 'set)
|
2022-08-05 20:41:32 +02:00
|
|
|
(require 'set)
|
2019-10-09 13:13:56 +02:00
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Library
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-new ()
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return a new, empty list."
|
|
|
|
'())
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-concat (&rest lists)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Joins `LISTS' into on list."
|
2022-08-05 22:33:28 +02:00
|
|
|
(apply #'append lists))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2022-08-05 21:34:15 +02:00
|
|
|
(defun list-duplicate (n x)
|
|
|
|
"Duplicates the given element, X, N times in a list."
|
|
|
|
(list-map (lambda (_) x) (number-sequence 1 n)))
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-join (joint xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Join a list of strings, XS, with JOINT."
|
2020-09-01 11:17:43 +02:00
|
|
|
(if (list-empty? xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
""
|
2020-09-01 11:17:43 +02:00
|
|
|
(list-reduce (list-first xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
(lambda (x acc)
|
2022-07-30 05:56:29 +02:00
|
|
|
(format "%s%s%s" acc joint x))
|
2020-09-01 11:17:43 +02:00
|
|
|
(list-tail xs))))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-length (xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return the number of elements in `XS'."
|
|
|
|
(length xs))
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-get (i xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return the value in `XS' at `I', or nil."
|
|
|
|
(nth i xs))
|
|
|
|
|
2022-08-05 21:34:15 +02:00
|
|
|
(defun list-first (xs &optional default)
|
2020-09-01 11:17:43 +02:00
|
|
|
"Alias for `list-head' for `XS'."
|
2022-08-05 21:34:15 +02:00
|
|
|
(if (list-empty? xs)
|
|
|
|
default
|
|
|
|
(car xs)))
|
|
|
|
|
|
|
|
(defun list-last (xs &optional default)
|
|
|
|
"Returns the last element in XS or DEFAULT if empty."
|
|
|
|
(if (list-empty? xs)
|
|
|
|
default
|
|
|
|
(nth (- (length xs) 1) xs)))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-tail (xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return the tail of `XS'."
|
|
|
|
(cdr xs))
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-reverse (xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Reverses `XS'."
|
|
|
|
(reverse xs))
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-cons (x xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Add `X' to the head of `XS'."
|
|
|
|
(cons x xs))
|
|
|
|
|
2022-08-05 21:34:15 +02:00
|
|
|
(defun list-delete (x xs)
|
|
|
|
"Deletes the given element, X, from XS.
|
|
|
|
Returns a new list without X. If X occurs more than once, only the first
|
|
|
|
occurrence is removed."
|
|
|
|
(let ((deleted? nil))
|
|
|
|
(list-reject (lambda (y)
|
|
|
|
(if deleted? nil
|
|
|
|
(when (equal x y)
|
|
|
|
(setq deleted? t) t)))
|
|
|
|
xs)))
|
|
|
|
|
2022-08-05 20:41:32 +02:00
|
|
|
(defun list-filter (p xs)
|
|
|
|
"Return a subset of XS where predicate P returned t."
|
|
|
|
(list--assert-instance xs)
|
|
|
|
(seq-filter p xs))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2022-08-05 20:41:32 +02:00
|
|
|
(defun list-map (f xs)
|
|
|
|
"Call `F' on each element of `XS'."
|
|
|
|
(list--assert-instance xs)
|
|
|
|
(seq-map f xs))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-reduce (acc f xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return over `XS' calling `F' on an element in `XS'and `ACC'."
|
2022-08-05 20:41:32 +02:00
|
|
|
(list--assert-instance xs)
|
|
|
|
(seq-reduce (lambda (acc x) (funcall f x acc)) xs acc))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-map-indexed (f xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Call `F' on each element of `XS' along with its index."
|
2020-09-01 11:17:43 +02:00
|
|
|
(list-reverse
|
2022-08-05 20:41:32 +02:00
|
|
|
(cdr
|
|
|
|
(list-reduce '(0 . nil)
|
|
|
|
(lambda (x acc)
|
|
|
|
(let ((i (car acc))
|
|
|
|
(result (cdr acc)))
|
|
|
|
`(,(+ 1 i) . ,(cons (funcall f x i) result))))
|
|
|
|
xs))))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-reject (p xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return a subset of XS where predicate of P return nil."
|
2020-09-01 11:17:43 +02:00
|
|
|
(list-filter (lambda (x) (not (funcall p x))) xs))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-find (p xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return the first x in XS that passes P or nil."
|
2022-08-05 20:41:32 +02:00
|
|
|
(list--assert-instance xs)
|
|
|
|
(seq-find p xs))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2022-07-29 05:29:07 +02:00
|
|
|
(defun list-dedupe-adjacent (xs)
|
|
|
|
"Return XS without adjacent duplicates."
|
2022-07-30 05:56:29 +02:00
|
|
|
(list-reverse
|
|
|
|
(list-reduce (list (list-first xs))
|
|
|
|
(lambda (x acc)
|
|
|
|
(if (equal x (list-first acc))
|
|
|
|
acc
|
|
|
|
(list-cons x acc)))
|
|
|
|
xs)))
|
2022-07-29 05:29:07 +02:00
|
|
|
|
2022-08-01 22:43:34 +02:00
|
|
|
(defun list-chunk (n xs)
|
|
|
|
"Chunk XS into lists of size N."
|
|
|
|
(if (> n (length xs))
|
|
|
|
(list xs)
|
2022-08-05 20:41:32 +02:00
|
|
|
(let* ((xs (list-reduce '(:curr () :result ())
|
|
|
|
(lambda (x acc)
|
|
|
|
(let ((curr (plist-get acc :curr))
|
|
|
|
(result (plist-get acc :result)))
|
|
|
|
(if (= (- n 1) (length curr))
|
|
|
|
`(:curr () :result ,(list-cons (list-reverse (list-cons x curr)) result))
|
|
|
|
`(:curr ,(list-cons x curr) :result
|
|
|
|
,result)))) xs))
|
|
|
|
(curr (plist-get xs :curr))
|
|
|
|
(result (plist-get xs :result)))
|
|
|
|
(list-reverse (if curr (list-cons curr result) result)))))
|
2022-08-01 22:43:34 +02:00
|
|
|
|
2022-08-05 22:34:18 +02:00
|
|
|
(defun list-wrap (xs)
|
|
|
|
"Wraps XS in a list if it is not a list already."
|
|
|
|
(if (list-instance? xs)
|
|
|
|
xs
|
|
|
|
(list xs)))
|
|
|
|
|
2019-10-09 13:13:56 +02:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Predicates
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-instance? (xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return t if `XS' is a list.
|
|
|
|
Be leery of using this with things like alists. Many data structures in Elisp
|
|
|
|
are implemented using linked lists."
|
|
|
|
(listp xs))
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-empty? (xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return t if XS are empty."
|
2020-09-01 11:17:43 +02:00
|
|
|
(= 0 (list-length xs)))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-all? (p xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return t if all `XS' pass the predicate, `P'."
|
2022-08-05 20:41:32 +02:00
|
|
|
(if (list-empty? xs)
|
|
|
|
t
|
|
|
|
(and (maybe-some? (funcall p (car xs)))
|
|
|
|
(list-all? p (cdr xs)))))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-any? (p xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return t if any `XS' pass the predicate, `P'."
|
2022-08-05 20:41:32 +02:00
|
|
|
(if (list-empty? xs)
|
|
|
|
nil
|
|
|
|
(or (maybe-some? (funcall p (car xs)))
|
|
|
|
(list-any? p (cdr xs)))))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-contains? (x xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return t if X is in XS using `equal'."
|
2022-08-05 20:41:32 +02:00
|
|
|
(list--assert-instance xs)
|
2022-08-05 21:34:15 +02:00
|
|
|
(maybe-some? (seq-contains-p xs x)))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-xs-distinct-by? (f xs)
|
2020-02-14 16:34:56 +01:00
|
|
|
"Return t if all elements in XS are distinct after applying F to each."
|
|
|
|
(= (length xs)
|
2022-08-05 20:41:32 +02:00
|
|
|
(set-count (set-from-list (list-map f xs)))))
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Helpers
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(defun list--assert-instance (xs)
|
|
|
|
(unless (list-instance? xs)
|
|
|
|
(error (format "Assertion failed: argument is not a list: %s" xs))))
|
2020-02-14 16:34:56 +01:00
|
|
|
|
2019-10-09 13:13:56 +02:00
|
|
|
(provide 'list)
|
|
|
|
;;; list.el ends here
|