;;; -*- Mode: Scheme -*- ;;;; Skip Lists ;;;; (Optimized for MIT Scheme ) ;;; This code is written by Taylor R. Campbell and placed in the Public ;;; Domain. All warranties are disclaimed. ;;; The skip list is a probabilistic data structure comparable to ;;; balanced binary trees in running time of map and set operations. ;;; ;;; This code depends on SRFIs 9 (DEFINE-RECORD-TYPE), 23 (ERROR), and ;;; 27 (random number generation). The dependency on SRFI 27 could be ;;; elided with a suitable replacement for the definition of FLIP-COIN, ;;; at the bottom of the file. ;;; ;;; This code is permanently stored at ;;; ;;; ; ;;; ;;; it was derived from the portable implementation at ;;; ;;; . ;;;; Documentation ;;; (MAKE-SKIP-LIST-TYPE ) -> skip-list-type ;;; (MAKE-SKIP-LIST-SET-TYPE ) -> skip-list-type ;;; (MAKE-SKIP-LIST-MAP-TYPE ) -> skip-list-type ;;; Constructors for skip list types. must be a ;;; procedure of one argument, an element appropriate for storage ;;; in a skip list of this type; it returns the key by which the ;;; element will be ordered. must be a procedure ;;; of two arguments, the keys of elements appropriate for storage ;;; in the skip list, and return a boolean value totally ordering ;;; the elements. ;;; ;;; Skip list types contain closures over the given key selectors ;;; and key comparators, and if these procedures are integrated at ;;; call sites, the actual generated code can be specialized ;;; without general procedure calls. MAKE-SKIP-LIST-SET-TYPE ;;; supplies a key selector of the identity procedure, for sets; ;;; MAKE-SKIP-LIST-MAP-TYPE supplies a key selector of CAR, for ;;; maps whose associations are represented with pairs. ;;; ;;; (SKIP-LIST-TYPE? ) -> boolean ;;; Disjoint type predicate for skip list types. ;;; ;;; (SKIP-LIST-TYPE/KEY-SELECTOR ) -> key-selector ;;; (SKIP-LIST-TYPE/KEY-COMPARATOR ) -> key-comparator ;;; Accessors for the key selector and key comparator procedures of ;;; skip list types. ;;; ;;; (MAKE-SKIP-LIST []) -> skip-list ;;; Skip list constructor. must be a skip list ;;; type, and must be a non-negative integer, which ;;; indicates to what depth the skip list is allowed to expand. ;;; The default is 32, which suffices for four billion elements to ;;; be accessible on average with fewer than 32 key comparisons. ;;; ;;; (SKIP-LIST? ) -> boolean ;;; Disjoint type predicate for skip lists. ;;; ;;; (SKIP-LIST/TYPE ) -> skip-list-type ;;; Returns 's type. ;;; ;;; (SKIP-LIST/COUNT ) -> exact, non-negative integer ;;; Returns the number of elements in . ;;; ;;; (LIST->SKIP-LIST ) -> skip-list ;;; (SKIP-LIST->LIST ) -> list ;;; LIST->SKIP-LIST returns a skip list of the given type ;;; containing all of the elements in . SKIP-LIST->LIST ;;; returns a sorted list (according to 's type's key ;;; comparator) of all the elements in . ;;;;; Skip List Operations ;;; (SKIP-LIST/LOOKUP ) -> element ;;; Returns the element in whose key is equivalent to ;;; , or if there is no such element. ;;; must be supplied because of the common error of accidentally ;;; preventing sets from containing #F caused by the interface to ;;; the set; by requiring programmers to write it explicitly, at ;;; least they are acknowledging that #F means an out-of-band ;;; token. ;;; ;;; (SKIP-LIST/CONTAINS-KEY? ) -> boolean ;;; Returns true of contains an element whose key is ;;; equivalent to , and false if not. ;;; ;;; (SKIP-LIST/INSERT! ) ;;; Inserts into . If already ;;; contains an element with a key equivalent to 's, ;;; SKIP-LIST/INSERT! replaces that element with . ;;; ;;; (SKIP-LIST/INTERN! ) -> element ;;; If contains an element with a key equivalent to ;;; , SKIP-LIST/INTERN! returns that element. Otherwise, it ;;; inserts and returns the element yielded by calling the ;;; procedure with one argument, . It is an error ;;; if returns an element that does not have a key ;;; equivalent to . ;;; ;;; (SKIP-LIST/DELETE! ) ;;; If contains an element with a key equivalent to ;;; , SKIP-LIST/DELETE! deletes that element from ;;; . ;;; ;;; (SKIP-LIST/MIN ) -> element ;;; Returns the element with the smallest key in . It ;;; is an error if is empty. ;;; ;;; (SKIP-LIST/DELETE-MIN! ) -> element ;;; Returns and deletes from its element with the ;;; smallest key. It is an error if is empty. ;;;;; Primitive Skip List Operations ;;; The above operations on skip list are all implemented in terms of ;;; the following general skip list operations: ;;; ;;; (SKIP-LIST/SEARCH ) ;;; Searches in for an element with a key equivalent to ;;; . If such an element is found, SKIP-LIST/SEARCH calls the ;;; procedure in a tail position with the element as its ;;; sole argument; otherwise, it calls the procedure ;;; with zero arguments. For example, SKIP-LIST/LOOKUP could have ;;; been defined in terms of SKIP-LIST/SEARCH like so: ;;; ;;; (define (skip-list/lookup skip-list key default) ;;; (skip-list/search skip-list key ;;; (lambda (element) element) ;;; (lambda () default))) ;;; ;;; (SKIP-LIST/UPDATE! ) ;;; Searches in for an element with a key equivalent to ;;; , building up the necessary internal data structures for ;;; updating the structure of . If such an element is ;;; found, then SKIP-LIST/UPDATE! calls the procedure in ;;; a tail position with three arguments: ;;; ;;; 1. The element that was found. ;;; 2. A unary procedure to replace the element with another ;;; one: (REPLACE ). must have a ;;; key equivalent to ; it is an (unchecked) error if ;;; does not. ;;; 3. A nullary procedure to delete the element: (DELETE). ;;; ;;; If no element with a key equivalent to is found, then ;;; the procedure is called with one argument, a ;;; unary procedure to insert an element with the key in the skip ;;; list: (INSERT ). It is an (unchecked) error if ;;; does not have a key equivalent to . ;;; ;;; For example, SKIP-LIST/INTERN! could have been defined like so: ;;; ;;; (define (skip-list/intern! skip-list key generator) ;;; (skip-list/update! skip-list key ;;; (lambda (element replace delete) ;;; replace delete ;ignore ;;; element) ;;; (lambda (insert) ;;; (let ((element (generator key))) ;;; (insert element) ;;; element)))) ;;; ;;; (SKIP-LIST/UPDATE-MIN! ) ;;; If is non-empty, this is like SKIP-LIST/UPDATE!, ;;; but for the element with the smallest key in , ;;; rather than the element corresponding with a given key. If ;;; is empty, this calls in a tail position ;;; with zero arguments. Example: ;;; ;;; (define (skip-list/delete-min! skip-list) ;;; (skip-list/update-min! skip-list ;;; (lambda (element replace delete) ;;; replace ;ignore ;;; (delete) ;;; element) ;;; (lambda () (error "Empty skip list:" skip-list)))) ;;;;; Skip List Map Operations ;;; Skip lists are often useful specialized as maps, where the elements ;;; of the skip list are pairs whose cars are the keys of the map and ;;; whose cdrs are the data of the map. The MAKE-SKIP-LIST-MAP-TYPE ;;; constructs skip list types that follow this structure, given a key ;;; comparator. Several operations are provided for this special ;;; case. Whether mutative operations such as SKIP-LIST/MAP-UPDATE! ;;; replace elements (associations) in the skip list, or alter the cdrs ;;; of existing elements, is unspecified. ;;; ;;; (SKIP-LIST/MAP-SEARCH ) ;;; Searches in for a datum associated with . If ;;; such a datum is found, SKIP-LIST/MAP-SEARCH calls in ;;; a tail position with that datum as a single argument; otherwise, ;;; SKIP-LIST/MAP-SEARCH calls in a tail position ;;; with zero arguments. ;;; ;;; (SKIP-LIST/MAP-LOOKUP ) -> datum ;;; Returns the datum associated with in , or ;;; if there is none. ;;; ;;; (SKIP-LIST/MAP-UPDATE! ;;; ) ;;; Like SKIP-LIST/UPDATE!, but calls the procedure with ;;; the datum associated with in , rather than the ;;; element (association) whose key is . ;;; ;;; (SKIP-LIST/MAP-INSERT! ) ;;; Inserts an association between and , i.e. a pair ;;; ( . ), into . ;;; ;;; (SKIP-LIST/MAP-MODIFY! ) ;;; If contains a datum associated with , ;;; SKIP-LIST/MAP-MODIFY! replaces that datum with the result of ;;; applying to it. Otherwise, SKIP-LIST/MAP-MODIFY! ;;; inserts an association whose key is and whose datum is ;;; the result of applying to . ;;; ;;; (SKIP-LIST/MAP-INTERN! ) ;;; If contains a datum associated with , ;;; SKIP-LIST/MAP-INTERN! returns that datum; otherwise, it calls ;;; the procedure with zero arguments to generate a new ;;; datum, inserts an association between and that datum into ;;; , and returns that datum. ;;; ;;; (SKIP-LIST/MAP-MIN ) -> [key datum] ;;; Returns two values: the smallest key in and its ;;; associated datum. It is an error if is empty. ;;; ;;; (SKIP-LIST/MAP-DELETE-MIN! ) -> [key datum] ;;; Returns two values -- the smallest key in and its ;;; associated datum --, and removes the association from ;;; . It is an error if is empty. ;;; ;;; (SKIP-LIST/MAP-UPDATE-MIN! ) ;;; Like SKIP-LIST/UPDATE-MIN!, but passes the smallest key and its ;;; associated datum, rather than the element whose key is , ;;; to , and the REPLACE procedure takes a datum to ;;; associate with , not an element whose key is . ;;;;; Built-In Skip List Types ;;; Several skip list types are provided already defined. ;;; ;;; SKIP-LIST-TYPE:REAL-NUMBER-SET ;;; SKIP-LIST-TYPE:REAL-NUMBER-MAP ;;; Arbitrary real numbers -- any object on which the `<' ;;; procedure is defined, which by the R5RS is any object ;;; satisfying the REAL? predicate. ;;; ;;; SKIP-LIST-TYPE:EXACT-INTEGER-SET ;;; SKIP-LIST-TYPE:EXACT-INTEGER-MAP ;;; Like above, but restricted to exact integers, for efficiency. ;;; These exact integers are not limited to a particular range. ;;; ;;; SKIP-LIST-TYPE:SYMBOL-SET ;;; SKIP-LIST-TYPE:SYMBOL-MAP ;;; Symbols, ordered by STRINGSTRING. ;;; ;;; SKIP-LIST-TYPE:STRING-SET SKIP-LIST-TYPE:STRING-SET-CI ;;; SKIP-LIST-TYPE:STRING-MAP SKIP-LIST-TYPE:STRING-MAP-CI ;;; Strings, ordered by STRINGKEY ;; and KEY (%make-skip-list-type key-selector key-comparator operation/search operation/update! operation/lookup operation/contains-key? operation/insert! operation/intern! operation/delete! ) skip-list-type? (key-selector skip-list-type/key-selector) (key-comparator skip-list-type/key-comparator) (operation/search skip-list-type/operation/search) (operation/update! skip-list-type/operation/update!) (operation/lookup skip-list-type/operation/lookup) (operation/contains-key? skip-list-type/operation/contains-key?) (operation/insert! skip-list-type/operation/insert!) (operation/intern! skip-list-type/operation/intern!) (operation/delete! skip-list-type/operation/delete!) ) (define-record-type (%make-skip-list type header max-level) skip-list? (type skip-list/type) (header skip-list/header set-skip-list/header!) (max-level skip-list/max-level)) (define (make-skip-list type . max-level) (%make-skip-list type (empty-header-node) (fix:+ (bottom-level) (if (pair? max-level) (car max-level) (default-max-level))))) (define-integrable (skip-list/level skip-list) (node-level (skip-list/header skip-list))) (define-integrable (default-max-level) 32) (define (random-level skip-list) (let ((max-level (skip-list/max-level skip-list))) (let loop ((level (fix:+ 1 (bottom-level)))) (if (and (fix:< level max-level) (flip-coin)) (loop (fix:+ level 1)) level)))) ;;;; Skip List Operations (define-integrable (skip-list/search skip-list key if-found if-not-found) ((skip-list-type/operation/search (skip-list/type skip-list)) skip-list key if-found if-not-found)) (define-integrable (skip-list/update! skip-list key if-found if-not-found) ((skip-list-type/operation/update! (skip-list/type skip-list)) skip-list key if-found if-not-found)) (define-integrable (skip-list/lookup skip-list key default) ((skip-list-type/operation/lookup (skip-list/type skip-list)) skip-list key default)) (define-integrable (skip-list/contains-key? skip-list key) ((skip-list-type/operation/contains-key? (skip-list/type skip-list)) skip-list key)) (define-integrable (skip-list/insert! skip-list element) ((skip-list-type/operation/insert! (skip-list/type skip-list)) skip-list element)) (define-integrable (skip-list/intern! skip-list key generator) ((skip-list-type/operation/intern! (skip-list/type skip-list)) skip-list key generator)) (define-integrable (skip-list/delete! skip-list key) ((skip-list-type/operation/delete! (skip-list/type skip-list)) skip-list key)) (define (skip-list->list skip-list) (let ((header (skip-list/header skip-list))) (if (bottom-level? (node-level header)) '() (let loop ((node (node-next header)) (elements '())) (let ((elements (cons (node-element node) elements)) (node* (node-next node))) (if (node? node*) (loop node* elements) (reverse elements))))))) (define (list->skip-list list skip-list-type) (let ((skip-list (make-skip-list skip-list-type))) (for-each (lambda (element) (skip-list/insert! skip-list element)) list) skip-list)) ;++ sorted-list->skip-list ;++ skip-list->ascending-list versus skip-list->descending-list? ;;;;; More Operations (define (skip-list/count skip-list) (let ((header (skip-list/header skip-list))) (if (bottom-level? (node-level header)) 0 (let loop ((node (node-next header)) (count 1)) (let ((node* (node-next node))) (if (node? node*) (loop node* (fix:+ count 1)) count)))))) (define (skip-list/min skip-list) (let ((header (skip-list/header skip-list))) (if (bottom-level? (node-level header)) (error "Empty skip list has no minimum:" skip-list) (node-element (node-next header))))) (define (skip-list/delete-min! skip-list) (let ((header (skip-list/header skip-list))) (if (bottom-level? (node-level header)) (error "Empty skip list has no minimum to delete:" skip-list) (let ((node (node-next header))) (if (node? (node-next node)) (move-forward! node (node-level node) header) (set-skip-list/header! skip-list (empty-header-node))) (node-element node))))) (define-integrable (skip-list/update-min! skip-list if-found if-empty) (let ((header (skip-list/header skip-list))) (if (bottom-level? (node-level header)) (if-empty) (let ((node (node-next header))) (if-found (node-element node) (lambda (replacement) ;replace (declare (integrate replacement)) (set-node-element! node replacement)) (lambda () ;delete (if (node? (node-next node)) (move-forward! node (node-level node) header) (set-skip-list/header! skip-list (empty-header-node))))))))) ;;;;; Map Operations (define-integrable (skip-list/map-search skip-list key if-found if-not-found) (skip-list/search skip-list key (lambda (element) (declare (integrate element)) (if-found (cdr element))) if-not-found)) (define-integrable (skip-list/map-lookup skip-list key default) (cdr (skip-list/lookup skip-list key (cons key default)))) (define-integrable (skip-list/map-update! skip-list key if-found if-not-found) (skip-list/update! skip-list key (lambda (element replace delete) (declare (ignore replace) (integrate delete)) (if-found (cdr element) (lambda (replacement) (set-cdr! element replacement)) delete)) (lambda (insert) (declare (integrate insert)) (if-not-found (lambda (datum) (insert (cons key datum))))))) (define-integrable (skip-list/map-modify! skip-list key default modifier) (skip-list/map-update! skip-list key (lambda (datum replace delete) (declare (ignore delete) (integrate datum replace)) (replace (modifier datum))) (lambda (insert) (declare (integrate insert)) (insert (modifier default))))) (define-integrable (skip-list/map-insert! skip-list key datum) (skip-list/insert! skip-list (cons key datum))) (define-integrable (skip-list/map-intern! skip-list key generator) (cdr (skip-list/intern! skip-list key (lambda (key) (cons key (generator key)))))) (define-integrable (skip-list/map-min skip-list) (let ((min (skip-list/min skip-list))) (values (car min) (cdr min)))) (define-integrable (skip-list/map-delete-min! skip-list) (let ((min (skip-list/delete-min! skip-list))) (values (car min) (cdr min)))) (define-integrable (skip-list/map-update-min! skip-list if-found if-empty) (skip-list/update-min! skip-list (lambda (element replace delete) (declare (ignore replace) (integrate delete)) (if-found (car element) (cdr element) (lambda (replacement) (set-cdr! element replacement)) delete)) if-empty)) ;;;; Node Abstraction ;;; We could cache the key associated with each object, but we expect ;;; them to be cheap to compute anyway, and this saves us some ;;; storage. ;;; ;;; Note: the NON-NODE procedure must return the same value each time, ;;; in the sense of EQ?. (define-integrable (make-header-node level) (make-node level 'HEADER-DUMMY)) (define-integrable (empty-header-node) (make-header-node (bottom-level))) ;;; This implementation of nodes uses an extra cons cell, but requires ;;; no frobnication of level indices. ;; (define-integrable (bottom-level) 0) ;; (define-integrable (bottom-level? level) (fix:zero? level)) ;; ;; (define-integrable (make-node level element) ;; (cons element (make-vector level (non-node)))) ;; ;; (define-integrable (non-node) #f) ;; (define-integrable (node? object) (pair? object)) ;; (define-integrable (node-level node) (vector-length (cdr node))) ;; (define-integrable (node-element node) (car node)) ;; (define-integrable (set-node-element! node element) (set-car! node element)) ;; (define-integrable (node-next node) (node-forward node 0)) ;; (define-integrable (node-forward node level) (vector-ref (cdr node) level)) ;; (define-integrable (set-node-forward! node level forward) ;; (vector-set! (cdr node) level forward)) ;; ;; (define (move-forward! source send target) ;; (subvector-move-right! (cdr source) 0 send (cdr target) 0)) ;; ;; (define (fill-forward! node start end forward) ;; (subvector-fill! (cdr node) start end forward)) ;;; This implementation of nodes uses one-element longer vectors, and ;;; zero-based level indices, but must adjust the level indices for ;;; any access to the vector. ;; (define-integrable (bottom-level) 0) ;; (define-integrable (bottom-level? level) (fix:zero? level)) ;; ;; (define-integrable (make-node level element) ;; (let ((node (make-vector (fix:+ level 1) (non-node)))) ;; (vector-set! node 0 element) ;; node)) ;; ;; (define-integrable (non-node) #f) ;; (define-integrable (node? object) (vector? object)) ;; (define-integrable (node-level node) (fix:- (vector-length node) 1)) ;; (define-integrable (node-element node) (vector-ref node 0)) ;; (define-integrable (set-node-element! node element) ;; (vector-set! node 0 element)) ;; (define-integrable (node-next node) (vector-ref node 1)) ;; (define-integrable (node-forward node level) ;; (vector-ref node (fix:+ level 1))) ;; (define-integrable (set-node-forward! node level forward) ;; (vector-set! node (fix:+ level 1) forward)) ;; ;; (define-integrable (move-forward! source send target) ;; (subvector-move-right! source 1 (fix:+ send 1) target 1)) ;; ;; (define-integrable (fill-forward! node start end forward) ;; (subvector-fill! node (fix:+ start 1) (fix:+ end 1) forward)) ;;; This implementation uses a one-element longer vector, and ;;; represents levels by one-based indices. In MIT Scheme this is ;;; about 5% faster than either of the above two. In most systems this ;;; will use much a little bit less storage than the pair-based one, ;;; proportional to the number of elements in the skip list. (define-integrable (bottom-level) 1) (define-integrable (bottom-level? level) (fix:= level 1)) (define-integrable (make-node level element) (let ((node (make-vector level (non-node)))) (vector-set! node 0 element) node)) (define-integrable (non-node) #f) (define-integrable (node? object) (vector? object)) (define-integrable (node-level node) (vector-length node)) (define-integrable (node-element node) (vector-ref node 0)) (define-integrable (set-node-element! node element) (vector-set! node 0 element)) (define-integrable (node-next node) (vector-ref node 1)) (define-integrable (node-forward node level) (vector-ref node level)) (define-integrable (set-node-forward! node level forward) (vector-set! node level forward)) (define-integrable (move-forward! source send target) (do ((si 1 (fix:+ si 1)) (ti 1 (fix:+ ti 1))) ((fix:= si send)) (vector-set! target ti (vector-ref source si)))) (define-integrable (fill-forward! node start end forward) (do ((index start (fix:+ index 1))) ((fix:= index end)) (vector-set! node index forward))) ;;;; Skip List Algorithms ;;; This is where the guts of the algorithms go. This is all part of ;;; one big procedure so that if we so desire we can copy the body of ;;; MAKE-SKIP-LIST-TYPE at call sites to specialize ELEMENT->KEY and ;;; KEYkey keykey keykey element*))) (cond ((keykey element*))) (cond ((key new-level old-level) (let ((old-header (skip-list/header skip-list)) (new-header (make-header-node new-level))) (move-forward! old-header old-level new-header) (fill-forward! new-header old-level new-level new-node) (set-skip-list/header! skip-list new-header))))))) ;;;; MAKE-SKIP-LIST-TYPE continued: deletion algorithm (define (do-delete skip-list key current-level current-node update) (let ((max-level (skip-list/level skip-list)) (delete-node (node-forward current-node current-level))) (define (unsplice level node) (set-node-forward! node level (node-forward delete-node level))) (define (delete-up level) (if (fix:< level max-level) (let* ((node (vector-ref update level)) (node* (node-forward node level))) (if (eq? node* delete-node) (begin (unsplice level node) (delete-up (fix:+ level 1))))))) (define (delete-across level node) (let ((node* (node-forward node level))) (if (node? node*) (let* ((element* (node-element node*)) (key* (element->key element*))) (if (keykey element) (lambda (element* replace delete) (declare (ignore element* delete) (integrate replace)) (replace element)) (lambda (insert) (declare (integrate insert)) (insert element)))) (define (intern! skip-list key generator) (update! skip-list key (lambda (element replace delete) (declare (ignore replace delete) (integrate element)) element) (lambda (insert) (declare (integrate insert)) (let ((element (generator key))) (let ((key* (element->key element))) (if (or (keykey key. (define-syntax in-skip-list (syntax-rules () ((IN-SKIP-LIST (element-variable) (skip-list-expression) next . rest) (next (((SKIP-LIST) skip-list-expression)) ;Outer bindings ((NODE (SKIP-LIST/INITIAL-NODE SKIP-LIST) ;Loop variables NODE*)) () ;Entry bindings ((NOT (NODE? NODE))) ;Termination conditions (((element-variable) (NODE-ELEMENT NODE)) ;Body bindings ((NODE*) (NODE-NEXT NODE))) () ;Final bindings . rest)))) (define (skip-list/initial-node skip-list) (let ((header (skip-list/header skip-list))) (if (bottom-level? (node-level header)) #f (node-next header)))) ;;;; Testing, Diagnostic, and General Utilities ;;; This is no excuse for a proper test suite. (define (test-skip-list count) (let ((set (make-skip-list skip-list-type:string-set))) (do ((i 0 (+ i 1))) ((>= i count)) (let ((s (number->string i))) (skip-list/insert! set s) (if (not (= i (string->number (skip-list/lookup set s "-1")))) (error "Intermediate lossage in insertion:" i)))) (do ((i 0 (+ i 2))) ((>= i count)) (let ((s (number->string i))) (skip-list/delete! set s) (if (skip-list/lookup set s #f) (error "Intermediate lossage in deletion:" i)))) (do ((i 0 (+ i 1))) ((>= i count)) (let ((s (number->string i))) (if (if (even? i) (skip-list/lookup set s #f) (not (= i (string->number (skip-list/lookup set s "-1"))))) (error "Post-lossage:" i)))) set)) ;++ Implement SKIP-LIST-HORIZONTALS. (define (skip-list/verticals skip-list) (define (vertical-elements node) (let loop ((level (bottom-level)) (elements '())) (if (fix:= level (node-level node)) elements (let ((node* (node-forward node level))) (if (node? node*) (loop (fix:+ level 1) (cons (node-element node*) elements)) elements))))) (let ((header (skip-list/header skip-list))) (if (bottom-level? (node-level header)) '() (let loop ((node header) (verticals '())) (let ((node* (node-next node))) (if (node? node*) (loop node* (cons (vertical-elements node) verticals)) (reverse verticals))))))) (define (flip-coin) (fix:zero? (random-integer 2)))