[racket] Detecting cycle in directed graph

From: Matthias Felleisen (matthias at ccs.neu.edu)
Date: Fri Feb 14 08:18:45 EST 2014

On Feb 14, 2014, at 7:42 AM, Erich Rast wrote:

> I have a directed graph given as list of pairs ((from-node .
> to-node) ...) and need to detect whether adding a pair would create a
> cycle.
> Is there an easy way to do this already, e.g. some Planet package or
> snippet someone would like to share? 
> I don't want to reinvent the wheel and this is not a homework question.

I thought it looked like the first homework from my junior-level sw dev course last year but when I checked the interface, my requirement was to conform to the triangle inequality. Sorry, no luck 

#lang racket

(require "../Lib/contract.rkt")

;; ---------------------------------------------------------------------------------------------------
;; for contracts see below

(interface graph& 
  (node?     (-> any/c boolean?))
  (edge?     contract?)
  (edge      (-> node? real? node? edge?)) ;; MF: forgot to export it
  (edge-from (-> edge? node?))
  (edge-to   (-> edge? node?))
  (edge-cost (-> edge? number?))
  (graph%    graph/c))

;; an interface needs a common terminology with an interpretation (see Fundamentals I, ontology)

;; nodes 
(define node? symbol?)

;; edges 
(define edge?
  (list/c node? real? node?))
(define/contract (edge from cost to)
  (-> node? real? node? edge?)
  (list from cost to))
(define edge-from first)
(define edge-cost second)
(define edge-to third)
(define reverse-edge reverse)

(define (cost/c low high)
  ;; MF: while I can't enforce invariants on fields 
  (λ (x) (<= low x high))
  (and/c  (>=/c low) (<=/c high)))

;; graphs 
(define graph/c
   ;; specify the interval of costs [low-cost,high-cost]
   (init-field (low-cost (and/c real? (>=/c 0)))) ;; MF: made cost non-negative 
   ;; MF: Racket's contract system is deficient here:
   (init-field (high-cost (and/c real? (>=/c 0) #;(>/c low-cost))))
   ;; and I want this to be interpreted as an invariant
    ;; the list of nodes of this graph 
    (->m (set/c node? #:cmp 'eq)))
    ;; the list of edges of this graph 
    (->m (set/c edge? #:cmp 'equal)))
    ;; adding an edge (from,to) with cost w to this graph 
    (->dm ((from node?) 
           (w (cost/c (get-field low-cost this) (get-field high-cost this)))
           (to node?))
          #:pre (triangle-condition-preserved (send this edges) from w to)
   ;; MF: I had not anticipated that I would want an 'inherited' call
   (inherit (add-edge
             ;; adding an edge (from,to) with cost w to this graph 
             (->dm ((from node?) 
                    (w (cost/c (get-field low-cost this) (get-field high-cost this)))
                    (to node?))
                   #:pre (triangle-condition-preserved (send this edges) from w to)
    ;; reverse all edges in this graph, keep costs 
    (->m any))
    ;; join the nodes of other graph to this graph 
    (->dm ((other (instanceof/c graph/c)))
          #:pre (and (set=? (set-intersect (send this nodes) (send other nodes)) (seteq))
                     (= (get-field low-cost this) (get-field low-cost other))
                     (= (get-field high-cost this) (get-field high-cost other)))
    ;; is there a path from f to t? 
    (->dm ((f node?) (t node?)) (result boolean?)))
    ;; (path f t) is there a path from f to t and its total cost in this graph 
    (->dm ((f node?) (t node?)) #:pre (send this path? f t)
          (values (cost real?) (p (listof edge?)))))))

;; [set/c edge?] node? real? node? -> boolean? 
;; does the new edge preserve all new triangle equations? 
(define (triangle-condition-preserved edges:set from cost-from->to to)
  (define edges (set->list edges:set))
  (define to-out (filter (λ (e) (and (eq? (edge-from e) to) (not (eq? (edge-to e) from)))) edges))
  (define to-in (filter (λ (e) (and (eq? (edge-to e) to) (not (eq? (edge-from e) from)))) edges))
  (define (edges-between s t) 
    (filter (λ (e) (and (eq? (edge-from e) s) (eq? (edge-to e) t))) edges))
   ;       from              to
   ;         *----- cost---->* 
   ;                         |
   ;                         | 
   ;                         |
   ;                         v
   ;                         * target 
   ;; there is only one way to complete this triangle: from->target
   (for/and ((to->target to-out))
     (define cost-from->to->target (+ cost-from->to (edge-cost to->target)))
     (for/and ((from->target (edges-between from (edge-to to->target))))
       (define cost-from->target (edge-cost from->target))
       (<= cost-from->target cost-from->to->target)))
   ;       from              to
   ;         *----- cost --->* 
   ;                         ^
   ;                         | 
   ;                         |
   ;                         |
   ;                         * intermediate 
   ;; there are two ways to complete this triangle: from -> intermediate, intermediate -> from
   (for/and ((intermediate->to to-in))
     (define cost-intermediate->to (edge-cost intermediate->to))
     (define intermediate (edge-from intermediate->to))
      (for/and ((intermediate-from (edges-between intermediate from)))
        (define cost-intermediate->from (edge-cost intermediate-from))
        (<= cost-intermediate->to (+ cost-intermediate->from cost-from->to)))
      (for/and ((from->intermediate (edges-between from intermediate)))
        (define cost-from->intermediate (edge-cost from->intermediate))
        (<= cost-from->to (+ cost-from->intermediate cost-intermediate->to)))))))

Posted on the users mailing list.