WIP on queue library, nearly finished.
This commit is contained in:
parent
241649fcae
commit
25934fc674
361
graph/graph/queue.lp2.rkt
Normal file
361
graph/graph/queue.lp2.rkt
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
#lang scribble/lp2
|
||||||
|
@(require "../lib/doc.rkt")
|
||||||
|
@doc-lib-setup
|
||||||
|
|
||||||
|
@title[#:style manual-doc-style]{Purely functional queue library}
|
||||||
|
|
||||||
|
@(table-of-contents)
|
||||||
|
|
||||||
|
@section{Introduction}
|
||||||
|
|
||||||
|
We need a function behaving like @tc[fold], but which allows to add elements to
|
||||||
|
the list being processed. In other words, we want to apply a @tc[lambda] to all
|
||||||
|
elements inside a queue, with the possibility to return new elements that should
|
||||||
|
be added to the queue.
|
||||||
|
|
||||||
|
We will first define a general version in section @secref{sec:general}, where
|
||||||
|
most implementation details are parametrizable. We then use this as a building
|
||||||
|
block to define more specialized version, for example the section
|
||||||
|
@secref{sec:sets-add} defines a version where the queue is a set, and elements
|
||||||
|
can only be added to the queue, not removed (therefore the @tc[lambda] is
|
||||||
|
expected to return only the elements to be added, and not the ones already
|
||||||
|
present in the queue).
|
||||||
|
|
||||||
|
@section[#:tag "sec:general"]{General version}
|
||||||
|
|
||||||
|
Since both lists, sets and other structures could be used to represent the
|
||||||
|
queue, we don't specify them here, and instead take the @tc[dequeue] and
|
||||||
|
@tc[empty?] functions as a parameter.
|
||||||
|
|
||||||
|
We also will leave the choice of how the result value is constructed: the third
|
||||||
|
argument transforms the accumulator into the result value for an empty queue,
|
||||||
|
but for non-empty queues, we directly return the result of calling @tc[process]
|
||||||
|
on the first element, leaving to @tc[process] the responsibility to combine the
|
||||||
|
result for this element with the remaining ones, either by constructing the
|
||||||
|
overall result in the @tc[accumulator], or by using the value returned by
|
||||||
|
@tc[rec].
|
||||||
|
|
||||||
|
The former case is shown in section @secref{sec:tail-call-reverse-map}, where
|
||||||
|
each element's result is combined with the accumulator using @tc[cons], whereas
|
||||||
|
the latter case is shown in @secref{sec:map}, where the result for an element is
|
||||||
|
combined with @tc[cons] to what @tc[process-rest] will return.
|
||||||
|
|
||||||
|
The lambda @tc[process] takes four parameters: the @tc[element] to process, the
|
||||||
|
current accumulator, current queue, and a function to call recursively with the
|
||||||
|
new accumulator and queue, and which will return the final result once recursive
|
||||||
|
calls have exhausted the queue.
|
||||||
|
|
||||||
|
The new queue passed to the recursive function should contain both the new
|
||||||
|
pending elements to add to the queue, and the ones passed as argument (unless
|
||||||
|
some of those need to be deleted before being processed).
|
||||||
|
|
||||||
|
@chunk[<fold-queue>
|
||||||
|
(: fold-queue (∀ (Queue Element Accumulator Result)
|
||||||
|
(→ Queue
|
||||||
|
Accumulator
|
||||||
|
(→ Accumulator Result)
|
||||||
|
(→ Queue (Values Element Queue))
|
||||||
|
(→ Queue Boolean)
|
||||||
|
(→ Element
|
||||||
|
Accumulator
|
||||||
|
Queue
|
||||||
|
(→ Queue Accumulator Result)
|
||||||
|
Result)
|
||||||
|
Result)))]
|
||||||
|
|
||||||
|
@chunk[<fold-queue>
|
||||||
|
(define (fold-queue initial-queue accumulator last-result dequeue empty? process)
|
||||||
|
(let process-rest ([queue initial-queue] [accumulator accumulator])
|
||||||
|
(if (empty? queue)
|
||||||
|
(last-result accumulator)
|
||||||
|
(% element rest-queue = (dequeue queue)
|
||||||
|
(process element accumulator rest-queue process-rest)))))]
|
||||||
|
|
||||||
|
@section[#:tag "sec:map"]{@racket[map]}
|
||||||
|
|
||||||
|
Here is a version that behaves like @tc[map] on the queue, returning a list of
|
||||||
|
the result of @tc[process] on the queue's elements, in the order in which they
|
||||||
|
were dequeued.
|
||||||
|
|
||||||
|
Here, @tc[process] takes an element, the current @tc[accumulator], the remaining
|
||||||
|
unprocessed elements in @tc[rest-queue], and returns three values: the
|
||||||
|
@tc[result] of processing the @tc[element], the @tc[new-accumulator] (which will
|
||||||
|
be discarded once all elements have been processed), and the @tc[new-queue].
|
||||||
|
|
||||||
|
@chunk[<map-queue>
|
||||||
|
(: map-queue (∀ (Queue Element Result Accumulator)
|
||||||
|
(→ Queue
|
||||||
|
Accumulator
|
||||||
|
(→ Queue (Values Element Queue))
|
||||||
|
(→ Queue Boolean)
|
||||||
|
(→ Element
|
||||||
|
Accumulator
|
||||||
|
Queue
|
||||||
|
(Values Result Queue Accumulator))
|
||||||
|
(Listof Result))))]
|
||||||
|
|
||||||
|
@chunk[<map-queue>
|
||||||
|
(define (map-queue queue accumulator dequeue empty? process)
|
||||||
|
(fold-queue queue
|
||||||
|
accumulator
|
||||||
|
(λ (_) : (Listof Result) '())
|
||||||
|
dequeue
|
||||||
|
empty?
|
||||||
|
<map-queue-process>))]
|
||||||
|
|
||||||
|
@chunk[<map-queue-process>
|
||||||
|
(ann (λ (element accumulator rest-queue process-rest)
|
||||||
|
(% result new-queue new-accumulator
|
||||||
|
= (process element accumulator rest-queue)
|
||||||
|
(cons result (process-rest new-queue new-accumulator))))
|
||||||
|
(→ Element Accumulator Queue (→ Queue Accumulator (Listof Result))
|
||||||
|
(Listof Result)))]
|
||||||
|
|
||||||
|
@section[#:tag "sec:tail-call-reverse-map"]{Tail-call @racket[map], with results
|
||||||
|
in reverse order}
|
||||||
|
|
||||||
|
@chunk[<tail-call-reverse-map-queue>
|
||||||
|
(: tail-call-reverse-map-queue
|
||||||
|
(∀ (Queue Element Result Accumulator)
|
||||||
|
(→ Queue
|
||||||
|
Accumulator
|
||||||
|
(→ Queue (Values Element Queue))
|
||||||
|
(→ Queue Boolean)
|
||||||
|
(→ Element
|
||||||
|
Accumulator
|
||||||
|
Queue
|
||||||
|
(Values Result Queue Accumulator))
|
||||||
|
(Listof Result))))]
|
||||||
|
|
||||||
|
@chunk[<tail-call-reverse-map-queue>
|
||||||
|
(define (tail-call-reverse-map-queue
|
||||||
|
queue accumulator dequeue empty? process)
|
||||||
|
(define-type RAccumulator (Pairof (Listof Result) Accumulator))
|
||||||
|
(fold-queue queue
|
||||||
|
(cons (ann '() (Listof Result)) accumulator)
|
||||||
|
(inst car (Listof Result) Accumulator)
|
||||||
|
dequeue
|
||||||
|
empty?
|
||||||
|
<tail-call-reverse-map-queue-process>))]
|
||||||
|
|
||||||
|
@chunk[<tail-call-reverse-map-queue-process>
|
||||||
|
(ann (λ (element accumulator rest-queue process-rest)
|
||||||
|
(% result new-queue new-accumulator
|
||||||
|
= (process element (cdr accumulator) rest-queue)
|
||||||
|
(process-rest new-queue
|
||||||
|
(cons (cons result (car accumulator))
|
||||||
|
new-accumulator))))
|
||||||
|
(→ Element RAccumulator Queue (→ Queue RAccumulator (Listof Result))
|
||||||
|
(Listof Result)))]
|
||||||
|
|
||||||
|
@section[#:tag "sec:sets-add"]{Variant using sets}
|
||||||
|
|
||||||
|
We define in this section a fold over queues represented using sets. This
|
||||||
|
version also disallows removing elements from the queue by making the union of
|
||||||
|
the existing queue and the new one returned by the @tc[process] function,
|
||||||
|
instead of using the latter to replace the former.
|
||||||
|
|
||||||
|
Moreover, this function does not build just a list of results, instead it builds
|
||||||
|
a dictionary with the processed elements as keys, and the corresponding results
|
||||||
|
as values.
|
||||||
|
|
||||||
|
@chunk[<fold-queue-sets>
|
||||||
|
(: fold-queue-sets
|
||||||
|
(∀ (Element Accumulator Result)
|
||||||
|
(→ (Setof Element)
|
||||||
|
Accumulator
|
||||||
|
(→ Element Accumulator
|
||||||
|
(Values Result Accumulator (Setof Element)))
|
||||||
|
(values (HashTable Element Result) Accumulator))))]
|
||||||
|
|
||||||
|
@chunk[<inst-fold-queue-sets>
|
||||||
|
(inst fold-queue
|
||||||
|
(Setof Element)
|
||||||
|
Element
|
||||||
|
(List (Setof Element) (HashTable Element Result) Accumulator)
|
||||||
|
(List (HashTable Element Result) Accumulator))]
|
||||||
|
|
||||||
|
@chunk[<fold-queue-sets>
|
||||||
|
(define (fold-queue-sets initial-queue accumulator process)
|
||||||
|
(apply values
|
||||||
|
(<inst-fold-queue-sets>
|
||||||
|
initial-queue
|
||||||
|
(list ((inst set Element))
|
||||||
|
((inst hash Element Result))
|
||||||
|
accumulator)
|
||||||
|
cdr
|
||||||
|
(λ ([s : (Setof Element)]) (values (set-first s) (set-rest s)))
|
||||||
|
set-empty?
|
||||||
|
<fold-queue-sets-process>)))]
|
||||||
|
|
||||||
|
@chunk[<fold-queue-sets-process>
|
||||||
|
(λ (element accumulator rest-queue process-rest)
|
||||||
|
(% (past-queue result-hash acc) = accumulator
|
||||||
|
result new-acc more-elements = (process element acc)
|
||||||
|
|
||||||
|
(process-rest
|
||||||
|
(set-union rest-queue (set-subtract more-elements past-queue))
|
||||||
|
(list (set-add past-queue element)
|
||||||
|
(if (hash-has-key? result-hash element)
|
||||||
|
(error (string-append
|
||||||
|
"Duplicate key in fold-queue-sets."
|
||||||
|
"Are you using mutable elements?"))
|
||||||
|
(hash-set result-hash element result))
|
||||||
|
new-acc))))]
|
||||||
|
|
||||||
|
@subsection{Adding tags, using a mutable dictionary}
|
||||||
|
|
||||||
|
We build upon this version a new one which allows associating a custom tag for
|
||||||
|
each element. This tag allows symbolically to refer to an element which hasn't
|
||||||
|
been processed yet, and we return two dictionaries, one associating elements
|
||||||
|
with their tag and result, and the other associating tags with their element and
|
||||||
|
result.
|
||||||
|
|
||||||
|
Here is a first implementation using a mutable store of tags, and also makes it
|
||||||
|
unnecessary for the @tc[process] lambda to return the set of elements to add to
|
||||||
|
the queue (since all elements for which a tag is requested are implicitly added
|
||||||
|
to the queue).
|
||||||
|
|
||||||
|
@chunk[<fold-queue-sets-tags>
|
||||||
|
(: fold-queue-sets-tags
|
||||||
|
(∀ (Element Accumulator Tag Result)
|
||||||
|
(→ (Setof Element)
|
||||||
|
Accumulator
|
||||||
|
(→ Element Accumulator (Values Tag Accumulator))
|
||||||
|
(→ Element
|
||||||
|
Accumulator
|
||||||
|
(→ Element Accumulator (Values Tag Accumulator))
|
||||||
|
(values Result Accumulator))
|
||||||
|
(Values (HashTable (Pairof Element Tag) Result) Accumulator))))]
|
||||||
|
|
||||||
|
@chunk[<inst-fold-queue-sets-tags>
|
||||||
|
(inst fold-queue-sets
|
||||||
|
(Pairof Element Tag)
|
||||||
|
Accumulator
|
||||||
|
Result)]
|
||||||
|
|
||||||
|
@chunk[<fold-queue-sets-tags>
|
||||||
|
(define (fold-queue-sets-tags initial-queue accumulator make-tag process)
|
||||||
|
(% all-tags = ((inst hash Element Tag))
|
||||||
|
initial-tagged-queue new-accumulator =
|
||||||
|
(map+fold (λ ([e : Element] [acc : Accumulator])
|
||||||
|
(% tag new-acc = (make-tag e acc)
|
||||||
|
(values (cons e tag) new-acc)))
|
||||||
|
accumulator
|
||||||
|
(set->list initial-queue))
|
||||||
|
|
||||||
|
(<inst-fold-queue-sets-tags>
|
||||||
|
(list->set initial-tagged-queue)
|
||||||
|
new-accumulator
|
||||||
|
(λ (e acc)
|
||||||
|
(let ([new-tagged ((inst set (Pairof Element Tag)))])
|
||||||
|
<mutable-get-tag-for>
|
||||||
|
(% result new-acc = (process (car e) acc get-tag-for)
|
||||||
|
(values result new-acc new-tagged)))))))]
|
||||||
|
|
||||||
|
@chunk[<mutable-get-tag-for>
|
||||||
|
(define (get-tag-for [x : Element] [acc : Accumulator])
|
||||||
|
(if (hash-has-key? all-tags x)
|
||||||
|
(values (hash-ref all-tags x) acc)
|
||||||
|
(% tag new-acc = (make-tag x acc)
|
||||||
|
(set! new-tagged (set-add new-tagged (cons x tag)))
|
||||||
|
(hash-set all-tags x tag)
|
||||||
|
(values tag new-acc))))]
|
||||||
|
|
||||||
|
@subsection{Adding tags, using an immutable dictionary}
|
||||||
|
|
||||||
|
An alternative approach is to provide these tags using an immutable dictionary:
|
||||||
|
the @tc[get-tag] function now returns a tag and the new dictionary.
|
||||||
|
|
||||||
|
@chunk[<fold-queue-sets-immutable-tags>
|
||||||
|
(: fold-queue-sets-immutable-tags
|
||||||
|
(∀ (Element Accumulator Tag Result)
|
||||||
|
(→ (Setof Element)
|
||||||
|
Accumulator
|
||||||
|
(→ Element Accumulator (Values Tag Accumulator))
|
||||||
|
<fold-queue-sets-immutable-tags-process-type>
|
||||||
|
(Values (HashTable (Pairof Element Tag) Result)
|
||||||
|
(Pairof (HashTable Element Tag)
|
||||||
|
Accumulator)))))]
|
||||||
|
|
||||||
|
The @tc[process] lambda now takes an element, as well as a purely functional tag
|
||||||
|
provider, which uses an opaque database type @tc[X] to know for which elements
|
||||||
|
was a tag requested.
|
||||||
|
|
||||||
|
@chunk[<fold-queue-sets-immutable-tags-process-type>
|
||||||
|
(∀ (X) (→ Element Accumulator X (→ Element Accumulator X (Values Tag Accumulator X))
|
||||||
|
(Values Result Accumulator X)))]
|
||||||
|
|
||||||
|
@chunk[<inst-fold-queue-sets-immutable-tags>
|
||||||
|
(inst fold-queue-sets
|
||||||
|
(Pairof Element Tag)
|
||||||
|
(Pairof (HashTable Element Tag) Accumulator)
|
||||||
|
Result)]
|
||||||
|
|
||||||
|
@chunk[<fold-queue-sets-immutable-tags>
|
||||||
|
(define (fold-queue-sets-immutable-tags initial-queue
|
||||||
|
accumulator
|
||||||
|
make-tag
|
||||||
|
process)
|
||||||
|
<immutable-get-tag-for>
|
||||||
|
(% initial-tagged-queue new-accumulator =
|
||||||
|
(map+fold (λ ([e : Element] [acc : Accumulator])
|
||||||
|
(% tag new-acc = (make-tag e acc)
|
||||||
|
(values (cons e tag) new-acc)))
|
||||||
|
accumulator
|
||||||
|
(set->list initial-queue))
|
||||||
|
|
||||||
|
(<inst-fold-queue-sets-immutable-tags>
|
||||||
|
(list->set initial-tagged-queue)
|
||||||
|
(cons ((inst hash Element Tag)) new-accumulator)
|
||||||
|
(λ (e h+acc)
|
||||||
|
(% (h . acc) = h+acc
|
||||||
|
empty-s = ((inst set (Pairof Element Tag)))
|
||||||
|
r new-acc (_ . s) = (process (car e)
|
||||||
|
acc
|
||||||
|
(cons h empty-s)
|
||||||
|
get-tag-for)
|
||||||
|
new-h = (hash-set** h (set->list s))
|
||||||
|
(values r (cons new-h new-acc) s))))))]
|
||||||
|
|
||||||
|
@chunk[<immutable-get-tag-for>
|
||||||
|
(define% (get-tag-for [x : Element]
|
||||||
|
[acc : Accumulator]
|
||||||
|
[(h . s) : (Pairof (HashTable Element Tag)
|
||||||
|
(Setof (Pairof Element Tag)))])
|
||||||
|
(if (hash-has-key? h x)
|
||||||
|
(values (hash-ref h x)
|
||||||
|
acc
|
||||||
|
(cons h s))
|
||||||
|
(% tag new-acc = (make-tag x acc)
|
||||||
|
(values tag
|
||||||
|
new-acc
|
||||||
|
(cons (hash-set h x tag) (set-add s `(,x . ,tag)))))))]
|
||||||
|
|
||||||
|
@section{Conclusion}
|
||||||
|
|
||||||
|
@chunk[<*>
|
||||||
|
(begin
|
||||||
|
(module main typed/racket
|
||||||
|
(require (for-syntax syntax/parse
|
||||||
|
racket/syntax
|
||||||
|
"../lib/low-untyped.rkt")
|
||||||
|
"../lib/low.rkt"
|
||||||
|
racket/set)
|
||||||
|
|
||||||
|
<fold-queue>
|
||||||
|
<map-queue>
|
||||||
|
<tail-call-reverse-map-queue>
|
||||||
|
<fold-queue-sets>
|
||||||
|
<fold-queue-sets-tags>
|
||||||
|
<fold-queue-sets-immutable-tags>)
|
||||||
|
|
||||||
|
(require 'main)
|
||||||
|
(provide (all-from-out 'main))
|
||||||
|
|
||||||
|
(module* test typed/racket
|
||||||
|
(require (submod "..")
|
||||||
|
typed/rackunit)
|
||||||
|
|
||||||
|
|
||||||
|
(require (submod ".." doc))))]
|
Loading…
Reference in New Issue
Block a user