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
|
2019-10-09 13:13:56 +02:00
|
|
|
;; of the functions that are defined in the the global namespace in ELisp. I
|
|
|
|
;; sometimes forget the names of these functions, so it's nice for them to be
|
|
|
|
;; organized like this.
|
|
|
|
;;
|
|
|
|
;; Motivation:
|
|
|
|
;; Here are some examples of function names that I cannot tolerate:
|
|
|
|
;; - `car': Return the first element (i.e. "head") of a linked list
|
|
|
|
;; - `cdr': Return the tail of a linked list
|
|
|
|
|
|
|
|
;; As are most APIs for standard libraries that I write, this is heavily
|
|
|
|
;; influenced by Elixir's standard library.
|
|
|
|
;;
|
|
|
|
;; Elixir's List library:
|
|
|
|
;; - ++/2
|
|
|
|
;; - --/2
|
|
|
|
;; - hd/1
|
|
|
|
;; - tl/1
|
|
|
|
;; - in/2
|
|
|
|
;; - length/1
|
|
|
|
;;
|
|
|
|
;; Similar libraries:
|
|
|
|
;; - dash.el: Functional library that mimmicks Clojure. It is consumed herein.
|
|
|
|
;; - list-utils.el: Utility library that covers things that dash.el may not
|
|
|
|
;; cover.
|
|
|
|
;; stream.el: Elisp implementation of streams, "implemented as delayed
|
|
|
|
;; evaluation of cons cells."
|
|
|
|
|
|
|
|
;; TODO: Consider naming this file linked-list.el.
|
|
|
|
|
|
|
|
;; TODO: Support module-like macro that auto-namespaces functions.
|
|
|
|
|
|
|
|
;; TODO: Consider wrapping most data structures like linked-lists,
|
|
|
|
;; associative-lists, etc in a `cl-defstruct', so that the dispatching by type
|
|
|
|
;; can be nominal instead of duck-typing. I'm not sure if this is a good idea
|
|
|
|
;; or not. If I do this, I should provide isomorphisms to map between idiomatic
|
|
|
|
;; ways of working with Elisp data structures and my wrapped variants.
|
|
|
|
|
|
|
|
;; TODO: Are function aliases/synonyms even a good idea? Or do they just
|
|
|
|
;; bloat the API unnecessarily?
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Dependencies
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2020-08-31 18:05:31 +02:00
|
|
|
;; TODO: Move `prelude-assert' elsewhere so that I can require it without
|
2019-12-11 11:31:57 +01:00
|
|
|
;; introducing the circular dependency of list.el -> prelude.el -> list.el.
|
|
|
|
;;(require 'prelude)
|
2019-10-09 13:13:56 +02:00
|
|
|
(require 'dash)
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Constants
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defconst list-tests? t
|
2019-10-09 13:13:56 +02:00
|
|
|
"When t, run the test suite.")
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; 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."
|
|
|
|
(apply #'-concat lists))
|
|
|
|
|
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)
|
2020-09-01 00:28:47 +02:00
|
|
|
(string-concat 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))
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-head (xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return the head of `XS'."
|
|
|
|
(car xs))
|
|
|
|
|
|
|
|
;; TODO: Learn how to write proper function aliases.
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-first (xs)
|
|
|
|
"Alias for `list-head' for `XS'."
|
|
|
|
(list-head 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))
|
|
|
|
|
|
|
|
;; map, filter, reduce
|
|
|
|
|
|
|
|
;; TODO: Create function adapters like swap.
|
|
|
|
;; (defun adapter/swap (f)
|
|
|
|
;; "Return a new function that wraps `F' and swaps the arguments."
|
|
|
|
;; (lambda (a b)
|
|
|
|
;; (funcall f b a)))
|
|
|
|
|
|
|
|
;; TODO: Make this function work.
|
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'."
|
|
|
|
(-reduce-from (lambda (acc x) (funcall f x acc)) acc xs))
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-map (f xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Call `F' on each element of `XS'."
|
|
|
|
(-map f xs))
|
|
|
|
|
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."
|
|
|
|
(-map-indexed (lambda (i x) (funcall f x i)) xs))
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-filter (p xs)
|
2019-10-09 13:13:56 +02:00
|
|
|
"Return a subset of XS where predicate P returned t."
|
2020-09-01 11:17:43 +02:00
|
|
|
(list-reverse
|
|
|
|
(list-reduce
|
2019-10-09 13:13:56 +02:00
|
|
|
'()
|
|
|
|
(lambda (x acc)
|
|
|
|
(if (funcall p x)
|
2020-09-01 11:17:43 +02:00
|
|
|
(list-cons x acc)
|
2019-10-09 13:13:56 +02:00
|
|
|
acc))
|
|
|
|
xs)))
|
|
|
|
|
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."
|
|
|
|
(-find p xs))
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; 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'."
|
|
|
|
(-all? p xs))
|
|
|
|
|
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'."
|
|
|
|
(-any? p xs))
|
|
|
|
|
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'."
|
|
|
|
(-contains? xs x))
|
|
|
|
|
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)
|
2020-09-01 11:17:43 +02:00
|
|
|
(->> xs (-map f) set-from-list set-count)))
|
2020-02-14 16:34:56 +01:00
|
|
|
|
2020-01-16 02:16:58 +01:00
|
|
|
;; TODO: Support dedupe.
|
|
|
|
;; TODO: Should we call this unique? Or distinct?
|
|
|
|
|
|
|
|
;; TODO: Add tests.
|
2020-09-01 11:17:43 +02:00
|
|
|
(defun list-dedupe-adjacent (xs)
|
2020-01-16 02:16:58 +01:00
|
|
|
"Return XS without adjacent duplicates."
|
2020-09-01 11:17:43 +02:00
|
|
|
(prelude-assert (not (list-empty? xs)))
|
|
|
|
(list-reduce (list (list-first xs))
|
2020-01-16 02:16:58 +01:00
|
|
|
(lambda (x acc)
|
2020-09-01 11:17:43 +02:00
|
|
|
(if (equal x (list-first acc))
|
2020-01-16 02:16:58 +01:00
|
|
|
acc
|
2020-09-01 11:17:43 +02:00
|
|
|
(list-cons x acc)))
|
2020-01-16 02:16:58 +01:00
|
|
|
xs))
|
|
|
|
|
2019-10-09 13:13:56 +02:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Tests
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2020-09-01 11:17:43 +02:00
|
|
|
;; (when list-tests?
|
2020-08-31 18:05:31 +02:00
|
|
|
;; (prelude-assert
|
2019-12-11 11:31:57 +01:00
|
|
|
;; (= 0
|
2020-09-01 11:17:43 +02:00
|
|
|
;; (list-length '())))
|
2020-08-31 18:05:31 +02:00
|
|
|
;; (prelude-assert
|
2019-12-11 11:31:57 +01:00
|
|
|
;; (= 5
|
2020-09-01 11:17:43 +02:00
|
|
|
;; (list-length '(1 2 3 4 5))))
|
2020-08-31 18:05:31 +02:00
|
|
|
;; (prelude-assert
|
2019-12-11 11:31:57 +01:00
|
|
|
;; (= 16
|
2020-09-01 11:17:43 +02:00
|
|
|
;; (list-reduce 1 (lambda (x acc) (+ x acc)) '(1 2 3 4 5))))
|
2020-08-31 18:05:31 +02:00
|
|
|
;; (prelude-assert
|
2019-12-11 11:31:57 +01:00
|
|
|
;; (equal '(2 4 6 8 10)
|
2020-09-01 11:17:43 +02:00
|
|
|
;; (list-map (lambda (x) (* x 2)) '(1 2 3 4 5)))))
|
2019-10-09 13:13:56 +02:00
|
|
|
|
|
|
|
(provide 'list)
|
|
|
|
;;; list.el ends here
|