I'm writing (another) basic graph library, and would like to treat inputs
depending on the type of the graph. A graph can be

   - Directed, in which case edges are vectors. Otherwise, edges are sets;
   - Looped, allowing edges from a node to itself;
   - Pseudo (or multi), allowing multiples edges between the same
   endpoints; and
   - Hyper, allowing edges with more than two vertices.

To illustrate better these characteristics you can think of a scientific
publication network as a directed, looped, pseudo-hypergraph. Vertices are
authors, and edges are articles containing multiple researchers (hyper) who
can publish alone (looped). There are multiple articles between the same
researchers (pseudo) and in some contexts author order matters (directed).

Now, I've created a flowchart <http://imgur.com/IdgsGFG> to decide if an
edge should be conjed in a graph :edges entry, that leads to the following
straightforward function:
(defn add-edge
  ([graph v1 v2 & vs] (add-edge graph (concat [v1 v2] vs)))
  ([graph edge]
  (if (and (multi? graph) (not= 2 (count edge)))
    graph
    (if (and (looped? graph) (not (distinct? edge)))
      graph
      (let [e (if (directed? edge) (vec edge) (set edge))]
        (update-in graph [:edges] conj e))))))

That looks ugly and a pattern that could propagate in a codebase. So I
tried to factor out multimethods from it, and ended with the following:

(defmulti ^:private add-edge0 (fn [g e] (hyper? g)))
(defmulti ^:private add-edge1 (fn [g e] (looped? g)))
(defmulti ^:private add-edge2 (fn [g e] (directed? g)))
(defn     ^:private add-edge3 [g e]
  (update-in g [:edges] conj e))

(defmethod add-edge0 :hyper [g e] (add-edge1 g e))
(defmethod add-edge0 :default [g e] (if (= 2 (count e))
                                      (add-edge1 g e)
                                      g))
(defmethod add-edge1 :looped  [g e] (add-edge2 g e))
(defmethod add-edge1 :default [g e] (if (distinct? e)
                                      (add-edge2 g e)
                                       g))
(defmethod add-edge2 :directed [g e] (add-edge3 g (vec e)))
(defmethod add-edge2 :default  [g e] (add-edge3 g (set e)))

(defn add-edge
  ([g v1 v2 & vs] (add-edge g (concat [v1 v2] vs)))
  ([g edge]       (add-edge0 g edge)))

That doesn't look much better, as the amount of boilerplate increased, but
at least the concerns for each type are separated.

Do you have any suggestions on how to improve this design? Thanks for any
consideration!

Bruno Kim Medeiros Cesar
Engenheiro de Computação
Pesquisador em Redes Complexas
www.brunokim.com.br

-- 
-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to