RosettaCodeData/Task/Associative-array-Creation/Scheme/associative-array-creation-...

384 lines
14 KiB
Scheme
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(cond-expand
(r7rs)
(chicken (import r7rs)))
(define-library (avl-trees)
;;
;; This library implements persistent (that is, immutable) AVL
;; trees for R7RS Scheme.
;;
;; References:
;;
;; * Niklaus Wirth, 1976. Algorithms + Data Structures =
;; Programs. Prentice-Hall, Englewood Cliffs, New Jersey.
;;
;; * Niklaus Wirth, 2004. Algorithms and Data Structures. Updated
;; by Fyodor Tkachov, 2014.
;;
;; THIS IS A TRIMMED-DOWN VERSION OF MY SOLUTION TO THE AVL TREES
;; TASK: https://rosettacode.org/wiki/AVL_tree#Scheme
;;
(export avl)
(export avl?)
(export avl-empty?)
(export avl-insert)
(export avl-search-values)
(export avl-check-usage)
(import (scheme base))
(import (scheme case-lambda))
(import (scheme process-context))
(import (scheme write))
(begin
(define-syntax avl-check-usage
(syntax-rules ()
((_ pred msg)
(or pred (usage-error msg)))))
(define-record-type <avl>
(%avl key data bal left right)
avl?
(key %key)
(data %data)
(bal %bal)
(left %left)
(right %right))
(define (avl)
(%avl #f #f #f #f #f))
(define (avl-empty? tree)
(avl-check-usage
(avl? tree)
"avl-empty? expects an AVL tree as argument")
(not (%bal tree)))
(define (avl-search-values pred<? tree key)
;; Return two values: the data matching the key, or #f is the
;; key is not found; and a second value that is either #f or #t,
;; depending on whether the key is found.
(define (search p)
(if (not p)
(values #f #f)
(let ((k (%key p)))
(cond ((pred<? key k) (search (%left p)))
((pred<? k key) (search (%right p)))
(else (values (%data p) #t))))))
(avl-check-usage
(procedure? pred<?)
"avl-search-values expects a procedure as first argument")
(if (avl-empty? tree)
(values #f #f)
(search tree)))
(define (avl-insert pred<? tree key data)
(define (search p fix-balance?)
(cond
((not p)
;; The key was not found. Make a new node and set
;; fix-balance?
(values (%avl key data 0 #f #f) #t))
((pred<? key (%key p))
;; Continue searching.
(let-values (((p1 fix-balance?)
(search (%left p) fix-balance?)))
(cond
((not fix-balance?)
(let ((p^ (%avl (%key p) (%data p) (%bal p)
p1 (%right p))))
(values p^ #f)))
(else
;; A new node has been inserted on the left side.
(case (%bal p)
((1)
(let ((p^ (%avl (%key p) (%data p) 0
p1 (%right p))))
(values p^ #f)))
((0)
(let ((p^ (%avl (%key p) (%data p) -1
p1 (%right p))))
(values p^ fix-balance?)))
((-1)
;; Rebalance.
(case (%bal p1)
((-1)
;; A single LL rotation.
(let* ((p^ (%avl (%key p) (%data p) 0
(%right p1) (%right p)))
(p1^ (%avl (%key p1) (%data p1) 0
(%left p1) p^)))
(values p1^ #f)))
((0 1)
;; A double LR rotation.
(let* ((p2 (%right p1))
(bal2 (%bal p2))
(p^ (%avl (%key p) (%data p)
(- (min bal2 0))
(%right p2) (%right p)))
(p1^ (%avl (%key p1) (%data p1)
(- (max bal2 0))
(%left p1) (%left p2)))
(p2^ (%avl (%key p2) (%data p2) 0
p1^ p^)))
(values p2^ #f)))
(else (internal-error))))
(else (internal-error)))))))
((pred<? (%key p) key)
;; Continue searching.
(let-values (((p1 fix-balance?)
(search (%right p) fix-balance?)))
(cond
((not fix-balance?)
(let ((p^ (%avl (%key p) (%data p) (%bal p)
(%left p) p1)))
(values p^ #f)))
(else
;; A new node has been inserted on the right side.
(case (%bal p)
((-1)
(let ((p^ (%avl (%key p) (%data p) 0
(%left p) p1)))
(values p^ #f)))
((0)
(let ((p^ (%avl (%key p) (%data p) 1
(%left p) p1)))
(values p^ fix-balance?)))
((1)
;; Rebalance.
(case (%bal p1)
((1)
;; A single RR rotation.
(let* ((p^ (%avl (%key p) (%data p) 0
(%left p) (%left p1)))
(p1^ (%avl (%key p1) (%data p1) 0
p^ (%right p1))))
(values p1^ #f)))
((-1 0)
;; A double RL rotation.
(let* ((p2 (%left p1))
(bal2 (%bal p2))
(p^ (%avl (%key p) (%data p)
(- (max bal2 0))
(%left p) (%left p2)))
(p1^ (%avl (%key p1) (%data p1)
(- (min bal2 0))
(%right p2) (%right p1)))
(p2^ (%avl (%key p2) (%data p2) 0
p^ p1^)))
(values p2^ #f)))
(else (internal-error))))
(else (internal-error)))))))
(else
;; The key was found; p is an existing node.
(values (%avl key data (%bal p) (%left p) (%right p))
#f))))
(avl-check-usage
(procedure? pred<?)
"avl-insert expects a procedure as first argument")
(if (avl-empty? tree)
(%avl key data 0 #f #f)
(let-values (((p fix-balance?) (search tree #f)))
p)))
(define (internal-error)
(display "internal error\n" (current-error-port))
(emergency-exit 123))
(define (usage-error msg)
(display "Procedure usage error:\n" (current-error-port))
(display " " (current-error-port))
(display msg (current-error-port))
(newline (current-error-port))
(exit 1))
)) ;; end library (avl-trees)
(define-library (associative-arrays)
;;
;; Persistent associative arrays for R7RS Scheme.
;;
;; The story:
;;
;; An implementation of associative arrays, where keys are compared
;; with an equal to predicate, typically has three parts:
;;
;; * a hash function, which converts a key to a hash value; and
;; the hash value either has a less than predicate or can be
;; put in a radix tree;
;;
;; * a table keyed by the hash values;
;;
;; * a way to resolve hash value collisions.
;;
;; At one extreme is the association list, which can be viewed as
;; having a hash function that *always* collides. At a nearly
;; opposite extreme are ideal hash trees, which never have
;; collisions, but which, towards that end, require hash values to
;; grow on the fly.
;;
;; Perhaps the simplest form of associative array having all three
;; parts is separate chaining: the hash function generates an
;; integer modulo some table size; the table itself is an array of
;; that size; and collisions are resolved by falling back to an
;; association list.
;;
;; Below I use my solution to the AVL Tree task
;; (https://rosettacode.org/wiki/AVL_tree#Scheme) to implement
;; *persistent* (that is, immutable) associative arrays. The hash
;; function is whatever you want, as long as it produces (what
;; Scheme regards as) a real number. Hash value collisions are
;; resolved by falling back to association lists.
;;
(export assoc-array)
(export assoc-array?)
(export assoc-array-set)
(export assoc-array-ref)
(import (scheme base))
(import (scheme case-lambda))
(import (scheme write))
(import (avl-trees))
(cond-expand
(chicken (import (only (srfi 1) alist-delete)))
;; Insert whatever you need here for your Scheme.
(else))
(begin
(define-record-type <assoc-array>
(%assoc-array hashfunc pred=? default table)
assoc-array?
(hashfunc %hashfunc)
(pred=? %pred=?)
(default %default)
(table %table))
(define assoc-array
;; Create an associative array.
(case-lambda
((hashfunc)
(let ((pred=? equal?)
(default #f))
(assoc-array hashfunc pred=? default)))
((hashfunc pred=?)
(let ((default #f))
(assoc-array hashfunc pred=? default)))
((hashfunc pred=? default)
(%assoc-array hashfunc pred=? default (avl)))))
(define (assoc-array-set array key data)
;; Produce a new associative array that is the same as the input
;; array except for the given key-data association. The input
;; array is left unchanged (which is why the procedure is called
;; assoc-array-set rather than assoc-array-set!).
(let ((hashfunc (%hashfunc array))
(pred=? (%pred=? array))
(default (%default array))
(table (%table array)))
(let ((hash-value (hashfunc key)))
;; The following could be made more efficient by combining
;; the search and insert operations for the AVL tree.
(let*-values
(((alst found?) (avl-search-values < table hash-value)))
(cond
(found?
;; Add a new entry to the association list. Removal of
;; any old associations with the key is not strictly
;; necessary, but without it the associative array will
;; grow every time you replace an
;; association. (Alternatively, you could occasionally
;; clean the associative array of shadowed key
;; associations.)
(let* ((alst (alist-delete key alst pred=?))
(alst `((,key . ,data) . ,alst))
(table (avl-insert < table hash-value alst)))
(%assoc-array hashfunc pred=? default table)))
(else
;; Start a new association list.
(let* ((alst `((,key . ,data)))
(table (avl-insert < table hash-value alst)))
(%assoc-array hashfunc pred=? default table))))))))
(define (assoc-array-ref array key)
;; Return the data associated with the key. If the key is not in
;; the table, return the associative arrays default data.
(let* ((hashfunc (%hashfunc array))
(hash-value (hashfunc key)))
(let*-values
(((alst found?)
(avl-search-values < (%table array) hash-value)))
(if found?
(let ((pair (assoc key alst (%pred=? array))))
(if pair
(cdr pair)
(%default array)))
(%default array)))))
)) ;; end library (associative-arrays)
(cond-expand
(DEMONSTRATION
(begin
(import (scheme base))
(import (scheme write))
(import (srfi 151))
(import (associative-arrays))
;; I like SpookyHash, but for this demonstration I shall use the
;; simpler ElfHash and define it only for strings. See
;; https://en.wikipedia.org/w/index.php?title=PJW_hash_function&oldid=997863283
(define (hashfunc s)
(let ((n (string-length s))
(h 0))
(do ((i 0 (+ i 1)))
((= i n))
(let* ((ch
;; If the character is outside the 8-bit range,
;; probably I should break it into four bytes, each
;; incorporated separately into the hash. For this
;; demonstration, I shall simply discard the higher
;; bits.
(bitwise-and (char->integer (string-ref s i))
#xFF))
(h^ (+ (arithmetic-shift h 4) ch))
(high^ (bitwise-and h^ #xF0000000)))
(unless (zero? high^)
(set! h^
(bitwise-xor h^ (arithmetic-shift high^ -24))))
(set! h (bitwise-and h^ (bitwise-not high^)))))
h))
(let* ((a1 (assoc-array hashfunc))
(a2 (assoc-array-set a1 "A" #\A))
(a3 (assoc-array-set a2 "B" #x42)) ; ASCII B.
(a4 (assoc-array-set a3 "C" "C")))
(write (assoc-array-ref a1 "A")) (newline)
(write (assoc-array-ref a1 "B")) (newline)
(write (assoc-array-ref a1 "C")) (newline)
(write (assoc-array-ref a2 "A")) (newline)
(write (assoc-array-ref a2 "B")) (newline)
(write (assoc-array-ref a2 "C")) (newline)
(write (assoc-array-ref a3 "A")) (newline)
(write (assoc-array-ref a3 "B")) (newline)
(write (assoc-array-ref a3 "C")) (newline)
(write (assoc-array-ref a4 "A")) (newline)
(write (assoc-array-ref a4 "B")) (newline)
(write (assoc-array-ref a4 "C")) (newline))
))
(else))