2036 lines
71 KiB
HTML
2036 lines
71 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta name="generator" content=
|
||
"HTML Tidy for HTML5 for Linux version 5.2.0">
|
||
<title>Data structures</title>
|
||
<meta charset="utf-8">
|
||
<meta name="description" content="A collection of examples of using Common Lisp">
|
||
<meta name="viewport" content=
|
||
"width=device-width, initial-scale=1">
|
||
<link rel="icon" href=
|
||
"assets/cl-logo-blue.png"/>
|
||
<link rel="stylesheet" href=
|
||
"assets/style.css">
|
||
<script type="text/javascript" src=
|
||
"assets/highlight-lisp.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery-3.2.1.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery.toc/jquery.toc.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/toggle-toc.js">
|
||
</script>
|
||
|
||
<link rel="stylesheet" href=
|
||
"assets/github.css">
|
||
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||
</head>
|
||
<body>
|
||
<h1 id="title-xs"><a href="index.html">The Common Lisp Cookbook</a> – Data structures</h1>
|
||
<div id="logo-container">
|
||
<a href="index.html">
|
||
<img id="logo" src="assets/cl-logo-blue.png"/>
|
||
</a>
|
||
|
||
<div id="searchform-container">
|
||
<form onsubmit="duckSearch()" action="javascript:void(0)">
|
||
<input id="searchField" type="text" value="" placeholder="Search...">
|
||
</form>
|
||
</div>
|
||
|
||
<div id="toc-container" class="toc-close">
|
||
<div id="toc-title">Table of Contents</div>
|
||
<ul id="toc" class="list-unstyled"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="content-container">
|
||
<h1 id="title-non-xs"><a href="index.html">The Common Lisp Cookbook</a> – Data structures</h1>
|
||
|
||
<!-- Announcement we can keep for 1 month or more. I remove it and re-add it from time to time. -->
|
||
<!-- <p class="announce"> -->
|
||
<!-- 📢 🤶 ⭐ -->
|
||
<!-- <a style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPY-XMAS2023" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover our contributor's Lisp course with this Christmas coupon.</a> -->
|
||
<!-- <strong> -->
|
||
<!-- Recently added: 18 videos on MACROS. -->
|
||
<!-- </strong> -->
|
||
<!-- <a style="font-size: 90%" href="https://github.com/vindarel/common-lisp-course-in-videos/">Learn more</a>. -->
|
||
<!-- </p> -->
|
||
|
||
<p class="announce">
|
||
📢 New videos: <a href="https://www.youtube.com/watch?v=h_noB1sI_e8">web dev demo part 1</a>, <a href="https://www.youtube.com/watch?v=xnwc7irnc8k">dynamic page with HTMX</a>, <a href="https://www.youtube.com/watch?v=Zpn86AQRVN8">Weblocks demo</a>
|
||
</p>
|
||
|
||
<p class="announce-neutral">
|
||
📕 <a href="index.html#download-in-epub">Get the EPUB and PDF</a>
|
||
</p>
|
||
|
||
|
||
<div id="content"
|
||
<p>We hope to give here a clear reference of the common data
|
||
structures. To really learn the language, you should take the time to
|
||
read other resources. The following resources, which we relied upon,
|
||
also have many more details:</p>
|
||
|
||
<ul>
|
||
<li><a href="http://gigamonkeys.com/book/they-called-it-lisp-for-a-reason-list-processing.html">Practical CL</a>, by Peter Seibel</li>
|
||
<li><a href="http://weitz.de/cl-recipes/">CL Recipes</a>, by E. Weitz, full of explanations and tips,</li>
|
||
<li>the
|
||
<a href="https://franz.com/support/documentation/cl-ansi-standard-draft-w-sidebar.pdf">CL standard</a>
|
||
with – in the sidebar of your PDF reader – a nice TOC, functions reference, extensive descriptions, more
|
||
examples and warnings (i.e: everything). <a href="https://gitlab.com/vancan1ty/clstandard_build/-/blob/master/cl-ansi-standard-draft-w-sidebar.pdf">PDF mirror</a></li>
|
||
<li>a <a href="http://clqr.boundp.org/">Common Lisp quick reference</a></li>
|
||
</ul>
|
||
|
||
<p>Don’t miss the appendix and when you need more data structures, have a
|
||
look at the
|
||
<a href="https://github.com/CodyReichert/awesome-cl#data-structures">awesome-cl</a>
|
||
list and <a href="https://quickdocs.org/-/search?q=data%20structure">Quickdocs</a>.</p>
|
||
|
||
<h2 id="lists">Lists</h2>
|
||
|
||
<h3 id="building-lists-cons-cells-lists">Building lists. Cons cells, lists.</h3>
|
||
|
||
<p><em>A list is also a sequence, so we can use the functions shown below.</em></p>
|
||
|
||
<p>The list basic element is the cons cell. We build lists by assembling
|
||
cons cells.</p>
|
||
|
||
<pre><code class="language-lisp">(cons 1 2)
|
||
;; => (1 . 2) ;; representation with a point, a dotted pair.
|
||
</code></pre>
|
||
|
||
<p>It looks like this:</p>
|
||
|
||
<pre><code>[o|o]--- 2
|
||
|
|
||
1
|
||
</code></pre>
|
||
|
||
<p>If the <code>cdr</code> of the first cell is another cons cell, and if the <code>cdr</code> of
|
||
this last one is <code>nil</code>, we build a list:</p>
|
||
|
||
<pre><code class="language-lisp">(cons 1 (cons 2 nil))
|
||
;; => (1 2)
|
||
</code></pre>
|
||
|
||
<p>It looks like this:</p>
|
||
|
||
<pre><code>[o|o]---[o|/]
|
||
| |
|
||
1 2
|
||
</code></pre>
|
||
<p>(ascii art by <a href="https://github.com/cbaggers/draw-cons-tree">draw-cons-tree</a>).</p>
|
||
|
||
<p>See that the representation is not a dotted pair ? The Lisp printer
|
||
understands the convention.</p>
|
||
|
||
<p>Finally we can simply build a literal list with <code>list</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(list 1 2)
|
||
;; => (1 2)
|
||
</code></pre>
|
||
|
||
<p>or by calling quote:</p>
|
||
|
||
<pre><code class="language-lisp">'(1 2)
|
||
;; => (1 2)
|
||
</code></pre>
|
||
|
||
<p>which is shorthand notation for the function call <code>(quote (1 2))</code>.</p>
|
||
|
||
<h3 id="circular-lists">Circular lists</h3>
|
||
|
||
<p>A cons cell car or cdr can refer to other objects, including itself or
|
||
other cells in the same list. They can therefore be used to define
|
||
self-referential structures such as circular lists.</p>
|
||
|
||
<p>Before working with circular lists, tell the printer to recognise them
|
||
and not try to print the whole list by setting
|
||
<a href="http://clhs.lisp.se/Body/v_pr_cir.htm">*print-circle*</a>
|
||
to <code>T</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(setf *print-circle* t)
|
||
</code></pre>
|
||
|
||
<p>A function which modifies a list, so that the last <code>cdr</code> points to the
|
||
start of the list is:</p>
|
||
|
||
<pre><code class="language-lisp">(defun circular! (items)
|
||
"Modifies the last cdr of list ITEMS, returning a circular list"
|
||
(setf (cdr (last items)) items))
|
||
|
||
(circular! (list 1 2 3))
|
||
;; => #1=(1 2 3 . #1#)
|
||
|
||
(fifth (circular! (list 1 2 3)))
|
||
;; => 2
|
||
</code></pre>
|
||
|
||
<p>The <a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_list_l.htm#list-length">list-length</a>
|
||
function recognises circular lists, returning <code>nil</code>.</p>
|
||
|
||
<p>The reader can also create circular lists, using
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/02_dho.htm">Sharpsign Equal-Sign</a>
|
||
notation. An object (like a list) can be prefixed with <code>#n=</code> where <code>n</code>
|
||
is an unsigned decimal integer (one or more digits). The
|
||
label <code>#n#</code> can be used to refer to the object later in the
|
||
expression:</p>
|
||
|
||
<pre><code class="language-lisp">'#42=(1 2 3 . #42#)
|
||
;; => #1=(1 2 3 . #1#)
|
||
</code></pre>
|
||
|
||
<p>Note that the label given to the reader (<code>n=42</code>) is discarded after
|
||
reading, and the printer defines a new label (<code>n=1</code>).</p>
|
||
|
||
<p>Further reading</p>
|
||
|
||
<ul>
|
||
<li><a href="https://letoverlambda.com/index.cl/guest/chap4.html#sec_5">Let over Lambda</a> section on cyclic expressions</li>
|
||
</ul>
|
||
|
||
<h3 id="carcdr-or-firstrest-and-second-to-tenth">car/cdr or first/rest (and second… to tenth)</h3>
|
||
|
||
<pre><code class="language-lisp">(car (cons 1 2)) ;; => 1
|
||
(cdr (cons 1 2)) ;; => 2
|
||
(first (cons 1 2)) ;; => 1
|
||
(first '(1 2 3)) ;; => 1
|
||
(rest '(1 2 3)) ;; => (2 3)
|
||
</code></pre>
|
||
|
||
<p>We can assign <em>any</em> new value with <code>setf</code>.</p>
|
||
|
||
<h3 id="last-butlast-nbutlast-optional-n">last, butlast, nbutlast (&optional n)</h3>
|
||
|
||
<p>return the last cons cell in a list (or the nth last cons cells).</p>
|
||
|
||
<pre><code class="language-lisp">(last '(1 2 3))
|
||
;; => (3)
|
||
(car (last '(1 2 3)) ) ;; or (first (last …))
|
||
;; => 3
|
||
(butlast '(1 2 3))
|
||
;; => (1 2)
|
||
</code></pre>
|
||
|
||
<p>In <a href="https://common-lisp.net/project/alexandria/draft/alexandria.html#Conses">Alexandria</a>, <code>lastcar</code> is equivalent of <code>(first (last …))</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(alexandria:lastcar '(1 2 3))
|
||
;; => 3
|
||
</code></pre>
|
||
|
||
<h3 id="reverse-nreverse">reverse, nreverse</h3>
|
||
|
||
<p><code>reverse</code> and <code>nreverse</code> return a new sequence.</p>
|
||
|
||
<p><code>nreverse</code> is destructive. The N stands for <strong>non-consing</strong>, meaning
|
||
it doesn’t need to allocate any new cons cells. It <em>might</em> (but in
|
||
practice, does) reuse and modify the original sequence:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter mylist '(1 2 3))
|
||
;; => (1 2 3)
|
||
(reverse mylist)
|
||
;; => (3 2 1)
|
||
mylist
|
||
;; => (1 2 3)
|
||
(nreverse mylist)
|
||
;; => (3 2 1)
|
||
mylist
|
||
;; => (1) in SBCL but implementation dependent.
|
||
</code></pre>
|
||
|
||
<h3 id="append">append</h3>
|
||
|
||
<p><code>append</code> takes any number of list arguments and returns a new list
|
||
containing the elements of all its arguments:</p>
|
||
|
||
<pre><code class="language-lisp">(append (list 1 2) (list 3 4))
|
||
;; => (1 2 3 4)
|
||
</code></pre>
|
||
|
||
<p>The new list shares some cons cells with the <code>(3 4)</code>:</p>
|
||
|
||
<p>http://gigamonkeys.com/book/figures/after-append.png</p>
|
||
|
||
<p><code>nconc</code> is the recycling equivalent.</p>
|
||
|
||
<h3 id="push-pushnew-item-place">push, pushnew (item, place)</h3>
|
||
|
||
<p><code>push</code> prepends <em>item</em> to the list that is stored in <em>place</em>, stores
|
||
the resulting list in <em>place</em>, and returns the list.</p>
|
||
|
||
<p><code>pushnew</code> is similar, but it does nothing if the element already exists in the place.</p>
|
||
|
||
<p>See also <code>adjoin</code> below that doesn’t modify the target list.</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter mylist '(1 2 3))
|
||
(push 0 mylist)
|
||
;; => (0 1 2 3)
|
||
</code></pre>
|
||
|
||
<pre><code class="language-lisp">(defparameter x ’(a (b c) d))
|
||
;; => (A (B C) D)
|
||
(push 5 (cadr x))
|
||
;; => (5 B C)
|
||
x
|
||
;; => (A (5 B C) D)
|
||
</code></pre>
|
||
|
||
<p><code>push</code> is equivalent to <code>(setf place (cons item place ))</code> except that
|
||
the subforms of <em>place</em> are evaluated only once, and <em>item</em> is evaluated
|
||
before <em>place</em>.</p>
|
||
|
||
<p>There is no built-in function to <strong>add to the end of a list</strong>. It is a
|
||
more costly operation (have to traverse the whole list). So if you
|
||
need to do this: either consider using another data structure, either
|
||
just <code>reverse</code> your list when needed.</p>
|
||
|
||
<p><code>pushnew</code> accepts key arguments: <code>:key</code>, <code>:test</code>, <code>:test-not</code>.</p>
|
||
|
||
<h3 id="pop">pop</h3>
|
||
|
||
<p>a destructive operation.</p>
|
||
|
||
<h3 id="nthcdr-index-list">nthcdr (index, list)</h3>
|
||
|
||
<p>Use this if <code>first</code>, <code>second</code> and the rest up to <code>tenth</code> are not
|
||
enough.</p>
|
||
|
||
<h3 id="carcdr-and-composites-cadr-caadr---accessing-lists-inside-lists">car/cdr and composites (cadr, caadr…) - accessing lists inside lists</h3>
|
||
|
||
<p>They make sense when applied to lists containing other lists.</p>
|
||
|
||
<pre><code class="language-lisp">(caar (list 1 2 3)) ==> error
|
||
(caar (list (list 1 2) 3)) ==> 1
|
||
(cadr (list (list 1 2) (list 3 4))) ==> (3 4)
|
||
(caadr (list (list 1 2) (list 3 4))) ==> 3
|
||
</code></pre>
|
||
|
||
<h3 id="destructuring-bind-parameter-list">destructuring-bind (parameter*, list)</h3>
|
||
|
||
<p>It binds the parameter values to the list elements. We can destructure
|
||
trees, plists and even provide defaults.</p>
|
||
|
||
<p>Simple matching:</p>
|
||
|
||
<pre><code class="language-lisp">(destructuring-bind (x y z) (list 1 2 3)
|
||
(list :x x :y y :z z))
|
||
;; => (:X 1 :Y 2 :Z 3)
|
||
</code></pre>
|
||
|
||
<p>Matching inside sublists:</p>
|
||
|
||
<pre><code class="language-lisp">(destructuring-bind (x (y1 y2) z) (list 1 (list 2 20) 3)
|
||
(list :x x :y1 y1 :y2 y2 :z z))
|
||
;; => (:X 1 :Y1 2 :Y2 20 :Z 3)
|
||
</code></pre>
|
||
|
||
<p>The parameter list can use the usual <code>&optional</code>, <code>&rest</code> and <code>&key</code>
|
||
parameters.</p>
|
||
|
||
<pre><code class="language-lisp">(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3)
|
||
(list :x x :y1 y1 :y2 y2 :z z))
|
||
;; => (:X 1 :Y1 2 :Y2 NIL :Z 3)
|
||
</code></pre>
|
||
|
||
<pre><code class="language-lisp">(destructuring-bind (&key x y z) (list :z 1 :y 2 :x 3)
|
||
(list :x x :y y :z z))
|
||
;; => (:X 3 :Y 2 :Z 1)
|
||
</code></pre>
|
||
|
||
<p>The <code>&whole</code> parameter is bound to the whole list. It must be the
|
||
first one and others can follow.</p>
|
||
|
||
<pre><code class="language-lisp">(destructuring-bind (&whole whole-list &key x y z)
|
||
(list :z 1 :y 2 :x 3)
|
||
(list :x x :y y :z z :whole whole-list))
|
||
;; => (:X 3 :Y 2 :Z 1 :WHOLE-LIST (:Z 1 :Y 2 :X 3))
|
||
</code></pre>
|
||
|
||
<p>Destructuring a plist, giving defaults:</p>
|
||
|
||
<p>(example from Common Lisp Recipes, by E. Weitz, Apress, 2016)</p>
|
||
|
||
<pre><code class="language-lisp">(destructuring-bind (&key a (b :not-found) c
|
||
&allow-other-keys)
|
||
’(:c 23 :d "D" :a #\A :foo :whatever)
|
||
(list a b c))
|
||
;; => (#\A :NOT-FOUND 23)
|
||
</code></pre>
|
||
|
||
<p>If this gives you the will to do pattern matching, see
|
||
<a href="pattern_matching.html">pattern matching</a>.</p>
|
||
|
||
<h3 id="predicates-null-listp">Predicates: null, listp</h3>
|
||
|
||
<p><code>null</code> is equivalent to <code>not</code>, but considered better style.</p>
|
||
|
||
<p><code>listp</code> tests whether an object is a cons cell or nil.</p>
|
||
|
||
<p>and sequences’ predicates.</p>
|
||
|
||
<h3 id="ldiff-tailp-list-make-list-fill-revappend-nreconc-consp-atom">ldiff, tailp, list*, make-list, fill, revappend, nreconc, consp, atom</h3>
|
||
|
||
<pre><code class="language-lisp">(make-list 3 :initial-element "ta")
|
||
;; => ("ta" "ta" "ta")
|
||
</code></pre>
|
||
|
||
<pre><code class="language-lisp">(make-list 3)
|
||
;; => (NIL NIL NIL)
|
||
(fill * "hello")
|
||
;; => ("hello" "hello" "hello")
|
||
</code></pre>
|
||
|
||
<h3 id="member-elt-list">member (elt, list)</h3>
|
||
|
||
<p>Returns the tail of <code>list</code> beginning with the first element satisfying <code>eql</code>ity.</p>
|
||
|
||
<p>Accepts <code>:test</code>, <code>:test-not</code>, <code>:key</code> (functions or symbols).</p>
|
||
|
||
<pre><code class="language-lisp">(member 2 '(1 2 3))
|
||
;; (2 3)
|
||
</code></pre>
|
||
|
||
<h3 id="replacing-objects-in-a-tree-subst-sublis">Replacing objects in a tree: subst, sublis</h3>
|
||
|
||
<p><a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_substc.htm">subst</a> and
|
||
<code>subst-if</code> search and replace occurences of an element
|
||
or subexpression in a tree (when it satisfies the optional <code>test</code>):</p>
|
||
|
||
<pre><code class="language-lisp">(subst 'one 1 '(1 2 3))
|
||
;; => (ONE 2 3)
|
||
|
||
(subst '(1 . one) '(1 . 1) '((1 . 1) (2 . 2)) :test #'equal)
|
||
;; ((1 . ONE) (2 . 2))
|
||
</code></pre>
|
||
|
||
<p><a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_sublis.htm">sublis</a>
|
||
allows to replace many objects at once. It substitutes the objects
|
||
given in <code>alist</code> and found in <code>tree</code> with their new values given in
|
||
the alist:</p>
|
||
|
||
<pre><code class="language-lisp">(sublis '((x . 10) (y . 20))
|
||
'(* x (+ x y) (* y y)))
|
||
;; (* 10 (+ 10 20) (* 20 20))
|
||
</code></pre>
|
||
|
||
<p><code>sublis</code> accepts the <code>:test</code> and <code>:key</code> arguments. <code>:test</code> is a
|
||
function that takes two arguments, the key and the subtree.</p>
|
||
|
||
<pre><code class="language-lisp">(sublis '((t . "foo"))
|
||
'("one" 2 ("three" (4 5)))
|
||
:key #'stringp)
|
||
;; ("foo" 2 ("foo" (4 5)))
|
||
</code></pre>
|
||
|
||
<h2 id="sequences">Sequences</h2>
|
||
|
||
<p><strong>lists</strong> and <strong>vectors</strong> (and thus <strong>strings</strong>) are sequences.</p>
|
||
|
||
<p><em>Note</em>: see also the <a href="strings.html">strings</a> page.</p>
|
||
|
||
<p>Many of the sequence functions take keyword arguments. All keyword
|
||
arguments are optional and, if specified, may appear in any order.</p>
|
||
|
||
<p>Pay attention to the <code>:test</code> argument. It defaults to <code>eql</code> (for
|
||
strings, use <code>:equal</code>).</p>
|
||
|
||
<p>The <code>:key</code> argument should be passed either nil, or a function of one
|
||
argument. This key function is used as a filter through which the
|
||
elements of the sequence are seen. For instance, this:</p>
|
||
|
||
<pre><code class="language-lisp">(find x y :key 'car)
|
||
</code></pre>
|
||
|
||
<p>is similar to <code>(assoc* x y)</code>: It searches for an element of the list
|
||
whose car equals x, rather than for an element which equals x
|
||
itself. If <code>:key</code> is omitted or nil, the filter is effectively the
|
||
identity function.</p>
|
||
|
||
<p>Example with an alist (see definition below):</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter my-alist (list (cons 'foo "foo")
|
||
(cons 'bar "bar")))
|
||
;; => ((FOO . "foo") (BAR . "bar"))
|
||
(find 'bar my-alist)
|
||
;; => NIL
|
||
(find 'bar my-alist :key 'car)
|
||
;; => (BAR . "bar")
|
||
</code></pre>
|
||
|
||
<p>For more, use a <code>lambda</code> that takes one parameter.</p>
|
||
|
||
<pre><code class="language-lisp">(find 'bar my-alist :key (lambda (it) (car it)))
|
||
</code></pre>
|
||
|
||
<pre><code class="language-lisp">(find 'bar my-alist :key ^(car %))
|
||
(find 'bar my-alist :key (lm (it) (car it)))
|
||
</code></pre>
|
||
|
||
<h3 id="predicates-every-some">Predicates: every, some,…</h3>
|
||
|
||
<p><code>every, notevery (test, sequence)</code>: return nil or t, respectively, as
|
||
soon as one test on any set of the corresponding elements of sequences
|
||
returns nil.</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter foo '(1 2 3))
|
||
(every #'evenp foo)
|
||
;; => NIL
|
||
(some #'evenp foo)
|
||
;; => T
|
||
</code></pre>
|
||
|
||
<p>with a list of strings:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter str '("foo" "bar" "team"))
|
||
(every #'stringp str)
|
||
;; => T
|
||
(some (lambda (it) (= 3 (length it))) str)
|
||
;; => T
|
||
</code></pre>
|
||
|
||
<p><code>some</code>, <code>notany</code> <em>(test, sequence)</em>: return either the value of the test, or nil.</p>
|
||
|
||
<h3 id="functions">Functions</h3>
|
||
|
||
<p>See also sequence functions defined in
|
||
<a href="https://common-lisp.net/project/alexandria/draft/alexandria.html#Sequences">Alexandria</a>:
|
||
<code>starts-with</code>, <code>ends-with</code>, <code>ends-with-subseq</code>, <code>length=</code>, <code>emptyp</code>,…</p>
|
||
|
||
<h4 id="length-sequence">length (sequence)</h4>
|
||
|
||
<h4 id="elt-sequence-index---find-by-index">elt (sequence, index) - find by index</h4>
|
||
|
||
<p>beware, here the sequence comes first.</p>
|
||
|
||
<h4 id="count-foo-sequence">count (foo sequence)</h4>
|
||
|
||
<p>Return the number of elements in sequence that match <em>foo</em>.</p>
|
||
|
||
<p>Additional paramaters: <code>:from-end</code>, <code>:start</code>, <code>:end</code>.</p>
|
||
|
||
<p>See also <code>count-if</code>, <code>count-not</code> <em>(test-function sequence)</em>.</p>
|
||
|
||
<h4 id="subseq-sequence-start-end">subseq (sequence start, [end])</h4>
|
||
|
||
<pre><code class="language-lisp">(subseq (list 1 2 3) 0)
|
||
;; (1 2 3)
|
||
(subseq (list 1 2 3) 1 2)
|
||
;; (2)
|
||
</code></pre>
|
||
|
||
<p>However, watch out if the <code>end</code> is larger than the list:</p>
|
||
|
||
<pre><code class="language-lisp">(subseq (list 1 2 3) 0 99)
|
||
;; => Error: the bounding indices 0 and 99
|
||
;; are bad for a sequence of length 3.
|
||
</code></pre>
|
||
|
||
<p>To this end, use <code>alexandria-2:subseq*</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(alexandria-2:subseq* (list 1 2 3) 0 99)
|
||
;; (1 2 3)
|
||
</code></pre>
|
||
|
||
<p><code>subseq</code> is “setf”able, but only works if the new sequence has the same
|
||
length of the one to replace.</p>
|
||
|
||
<h4 id="sort-stable-sort-sequence-test--key-function">sort, stable-sort (sequence, test [, key function])</h4>
|
||
|
||
<p>These sort functions are destructive, so one may prefer to copy the sequence with <code>copy-seq</code> before sorting:</p>
|
||
|
||
<pre><code class="language-lisp">(sort (copy-seq seq) :test #'string<)
|
||
</code></pre>
|
||
|
||
<p>Unlike <code>sort</code>, <code>stable-sort</code> guarantees to keep the order of the argument.
|
||
In theory, the result of this:</p>
|
||
|
||
<pre><code class="language-lisp">(sort '((1 :a) (1 :b)) #'< :key #'first)
|
||
</code></pre>
|
||
|
||
<p>could be either <code>((1 :A) (1 :B))</code>, either <code>((1 :B) (1 :A))</code>. On my tests, the order is preserved, but the standard does not guarantee it.</p>
|
||
|
||
<h4 id="find-position-foo-sequence---get-index">find, position (foo, sequence) - get index</h4>
|
||
|
||
<p>also <code>find-if</code>, <code>find-if-not</code>, <code>position-if</code>, <code>position-if-not</code> <em>(test
|
||
sequence)</em>. See <code>:key</code> and <code>:test</code> parameters.</p>
|
||
|
||
<pre><code class="language-lisp">(find 20 '(10 20 30))
|
||
;; 20
|
||
(position 20 '(10 20 30))
|
||
;; 1
|
||
</code></pre>
|
||
|
||
<h4 id="search-and-mismatch-sequence-a-sequence-b">search and mismatch (sequence-a, sequence-b)</h4>
|
||
|
||
<p><code>search</code> searches in sequence-b for a subsequence that matches sequence-a. It returns the
|
||
<em>position</em> in sequence-b, or NIL. It has the <code>from-end</code>, <code>end1</code>, <code>end2</code> and the usual <code>test</code> and <code>key</code>
|
||
parameters.</p>
|
||
|
||
<pre><code class="language-lisp">(search '(20 30) '(10 20 30 40))
|
||
;; 1
|
||
(search '("b" "c") '("a" "b" "c"))
|
||
;; NIL
|
||
(search '("b" "c") '("a" "b" "c") :test #'equal)
|
||
;; 1
|
||
(search "bc" "abc")
|
||
;; 1
|
||
</code></pre>
|
||
|
||
<p><code>mismatch</code> returns the position where the two sequences start to differ:</p>
|
||
|
||
<pre><code class="language-lisp">(mismatch '(10 20 99) '(10 20 30))
|
||
;; 2
|
||
(mismatch "hellolisper" "helloworld")
|
||
;; 5
|
||
(mismatch "same" "same")
|
||
;; NIL
|
||
(mismatch "foo" "bar")
|
||
;; 0
|
||
</code></pre>
|
||
|
||
<h4 id="substitute-nsubstituteifif-not">substitute, nsubstitute[if,if-not]</h4>
|
||
|
||
<p>Return a sequence of the same kind as <code>sequence</code> with the same elements,
|
||
except that all elements equal to <code>old</code> are replaced with <code>new</code>.</p>
|
||
|
||
<pre><code class="language-lisp">(substitute #\o #\x "hellx") ;; => "hello"
|
||
(substitute :a :x '(:a :x :x)) ;; => (:A :A :A)
|
||
(substitute "a" "x" '("a" "x" "x") :test #'string=)
|
||
;; => ("a" "a" "a")
|
||
</code></pre>
|
||
|
||
<h4 id="sort-stable-sort-merge">sort, stable-sort, merge</h4>
|
||
|
||
<p>(see above)</p>
|
||
|
||
<h4 id="replace-sequence-a-sequence-b-key-start1-end1">replace (sequence-a, sequence-b, &key start1, end1)</h4>
|
||
|
||
<p>Destructively replace elements of sequence-a with elements of
|
||
sequence-b.</p>
|
||
|
||
<p>The full signature is:</p>
|
||
|
||
<pre><code class="language-lisp">(replace sequence1 sequence2
|
||
&rest args
|
||
&key (start1 0) (end1 nil) (start2 0) (end2 nil))
|
||
</code></pre>
|
||
|
||
<p>Elements are copied to the subseqeuence bounded by START1 and END1,
|
||
from the subsequence bounded by START2 and END2. If these subsequences
|
||
are not of the same length, then the shorter length determines how
|
||
many elements are copied.</p>
|
||
|
||
<pre><code class="language-lisp">(replace "xxx" "foo")
|
||
"foo"
|
||
|
||
(replace "xxx" "foo" :start1 1)
|
||
"xfo"
|
||
|
||
(replace "xxx" "foo" :start1 1 :start2 1)
|
||
"xoo"
|
||
|
||
(replace "xxx" "foo" :start1 1 :start2 1 :end2 2)
|
||
"xox"
|
||
</code></pre>
|
||
|
||
<h4 id="remove-delete-foo-sequence">remove, delete (foo sequence)</h4>
|
||
|
||
<p>Make a copy of sequence without elements matching foo. Has
|
||
<code>:start/end</code>, <code>:key</code> and <code>:count</code> parameters.</p>
|
||
|
||
<p><code>delete</code> is the recycling version of <code>remove</code>.</p>
|
||
|
||
<pre><code class="language-lisp">(remove "foo" '("foo" "bar" "foo") :test 'equal)
|
||
;; => ("bar")
|
||
</code></pre>
|
||
|
||
<p>see also <code>remove-if[-not]</code> below.</p>
|
||
|
||
<h4 id="remove-duplicates-delete-duplicates-sequence">remove-duplicates, delete-duplicates (sequence)</h4>
|
||
|
||
<p><a href="http://clhs.lisp.se/Body/f_rm_dup.htm">remove-duplicates</a> returns a
|
||
new sequence with uniq elements. <code>delete-duplicates</code> may modify the
|
||
original sequence.</p>
|
||
|
||
<p><code>remove-duplicates</code> accepts the following, usual arguments: <code>from-end
|
||
test test-not start end key</code>.</p>
|
||
|
||
<pre><code class="language-lisp">(remove-duplicates '(:foo :foo :bar))
|
||
(:FOO :BAR)
|
||
|
||
(remove-duplicates '("foo" "foo" "bar"))
|
||
("foo" "foo" "bar")
|
||
|
||
(remove-duplicates '("foo" "foo" "bar") :test #'string-equal)
|
||
("foo" "bar")
|
||
</code></pre>
|
||
|
||
<h3 id="mapping-map-mapcar-remove-if-not">mapping (map, mapcar, remove-if[-not],…)</h3>
|
||
|
||
<p>If you’re used to map and filter in other languages, you probably want
|
||
<code>mapcar</code>. But it only works on lists, so to iterate on vectors (and
|
||
produce either a vector or a list, use <code>(map 'list function vector)</code>.</p>
|
||
|
||
<p>mapcar also accepts multiple lists with <code>&rest more-seqs</code>. The
|
||
mapping stops as soon as the shortest sequence runs out.</p>
|
||
|
||
<p><code>map</code> takes the output-type as first argument (<code>'list</code>, <code>'vector</code> or
|
||
<code>'string</code>):</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter foo '(1 2 3))
|
||
(map 'list (lambda (it) (* 10 it)) foo)
|
||
</code></pre>
|
||
|
||
<p><code>reduce</code> <em>(function, sequence)</em>. Special parameter: <code>:initial-value</code>.</p>
|
||
|
||
<pre><code class="language-lisp">(reduce '- '(1 2 3 4))
|
||
;; => -8
|
||
(reduce '- '(1 2 3 4) :initial-value 100)
|
||
;; => 90
|
||
</code></pre>
|
||
|
||
<p><strong>Filter</strong> is here called <code>remove-if-not</code>.</p>
|
||
|
||
<h3 id="flatten-a-list-alexandria">Flatten a list (Alexandria)</h3>
|
||
|
||
<p>With
|
||
<a href="https://common-lisp.net/project/alexandria/draft/alexandria.html">Alexandria</a>,
|
||
we have the <code>flatten</code> function.</p>
|
||
|
||
<h3 id="creating-lists-with-variables">Creating lists with variables</h3>
|
||
|
||
<p>That’s one use of the <code>backquote</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter *var* "bar")
|
||
;; First try:
|
||
'("foo" *var* "baz") ;; no backquote
|
||
;; => ("foo" *VAR* "baz") ;; nope
|
||
</code></pre>
|
||
|
||
<p>Second try, with backquote interpolation:</p>
|
||
|
||
<pre><code class="language-lisp">`("foo" ,*var* "baz") ;; backquote, comma
|
||
;; => ("foo" "bar" "baz") ;; good
|
||
</code></pre>
|
||
|
||
<p>The backquote first warns we’ll do interpolation, the comma introduces
|
||
the value of the variable.</p>
|
||
|
||
<p>If our variable is a list:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter *var* '("bar" "baz"))
|
||
;; First try:
|
||
`("foo" ,*var*)
|
||
;; => ("foo" ("bar" "baz")) ;; nested list
|
||
`("foo" ,@*var*) ;; backquote, comma-@ to
|
||
;; => ("foo" "bar" "baz")
|
||
</code></pre>
|
||
|
||
<p>E. Weitz warns that “objects generated this way will very likely share
|
||
structure (see Recipe 2-7)”.</p>
|
||
|
||
<h3 id="comparing-lists">Comparing lists</h3>
|
||
|
||
<p>We can use sets functions.</p>
|
||
|
||
<h2 id="set">Set</h2>
|
||
|
||
<p>We show below how to use set operations on lists.</p>
|
||
|
||
<p>A set doesn’t contain twice the same element and is unordered.</p>
|
||
|
||
<p>Most of these functions have recycling (modifying) counterparts, starting with “n”: <code>nintersection</code>,… They all accept the usual <code>:key</code> and <code>:test</code> arguments, so use the test <code>#'string=</code> or <code>#'equal</code> if you are working with strings.</p>
|
||
|
||
<p>For more, see functions in
|
||
<a href="https://common-lisp.net/project/alexandria/draft/alexandria.html#Conses">Alexandria</a>:
|
||
<code>setp</code>, <code>set-equal</code>,… and the FSet library, shown in the next section.</p>
|
||
|
||
<h3 id="intersection-of-lists"><code>intersection</code> of lists</h3>
|
||
|
||
<p>What elements are both in list-a and list-b ?</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter list-a '(0 1 2 3))
|
||
(defparameter list-b '(0 2 4))
|
||
(intersection list-a list-b)
|
||
;; => (2 0)
|
||
</code></pre>
|
||
|
||
<h3 id="remove-the-elements-of-list-b-from-list-a-set-difference">Remove the elements of list-b from list-a (<code>set-difference</code>)</h3>
|
||
|
||
<pre><code class="language-lisp">(set-difference list-a list-b)
|
||
;; => (3 1)
|
||
(set-difference list-b list-a)
|
||
;; => (4)
|
||
</code></pre>
|
||
|
||
<h3 id="join-two-lists-with-uniq-elements-union">Join two lists with uniq elements (<code>union</code>)</h3>
|
||
|
||
<pre><code class="language-lisp">(union list-a list-b)
|
||
;; => (3 1 0 2 4) ;; order can be different in your lisp
|
||
</code></pre>
|
||
|
||
<h3 id="remove-elements-that-are-in-both-lists-set-exclusive-or">Remove elements that are in both lists (<code>set-exclusive-or</code>)</h3>
|
||
|
||
<pre><code class="language-lisp">(set-exclusive-or list-a list-b)
|
||
;; => (4 3 1)
|
||
</code></pre>
|
||
|
||
<h3 id="add-an-element-to-a-set-adjoin">Add an element to a set (<code>adjoin</code>)</h3>
|
||
|
||
<p>A new set is returned, the original set is not modified.</p>
|
||
|
||
<pre><code class="language-lisp">(adjoin 3 list-a)
|
||
;; => (0 1 2 3) ;; <-- nothing was changed, 3 was already there.
|
||
|
||
(adjoin 5 list-a)
|
||
;; => (5 0 1 2 3) ;; <-- element added in front.
|
||
|
||
list-a
|
||
;; => (0 1 2 3) ;; <-- original list unmodified.
|
||
</code></pre>
|
||
|
||
<p>You can also use <code>pushnew</code>, that modifies the list (see above).</p>
|
||
|
||
<h3 id="check-if-this-is-a-subset-subsetp">Check if this is a subset (<code>subsetp</code>)</h3>
|
||
|
||
<pre><code class="language-lisp">(subsetp '(1 2 3) list-a)
|
||
;; => T
|
||
|
||
(subsetp '(1 1 1) list-a)
|
||
;; => T
|
||
|
||
(subsetp '(3 2 1) list-a)
|
||
;; => T
|
||
|
||
(subsetp '(0 3) list-a)
|
||
;; => T
|
||
</code></pre>
|
||
|
||
<h2 id="fset---immutable-data-structure">Fset - immutable data structure</h2>
|
||
|
||
<p>You may want to have a look at the
|
||
<a href="https://common-lisp.net/project/fset/Site/FSet-Tutorial.html">FSet</a>
|
||
library (in Quicklisp).</p>
|
||
|
||
<h2 id="arrays-and-vectors">Arrays and vectors</h2>
|
||
|
||
<p><strong>Arrays</strong> have constant-time access characteristics.</p>
|
||
|
||
<p>They can be fixed or adjustable. A <em>simple array</em> is neither displaced
|
||
(using <code>:displaced-to</code>, to point to another array) nor adjustable
|
||
(<code>:adjust-array</code>), nor does it have a fill pointer (<code>fill-pointer</code>,
|
||
that moves when we add or remove elements).</p>
|
||
|
||
<p>A <strong>vector</strong> is an array with rank 1 (of one dimension). It is also a
|
||
<em>sequence</em> (see above).</p>
|
||
|
||
<p>A <em>simple vector</em> is a simple array that is also not specialized (it
|
||
doesn’t use <code>:element-type</code> to set the types of the elements).</p>
|
||
|
||
<h3 id="create-an-array-one-or-many-dimensions">Create an array, one or many dimensions</h3>
|
||
|
||
<p><code>make-array</code> <em>(sizes-list :adjustable bool)</em></p>
|
||
|
||
<p><code>adjust-array</code> <em>(array, sizes-list, :element-type, :initial-element)</em></p>
|
||
|
||
<h3 id="access-aref-array-i-j-">Access: aref (array i [j …])</h3>
|
||
|
||
<p><code>aref</code> <em>(array i j k …)</em> or <code>row-major-aref</code> <em>(array i)</em> equivalent to
|
||
<code>(aref i i i …)</code>.</p>
|
||
|
||
<p>The result is <code>setf</code>able.</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter myarray (make-array '(2 2 2) :initial-element 1))
|
||
myarray
|
||
;; => #3A(((1 1) (1 1)) ((1 1) (1 1)))
|
||
(aref myarray 0 0 0)
|
||
;; => 1
|
||
(setf (aref myarray 0 0 0) 9)
|
||
;; => 9
|
||
(row-major-aref myarray 0)
|
||
;; => 9
|
||
</code></pre>
|
||
|
||
<h3 id="sizes">Sizes</h3>
|
||
|
||
<p><code>array-total-size</code> <em>(array)</em>: how many elements will fit in the array ?</p>
|
||
|
||
<p><code>array-dimensions</code> <em>(array)</em>: list containing the length of the array’s dimensions.</p>
|
||
|
||
<p><code>array-dimension</code> <em>(array i)</em>: length of the <em>i</em>th dimension.</p>
|
||
|
||
<p><code>array-rank</code> number of dimensions of the array.</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter myarray (make-array '(2 2 2)))
|
||
;; => MYARRAY
|
||
myarray
|
||
;; => #3A(((0 0) (0 0)) ((0 0) (0 0)))
|
||
(array-rank myarray)
|
||
;; => 3
|
||
(array-dimensions myarray)
|
||
;; => (2 2 2)
|
||
(array-dimension myarray 0)
|
||
;; => 2
|
||
(array-total-size myarray)
|
||
;; => 8
|
||
</code></pre>
|
||
|
||
<h3 id="vectors">Vectors</h3>
|
||
|
||
<p>Create with <code>vector</code> or the reader macro <code>#()</code>. It returns a <em>simple
|
||
vector.</em></p>
|
||
|
||
<pre><code class="language-lisp">(vector 1 2 3)
|
||
;; => #(1 2 3)
|
||
#(1 2 3)
|
||
;; => #(1 2 3)
|
||
</code></pre>
|
||
|
||
<p>The following interface is available for vectors (or vector-like arrays):</p>
|
||
|
||
<ul>
|
||
<li><code>vector-push</code> <em>(new-element vector)</em>: replace the vector element pointed to by the fill pointer by <code>new-element</code>, then increment the fill pointer by one. Returns the index at which the new element was placed, or NIL if there’s not enough space.</li>
|
||
<li><code>vector-push-extend</code> <em>(new-element vector [extension])</em>: like <code>vector-push</code>, but if the fill pointer gets too large then the array is extended using <code>adjust-array</code>. <code>extension</code> is the minimum number of elements to add to the array if it must be extended.</li>
|
||
<li><code>vector-pop</code> <em>(vector)</em>: decrement the fill pointer, and return the element that it now points to.</li>
|
||
<li><code>fill-pointer</code> <em>(vector)</em>. <code>setf</code>able.</li>
|
||
</ul>
|
||
|
||
<p>and see also the <em>sequence</em> functions.</p>
|
||
|
||
<p>The following shows how to create an array that can be pushed to and popped from arbitrarily, growing its storage capacity as needed. This is roughly equivalent to a <code>list</code> in Python, an <code>ArrayList</code> in Java, or a <code>vector<T></code> in C++ – though note that elements are not erased when they’re popped.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *v* (make-array 0 :fill-pointer t :adjustable t))
|
||
*V*
|
||
CL-USER> *v*
|
||
#()
|
||
CL-USER> (vector-push-extend 42 *v*)
|
||
0
|
||
CL-USER> (vector-push-extend 43 *v*)
|
||
1
|
||
CL-USER> (vector-pop *v*)
|
||
43
|
||
CL-USER> *v*
|
||
#(42)
|
||
CL-USER> (aref *v* 1) ; beware, the element is still there!
|
||
43
|
||
CL-USER> (setf (aref *v* 1) nil) ; manually erase elements if necessary
|
||
</code></pre>
|
||
|
||
<h3 id="transforming-a-vector-to-a-list">Transforming a vector to a list.</h3>
|
||
|
||
<p>If you’re mapping over it, see the <code>map</code> function whose first parameter
|
||
is the result type.</p>
|
||
|
||
<p>Or use <code>(coerce vector 'list)</code>.</p>
|
||
|
||
<h2 id="hash-table">Hash Table</h2>
|
||
|
||
<p>Hash Tables are a powerful data structure, associating keys with
|
||
values in a very efficient way. Hash Tables are often preferred over
|
||
association lists whenever performance is an issue, but they introduce
|
||
a little overhead that makes assoc lists better if there are only a
|
||
few key-value pairs to maintain.</p>
|
||
|
||
<p>Alists can be used sometimes differently though:</p>
|
||
|
||
<ul>
|
||
<li>they can be ordered</li>
|
||
<li>we can push cons cells that have the same key, remove the one in
|
||
front and we have a stack</li>
|
||
<li>they have a human-readable printed representation</li>
|
||
<li>they can be easily (de)serialized</li>
|
||
<li>because of RASSOC, keys and values in alists are essentially
|
||
interchangeable; whereas in hash tables, keys and values play very
|
||
different roles (as usual, see CL Recipes for more).</li>
|
||
</ul>
|
||
|
||
<p><a name="create"></a></p>
|
||
|
||
<h3 id="creating-a-hash-table">Creating a Hash Table</h3>
|
||
|
||
<p>Hash Tables are created using the function
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_mk_has.htm"><code>make-hash-table</code></a>. It
|
||
has no required argument. Its most used optional keyword argument is
|
||
<code>:test</code>, specifying the function used to test the equality of keys.</p>
|
||
|
||
<div class="info-box info">
|
||
<strong>Note:</strong> see shorter notations in the <a href="https://github.com/ruricolist/serapeum/">Serapeum</a> or <a href="https://github.com/vseloved/rutils">Rutils</a> libraries. For example, Serapeum has <code>dict</code>, and Rutils a <code>#h</code> reader macro.
|
||
</div>
|
||
|
||
<p><a name="add"></a></p>
|
||
|
||
<h3 id="adding-an-element-to-a-hash-table">Adding an Element to a Hash Table</h3>
|
||
|
||
<p>If you want to add an element to a hash table, you can use <code>gethash</code>,
|
||
the function to retrieve elements from the hash table, in conjunction
|
||
with
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/m_setf_.htm"><code>setf</code></a>.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table))
|
||
*MY-HASH*
|
||
CL-USER> (setf (gethash 'one-entry *my-hash*) "one")
|
||
"one"
|
||
CL-USER> (setf (gethash 'another-entry *my-hash*) 2/4)
|
||
1/2
|
||
CL-USER> (gethash 'one-entry *my-hash*)
|
||
"one"
|
||
T
|
||
CL-USER> (gethash 'another-entry *my-hash*)
|
||
1/2
|
||
T
|
||
</code></pre>
|
||
|
||
<p>With Serapeum’s <code>dict</code>, we can create a hash-table and add elements to
|
||
it in one go:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter *my-hash* (dict :one-entry "one"
|
||
:another-entry 2/4))
|
||
;; =>
|
||
(dict
|
||
:ONE-ENTRY "one"
|
||
:ANOTHER-ENTRY 1/2
|
||
)
|
||
</code></pre>
|
||
|
||
<p><a name="get"></a></p>
|
||
|
||
<h3 id="getting-a-value-from-a-hash-table">Getting a value from a Hash Table</h3>
|
||
|
||
<p>The function
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_gethas.htm"><code>gethash</code></a>
|
||
takes two required arguments: a key and a hash table. It returns two
|
||
values: the value corresponding to the key in the hash table (or <code>nil</code>
|
||
if not found), and a boolean indicating whether the key was found in
|
||
the table. That second value is necessary since <code>nil</code> is a valid value
|
||
in a key-value pair, so getting <code>nil</code> as first value from <code>gethash</code>
|
||
does not necessarily mean that the key was not found in the table.</p>
|
||
|
||
<h4 id="getting-a-key-that-does-not-exist-with-a-default-value">Getting a key that does not exist with a default value</h4>
|
||
|
||
<p><code>gethash</code> has an optional third argument:</p>
|
||
|
||
<pre><code class="language-lisp">(gethash 'bar *my-hash* "default-bar")
|
||
;; => "default-bar"
|
||
;; NIL
|
||
</code></pre>
|
||
|
||
<h4 id="getting-all-keys-or-all-values-of-a-hash-table">Getting all keys or all values of a hash table</h4>
|
||
|
||
<p>The
|
||
<a href="https://common-lisp.net/project/alexandria/draft/alexandria.html">Alexandria</a>
|
||
library (in Quicklisp) has the functions <code>hash-table-keys</code> and
|
||
<code>hash-table-values</code> for that.</p>
|
||
|
||
<pre><code class="language-lisp">(ql:quickload "alexandria")
|
||
;; […]
|
||
(alexandria:hash-table-keys *my-hash*)
|
||
;; => (BAR)
|
||
</code></pre>
|
||
|
||
<p><a name="test"></a></p>
|
||
|
||
<h3 id="testing-for-the-presence-of-a-key-in-a-hash-table">Testing for the Presence of a Key in a Hash Table</h3>
|
||
|
||
<p>The first value returned by <code>gethash</code> is the object in the hash table
|
||
that’s associated with the key you provided as an argument to
|
||
<code>gethash</code> or <code>nil</code> if no value exists for this key. This value can act
|
||
as a
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_g.htm#generalized_boolean">generalized
|
||
boolean">generalized boolean</a> if you want to test for the presence of keys.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table))
|
||
*MY-HASH*
|
||
CL-USER> (setf (gethash 'one-entry *my-hash*) "one")
|
||
"one"
|
||
CL-USER> (if (gethash 'one-entry *my-hash*)
|
||
"Key exists"
|
||
"Key does not exist")
|
||
"Key exists"
|
||
CL-USER> (if (gethash 'another-entry *my-hash*)
|
||
"Key exists"
|
||
"Key does not exist")
|
||
"Key does not exist"
|
||
</code></pre>
|
||
|
||
<p>But note that this does <em>not</em> work if <code>nil</code> is amongst the values that
|
||
you want to store in the hash.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (setf (gethash 'another-entry *my-hash*) nil)
|
||
NIL
|
||
CL-USER> (if (gethash 'another-entry *my-hash*)
|
||
"Key exists"
|
||
"Key does not exist")
|
||
"Key does not exist"
|
||
</code></pre>
|
||
|
||
<p>In this case you’ll have to check the <em>second</em> return value of <code>gethash</code> which will always return <code>nil</code> if no value is found and T otherwise.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (if (nth-value 1 (gethash 'another-entry *my-hash*))
|
||
"Key exists"
|
||
"Key does not exist")
|
||
"Key exists"
|
||
CL-USER> (if (nth-value 1 (gethash 'no-entry *my-hash*))
|
||
"Key exists"
|
||
"Key does not exist")
|
||
"Key does not exist"
|
||
</code></pre>
|
||
|
||
<p><a name="del"></a></p>
|
||
|
||
<h3 id="deleting-from-a-hash-table">Deleting from a Hash Table</h3>
|
||
|
||
<p>Use
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_remhas.htm"><code>remhash</code></a>
|
||
to delete a hash entry. Both the key and its associated value will be
|
||
removed from the hash table. <code>remhash</code> returns T if there was such an
|
||
entry, <code>nil</code> otherwise.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table))
|
||
*MY-HASH*
|
||
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
|
||
ONE
|
||
CL-USER> (gethash 'first-key *my-hash*)
|
||
ONE
|
||
T
|
||
CL-USER> (remhash 'first-key *my-hash*)
|
||
T
|
||
CL-USER> (gethash 'first-key *my-hash*)
|
||
NIL
|
||
NIL
|
||
CL-USER> (gethash 'no-entry *my-hash*)
|
||
NIL
|
||
NIL
|
||
CL-USER> (remhash 'no-entry *my-hash*)
|
||
NIL
|
||
CL-USER> (gethash 'no-entry *my-hash*)
|
||
NIL
|
||
NIL
|
||
</code></pre>
|
||
|
||
<p><a name="del-tab"></a></p>
|
||
|
||
<h3 id="deleting-a-hash-table">Deleting a Hash Table</h3>
|
||
|
||
<p>Use
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_clrhas.htm"><code>clrhash</code></a>
|
||
to delete a hash table. This will remove all of the data from the hash table and return the deleted table.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table))
|
||
*MY-HASH*
|
||
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
|
||
ONE
|
||
CL-USER> (setf (gethash 'second-key *my-hash*) 'two)
|
||
TWO
|
||
CL-USER> *my-hash*
|
||
#<hash-table :TEST eql :COUNT 2 {10097BF4E3}>
|
||
CL-USER> (clrhash *my-hash*)
|
||
#<hash-table :TEST eql :COUNT 0 {10097BF4E3}>
|
||
CL-USER> (gethash 'first-key *my-hash*)
|
||
NIL
|
||
NIL
|
||
CL-USER> (gethash 'second-key *my-hash*)
|
||
NIL
|
||
NIL
|
||
</code></pre>
|
||
|
||
<p><a name="traverse"></a></p>
|
||
|
||
<h3 id="traversing-a-hash-table">Traversing a Hash Table</h3>
|
||
|
||
<p>If you want to perform an action on each entry (i.e., each key-value
|
||
pair) in a hash table, you have several options:</p>
|
||
|
||
<p>You can use
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_maphas.htm"><code>maphash</code></a>
|
||
which iterates over all entries in the hash table. Its first argument
|
||
must be a function which accepts <em>two</em> arguments, the key and the
|
||
value of each entry. Note that due to the nature of hash tables you
|
||
<em>can’t</em> control the order in which the entries are provided by
|
||
<code>maphash</code> (or other traversing constructs). <code>maphash</code> always returns
|
||
<code>nil</code>.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table))
|
||
*MY-HASH*
|
||
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
|
||
ONE
|
||
CL-USER> (setf (gethash 'second-key *my-hash*) 'two)
|
||
TWO
|
||
CL-USER> (setf (gethash 'third-key *my-hash*) nil)
|
||
NIL
|
||
CL-USER> (setf (gethash nil *my-hash*) 'nil-value)
|
||
NIL-VALUE
|
||
CL-USER> (defun print-hash-entry (key value)
|
||
(format t "The value associated with the key ~S is ~S~%"
|
||
key value))
|
||
PRINT-HASH-ENTRY
|
||
CL-USER> (maphash #'print-hash-entry *my-hash*)
|
||
The value associated with the key FIRST-KEY is ONE
|
||
The value associated with the key SECOND-KEY is TWO
|
||
The value associated with the key THIRD-KEY is NIL
|
||
The value associated with the key NIL is NIL-VALUE
|
||
</code></pre>
|
||
|
||
<p>You can also use
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/m_w_hash.htm"><code>with-hash-table-iterator</code></a>,
|
||
a macro which turns (via
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/s_flet_.htm"><code>macrolet</code></a>)
|
||
its first argument into an iterator that on each invocation returns
|
||
three values per hash table entry - a generalized boolean that’s true
|
||
if an entry is returned, the key of the entry, and the value of the
|
||
entry. If there are no more entries, only one value is returned -
|
||
<code>nil</code>.</p>
|
||
|
||
<pre><code class="language-lisp">;;; same hash-table as above
|
||
CL-USER> (with-hash-table-iterator (my-iterator *my-hash*)
|
||
(loop
|
||
(multiple-value-bind (entry-p key value)
|
||
(my-iterator)
|
||
(if entry-p
|
||
(print-hash-entry key value)
|
||
(return)))))
|
||
The value associated with the key FIRST-KEY is ONE
|
||
The value associated with the key SECOND-KEY is TWO
|
||
The value associated with the key THIRD-KEY is NIL
|
||
The value associated with the key NIL is NIL-VALUE
|
||
NIL
|
||
</code></pre>
|
||
|
||
<p>Note the following caveat from the HyperSpec: “It is unspecified what
|
||
happens if any of the implicit interior state of an iteration is
|
||
returned outside the dynamic extent of the <code>with-hash-table-iterator</code>
|
||
form such as by returning some closure over the invocation form.”</p>
|
||
|
||
<p>And there’s always <a href="http://www.lispworks.com/documentation/HyperSpec/Body/06_a.htm"><code>loop</code></a>:</p>
|
||
|
||
<pre><code class="language-lisp">;;; same hash-table as above
|
||
CL-USER> (loop for key being the hash-keys of *my-hash*
|
||
do (print key))
|
||
FIRST-KEY
|
||
SECOND-KEY
|
||
THIRD-KEY
|
||
NIL
|
||
NIL
|
||
CL-USER> (loop for key being the hash-keys of *my-hash*
|
||
using (hash-value value)
|
||
do (format t "The value associated with the key ~S is ~S~%"
|
||
key value))
|
||
The value associated with the key FIRST-KEY is ONE
|
||
The value associated with the key SECOND-KEY is TWO
|
||
The value associated with the key THIRD-KEY is NIL
|
||
The value associated with the key NIL is NIL-VALUE
|
||
NIL
|
||
CL-USER> (loop for value being the hash-values of *my-hash*
|
||
do (print value))
|
||
ONE
|
||
TWO
|
||
NIL
|
||
NIL-VALUE
|
||
NIL
|
||
CL-USER> (loop for value being the hash-values of *my-hash*
|
||
using (hash-key key)
|
||
do (format t "~&~A -> ~A" key value))
|
||
FIRST-KEY -> ONE
|
||
SECOND-KEY -> TWO
|
||
THIRD-KEY -> NIL
|
||
NIL -> NIL-VALUE
|
||
NIL
|
||
</code></pre>
|
||
|
||
<h4 id="traversing-keys-or-values">Traversing keys or values</h4>
|
||
|
||
<p>To map over keys or values we can again rely on Alexandria with
|
||
<code>maphash-keys</code> and <code>maphash-values</code>.</p>
|
||
|
||
<p><a name="count"></a></p>
|
||
|
||
<h3 id="counting-the-entries-in-a-hash-table">Counting the Entries in a Hash Table</h3>
|
||
|
||
<p>No need to use your fingers - Common Lisp has a built-in function to
|
||
do it for you:
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_hash_1.htm"><code>hash-table-count</code></a>.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table))
|
||
*MY-HASH*
|
||
CL-USER> (hash-table-count *my-hash*)
|
||
0
|
||
CL-USER> (setf (gethash 'first *my-hash*) 1)
|
||
1
|
||
CL-USER> (setf (gethash 'second *my-hash*) 2)
|
||
2
|
||
CL-USER> (setf (gethash 'third *my-hash*) 3)
|
||
3
|
||
CL-USER> (hash-table-count *my-hash*)
|
||
3
|
||
CL-USER> (setf (gethash 'second *my-hash*) 'two)
|
||
TWO
|
||
CL-USER> (hash-table-count *my-hash*)
|
||
3
|
||
CL-USER> (clrhash *my-hash*)
|
||
#<EQL hash table, 0 entries {48205F35}>
|
||
CL-USER> (hash-table-count *my-hash*)
|
||
0
|
||
</code></pre>
|
||
|
||
<h3 id="printing-a-hash-table-readably">Printing a Hash Table readably</h3>
|
||
|
||
<p><strong>With print-object</strong> (non portable)</p>
|
||
|
||
<p>It is very tempting to use <code>print-object</code>. It works under several
|
||
implementations, but this method is actually not portable. The
|
||
standard doesn’t permit to do so, so this is undefined behaviour.</p>
|
||
|
||
<pre><code class="language-lisp">(defmethod print-object ((object hash-table) stream)
|
||
(format stream "#HASH{~{~{(~a : ~a)~}~^ ~}}"
|
||
(loop for key being the hash-keys of object
|
||
using (hash-value value)
|
||
collect (list key value))))
|
||
</code></pre>
|
||
|
||
<p>gives:</p>
|
||
|
||
<pre><code>;; WARNING:
|
||
;; redefining PRINT-OBJECT (#<STRUCTURE-CLASS COMMON-LISP:HASH-TABLE>
|
||
;; #<SB-PCL:SYSTEM-CLASS COMMON-LISP:T>) in DEFMETHOD
|
||
;; #<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT (HASH-TABLE T) {1006A0D063}>
|
||
</code></pre>
|
||
|
||
<p>and let’s try it:</p>
|
||
|
||
<pre><code class="language-lisp">(let ((ht (make-hash-table)))
|
||
(setf (gethash :foo ht) :bar)
|
||
ht)
|
||
;; #HASH{(FOO : BAR)}
|
||
</code></pre>
|
||
|
||
<p><strong>With a custom function</strong> (portable way)</p>
|
||
|
||
<p>Here’s a portable way.</p>
|
||
|
||
<p>This snippets prints the keys, values and the test function of a
|
||
hash-table, and uses <code>alexandria:alist-hash-table</code> to read it back in:</p>
|
||
|
||
<pre><code class="language-lisp">;; https://github.com/phoe/phoe-toolbox/blob/master/phoe-toolbox.lisp
|
||
(defun print-hash-table-readably (hash-table
|
||
&optional
|
||
(stream *standard-output*))
|
||
"Prints a hash table readably using ALEXANDRIA:ALIST-HASH-TABLE."
|
||
(let ((test (hash-table-test hash-table))
|
||
(*print-circle* t)
|
||
(*print-readably* t))
|
||
(format stream "#.(ALEXANDRIA:ALIST-HASH-TABLE '(~%")
|
||
(maphash (lambda (k v) (format stream " (~S . ~S)~%" k v)) hash-table)
|
||
(format stream " ) :TEST '~A)" test)
|
||
hash-table))
|
||
</code></pre>
|
||
|
||
<p>Example output:</p>
|
||
|
||
<pre><code>#.(ALEXANDRIA:ALIST-HASH-TABLE
|
||
'((ONE . 1))
|
||
:TEST 'EQL)
|
||
#<HASH-TABLE :TEST EQL :COUNT 1 {10046D4863}>
|
||
</code></pre>
|
||
|
||
<p>This output can be read back in to create a hash-table:</p>
|
||
|
||
<pre><code class="language-lisp">(read-from-string
|
||
(with-output-to-string (s)
|
||
(print-hash-table-readably
|
||
(alexandria:alist-hash-table
|
||
'((a . 1) (b . 2) (c . 3))) s)))
|
||
;; #<HASH-TABLE :TEST EQL :COUNT 3 {1009592E23}>
|
||
;; 83
|
||
</code></pre>
|
||
|
||
<p><strong>With Serapeum</strong> (readable and portable)</p>
|
||
|
||
<p>The <a href="https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#hash-tables">Serapeum library</a>
|
||
has the <code>dict</code> constructor, the function <code>pretty-print-hash-table</code> and
|
||
the <code>toggle-pretty-print-hash-table</code> switch, all which do <em>not</em> use
|
||
<code>print-object</code> under the hood.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (serapeum:toggle-pretty-print-hash-table)
|
||
T
|
||
CL-USER> (serapeum:dict :a 1 :b 2 :c 3)
|
||
(dict
|
||
:A 1
|
||
:B 2
|
||
:C 3
|
||
)
|
||
</code></pre>
|
||
|
||
<p>This printed representation can be read back in.</p>
|
||
|
||
<p><a name="size"></a></p>
|
||
|
||
<h3 id="thread-safe-hash-tables">Thread-safe Hash Tables</h3>
|
||
|
||
<p>The standard hash-table in Common Lisp is not thread-safe. That means
|
||
that simple access operations can be interrupted in the middle and
|
||
return a wrong result.</p>
|
||
|
||
<p>Implementations offer different solutions.</p>
|
||
|
||
<p>With <strong>SBCL</strong>, we can create thread-safe hash tables with the <code>:synchronized</code> keyword to <code>make-hash-table</code>: <a href="http://www.sbcl.org/manual/#Hash-Table-Extensions">http://www.sbcl.org/manual/#Hash-Table-Extensions</a>.</p>
|
||
|
||
<blockquote>
|
||
<p>If nil (the default), the hash-table may have multiple concurrent readers, but results are undefined if a thread writes to the hash-table concurrently with another reader or writer. If t, all concurrent accesses are safe, but note that <a href="http://www.lispworks.com/documentation/HyperSpec/Body/03_f.htm">clhs 3.6 (Traversal Rules and Side Effects)</a> remains in force. See also: sb-ext:with-locked-hash-table.</p>
|
||
</blockquote>
|
||
|
||
<pre><code class="language-lisp">(defparameter *my-hash* (make-hash-table :synchronized t))
|
||
</code></pre>
|
||
|
||
<p>But, operations that expand to two accesses, like the modify macros (<code>incf</code>) or this:</p>
|
||
|
||
<pre><code class="language-lisp">(setf (gethash :a *my-hash*) :new-value)
|
||
</code></pre>
|
||
|
||
<p>need to be wrapped around <code>sb-ext:with-locked-hash-table</code>:</p>
|
||
|
||
<blockquote>
|
||
<p>Limits concurrent accesses to HASH-TABLE for the duration of BODY. If HASH-TABLE is synchronized, BODY will execute with exclusive ownership of the table. If HASH-TABLE is not synchronized, BODY will execute with other WITH-LOCKED-HASH-TABLE bodies excluded – exclusion of hash-table accesses not surrounded by WITH-LOCKED-HASH-TABLE is unspecified.</p>
|
||
</blockquote>
|
||
|
||
<pre><code class="language-lisp">(sb-ext:with-locked-hash-table (*my-hash*)
|
||
(setf (gethash :a *my-hash*) :new-value))
|
||
</code></pre>
|
||
|
||
<p>In <strong>LispWorks</strong>, hash-tables are thread-safe by default. But
|
||
likewise, there is no guarantee of atomicity <em>between</em> access
|
||
operations, so we can use
|
||
<a href="http://www.lispworks.com/documentation/lw71/LW/html/lw-144.htm#pgfId-900768">with-hash-table-locked</a>.</p>
|
||
|
||
<p>Ultimately, you might like what the <a href="https://mdbergmann.github.io/cl-gserver/index.html#toc-2-4-1-hash-table-agent"><strong>cl-gserver library</strong></a>
|
||
proposes. It offers helper functions around hash-tables and its
|
||
actors/agent system to allow thread-safety. They also maintain the
|
||
order of updates and reads.</p>
|
||
|
||
<h3 id="performance-issues-the-size-of-your-hash-table">Performance Issues: The Size of your Hash Table</h3>
|
||
|
||
<p>The <code>make-hash-table</code> function has a couple of optional parameters
|
||
which control the initial size of your hash table and how it’ll grow
|
||
if it needs to grow. This can be an important performance issue if
|
||
you’re working with large hash tables. Here’s an (admittedly not very
|
||
scientific) example with <a href="http://www.cons.org/cmucl">CMUCL</a> pre-18d on
|
||
Linux:</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table))
|
||
*MY-HASH*
|
||
CL-USER> (hash-table-size *my-hash*)
|
||
65
|
||
CL-USER> (hash-table-rehash-size *my-hash*)
|
||
1.5
|
||
CL-USER> (time (dotimes (n 100000)
|
||
(setf (gethash n *my-hash*) n)))
|
||
Compiling LAMBDA NIL:
|
||
Compiling Top-Level Form:
|
||
|
||
Evaluation took:
|
||
0.27 seconds of real time
|
||
0.25 seconds of user run time
|
||
0.02 seconds of system run time
|
||
0 page faults and
|
||
8754768 bytes consed.
|
||
NIL
|
||
CL-USER> (time (dotimes (n 100000)
|
||
(setf (gethash n *my-hash*) n)))
|
||
Compiling LAMBDA NIL:
|
||
Compiling Top-Level Form:
|
||
|
||
Evaluation took:
|
||
0.05 seconds of real time
|
||
0.05 seconds of user run time
|
||
0.0 seconds of system run time
|
||
0 page faults and
|
||
0 bytes consed.
|
||
NIL
|
||
</code></pre>
|
||
|
||
<p>The values for
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_hash_4.htm"><code>hash-table-size</code></a>
|
||
and
|
||
<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_hash_2.htm"><code>hash-table-rehash-size</code></a>
|
||
are implementation-dependent. In our case, CMUCL chooses and initial
|
||
size of 65, and it will increase the size of the hash by 50 percent
|
||
whenever it needs to grow. Let’s see how often we have to re-size the
|
||
hash until we reach the final size…</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (log (/ 100000 65) 1.5)
|
||
18.099062
|
||
CL-USER> (let ((size 65))
|
||
(dotimes (n 20)
|
||
(print (list n size))
|
||
(setq size (* 1.5 size))))
|
||
(0 65)
|
||
(1 97.5)
|
||
(2 146.25)
|
||
(3 219.375)
|
||
(4 329.0625)
|
||
(5 493.59375)
|
||
(6 740.3906)
|
||
(7 1110.5859)
|
||
(8 1665.8789)
|
||
(9 2498.8184)
|
||
(10 3748.2275)
|
||
(11 5622.3413)
|
||
(12 8433.512)
|
||
(13 12650.268)
|
||
(14 18975.402)
|
||
(15 28463.104)
|
||
(16 42694.656)
|
||
(17 64041.984)
|
||
(18 96062.98)
|
||
(19 144094.47)
|
||
NIL
|
||
</code></pre>
|
||
|
||
<p>The hash has to be re-sized 19 times until it’s big enough to hold
|
||
100,000 entries. That explains why we saw a lot of consing and why it
|
||
took rather long to fill the hash table. It also explains why the
|
||
second run was much faster - the hash table already had the correct
|
||
size.</p>
|
||
|
||
<p>Here’s a faster way to do it:
|
||
If we know in advance how big our hash will be, we can start with the right size:</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table :size 100000))
|
||
*MY-HASH*
|
||
CL-USER> (hash-table-size *my-hash*)
|
||
100000
|
||
CL-USER> (time (dotimes (n 100000)
|
||
(setf (gethash n *my-hash*) n)))
|
||
Compiling LAMBDA NIL:
|
||
Compiling Top-Level Form:
|
||
|
||
Evaluation took:
|
||
0.04 seconds of real time
|
||
0.04 seconds of user run time
|
||
0.0 seconds of system run time
|
||
0 page faults and
|
||
0 bytes consed.
|
||
NIL
|
||
</code></pre>
|
||
|
||
<p>That’s obviously much faster. And there was no consing involved
|
||
because we didn’t have to re-size at all. If we don’t know the final
|
||
size in advance but can guess the growth behaviour of our hash table
|
||
we can also provide this value to <code>make-hash-table</code>. We can provide an
|
||
integer to specify absolute growth or a float to specify relative
|
||
growth.</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (defparameter *my-hash* (make-hash-table :rehash-size 100000))
|
||
*MY-HASH*
|
||
CL-USER> (hash-table-size *my-hash*)
|
||
65
|
||
CL-USER> (hash-table-rehash-size *my-hash*)
|
||
100000
|
||
CL-USER> (time (dotimes (n 100000)
|
||
(setf (gethash n *my-hash*) n)))
|
||
Compiling LAMBDA NIL:
|
||
Compiling Top-Level Form:
|
||
|
||
Evaluation took:
|
||
0.07 seconds of real time
|
||
0.05 seconds of user run time
|
||
0.01 seconds of system run time
|
||
0 page faults and
|
||
2001360 bytes consed.
|
||
NIL
|
||
</code></pre>
|
||
|
||
<p>Also rather fast (we only needed one re-size) but much more consing
|
||
because almost the whole hash table (minus 65 initial elements) had to
|
||
be built during the loop.</p>
|
||
|
||
<p>Note that you can also specify the <code>rehash-threshold</code> while creating a
|
||
new hash table. One final remark: Your implementation is allowed to
|
||
<em>completely ignore</em> the values provided for <code>rehash-size</code> and
|
||
<code>rehash-threshold</code>…</p>
|
||
|
||
<h2 id="alist">Alist</h2>
|
||
|
||
<h3 id="definition">Definition</h3>
|
||
|
||
<p>An association list is a list of cons cells.</p>
|
||
|
||
<p>This simple example:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter *my-alist* (list (cons 'foo "foo")
|
||
(cons 'bar "bar")))
|
||
;; => ((FOO . "foo") (BAR . "bar"))
|
||
</code></pre>
|
||
|
||
<p>looks like this:</p>
|
||
|
||
<pre><code>[o|o]---[o|/]
|
||
| |
|
||
| [o|o]---"bar"
|
||
| |
|
||
| BAR
|
||
|
|
||
[o|o]---"foo"
|
||
|
|
||
FOO
|
||
</code></pre>
|
||
|
||
<h3 id="construction">Construction</h3>
|
||
|
||
<p>We can construct an alist like its representation:</p>
|
||
|
||
<pre><code class="language-lisp">(setf *my-alist* '((:foo . "foo")
|
||
(:bar . "bar")))
|
||
</code></pre>
|
||
|
||
<p>The constructor <code>pairlis</code> associates a list of keys and a list of values:</p>
|
||
|
||
<pre><code class="language-lisp">(pairlis '(:foo :bar)
|
||
'("foo" "bar"))
|
||
;; => ((:BAR . "bar") (:FOO . "foo"))
|
||
</code></pre>
|
||
|
||
<p>Alists are just lists, so you can have the same key multiple times in the same alist:</p>
|
||
|
||
<pre><code class="language-lisp">(setf *alist-with-duplicate-keys*
|
||
'((:a . 1)
|
||
(:a . 2)
|
||
(:b . 3)
|
||
(:a . 4)
|
||
(:c . 5)))
|
||
</code></pre>
|
||
|
||
<h3 id="access">Access</h3>
|
||
|
||
<p>To get a key, we have <code>assoc</code> (use <code>:test 'equal</code> when your keys are
|
||
strings, as usual). It returns the whole cons cell, so you may want to
|
||
use <code>cdr</code> or <code>second</code> to get the value, or even <code>assoc-value list key</code> from <code>Alexandria</code>.</p>
|
||
|
||
<pre><code class="language-lisp">(assoc :foo *my-alist*)
|
||
;; (:FOO . "foo")
|
||
(cdr *)
|
||
;; "foo"
|
||
</code></pre>
|
||
|
||
<pre><code class="language-lisp">(alexandria:assoc-value *my-alist* :foo)
|
||
;; "foo"
|
||
;; (:FOO . "FOO")
|
||
;; It actually returned 2 values.
|
||
</code></pre>
|
||
|
||
<p>There is <code>assoc-if</code>, and <code>rassoc</code> to get a cons cell by its value:</p>
|
||
|
||
<pre><code class="language-lisp">(rassoc "foo" *my-alist*)
|
||
;; NIL
|
||
;; bummer! The value "foo" is a string, so use:
|
||
(rassoc "foo" *my-alist* :test #'equal)
|
||
;; (:FOO . "foo")
|
||
</code></pre>
|
||
|
||
<p>If the alist has repeating (duplicate) keys, you can use <code>remove-if-not</code>, for example, to retrieve all of them.</p>
|
||
|
||
<pre><code class="language-lisp">(remove-if-not
|
||
(lambda (entry)
|
||
(eq :a entry))
|
||
*alist-with-duplicate-keys*
|
||
:key #'car)
|
||
</code></pre>
|
||
|
||
<h3 id="insert-and-remove-entries">Insert and remove entries</h3>
|
||
|
||
<p>To add a key, we <code>push</code> another cons cell:</p>
|
||
|
||
<pre><code class="language-lisp">(push (cons 'team "team") *my-alist*)
|
||
;; => ((TEAM . "team") (FOO . "foo") (BAR . "bar"))
|
||
</code></pre>
|
||
|
||
<p>We can use <code>pop</code> and other functions that operate on lists, like <code>remove</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(remove :team *my-alist*)
|
||
;; ((:TEAM . "team") (FOO . "foo") (BAR . "bar"))
|
||
;; => didn't remove anything
|
||
(remove :team *my-alist* :key 'car)
|
||
;; ((FOO . "foo") (BAR . "bar"))
|
||
;; => returns a copy
|
||
</code></pre>
|
||
|
||
<p>Remove only one element with <code>:count</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(push (cons 'bar "bar2") *my-alist*)
|
||
;; ((BAR . "bar2") (TEAM . "team") (FOO . "foo") (BAR . "bar"))
|
||
;; => twice the 'bar key
|
||
|
||
(remove 'bar *my-alist* :key 'car :count 1)
|
||
;; ((TEAM . "team") (FOO . "foo") (BAR . "bar"))
|
||
|
||
;; because otherwise:
|
||
(remove 'bar *my-alist* :key 'car)
|
||
;; ((TEAM . "team") (FOO . "foo"))
|
||
;; => no more 'bar
|
||
</code></pre>
|
||
|
||
<h3 id="update-entries">Update entries</h3>
|
||
|
||
<p>Replace a value:</p>
|
||
|
||
<pre><code class="language-lisp">*my-alist*
|
||
;; => '((:FOO . "foo") (:BAR . "bar"))
|
||
(assoc :foo *my-alist*)
|
||
;; => (:FOO . "foo")
|
||
(setf (cdr (assoc :foo *my-alist*)) "new-value")
|
||
;; => "new-value"
|
||
*my-alist*
|
||
;; => '((:foo . "new-value") (:BAR . "bar"))
|
||
</code></pre>
|
||
|
||
<p>Replace a key:</p>
|
||
|
||
<pre><code class="language-lisp">*my-alist*
|
||
;; => '((:FOO . "foo") (:BAR . "bar")))
|
||
(setf (car (assoc :bar *my-alist*)) :new-key)
|
||
;; => :NEW-KEY
|
||
*my-alist*
|
||
;; => '((:FOO . "foo") (:NEW-KEY . "bar")))
|
||
</code></pre>
|
||
|
||
<p>In the
|
||
<a href="https://common-lisp.net/project/alexandria/draft/alexandria.html#Conses">Alexandria</a>
|
||
library, see more functions like <code>hash-table-alist</code>, <code>alist-plist</code>,…</p>
|
||
|
||
<h2 id="plist">Plist</h2>
|
||
|
||
<p>A property list is simply a list that alternates a key, a value, and
|
||
so on, where its keys are symbols (we can not set its <code>:test</code>). More
|
||
precisely, it first has a cons cell whose <code>car</code> is the key, whose
|
||
<code>cdr</code> points to the following cons cell whose <code>car</code> is the
|
||
value.</p>
|
||
|
||
<p>For example this plist:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter my-plist (list 'foo "foo" 'bar "bar"))
|
||
</code></pre>
|
||
|
||
<p>looks like this:</p>
|
||
|
||
<pre><code>[o|o]---[o|o]---[o|o]---[o|/]
|
||
| | | |
|
||
FOO "foo" BAR "bar"
|
||
|
||
</code></pre>
|
||
|
||
<p>We access an element with <code>getf (list elt)</code> (it returns the value)
|
||
(the list comes as first element),</p>
|
||
|
||
<p>we remove an element with <code>remf</code>.</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter my-plist (list 'foo "foo" 'bar "bar"))
|
||
;; => (FOO "foo" BAR "bar")
|
||
(setf (getf my-plist 'foo) "foo!!!")
|
||
;; => "foo!!!"
|
||
</code></pre>
|
||
|
||
<h2 id="structures">Structures</h2>
|
||
|
||
<p>Structures offer a way to store data in named slots. They support
|
||
single inheritance.</p>
|
||
|
||
<p>Classes provided by the Common Lisp Object System (CLOS) are more flexible however structures may offer better performance (see for example the SBCL manual).</p>
|
||
|
||
<h3 id="creation">Creation</h3>
|
||
|
||
<p>Use <code>defstruct</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(defstruct person
|
||
id name age)
|
||
</code></pre>
|
||
|
||
<p>At creation slots are optional and default to <code>nil</code>.</p>
|
||
|
||
<p>To set a default value:</p>
|
||
|
||
<pre><code class="language-lisp">(defstruct person
|
||
id
|
||
(name "john doe")
|
||
age)
|
||
</code></pre>
|
||
|
||
<p>Also specify the type after the default value:</p>
|
||
|
||
<pre><code class="language-lisp">(defstruct person
|
||
id
|
||
(name "john doe" :type string)
|
||
age)
|
||
</code></pre>
|
||
|
||
<p>We create an instance with the generated constructor <code>make-</code> +
|
||
<code><structure-name></code>, so <code>make-person</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter *me* (make-person))
|
||
*me*
|
||
#S(PERSON :ID NIL :NAME "john doe" :AGE NIL)
|
||
</code></pre>
|
||
|
||
<p>note that printed representations can be read back by the reader.</p>
|
||
|
||
<p>With a bad name type:</p>
|
||
|
||
<pre><code class="language-lisp">(defparameter *bad-name* (make-person :name 123))
|
||
</code></pre>
|
||
|
||
<pre><code>Invalid initialization argument:
|
||
:NAME
|
||
in call for class #<STRUCTURE-CLASS PERSON>.
|
||
[Condition of type SB-PCL::INITARG-ERROR]
|
||
</code></pre>
|
||
|
||
<p>We can set the structure’s constructor so as to create the structure
|
||
without using keyword arguments, which can be more convenient
|
||
sometimes. We give it a name and the order of the arguments:</p>
|
||
|
||
<pre><code class="language-lisp">(defstruct (person (:constructor create-person (id name age)))
|
||
id
|
||
name
|
||
age)
|
||
</code></pre>
|
||
|
||
<p>Our new constructor is <code>create-person</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(create-person 1 "me" 7)
|
||
#S(PERSON :ID 1 :NAME "me" :AGE 7)
|
||
</code></pre>
|
||
|
||
<p>However, the default <code>make-person</code> does <em>not</em> work any more:</p>
|
||
|
||
<pre><code class="language-lisp">(make-person :name "me")
|
||
;; debugger:
|
||
obsolete structure error for a structure of type PERSON
|
||
[Condition of type SB-PCL::OBSOLETE-STRUCTURE]
|
||
</code></pre>
|
||
|
||
<h3 id="slot-access">Slot access</h3>
|
||
|
||
<p>We access the slots with accessors created by <code><name-of-the-struct>-</code> + <code>slot-name</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(person-name *me*)
|
||
;; "john doe"
|
||
</code></pre>
|
||
|
||
<p>we then also have <code>person-age</code> and <code>person-id</code>.</p>
|
||
|
||
<h3 id="setting">Setting</h3>
|
||
|
||
<p>Slots are <code>setf</code>-able:</p>
|
||
|
||
<pre><code class="language-lisp">(setf (person-name *me*) "Cookbook author")
|
||
(person-name *me*)
|
||
;; "Cookbook author"
|
||
</code></pre>
|
||
|
||
<h3 id="predicate">Predicate</h3>
|
||
|
||
<p>A predicate function is generated:</p>
|
||
|
||
<pre><code class="language-lisp">(person-p *me*)
|
||
T
|
||
</code></pre>
|
||
|
||
<h3 id="single-inheritance">Single inheritance</h3>
|
||
|
||
<p>Use single inheritance with the <code>:include <struct></code> argument:</p>
|
||
|
||
<pre><code class="language-lisp">(defstruct (female (:include person))
|
||
(gender "female" :type string))
|
||
(make-female :name "Lilie")
|
||
;; #S(FEMALE :ID NIL :NAME "Lilie" :AGE NIL :GENDER "female")
|
||
</code></pre>
|
||
|
||
<p>Note that the CLOS object system is more powerful.</p>
|
||
|
||
<h3 id="limitations">Limitations</h3>
|
||
|
||
<p>After a change, instances are not updated.</p>
|
||
|
||
<p>If we try to add a slot (<code>email</code> below), we have the choice to lose
|
||
all instances, or to continue using the new definition of
|
||
<code>person</code>. But the effects of redefining a structure are undefined by
|
||
the standard, so it is best to re-compile and re-run the changed
|
||
code.</p>
|
||
|
||
<pre><code class="language-lisp">(defstruct person
|
||
id
|
||
(name "john doe" :type string)
|
||
age
|
||
email)
|
||
</code></pre>
|
||
|
||
<p>gives an error and we drop in the debugger:</p>
|
||
|
||
<pre><code>attempt to redefine the STRUCTURE-OBJECT class PERSON
|
||
incompatibly with the current definition
|
||
[Condition of type SIMPLE-ERROR]
|
||
|
||
Restarts:
|
||
0: [CONTINUE] Use the new definition of PERSON, invalidating already-loaded code and instances.
|
||
1: [RECKLESSLY-CONTINUE] Use the new definition of PERSON as if it were compatible, allowing old accessors to use new instances and allowing new accessors to use old instances.
|
||
2: [CLOBBER-IT] (deprecated synonym for RECKLESSLY-CONTINUE)
|
||
3: [RETRY] Retry SLIME REPL evaluation request.
|
||
4: [*ABORT] Return to SLIME's top level.
|
||
5: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1002A0FFA3}>)
|
||
</code></pre>
|
||
|
||
<p>If we choose restart <code>0</code>, to use the new definition, we lose access to <code>*me*</code>:</p>
|
||
|
||
<pre><code class="language-lisp">*me*
|
||
obsolete structure error for a structure of type PERSON
|
||
[Condition of type SB-PCL::OBSOLETE-STRUCTURE]
|
||
</code></pre>
|
||
|
||
<p>There is also very little introspection.
|
||
Portable Common Lisp does not define ways of finding out defined super/sub-structures nor what slots a structure has.</p>
|
||
|
||
<p>The Common Lisp Object System (which came after into the language)
|
||
doesn’t have such limitations. See the <a href="clos.html">CLOS section</a>.</p>
|
||
|
||
<ul>
|
||
<li><a href="http://www.lispworks.com/documentation/HyperSpec/Body/08_.htm">structures on the hyperspec</a></li>
|
||
<li>David B. Lamkins, <a href="http://www.communitypicks.com/r/lisp/s/17592186045679-successful-lisp-how-to-understand-and-use-common">“Successful Lisp, How to Understand and Use Common Lisp”</a>.</li>
|
||
</ul>
|
||
|
||
<h2 id="tree">Tree</h2>
|
||
|
||
<p><code>tree-equal</code>, <code>copy-tree</code>. They descend recursively into the car and
|
||
the cdr of the cons cells they visit.</p>
|
||
|
||
<h3 id="sycamore---purely-functional-weight-balanced-binary-trees">Sycamore - purely functional weight-balanced binary trees</h3>
|
||
|
||
<p><a href="https://github.com/ndantam/sycamore">https://github.com/ndantam/sycamore</a></p>
|
||
|
||
<p>Features:</p>
|
||
|
||
<ul>
|
||
<li>Fast, purely functional weight-balanced binary trees.
|
||
<ul>
|
||
<li>Leaf nodes are simple-vectors, greatly reducing tree height.</li>
|
||
</ul>
|
||
</li>
|
||
<li>Interfaces for tree Sets and Maps (dictionaries).</li>
|
||
<li><a href="http://en.wikipedia.org/wiki/Rope_(data_structure)">Ropes</a></li>
|
||
<li>Purely functional <a href="http://en.wikipedia.org/wiki/Pairing_heap">pairing heaps</a></li>
|
||
<li>Purely functional amortized queue.</li>
|
||
</ul>
|
||
|
||
<h2 id="controlling-how-much-of-data-to-print-print-length-print-level">Controlling how much of data to print (<code>*print-length*</code>, <code>*print-level*</code>)</h2>
|
||
|
||
<p>Use <code>*print-length*</code> and <code>*print-level*</code>.</p>
|
||
|
||
<p>They are both <code>nil</code> by default.</p>
|
||
|
||
<p>If you have a very big list, printing it on the REPL or in a
|
||
stacktrace can take a long time and bring your editor or even your
|
||
server down. Use <code>*print-length*</code> to choose the maximum of elements of
|
||
the list to print, and to show there is a rest with a <code>...</code>
|
||
placeholder:</p>
|
||
|
||
<pre><code class="language-lisp">(setf *print-length* 2)
|
||
(list :A :B :C :D :E)
|
||
;; (:A :B ...)
|
||
</code></pre>
|
||
|
||
<p>And if you have a very nested data structure, set <code>*print-level*</code> to
|
||
choose the depth to print:</p>
|
||
|
||
<pre><code class="language-lisp">(let ((*print-level* 2))
|
||
(print '(:a (:b (:c (:d :e))))))
|
||
;; (:A (:B #)) <= *print-level* in action
|
||
;; (:A (:B (:C (:D :E))))
|
||
;; => the list is returned,
|
||
;; the let binding is not in effect anymore.
|
||
</code></pre>
|
||
|
||
<p><code>*print-length*</code> will be applied at each level.</p>
|
||
|
||
<p>Reference: the <a href="http://clhs.lisp.se/Body/v_pr_lev.htm">HyperSpec</a>.</p>
|
||
|
||
<h2 id="appendix-a---generic-and-nested-access-of-alists-plists-hash-tables-and-clos-slots">Appendix A - generic and nested access of alists, plists, hash-tables and CLOS slots</h2>
|
||
|
||
<p>The solutions presented below might help you getting started, but keep
|
||
in mind that they’ll have a performance impact and that error messages
|
||
will be less explicit.</p>
|
||
|
||
<ul>
|
||
<li>the <a href="https://github.com/AccelerationNet/access">access</a> library (battle tested, used by the Djula templating system) has a generic <code>(access my-var :elt)</code> (<a href="https://lisp-journey.gitlab.io/blog/generice-consistent-access-of-data-structures-dotted-path/">blog post</a>). It also has <code>accesses</code> (plural) to access and set nested values.</li>
|
||
<li><a href="https://github.com/vseloved/rutils">rutils</a> as a generic <code>generic-elt</code> or <code>?</code>,</li>
|
||
</ul>
|
||
|
||
<h2 id="appendix-b---accessing-nested-data-structures">Appendix B - accessing nested data structures</h2>
|
||
|
||
<p>Sometimes we work with nested data structures, and we might want an
|
||
easier way to access a nested element than intricated “getf” and
|
||
“assoc” and all. Also, we might want to just be returned a <code>nil</code> when
|
||
an intermediary key doesn’t exist.</p>
|
||
|
||
<p>The <code>access</code> library given above provides this, with <code>(accesses var key1 key2…)</code>.</p>
|
||
|
||
|
||
<p class="page-source">
|
||
Page source: <a href="https://github.com/LispCookbook/cl-cookbook/blob/master/data-structures.md">data-structures.md</a>
|
||
</p>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
|
||
// Don't write the TOC on the index.
|
||
if (window.location.pathname != "/cl-cookbook/") {
|
||
$("#toc").toc({
|
||
content: "#content", // will ignore the first h1 with the site+page title.
|
||
headings: "h1,h2,h3,h4"});
|
||
}
|
||
|
||
$("#two-cols + ul").css({
|
||
"column-count": "2",
|
||
});
|
||
$("#contributors + ul").css({
|
||
"column-count": "4",
|
||
});
|
||
</script>
|
||
|
||
|
||
|
||
<div>
|
||
<footer class="footer">
|
||
<hr/>
|
||
© 2002–2023 the Common Lisp Cookbook Project
|
||
<div>
|
||
📹 Discover <a style="color: darkgrey; text-decoration: underline", href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">our contributor's Common Lisp video course on Udemy</a>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div id="toc-btn">T<br>O<br>C</div>
|
||
</div>
|
||
|
||
<script text="javascript">
|
||
HighlightLisp.highlight_auto({className: null});
|
||
</script>
|
||
|
||
<script type="text/javascript">
|
||
function duckSearch() {
|
||
var searchField = document.getElementById("searchField");
|
||
if (searchField && searchField.value) {
|
||
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
|
||
window.location.href = "https://duckduckgo.com/?kj=b2&kf=-1&ko=1&q=" + query;
|
||
// https://duckduckgo.com/params
|
||
// kj=b2: blue header in results page
|
||
// kf=-1: no favicons
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script async defer data-domain="lispcookbook.github.io/cl-cookbook" src="https://plausible.io/js/plausible.js"></script>
|
||
|
||
</body>
|
||
</html>
|