License Plates
May 16, 2017
There are lots of ways to store the set of license plate numbers. Since 26 × 26 × 27 = 17576 isn’t a particularly big number, we decide to create an array of 17576 lists; the array is indexed from AAA = 0 to ZZZ = 17575, and the lists are unsorted, which is sufficient because the average list will have only about six items. Here is our interface to the license plate set:
(define licenses (make-vector 17576 (list)))
(define (hash license) (define (c->i c) (- (char->integer (char-upcase c)) 65)) (let loop ((cs (string->list license)) (e 2) (s 0)) (if (negative? e) s (loop (cdr cs) (- e 1) (+ (* 26 s) (c->i (car cs)))))))
(define (adjoin license) (when (not (license? license)) (vector-set! licenses (hash license) (cons (substring license 3 6) (vector-ref licenses (hash license))))))
(define (license? license) (member (substring license 3 6) (vector-ref licenses (hash license))))
(define (start-list half-license) (sort string<? (vector-ref licenses (hash half-license))))
For testing and demonstration, here is a function that clears the licenses
table, generates a hundred thousand random license plates, and inserts them in the license plate set:
(define (create-random-data n) (define (i->c i) (integer->char (+ i 65))) (define (i->d i) (integer->char (+ i 48))) (set! licenses (make-vector 17576 (list))) (do ((n n (- n 1))) ((zero? n)) (adjoin (list->string (cons (i->c (randint 26)) (cons (i->c (randint 26)) (cons (i->c (randint 26)) (cons (i->d (randint 10)) (cons (i->d (randint 10)) (list (i->d (randint 10))))))))))))
And here are the answers to the three questions in the exercise, for our random set of license plates:
> (license? "PLB123") #f > (length (start-list "PLB")) 2 > (start-list "PLB") ("287" "345")
The longest list has 19 elements, there are 68 unused license plate starters, and our random generator created 272 duplicate license plate numbers:
> (apply max (map length (vector->list licenses))) 19 > (length (filter zero? (map length (vector->list licenses)))) 68 > (apply + (map length (vector->list licenses))) 99728
You can run the program at https://programmingpraxis.com/2017/05/16/license-plates/3/.
There are many other ways to write the requested program. You could write the list of license plate numbers to a file, then use grep
to search. You could use hash tables or tries. A ternary search trie might be the best solution, as it works extremely well on string data and admits sorted searches on the data.
In Python
MUMPS V1
MUMPS V1
Using SQLite.
I used this shell program to generate a list of license plate numbers:
#!/bin/bash
NUM_TIMES=100000
for ((i == 0 ; i < NUM_TIMES ; i++)); do
HEAD=$(head -c 3 < /dev/urandom | tr --complement 'A-Z' 'A-ZA-ZA-ZA-ZA-ZA-ZA-ZA-Z')
TAIL=$(head -c 3 < /dev/urandom | tr --complement '0-9' '0-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-9')
echo $HEAD-$TAIL
done
Then I piped it through
sort
anduniq
to make a list with no duplicates intocleaned.txt
.Then I can use this shell program to query in the 3 ways required (membership, prefix count, and prefix listing):
#!/bin/bash
case "$1" in
(member) # membership query #
<cleaned.txt grep --silent '^'"$2"'$'
if test "$?" -eq 0
then
echo "'$2' is a member of this set."
else
echo "'$2' is not a member of this set."
exit 1
fi
;;
(prefix) # prefix count #
COUNT=$(<cleaned.txt grep --count '^'"$2")
if test "$COUNT" -eq 0
then
echo "No license plates begin with '$2'."
exit 1
elif test "$COUNT" -eq 1
then
echo "1 license plate begins with '$2'."
else
echo "$COUNT license plates begin with '$2'."
fi
;;
(list) # prefix list #
<cleaned.txt grep '^'"$2"
if test "$?" -ne 0
then
echo "No license plates begin with '$2'."
exit 1
fi
;;
(*)
echo "unknown command - use 'member', 'prefix', or 'list'."
exit 1
;;
esac
edit of above comment:
#!/bin/bash
NUM_TIMES=100000
for ((i == 0 ; i < NUM_TIMES ; i++)); do
HEAD=$(head -c 3 < /dev/urandom | tr --complement 'A-Z' 'A-ZA-ZA-ZA-ZA-ZA-ZA-ZA-Z')
TAIL=$(head -c 3 < /dev/urandom | tr --complement '0-9' '0-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-9')
echo $HEAD-$TAIL
done
—
#!/bin/bash
case "$1" in
(member) # membership query #
<cleaned.txt grep --silent '^'"$2"'$'
if test "$?" -eq 0
then
echo "'$2' is a member of this set."
else
echo "'$2' is not a member of this set."
exit 1
fi
;;
(prefix) # prefix count #
COUNT=$(<cleaned.txt grep --count '^'"$2")
if test "$COUNT" -eq 0
then
echo "No license plates begin with '$2'."
exit 1
elif test "$COUNT" -eq 1
then
echo "1 license plate begins with '$2'."
else
echo "$COUNT license plates begin with '$2'."
fi
;;
(list) # prefix list #
<cleaned.txt grep '^'"$2"
if test "$?" -ne 0
then
echo "No license plates begin with '$2'."
exit 1
fi
;;
(*)
>&2 echo "unknown command - use 'member', 'prefix', or 'list'."
exit 1
;;
esac