Insertion-order sets.
In Emacs Lisp:
- If you want fast (constant) lookup time, you use a hash table
- If you want to preserve order, you use a list
But sometimes you want a collection that has both constant lookup time and the ability to preserve element order. This library implements that.
Now on MELPA!
(straight-use-package 'ordered-set)Or without MELPA:
(straight-use-package
'(ordered-set :host github :repo "kisaragi-hiu/ordered-set.el"))- Sets are implemented as structs called
ordered-setwith two slots: a hash table, and a list. - The hash table is used for testing membership. The list is used to preserve order.
- The hash table and the list are maintained while adding or deleting elements.
- Membership equality uses
equal. Allowing the hash table to accept other equality functions might be doable but would open up too much complexity.
For functions dealing with collection, there is a choice between if values or the collection should come first in arguments.
- Values first:
(member ELT LIST),(puthash KEY VALUE TABLE),(push NEWELT PLACE) - Collection first:
(ht-remove TABLE KEY),(seq-contains-p SEQUENCE ELT)
I've chosen to put collections first to be more like seq.el.
(defun my-own-uniq (sequence)
"Return a list of elements of SEQUENCE without duplicates."
(let ((set (ordered-set-create)))
(dolist (it sequence)
(ordered-set-add set it))
(ordered-set-lst set)))-
ordered-set-create (&optional seq)Create a new ordered set. If
seqis provided, create one with the elements ofseq.
ordered-set-lst (set): Return the underlying list ofset. Modifying this list without making a copy will break theordered-setobject.ordered-set-ht (set): Return the underlying hash table ofset. Modifying the hash table without making a copy will break theordered-setobject.(seq-into set type): Convertsetto any other type supported byseq-into.(seq-into sequence 'ordered-set): Convert any sequence to an ordered set.
ordered-set-add (set value): Addvaluetoset. Returnset.ordered-set-push (set value): an alias ofordered-set-add.ordered-set-delete (set value): Deletevaluefromset. Return t ifvaluewas inset, nil otherwise.ordered-set-has (set value): Return whethervalueis inset.ordered-set-clear (set): Clearsetso that it is empty. Implemented for parity with JavaScript's Set only.
Set methods à la tc39/proposal-set-methods
These functions accept any sequence, like seq functions. The difference between, say, ordered-set-intersection and seq-intersection is that ordered-set-intersection returns an ordered set whereas seq-intersection returns a list.
-
ordered-set-intersection (seq1 seq2): (A B) ∩ (B C) → (B) -
ordered-set-union (seq1 seq2): (A B) ∪ (B C) → (A B C) -
ordered-set-difference (seq1 seq2): (A B) ∖ (B C D) → (A)Elements that are only in
seq2are not included. That sort of “difference” is provided byordered-set-symmetric-difference. -
ordered-set-symmetric-difference (seq1 seq2): (A B) ⊖ (B C D) → (A C D) -
ordered-set-subset-p (sub super): whether every element ofsubis insuper -
ordered-set-superset-p (super sub): whether every element ofsubis insuper -
ordered-set-disjoint-p (set1 set2): whetherseq1andseq2do not intersect
seq.el methods just work.
- Iteration maps over the underlying list.
- Membership tests use the underlying hash table.
- When uniqueness
testfnarguments are not given or are nil, the underlying list is used directly as the members' uniqueness had already been guaranteed when they were added.
Of particular note:
(setf (seq-elt set n) value)is not implemented. There is no right answer to what modifying a set by index is supposed to mean, even if the set is order-aware.seq-into-sequencejust does nothing, as is also the case for builtin seq types.
Here are the function signatures:
-
(seq-into set type): Convertsetto any other type supported byseq-into. -
(seq-into sequence 'ordered-set): Convert any sequence to a set. -
(seqp my-set): Return t for sets. -
(seq-into-sequence set): do nothing and returnsetlike the generic implementation. -
(seq-elt set n): Returnnth element ofset. Last added elements come first. -
(seq-length set): Return the number of elements inset. -
(seq-do function set): Applyfunctionto each element ofset, presumably for side effects. Returnset. -
(seq-copy set): Return a shallow copy ofset. -
(seq-subseq set start &optional end): Return a new set containing elements ofsetfromstarttoend. -
(seq-map function set): Runfunctionon each element ofsetand collect them in a list. Possibly faster implementation utilizing mapcar. -
(seq-sort pred set): Sortsetusingpredas the comparison function. The original set is not modified. Returns a new set. -
(seq-reverse set): Return a new set which is a reversed version ofset. -
(seq-concatenate 'ordered-set &rest sequences): Concatenatesequencesinto a single set. -
(seq-uniq set &optional testfn): Return a list of unique elements inset.This is very fast if
testfnis nil, as sets already avoid adding duplicates. -
(seq-contains-p set elt &optional testfn): Return non-nil ifsetcontains an element equal toelt.This is very fast if
testfnis nil, as the membership test utilizes the underlying hash table.
- ht.el - for the project structure
- seq.el - this library implements the seq.el interface
- ECMAScript - this library is strongly inspired by JavaScript Sets.