162 lines
5.4 KiB
Racket
162 lines
5.4 KiB
Racket
#lang scheme/base
|
|
|
|
(require tests/eli-tester profile/structs profile/utils
|
|
scheme/list scheme/match)
|
|
|
|
(define arrow-sym->times
|
|
;; arrows with caller/callee times
|
|
;; `->' defaults to `1->1', and `N->' defaults to `N->N'
|
|
;; note: A 1->2 B means that the caller time is 2 and the callee time is one,
|
|
;; since the time are wrt the other node.
|
|
(let ([t (make-hasheq)])
|
|
(lambda (arr)
|
|
(hash-ref! t arr
|
|
(lambda ()
|
|
(define m
|
|
(regexp-match #rx"^(?:([0-9]+)|)->(?:([0-9]+)|)$"
|
|
(symbol->string arr)))
|
|
(and m
|
|
(list (string->number (or (caddr m) (cadr m) "1"))
|
|
(string->number (or (cadr m) "1")))))))))
|
|
|
|
(define (connect! from from-time to to-time)
|
|
(if (memq from (map edge-caller (node-callers to)))
|
|
(error (format "bad graph spec in tests, ~s->~s already connected" from to))
|
|
(let ([edge (make-edge 0 from from-time to to-time)])
|
|
(set-node-callers! to (cons edge (node-callers to )))
|
|
(set-node-callees! from (cons edge (node-callees from))))))
|
|
|
|
(define-match-expander arrow
|
|
(syntax-rules () [(_ ler lee) (app arrow-sym->times (list ler lee))]))
|
|
|
|
(define (set=? l1 l2) (null? (append (remq* l1 l2) (remq* l2 l1))))
|
|
|
|
(define (sort-graph . edges)
|
|
(define names
|
|
(remove-duplicates (filter (lambda (s) (not (arrow-sym->times s)))
|
|
(append* edges))))
|
|
(define nodes (map (lambda (sym) (make-node sym #f '() 0 0 '() '())) names))
|
|
(define ->node (make-immutable-hasheq (map cons names nodes)))
|
|
(for ([edges edges])
|
|
(let loop ([xs edges])
|
|
(match xs
|
|
[(list from (arrow ftime ttime) to (arrow _ _) _ ...)
|
|
(connect! (hash-ref ->node from) ftime (hash-ref ->node to) ttime)
|
|
(loop (cddr xs))]
|
|
[(list from (arrow ftime ttime) to _ ...)
|
|
(connect! (hash-ref ->node from) ftime (hash-ref ->node to) ttime)
|
|
(loop (cdddr xs))]
|
|
['() (void)])))
|
|
(let ([sorted (topological-sort (car nodes))])
|
|
(unless (set=? nodes (cons (car nodes) (append* sorted)))
|
|
(error 'sort-graph
|
|
"not all nodes appear in sorted output; expected ~e, got ~e"
|
|
nodes (cons (car nodes) (append* sorted))))
|
|
(map (lambda (nodes) (map node-id nodes)) sorted)))
|
|
|
|
;; `expected' is the desired result, modulo ordering inside the levels
|
|
;; can have more than one if there are several valid outputs
|
|
(define (same-levels graph expected . more-valid)
|
|
(define sorted (sort-graph graph))
|
|
(or (ormap (lambda (expected)
|
|
(and (= (length sorted) (length expected))
|
|
(andmap set=? sorted expected)))
|
|
(cons expected more-valid))
|
|
(error (format "bad result, got ~s" sorted))))
|
|
|
|
;; to see a result: (sort-graph '(* -> A)) (exit)
|
|
|
|
(provide topological-sort-tests)
|
|
(define (topological-sort-tests)
|
|
(test
|
|
|
|
;; sanity check: works with an empty stack
|
|
;; actually, this version does get stuck in a loop -- which shouldn't happen
|
|
;; in reality since there are no *->* edges
|
|
;; (same-levels '(* -> *)
|
|
;; '())
|
|
;; instead, just have a single * node:
|
|
(topological-sort (make-node '* #f '() 0 0 '() '()))
|
|
=> '()
|
|
|
|
(same-levels '(* -> A)
|
|
'((A)))
|
|
|
|
(same-levels '(* -> A -> B -> *)
|
|
'((A) (B)))
|
|
|
|
(same-levels '(* -> A -> *
|
|
* -> B -> *
|
|
A -> A)
|
|
'((A B)))
|
|
|
|
;; note that ((B) (A)) would also be consistent, but the code organizes for
|
|
;; this result
|
|
(same-levels '(* -> A -> B -> A)
|
|
'((A) (B)))
|
|
|
|
;; this is an example using the actual times for the A->B->A case that is
|
|
;; tested in main.ss
|
|
(same-levels '(* 2->1 A 1->2 B 2->1 A 1->2 *)
|
|
'((A) (B)))
|
|
|
|
|
|
(same-levels '(* -> A 2-> B -> A)
|
|
'((A) (B)))
|
|
|
|
(same-levels '(* -> A 2-> B 3-> A)
|
|
'((B) (A)))
|
|
|
|
(same-levels '(* -> A -> B -> C
|
|
* -> C)
|
|
'((A) (B) (C)))
|
|
|
|
(same-levels '(* -> A -> B -> C -> * ; check with *s too
|
|
* -> C)
|
|
'((A) (B) (C)))
|
|
|
|
(same-levels '(* -> X -> A -> B -> C
|
|
X -> C )
|
|
'((X) (A) (B) (C)))
|
|
|
|
(same-levels '(* -> A -> B -> C -> *
|
|
* -> C 3-> B -> *)
|
|
'((A C) (B)))
|
|
|
|
(same-levels '(* -> A -> B -> A
|
|
* -> C)
|
|
'((A C) (B)))
|
|
|
|
(same-levels '(* -> A -> B -> *
|
|
* -> B -> A -> *)
|
|
'((A) (B))
|
|
'((B) (A)))
|
|
|
|
(same-levels '(* -> A
|
|
* -> B
|
|
B -> B)
|
|
'((A B)))
|
|
|
|
(same-levels '(* -> A
|
|
* -> B -> C
|
|
* -> C -> B)
|
|
'((A B) (C))
|
|
'((A C) (B)))
|
|
|
|
;; Note that X could be pushed to the third level, making it closer to D,
|
|
;; but this is not done since for a printout it makes more sense to have all
|
|
;; the sources at the top. (It might make sense to do this compacting in
|
|
;; the gui, but even there it might be more convenient to see all roots at
|
|
;; the top.)
|
|
(same-levels '(* -> A -> B -> C -> D -> *
|
|
* -> X -> D)
|
|
'((X A) (B) (C) (D)))
|
|
|
|
(same-levels '(* -> A1 -> A2 -> A3 -> A4 -> A5
|
|
* -> B1 -> B2 -> B3
|
|
* -> C1
|
|
* -> D1 -> D2 -> D3 -> D4)
|
|
'((A1 B1 C1 D1) (A2 B2 D2) (A3 B3 D3) (A4 D4) (A5)))
|
|
|
|
))
|