You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3279 lines
76 KiB
3279 lines
76 KiB
# graph_tcl.tcl -- |
|
# |
|
# Implementation of a graph data structure for Tcl. |
|
# |
|
# Copyright (c) 2000-2009,2019 by Andreas Kupries <andreas_kupries@users.sourceforge.net> |
|
# Copyright (c) 2008 by Alejandro Paz <vidriloco@gmail.com> |
|
# |
|
# See the file "license.terms" for information on usage and redistribution |
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
|
|
|
package require Tcl 8.4 |
|
package require struct::list |
|
package require struct::set |
|
|
|
namespace eval ::struct::graph { |
|
# Data storage in the graph module |
|
# ------------------------------- |
|
# |
|
# There's a lot of bits to keep track of for each graph: |
|
# nodes |
|
# node values |
|
# node relationships (arcs) |
|
# arc values |
|
# |
|
# It would quickly become unwieldy to try to keep these in arrays or lists |
|
# within the graph namespace itself. Instead, each graph structure will |
|
# get its own namespace. Each namespace contains: |
|
# node:$node array mapping keys to values for the node $node |
|
# arc:$arc array mapping keys to values for the arc $arc |
|
# inArcs array mapping nodes to the list of incoming arcs |
|
# outArcs array mapping nodes to the list of outgoing arcs |
|
# arcNodes array mapping arcs to the two nodes (start & end) |
|
|
|
# counter is used to give a unique name for unnamed graph |
|
variable counter 0 |
|
|
|
# Only export one command, the one used to instantiate a new graph |
|
namespace export graph_tcl |
|
} |
|
|
|
# ::struct::graph::graph_tcl -- |
|
# |
|
# Create a new graph with a given name; if no name is given, use |
|
# graphX, where X is a number. |
|
# |
|
# Arguments: |
|
# name name of the graph; if null, generate one. |
|
# |
|
# Results: |
|
# name name of the graph created |
|
|
|
proc ::struct::graph::graph_tcl {args} { |
|
variable counter |
|
|
|
set src {} |
|
set srctype {} |
|
|
|
switch -exact -- [llength [info level 0]] { |
|
1 { |
|
# Missing name, generate one. |
|
incr counter |
|
set name "graph${counter}" |
|
} |
|
2 { |
|
# Standard call. New empty graph. |
|
set name [lindex $args 0] |
|
} |
|
4 { |
|
# Copy construction. |
|
foreach {name as src} $args break |
|
switch -exact -- $as { |
|
= - := - as { |
|
set srctype graph |
|
} |
|
deserialize { |
|
set srctype serial |
|
} |
|
default { |
|
return -code error \ |
|
"wrong # args: should be \"struct::graph ?name ?=|:=|as|deserialize source??\"" |
|
} |
|
} |
|
} |
|
default { |
|
# Error. |
|
return -code error \ |
|
"wrong # args: should be \"struct::graph ?name ?=|:=|as|deserialize source??\"" |
|
} |
|
} |
|
|
|
# FIRST, qualify the name. |
|
if {![string match "::*" $name]} { |
|
# Get caller's namespace; append :: if not global namespace. |
|
set ns [uplevel 1 [list namespace current]] |
|
if {"::" != $ns} { |
|
append ns "::" |
|
} |
|
|
|
set name "$ns$name" |
|
} |
|
if {[llength [info commands $name]]} { |
|
return -code error "command \"$name\" already exists, unable to create graph" |
|
} |
|
|
|
# Set up the namespace |
|
namespace eval $name { |
|
|
|
# Set up the map for values associated with the graph itself |
|
variable graphAttr |
|
array set graphAttr {} |
|
|
|
# Set up the node attribute mapping |
|
variable nodeAttr |
|
array set nodeAttr {} |
|
|
|
# Set up the arc attribute mapping |
|
variable arcAttr |
|
array set arcAttr {} |
|
|
|
# Set up the map from nodes to the arcs coming to them |
|
variable inArcs |
|
array set inArcs {} |
|
|
|
# Set up the map from nodes to the arcs going out from them |
|
variable outArcs |
|
array set outArcs {} |
|
|
|
# Set up the map from arcs to the nodes they touch. |
|
variable arcNodes |
|
array set arcNodes {} |
|
|
|
# Set up a value for use in creating unique node names |
|
variable nextUnusedNode |
|
set nextUnusedNode 1 |
|
|
|
# Set up a value for use in creating unique arc names |
|
variable nextUnusedArc |
|
set nextUnusedArc 1 |
|
|
|
# Set up a counter for use in creating attribute arrays. |
|
variable nextAttr |
|
set nextAttr 0 |
|
|
|
# Set up a map from arcs to their weights. Note: Only arcs |
|
# which actually have a weight are recorded in the map, to |
|
# keep memory usage down. |
|
variable arcWeight |
|
array set arcWeight {} |
|
} |
|
|
|
# Create the command to manipulate the graph |
|
interp alias {} $name {} ::struct::graph::GraphProc $name |
|
|
|
# Automatic execution of assignment if a source |
|
# is present. |
|
if {$src != {}} { |
|
switch -exact -- $srctype { |
|
graph {_= $name $src} |
|
serial {_deserialize $name $src} |
|
default { |
|
return -code error \ |
|
"Internal error, illegal srctype \"$srctype\"" |
|
} |
|
} |
|
} |
|
|
|
return $name |
|
} |
|
|
|
########################## |
|
# Private functions follow |
|
|
|
# ::struct::graph::GraphProc -- |
|
# |
|
# Command that processes all graph object commands. |
|
# |
|
# Arguments: |
|
# name name of the graph object to manipulate. |
|
# args command name and args for the command |
|
# |
|
# Results: |
|
# Varies based on command to perform |
|
|
|
proc ::struct::graph::GraphProc {name {cmd ""} args} { |
|
# Do minimal args checks here |
|
if { [llength [info level 0]] == 2 } { |
|
return -code error "wrong # args: should be \"$name option ?arg arg ...?\"" |
|
} |
|
|
|
# Split the args into command and args components |
|
set sub _$cmd |
|
if { [llength [info commands ::struct::graph::$sub]] == 0 } { |
|
set optlist [lsort [info commands ::struct::graph::_*]] |
|
set xlist {} |
|
foreach p $optlist { |
|
set p [namespace tail $p] |
|
if {[string match __* $p]} {continue} |
|
lappend xlist [string range $p 1 end] |
|
} |
|
set optlist [linsert [join $xlist ", "] "end-1" "or"] |
|
return -code error \ |
|
"bad option \"$cmd\": must be $optlist" |
|
} |
|
uplevel 1 [linsert $args 0 ::struct::graph::$sub $name] |
|
} |
|
|
|
# ::struct::graph::_= -- |
|
# |
|
# Assignment operator. Copies the source graph into the |
|
# destination, destroying the original information. |
|
# |
|
# Arguments: |
|
# name Name of the graph object we are copying into. |
|
# source Name of the graph object providing us with the |
|
# data to copy. |
|
# |
|
# Results: |
|
# Nothing. |
|
|
|
proc ::struct::graph::_= {name source} { |
|
_deserialize $name [$source serialize] |
|
return |
|
} |
|
|
|
# ::struct::graph::_--> -- |
|
# |
|
# Reverse assignment operator. Copies this graph into the |
|
# destination, destroying the original information. |
|
# |
|
# Arguments: |
|
# name Name of the graph object to copy |
|
# dest Name of the graph object we are copying to. |
|
# |
|
# Results: |
|
# Nothing. |
|
|
|
proc ::struct::graph::_--> {name dest} { |
|
$dest deserialize [_serialize $name] |
|
return |
|
} |
|
|
|
# ::struct::graph::_append -- |
|
# |
|
# Append a value for an attribute in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# args key value |
|
# |
|
# Results: |
|
# val value associated with the given key of the given arc |
|
|
|
proc ::struct::graph::_append {name key value} { |
|
variable ${name}::graphAttr |
|
return [append graphAttr($key) $value] |
|
} |
|
|
|
# ::struct::graph::_lappend -- |
|
# |
|
# lappend a value for an attribute in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# args key value |
|
# |
|
# Results: |
|
# val value associated with the given key of the given arc |
|
|
|
proc ::struct::graph::_lappend {name key value} { |
|
variable ${name}::graphAttr |
|
return [lappend graphAttr($key) $value] |
|
} |
|
|
|
# ::struct::graph::_arc -- |
|
# |
|
# Dispatches the invocation of arc methods to the proper handler |
|
# procedure. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# cmd arc command to invoke |
|
# args arguments to propagate to the handler for the arc command |
|
# |
|
# Results: |
|
# As of the invoked handler. |
|
|
|
proc ::struct::graph::_arc {name cmd args} { |
|
# Split the args into command and args components |
|
|
|
set sub __arc_$cmd |
|
if { [llength [info commands ::struct::graph::$sub]] == 0 } { |
|
set optlist [lsort [info commands ::struct::graph::__arc_*]] |
|
set xlist {} |
|
foreach p $optlist { |
|
set p [namespace tail $p] |
|
lappend xlist [string range $p 6 end] |
|
} |
|
set optlist [linsert [join $xlist ", "] "end-1" "or"] |
|
return -code error \ |
|
"bad option \"$cmd\": must be $optlist" |
|
} |
|
uplevel 1 [linsert $args 0 ::struct::graph::$sub $name] |
|
} |
|
|
|
# ::struct::graph::__arc_delete -- |
|
# |
|
# Remove an arc from a graph, including all of its values. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# args list of arcs to delete. |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::__arc_delete {name args} { |
|
if {![llength $args]} { |
|
return {wrong # args: should be "::struct::graph::__arc_delete name arc arc..."} |
|
} |
|
|
|
# seen is used to catch duplicate arcs in the args |
|
array set seen {} |
|
foreach arc $args { |
|
if {[info exists seen($arc)]} { |
|
return -code error "arc \"$arc\" does not exist in graph \"$name\"" |
|
} |
|
CheckMissingArc $name $arc |
|
set seen($arc) . |
|
} |
|
|
|
variable ${name}::inArcs |
|
variable ${name}::outArcs |
|
variable ${name}::arcNodes |
|
variable ${name}::arcAttr |
|
variable ${name}::arcWeight |
|
|
|
foreach arc $args { |
|
foreach {source target} $arcNodes($arc) break ; # lassign |
|
|
|
unset arcNodes($arc) |
|
|
|
if {[info exists arcAttr($arc)]} { |
|
unset ${name}::$arcAttr($arc) ;# Note the double indirection here |
|
unset arcAttr($arc) |
|
} |
|
if {[info exists arcWeight($arc)]} { |
|
unset arcWeight($arc) |
|
} |
|
|
|
# Remove arc from the arc lists of source and target nodes. |
|
|
|
set index [lsearch -exact $outArcs($source) $arc] |
|
ldelete outArcs($source) $index |
|
|
|
set index [lsearch -exact $inArcs($target) $arc] |
|
ldelete inArcs($target) $index |
|
} |
|
|
|
return |
|
} |
|
|
|
# ::struct::graph::__arc_exists -- |
|
# |
|
# Test for existence of a given arc in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to look for. |
|
# |
|
# Results: |
|
# 1 if the arc exists, 0 else. |
|
|
|
proc ::struct::graph::__arc_exists {name arc} { |
|
return [info exists ${name}::arcNodes($arc)] |
|
} |
|
|
|
# ::struct::graph::__arc_flip -- |
|
# |
|
# Exchanges origin and destination node of the specified arc. |
|
# |
|
# Arguments: |
|
# name name of the graph object. |
|
# arc arc to change. |
|
# |
|
# Results: |
|
# None |
|
|
|
proc ::struct::graph::__arc_flip {name arc} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcNodes |
|
variable ${name}::outArcs |
|
variable ${name}::inArcs |
|
|
|
set oldsource [lindex $arcNodes($arc) 0] |
|
set oldtarget [lindex $arcNodes($arc) 1] |
|
|
|
if {[string equal $oldsource $oldtarget]} return |
|
|
|
set newtarget $oldsource |
|
set newsource $oldtarget |
|
|
|
set arcNodes($arc) [lreplace $arcNodes($arc) 0 0 $newsource] |
|
lappend outArcs($newsource) $arc |
|
ldelete outArcs($oldsource) [lsearch -exact $outArcs($oldsource) $arc] |
|
|
|
set arcNodes($arc) [lreplace $arcNodes($arc) 1 1 $newtarget] |
|
lappend inArcs($newtarget) $arc |
|
ldelete inArcs($oldtarget) [lsearch -exact $inArcs($oldtarget) $arc] |
|
return |
|
} |
|
|
|
# ::struct::graph::__arc_get -- |
|
# |
|
# Get a keyed value from an arc in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to query. |
|
# key key to lookup |
|
# |
|
# Results: |
|
# value value associated with the key given. |
|
|
|
proc ::struct::graph::__arc_get {name arc key} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcAttr |
|
if {![info exists arcAttr($arc)]} { |
|
# No attribute data for this arc, key has to be invalid. |
|
return -code error "invalid key \"$key\" for arc \"$arc\"" |
|
} |
|
|
|
upvar ${name}::$arcAttr($arc) data |
|
if { ![info exists data($key)] } { |
|
return -code error "invalid key \"$key\" for arc \"$arc\"" |
|
} |
|
return $data($key) |
|
} |
|
|
|
# ::struct::graph::__arc_getall -- |
|
# |
|
# Get a serialized array of key/value pairs from an arc in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to query. |
|
# pattern optional glob pattern to restrict retrieval |
|
# |
|
# Results: |
|
# value serialized array of key/value pairs. |
|
|
|
proc ::struct::graph::__arc_getall {name arc {pattern *}} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcAttr |
|
if {![info exists arcAttr($arc)]} { |
|
# No attributes ... |
|
return {} |
|
} |
|
|
|
upvar ${name}::$arcAttr($arc) data |
|
return [array get data $pattern] |
|
} |
|
|
|
# ::struct::graph::__arc_keys -- |
|
# |
|
# Get a list of keys for an arc in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to query. |
|
# pattern optional glob pattern to restrict retrieval |
|
# |
|
# Results: |
|
# value value associated with the key given. |
|
|
|
proc ::struct::graph::__arc_keys {name arc {pattern *}} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcAttr |
|
if {![info exists arcAttr($arc)]} { |
|
# No attributes ... |
|
return {} |
|
} |
|
|
|
upvar ${name}::$arcAttr($arc) data |
|
return [array names data $pattern] |
|
} |
|
|
|
# ::struct::graph::__arc_keyexists -- |
|
# |
|
# Test for existence of a given key for a given arc in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to query. |
|
# key key to lookup |
|
# |
|
# Results: |
|
# 1 if the key exists, 0 else. |
|
|
|
proc ::struct::graph::__arc_keyexists {name arc key} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcAttr |
|
if {![info exists arcAttr($arc)]} { |
|
# No attribute data for this arc, key cannot exist. |
|
return 0 |
|
} |
|
|
|
upvar ${name}::$arcAttr($arc) data |
|
return [info exists data($key)] |
|
} |
|
|
|
# ::struct::graph::__arc_insert -- |
|
# |
|
# Add an arc to a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# source source node of the new arc |
|
# target target node of the new arc |
|
# args arc to insert; must be unique. If none is given, |
|
# the routine will generate a unique node name. |
|
# |
|
# Results: |
|
# arc The name of the new arc. |
|
|
|
proc ::struct::graph::__arc_insert {name source target args} { |
|
|
|
if { [llength $args] == 0 } { |
|
# No arc name was given; generate a unique one |
|
set arc [__generateUniqueArcName $name] |
|
} elseif { [llength $args] > 1 } { |
|
return {wrong # args: should be "::struct::graph::__arc_insert name source target ?arc?"} |
|
} else { |
|
set arc [lindex $args 0] |
|
} |
|
|
|
CheckDuplicateArc $name $arc |
|
CheckMissingNode $name $source {source } |
|
CheckMissingNode $name $target {target } |
|
|
|
variable ${name}::inArcs |
|
variable ${name}::outArcs |
|
variable ${name}::arcNodes |
|
|
|
# Set up the new arc |
|
set arcNodes($arc) [list $source $target] |
|
|
|
# Add this arc to the arc lists of its source resp. target nodes. |
|
lappend outArcs($source) $arc |
|
lappend inArcs($target) $arc |
|
|
|
return $arc |
|
} |
|
|
|
# ::struct::graph::__arc_rename -- |
|
# |
|
# Rename a arc in place. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc Name of the arc to rename |
|
# newname The new name of the arc. |
|
# |
|
# Results: |
|
# The new name of the arc. |
|
|
|
proc ::struct::graph::__arc_rename {name arc newname} { |
|
CheckMissingArc $name $arc |
|
CheckDuplicateArc $name $newname |
|
|
|
set oldname $arc |
|
|
|
# Perform the rename in the internal |
|
# data structures. |
|
|
|
# - graphAttr - not required, arc independent. |
|
# - nodeAttr - not required, arc independent. |
|
# - counters - not required |
|
|
|
variable ${name}::arcAttr |
|
variable ${name}::inArcs |
|
variable ${name}::outArcs |
|
variable ${name}::arcNodes |
|
variable ${name}::arcWeight |
|
|
|
# Arc relocation |
|
|
|
set arcNodes($newname) [set nodes $arcNodes($oldname)] |
|
unset arcNodes($oldname) |
|
|
|
# Update the two nodes ... |
|
foreach {start end} $nodes break |
|
|
|
set pos [lsearch -exact $inArcs($end) $oldname] |
|
lset inArcs($end) $pos $newname |
|
|
|
set pos [lsearch -exact $outArcs($start) $oldname] |
|
lset outArcs($start) $pos $newname |
|
|
|
if {[info exists arcAttr($oldname)]} { |
|
set arcAttr($newname) $arcAttr($oldname) |
|
unset arcAttr($oldname) |
|
} |
|
|
|
if {[info exists arcWeight($oldname)]} { |
|
set arcWeight($newname) $arcWeight($oldname) |
|
unset arcWeight($oldname) |
|
} |
|
|
|
return $newname |
|
} |
|
|
|
# ::struct::graph::__arc_set -- |
|
# |
|
# Set or get a value for an arc in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to modify or query. |
|
# key attribute to modify or query |
|
# args ?value? |
|
# |
|
# Results: |
|
# val value associated with the given key of the given arc |
|
|
|
proc ::struct::graph::__arc_set {name arc key args} { |
|
if { [llength $args] > 1 } { |
|
return -code error "wrong # args: should be \"$name arc set arc key ?value?\"" |
|
} |
|
CheckMissingArc $name $arc |
|
|
|
if { [llength $args] > 0 } { |
|
# Setting the value. This may have to create |
|
# the attribute array for this particular |
|
# node |
|
|
|
variable ${name}::arcAttr |
|
if {![info exists arcAttr($arc)]} { |
|
# No attribute data for this node, |
|
# so create it as we need it now. |
|
GenAttributeStorage $name arc $arc |
|
} |
|
|
|
upvar ${name}::$arcAttr($arc) data |
|
return [set data($key) [lindex $args end]] |
|
} else { |
|
# Getting a value |
|
return [__arc_get $name $arc $key] |
|
} |
|
} |
|
|
|
# ::struct::graph::__arc_append -- |
|
# |
|
# Append a value for an arc in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to modify or query. |
|
# args key value |
|
# |
|
# Results: |
|
# val value associated with the given key of the given arc |
|
|
|
proc ::struct::graph::__arc_append {name arc key value} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcAttr |
|
if {![info exists arcAttr($arc)]} { |
|
# No attribute data for this arc, |
|
# so create it as we need it. |
|
GenAttributeStorage $name arc $arc |
|
} |
|
|
|
upvar ${name}::$arcAttr($arc) data |
|
return [append data($key) $value] |
|
} |
|
|
|
# ::struct::graph::__arc_attr -- |
|
# |
|
# Return attribute data for one key and multiple arcs, possibly all. |
|
# |
|
# Arguments: |
|
# name Name of the graph object. |
|
# key Name of the attribute to retrieve. |
|
# |
|
# Results: |
|
# children Dictionary mapping arcs to attribute data. |
|
|
|
proc ::struct::graph::__arc_attr {name key args} { |
|
# Syntax: |
|
# |
|
# t attr key |
|
# t attr key -arcs {arclist} |
|
# t attr key -glob arcpattern |
|
# t attr key -regexp arcpattern |
|
|
|
variable ${name}::arcAttr |
|
|
|
set usage "wrong # args: should be \"[list $name] arc attr key ?-arcs list|-glob pattern|-regexp pattern?\"" |
|
if {([llength $args] != 0) && ([llength $args] != 2)} { |
|
return -code error $usage |
|
} elseif {[llength $args] == 0} { |
|
# This automatically restricts the list |
|
# to arcs which can have the attribute |
|
# in question. |
|
|
|
set arcs [array names arcAttr] |
|
} else { |
|
# Determine a list of arcs to look at |
|
# based on the chosen restriction. |
|
|
|
foreach {mode value} $args break |
|
switch -exact -- $mode { |
|
-arcs { |
|
# This is the only branch where we have to |
|
# perform an explicit restriction to the |
|
# arcs which have attributes. |
|
set arcs {} |
|
foreach n $value { |
|
if {![info exists arcAttr($n)]} continue |
|
lappend arcs $n |
|
} |
|
} |
|
-glob { |
|
set arcs [array names arcAttr $value] |
|
} |
|
-regexp { |
|
set arcs {} |
|
foreach n [array names arcAttr] { |
|
if {![regexp -- $value $n]} continue |
|
lappend arcs $n |
|
} |
|
} |
|
default { |
|
return -code error "bad type \"$mode\": must be -arcs, -glob, or -regexp" |
|
} |
|
} |
|
} |
|
|
|
# Without possibly matching arcs |
|
# the result has to be empty. |
|
|
|
if {![llength $arcs]} { |
|
return {} |
|
} |
|
|
|
# Now locate matching keys and their values. |
|
|
|
set result {} |
|
foreach n $arcs { |
|
upvar ${name}::$arcAttr($n) data |
|
if {[info exists data($key)]} { |
|
lappend result $n $data($key) |
|
} |
|
} |
|
|
|
return $result |
|
} |
|
|
|
# ::struct::graph::__arc_lappend -- |
|
# |
|
# lappend a value for an arc in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to modify or query. |
|
# args key value |
|
# |
|
# Results: |
|
# val value associated with the given key of the given arc |
|
|
|
proc ::struct::graph::__arc_lappend {name arc key value} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcAttr |
|
if {![info exists arcAttr($arc)]} { |
|
# No attribute data for this arc, |
|
# so create it as we need it. |
|
GenAttributeStorage $name arc $arc |
|
} |
|
|
|
upvar ${name}::$arcAttr($arc) data |
|
return [lappend data($key) $value] |
|
} |
|
|
|
# ::struct::graph::__arc_source -- |
|
# |
|
# Return the node at the beginning of the specified arc. |
|
# |
|
# Arguments: |
|
# name name of the graph object. |
|
# arc arc to look up. |
|
# |
|
# Results: |
|
# node name of the node. |
|
|
|
proc ::struct::graph::__arc_source {name arc} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcNodes |
|
return [lindex $arcNodes($arc) 0] |
|
} |
|
|
|
# ::struct::graph::__arc_target -- |
|
# |
|
# Return the node at the end of the specified arc. |
|
# |
|
# Arguments: |
|
# name name of the graph object. |
|
# arc arc to look up. |
|
# |
|
# Results: |
|
# node name of the node. |
|
|
|
proc ::struct::graph::__arc_target {name arc} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcNodes |
|
return [lindex $arcNodes($arc) 1] |
|
} |
|
|
|
# ::struct::graph::__arc_nodes -- |
|
# |
|
# Return a list containing both source and target nodes of the arc. |
|
# |
|
# Arguments: |
|
# name name of the graph object. |
|
# arc arc to look up. |
|
# |
|
# Results: |
|
# nodes list containing the names of the connected nodes node. |
|
# None |
|
|
|
proc ::struct::graph::__arc_nodes {name arc} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcNodes |
|
return $arcNodes($arc) |
|
} |
|
|
|
# ::struct::graph::__arc_move-target -- |
|
# |
|
# Change the destination node of the specified arc. |
|
# The arc is rotated around its origin to a different |
|
# node. |
|
# |
|
# Arguments: |
|
# name name of the graph object. |
|
# arc arc to change. |
|
# newtarget new destination/target of the arc. |
|
# |
|
# Results: |
|
# None |
|
|
|
proc ::struct::graph::__arc_move-target {name arc newtarget} { |
|
CheckMissingArc $name $arc |
|
CheckMissingNode $name $newtarget |
|
|
|
variable ${name}::arcNodes |
|
variable ${name}::inArcs |
|
|
|
set oldtarget [lindex $arcNodes($arc) 1] |
|
if {[string equal $oldtarget $newtarget]} return |
|
|
|
set arcNodes($arc) [lreplace $arcNodes($arc) 1 1 $newtarget] |
|
|
|
lappend inArcs($newtarget) $arc |
|
ldelete inArcs($oldtarget) [lsearch -exact $inArcs($oldtarget) $arc] |
|
return |
|
} |
|
|
|
# ::struct::graph::__arc_move-source -- |
|
# |
|
# Change the origin node of the specified arc. |
|
# The arc is rotated around its destination to a different |
|
# node. |
|
# |
|
# Arguments: |
|
# name name of the graph object. |
|
# arc arc to change. |
|
# newsource new origin/source of the arc. |
|
# |
|
# Results: |
|
# None |
|
|
|
proc ::struct::graph::__arc_move-source {name arc newsource} { |
|
CheckMissingArc $name $arc |
|
CheckMissingNode $name $newsource |
|
|
|
variable ${name}::arcNodes |
|
variable ${name}::outArcs |
|
|
|
set oldsource [lindex $arcNodes($arc) 0] |
|
if {[string equal $oldsource $newsource]} return |
|
|
|
set arcNodes($arc) [lreplace $arcNodes($arc) 0 0 $newsource] |
|
|
|
lappend outArcs($newsource) $arc |
|
ldelete outArcs($oldsource) [lsearch -exact $outArcs($oldsource) $arc] |
|
return |
|
} |
|
|
|
# ::struct::graph::__arc_move -- |
|
# |
|
# Changes both origin and destination node of the specified arc. |
|
# |
|
# Arguments: |
|
# name name of the graph object. |
|
# arc arc to change. |
|
# newsource new origin/source of the arc. |
|
# newtarget new destination/target of the arc. |
|
# |
|
# Results: |
|
# None |
|
|
|
proc ::struct::graph::__arc_move {name arc newsource newtarget} { |
|
CheckMissingArc $name $arc |
|
CheckMissingNode $name $newsource |
|
CheckMissingNode $name $newtarget |
|
|
|
variable ${name}::arcNodes |
|
variable ${name}::outArcs |
|
variable ${name}::inArcs |
|
|
|
set oldsource [lindex $arcNodes($arc) 0] |
|
if {![string equal $oldsource $newsource]} { |
|
set arcNodes($arc) [lreplace $arcNodes($arc) 0 0 $newsource] |
|
lappend outArcs($newsource) $arc |
|
ldelete outArcs($oldsource) [lsearch -exact $outArcs($oldsource) $arc] |
|
} |
|
|
|
set oldtarget [lindex $arcNodes($arc) 1] |
|
if {![string equal $oldtarget $newtarget]} { |
|
set arcNodes($arc) [lreplace $arcNodes($arc) 1 1 $newtarget] |
|
lappend inArcs($newtarget) $arc |
|
ldelete inArcs($oldtarget) [lsearch -exact $inArcs($oldtarget) $arc] |
|
} |
|
return |
|
} |
|
|
|
# ::struct::graph::__arc_unset -- |
|
# |
|
# Remove a keyed value from a arc. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to modify. |
|
# key attribute to remove |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::__arc_unset {name arc key} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcAttr |
|
if {![info exists arcAttr($arc)]} { |
|
# No attribute data for this arc, |
|
# nothing to do. |
|
return |
|
} |
|
|
|
upvar ${name}::$arcAttr($arc) data |
|
catch {unset data($key)} |
|
|
|
if {[array size data] == 0} { |
|
# No attributes stored for this arc, squash the whole array. |
|
unset arcAttr($arc) |
|
unset data |
|
} |
|
return |
|
} |
|
|
|
# ::struct::graph::__arc_getunweighted -- |
|
# |
|
# Return the arcs which have no weight defined. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# |
|
# Results: |
|
# arcs list of arcs without weights. |
|
|
|
proc ::struct::graph::__arc_getunweighted {name} { |
|
variable ${name}::arcNodes |
|
variable ${name}::arcWeight |
|
return [struct::set difference \ |
|
[array names arcNodes] \ |
|
[array names arcWeight]] |
|
} |
|
|
|
# ::struct::graph::__arc_getweight -- |
|
# |
|
# Get the weight given to an arc in a graph. |
|
# Throws an error if the arc has no weight defined for it. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to query. |
|
# |
|
# Results: |
|
# weight The weight defined for the arc. |
|
|
|
proc ::struct::graph::__arc_getweight {name arc} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcWeight |
|
if {![info exists arcWeight($arc)]} { |
|
return -code error "arc \"$arc\" has no weight" |
|
} |
|
return $arcWeight($arc) |
|
} |
|
|
|
# ::struct::graph::__arc_setunweighted -- |
|
# |
|
# Define a weight for all arcs which have no weight defined. |
|
# After this call no arc will be unweighted. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# defval weight to give to all unweighted arcs |
|
# |
|
# Results: |
|
# None |
|
|
|
proc ::struct::graph::__arc_setunweighted {name {weight 0}} { |
|
variable ${name}::arcWeight |
|
foreach arc [__arc_getunweighted $name] { |
|
set arcWeight($arc) $weight |
|
} |
|
return |
|
} |
|
|
|
# ::struct::graph::__arc_setweight -- |
|
# |
|
# Define a weight for an arc. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to modify |
|
# weight the weight to set for the arc |
|
# |
|
# Results: |
|
# weight The new weight |
|
|
|
proc ::struct::graph::__arc_setweight {name arc weight} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcWeight |
|
set arcWeight($arc) $weight |
|
return $weight |
|
} |
|
|
|
# ::struct::graph::__arc_unsetweight -- |
|
# |
|
# Remove the weight for an arc. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to modify |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::__arc_unsetweight {name arc} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcWeight |
|
if {[info exists arcWeight($arc)]} { |
|
unset arcWeight($arc) |
|
} |
|
return |
|
} |
|
|
|
# ::struct::graph::__arc_hasweight -- |
|
# |
|
# Remove the weight for an arc. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# arc arc to modify |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::__arc_hasweight {name arc} { |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcWeight |
|
return [info exists arcWeight($arc)] |
|
} |
|
|
|
# ::struct::graph::__arc_weights -- |
|
# |
|
# Return the arcs and weights for all arcs which have such. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# |
|
# Results: |
|
# aw dictionary mapping arcs to their weights. |
|
|
|
proc ::struct::graph::__arc_weights {name} { |
|
variable ${name}::arcWeight |
|
return [array get arcWeight] |
|
} |
|
|
|
# ::struct::graph::_arcs -- |
|
# |
|
# Return a list of all arcs in a graph satisfying some |
|
# node based restriction. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# |
|
# Results: |
|
# arcs list of arcs |
|
|
|
proc ::struct::graph::_arcs {name args} { |
|
|
|
CheckE $name arcs $args |
|
|
|
switch -exact -- $cond { |
|
none {set arcs [ArcsNONE $name]} |
|
in {set arcs [ArcsIN $name $condNodes]} |
|
out {set arcs [ArcsOUT $name $condNodes]} |
|
adj {set arcs [ArcsADJ $name $condNodes]} |
|
inner {set arcs [ArcsINN $name $condNodes]} |
|
embedding {set arcs [ArcsEMB $name $condNodes]} |
|
default {return -code error "Can't happen, panic"} |
|
} |
|
|
|
# |
|
# We have a list of arcs that match the relation to the nodes. |
|
# Now filter according to -key and -value. |
|
# |
|
|
|
if {$haveKey && $haveValue} { |
|
set arcs [ArcsKV $name $key $value $arcs] |
|
} elseif {$haveKey} { |
|
set arcs [ArcsK $name $key $arcs] |
|
} |
|
|
|
# |
|
# Apply the general filter command, if specified. |
|
# |
|
|
|
if {$haveFilter} { |
|
lappend fcmd $name |
|
set arcs [uplevel 1 [list ::struct::list filter $arcs $fcmd]] |
|
} |
|
|
|
return $arcs |
|
} |
|
|
|
proc ::struct::graph::ArcsIN {name cn} { |
|
# arcs -in. "Arcs going into the node set" |
|
# |
|
# ARC/in (NS) := { a | target(a) in NS } |
|
|
|
# The result is all arcs going to at least one node in the set |
|
# 'cn' of nodes. |
|
|
|
# As an arc has only one destination, i.e. is the |
|
# in-arc of exactly one node it is impossible to |
|
# count an arc twice. Therefore there is no need |
|
# to keep track of arcs to avoid duplicates. |
|
|
|
variable ${name}::inArcs |
|
|
|
set arcs {} |
|
foreach node $cn { |
|
foreach e $inArcs($node) { |
|
lappend arcs $e |
|
} |
|
} |
|
|
|
return $arcs |
|
} |
|
|
|
proc ::struct::graph::ArcsOUT {name cn} { |
|
# arcs -out. "Arcs coming from the node set" |
|
# |
|
# ARC/out (NS) := { a | source(a) in NS } |
|
|
|
# The result is all arcs coming from at least one node in the list |
|
# of arguments. |
|
|
|
variable ${name}::outArcs |
|
|
|
set arcs {} |
|
foreach node $cn { |
|
foreach e $outArcs($node) { |
|
lappend arcs $e |
|
} |
|
} |
|
|
|
return $arcs |
|
} |
|
|
|
proc ::struct::graph::ArcsADJ {name cn} { |
|
# arcs -adj. "Arcs adjacent to the node set" |
|
# |
|
# ARC/adj (NS) := ARC/in (NS) + ARC/out (NS) |
|
|
|
# Result is all arcs coming from or going to at |
|
# least one node in the list of arguments. |
|
|
|
return [struct::set union \ |
|
[ArcsIN $name $cn] \ |
|
[ArcsOUT $name $cn]] |
|
if 0 { |
|
# Alternate implementation using arrays, |
|
# implementing the set union directly, |
|
# intertwined with the data retrieval. |
|
|
|
array set coll {} |
|
foreach node $condNodes { |
|
foreach e $inArcs($node) { |
|
if {[info exists coll($e)]} {continue} |
|
lappend arcs $e |
|
set coll($e) . |
|
} |
|
foreach e $outArcs($node) { |
|
if {[info exists coll($e)]} {continue} |
|
lappend arcs $e |
|
set coll($e) . |
|
} |
|
} |
|
} |
|
} |
|
|
|
proc ::struct::graph::ArcsINN {name cn} { |
|
# arcs -adj. "Arcs inside the node set" |
|
# |
|
# ARC/inner (NS) := ARC/in (NS) * ARC/out (NS) |
|
|
|
# Result is all arcs running between nodes |
|
# in the list. |
|
|
|
return [struct::set intersect \ |
|
[ArcsIN $name $cn] \ |
|
[ArcsOUT $name $cn]] |
|
if 0 { |
|
# Alternate implementation using arrays, |
|
# implementing the set intersection |
|
# directly, intertwined with the data |
|
# retrieval. |
|
|
|
array set coll {} |
|
# Here we do need 'coll' as each might be an in- and |
|
# out-arc for one or two nodes in the list of arguments. |
|
|
|
array set group {} |
|
foreach node $condNodes { |
|
set group($node) . |
|
} |
|
|
|
foreach node $condNodes { |
|
foreach e $inArcs($node) { |
|
set n [lindex $arcNodes($e) 0] |
|
if {![info exists group($n)]} {continue} |
|
if { [info exists coll($e)]} {continue} |
|
lappend arcs $e |
|
set coll($e) . |
|
} |
|
# Second iteration over outgoing arcs not |
|
# required. Any arc found above would be found here as |
|
# well, and arcs not recognized above can't be |
|
# recognized by the out loop either. |
|
} |
|
} |
|
} |
|
|
|
proc ::struct::graph::ArcsEMB {name cn} { |
|
# arcs -adj. "Arcs bordering the node set" |
|
# |
|
# ARC/emb (NS) := ARC/inner (NS) - ARC/adj (NS) |
|
# <=> (ARC/in + ARC/out) - (ARC/in * ARC/out) |
|
# <=> (ARC/in - ARC/out) + (ARC/out - ARC/in) |
|
# <=> symmetric difference (ARC/in, ARC/out) |
|
|
|
# Result is all arcs from -adj minus the arcs from -inner. |
|
# IOW all arcs going from a node in the list to a node |
|
# which is *not* in the list |
|
|
|
return [struct::set symdiff \ |
|
[ArcsIN $name $cn] \ |
|
[ArcsOUT $name $cn]] |
|
if 0 { |
|
# Alternate implementation using arrays, |
|
# implementing the set intersection |
|
# directly, intertwined with the data |
|
# retrieval. |
|
|
|
# This also means that no arc can be counted twice as it |
|
# is either going to a node, or coming from a node in the |
|
# list, but it can't do both, because then it is part of |
|
# -inner, which was excluded! |
|
|
|
array set group {} |
|
foreach node $condNodes { |
|
set group($node) . |
|
} |
|
|
|
foreach node $condNodes { |
|
foreach e $inArcs($node) { |
|
set n [lindex $arcNodes($e) 0] |
|
if {[info exists group($n)]} {continue} |
|
# if {[info exists coll($e)]} {continue} |
|
lappend arcs $e |
|
# set coll($e) . |
|
} |
|
foreach e $outArcs($node) { |
|
set n [lindex $arcNodes($e) 1] |
|
if {[info exists group($n)]} {continue} |
|
# if {[info exists coll($e)]} {continue} |
|
lappend arcs $e |
|
# set coll($e) . |
|
} |
|
} |
|
} |
|
} |
|
|
|
proc ::struct::graph::ArcsNONE {name} { |
|
variable ${name}::arcNodes |
|
return [array names arcNodes] |
|
} |
|
|
|
proc ::struct::graph::ArcsKV {name key value arcs} { |
|
set filteredArcs {} |
|
foreach arc $arcs { |
|
catch { |
|
set aval [__arc_get $name $arc $key] |
|
if {$aval == $value} { |
|
lappend filteredArcs $arc |
|
} |
|
} |
|
} |
|
return $filteredArcs |
|
} |
|
|
|
proc ::struct::graph::ArcsK {name key arcs} { |
|
set filteredArcs {} |
|
foreach arc $arcs { |
|
catch { |
|
__arc_get $name $arc $key |
|
lappend filteredArcs $arc |
|
} |
|
} |
|
return $filteredArcs |
|
} |
|
|
|
# ::struct::graph::_deserialize -- |
|
# |
|
# Assignment operator. Copies a serialization into the |
|
# destination, destroying the original information. |
|
# |
|
# Arguments: |
|
# name Name of the graph object we are copying into. |
|
# serial Serialized graph to copy from. |
|
# |
|
# Results: |
|
# Nothing. |
|
|
|
proc ::struct::graph::_deserialize {name serial} { |
|
# As we destroy the original graph as part of |
|
# the copying process we don't have to deal |
|
# with issues like node names from the new graph |
|
# interfering with the old ... |
|
|
|
# I. Get the serialization of the source graph |
|
# and check it for validity. |
|
|
|
CheckSerialization $serial \ |
|
gattr nattr aattr ina outa arcn arcw |
|
|
|
# Get all the relevant data into the scope |
|
|
|
variable ${name}::graphAttr |
|
variable ${name}::nodeAttr |
|
variable ${name}::arcAttr |
|
variable ${name}::inArcs |
|
variable ${name}::outArcs |
|
variable ${name}::arcNodes |
|
variable ${name}::nextAttr |
|
variable ${name}::arcWeight |
|
|
|
# Kill the existing information and insert the new |
|
# data in their place. |
|
|
|
array unset inArcs * |
|
array unset outArcs * |
|
array set inArcs [array get ina] |
|
array set outArcs [array get outa] |
|
unset ina outa |
|
|
|
array unset arcNodes * |
|
array set arcNodes [array get arcn] |
|
unset arcn |
|
|
|
array unset arcWeight * |
|
array set arcWeight [array get arcw] |
|
unset arcw |
|
|
|
set nextAttr 0 |
|
foreach a [array names nodeAttr] { |
|
unset ${name}::$nodeAttr($a) |
|
} |
|
foreach a [array names arcAttr] { |
|
unset ${name}::$arcAttr($a) |
|
} |
|
foreach n [array names nattr] { |
|
GenAttributeStorage $name node $n |
|
array set ${name}::$nodeAttr($n) $nattr($n) |
|
} |
|
foreach a [array names aattr] { |
|
GenAttributeStorage $name arc $a |
|
array set ${name}::$arcAttr($a) $aattr($a) |
|
} |
|
|
|
array unset graphAttr * |
|
array set graphAttr $gattr |
|
|
|
## Debug ## Dump internals ... |
|
if {0} { |
|
puts "___________________________________ $name" |
|
parray inArcs |
|
parray outArcs |
|
parray arcNodes |
|
parray nodeAttr |
|
parray arcAttr |
|
parray graphAttr |
|
parray arcWeight |
|
puts ___________________________________ |
|
} |
|
return |
|
} |
|
|
|
# ::struct::graph::_destroy -- |
|
# |
|
# Destroy a graph, including its associated command and data storage. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::_destroy {name} { |
|
namespace delete $name |
|
interp alias {} $name {} |
|
} |
|
|
|
# ::struct::graph::__generateUniqueArcName -- |
|
# |
|
# Generate a unique arc name for the given graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# |
|
# Results: |
|
# arc name of a arc guaranteed to not exist in the graph. |
|
|
|
proc ::struct::graph::__generateUniqueArcName {name} { |
|
variable ${name}::nextUnusedArc |
|
while {[__arc_exists $name "arc${nextUnusedArc}"]} { |
|
incr nextUnusedArc |
|
} |
|
return "arc${nextUnusedArc}" |
|
} |
|
|
|
# ::struct::graph::__generateUniqueNodeName -- |
|
# |
|
# Generate a unique node name for the given graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# |
|
# Results: |
|
# node name of a node guaranteed to not exist in the graph. |
|
|
|
proc ::struct::graph::__generateUniqueNodeName {name} { |
|
variable ${name}::nextUnusedNode |
|
while {[__node_exists $name "node${nextUnusedNode}"]} { |
|
incr nextUnusedNode |
|
} |
|
return "node${nextUnusedNode}" |
|
} |
|
|
|
# ::struct::graph::_get -- |
|
# |
|
# Get a keyed value from the graph itself |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# key key to lookup |
|
# |
|
# Results: |
|
# value value associated with the key given. |
|
|
|
proc ::struct::graph::_get {name key} { |
|
variable ${name}::graphAttr |
|
if { ![info exists graphAttr($key)] } { |
|
return -code error "invalid key \"$key\" for graph \"$name\"" |
|
} |
|
return $graphAttr($key) |
|
} |
|
|
|
# ::struct::graph::_getall -- |
|
# |
|
# Get an attribute dictionary from a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# pattern optional, glob pattern |
|
# |
|
# Results: |
|
# value value associated with the key given. |
|
|
|
proc ::struct::graph::_getall {name {pattern *}} { |
|
variable ${name}::graphAttr |
|
return [array get graphAttr $pattern] |
|
} |
|
|
|
# ::struct::graph::_keys -- |
|
# |
|
# Get a list of keys from a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# pattern optional, glob pattern |
|
# |
|
# Results: |
|
# value list of known keys |
|
|
|
proc ::struct::graph::_keys {name {pattern *}} { |
|
variable ${name}::graphAttr |
|
return [array names graphAttr $pattern] |
|
} |
|
|
|
# ::struct::graph::_keyexists -- |
|
# |
|
# Test for existence of a given key in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# key key to lookup |
|
# |
|
# Results: |
|
# 1 if the key exists, 0 else. |
|
|
|
proc ::struct::graph::_keyexists {name key} { |
|
variable ${name}::graphAttr |
|
return [info exists graphAttr($key)] |
|
} |
|
|
|
# ::struct::graph::_node -- |
|
# |
|
# Dispatches the invocation of node methods to the proper handler |
|
# procedure. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# cmd node command to invoke |
|
# args arguments to propagate to the handler for the node command |
|
# |
|
# Results: |
|
# As of the the invoked handler. |
|
|
|
proc ::struct::graph::_node {name cmd args} { |
|
# Split the args into command and args components |
|
set sub __node_$cmd |
|
if { [llength [info commands ::struct::graph::$sub]] == 0 } { |
|
set optlist [lsort [info commands ::struct::graph::__node_*]] |
|
set xlist {} |
|
foreach p $optlist { |
|
set p [namespace tail $p] |
|
lappend xlist [string range $p 7 end] |
|
} |
|
set optlist [linsert [join $xlist ", "] "end-1" "or"] |
|
return -code error \ |
|
"bad option \"$cmd\": must be $optlist" |
|
} |
|
uplevel 1 [linsert $args 0 ::struct::graph::$sub $name] |
|
} |
|
|
|
# ::struct::graph::__node_degree -- |
|
# |
|
# Return the number of arcs adjacent to the specified node. |
|
# If one of the restrictions -in or -out is given only |
|
# incoming resp. outgoing arcs are counted. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# args option, followed by the node. |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::__node_degree {name args} { |
|
|
|
if {([llength $args] < 1) || ([llength $args] > 2)} { |
|
return -code error "wrong # args: should be \"$name node degree ?-in|-out? node\"" |
|
} |
|
|
|
switch -exact -- [llength $args] { |
|
1 { |
|
set opt {} |
|
set node [lindex $args 0] |
|
} |
|
2 { |
|
set opt [lindex $args 0] |
|
set node [lindex $args 1] |
|
} |
|
default {return -code error "Can't happen, panic"} |
|
} |
|
|
|
# Validate the option. |
|
|
|
switch -exact -- $opt { |
|
{} - |
|
-in - |
|
-out {} |
|
default { |
|
return -code error "bad option \"$opt\": must be -in or -out" |
|
} |
|
} |
|
|
|
# Validate the node |
|
|
|
CheckMissingNode $name $node |
|
|
|
variable ${name}::inArcs |
|
variable ${name}::outArcs |
|
|
|
switch -exact -- $opt { |
|
-in { |
|
set result [llength $inArcs($node)] |
|
} |
|
-out { |
|
set result [llength $outArcs($node)] |
|
} |
|
{} { |
|
set result [expr {[llength $inArcs($node)] \ |
|
+ [llength $outArcs($node)]}] |
|
|
|
# loops count twice, don't do <set> arithmetics, i.e. no union! |
|
if {0} { |
|
array set coll {} |
|
set result [llength $inArcs($node)] |
|
|
|
foreach e $inArcs($node) { |
|
set coll($e) . |
|
} |
|
foreach e $outArcs($node) { |
|
if {[info exists coll($e)]} {continue} |
|
incr result |
|
set coll($e) . |
|
} |
|
} |
|
} |
|
default {return -code error "Can't happen, panic"} |
|
} |
|
|
|
return $result |
|
} |
|
|
|
# ::struct::graph::__node_delete -- |
|
# |
|
# Remove a node from a graph, including all of its values. |
|
# Additionally removes the arcs connected to this node. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# args list of the nodes to delete. |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::__node_delete {name args} { |
|
if {![llength $args]} { |
|
return {wrong # args: should be "::struct::graph::__node_delete name node node..."} |
|
} |
|
# seen is used to catch duplicate nodes in the args |
|
array set seen {} |
|
foreach node $args { |
|
if {[info exists seen($node)]} { |
|
return -code error "node \"$node\" does not exist in graph \"$name\"" |
|
} |
|
CheckMissingNode $name $node |
|
set seen($node) . |
|
} |
|
|
|
variable ${name}::inArcs |
|
variable ${name}::outArcs |
|
variable ${name}::nodeAttr |
|
|
|
foreach node $args { |
|
# Remove all the arcs connected to this node |
|
foreach e $inArcs($node) { |
|
__arc_delete $name $e |
|
} |
|
foreach e $outArcs($node) { |
|
# Check existence to avoid problems with |
|
# loops (they are in and out arcs! at |
|
# the same time and thus already deleted) |
|
if { [__arc_exists $name $e] } { |
|
__arc_delete $name $e |
|
} |
|
} |
|
|
|
unset inArcs($node) |
|
unset outArcs($node) |
|
|
|
if {[info exists nodeAttr($node)]} { |
|
unset ${name}::$nodeAttr($node) |
|
unset nodeAttr($node) |
|
} |
|
} |
|
|
|
return |
|
} |
|
|
|
# ::struct::graph::__node_exists -- |
|
# |
|
# Test for existence of a given node in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to look for. |
|
# |
|
# Results: |
|
# 1 if the node exists, 0 else. |
|
|
|
proc ::struct::graph::__node_exists {name node} { |
|
return [info exists ${name}::inArcs($node)] |
|
} |
|
|
|
# ::struct::graph::__node_get -- |
|
# |
|
# Get a keyed value from a node in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to query. |
|
# key key to lookup |
|
# |
|
# Results: |
|
# value value associated with the key given. |
|
|
|
proc ::struct::graph::__node_get {name node key} { |
|
CheckMissingNode $name $node |
|
|
|
variable ${name}::nodeAttr |
|
if {![info exists nodeAttr($node)]} { |
|
# No attribute data for this node, key has to be invalid. |
|
return -code error "invalid key \"$key\" for node \"$node\"" |
|
} |
|
|
|
upvar ${name}::$nodeAttr($node) data |
|
if { ![info exists data($key)] } { |
|
return -code error "invalid key \"$key\" for node \"$node\"" |
|
} |
|
return $data($key) |
|
} |
|
|
|
# ::struct::graph::__node_getall -- |
|
# |
|
# Get a serialized list of key/value pairs from a node in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to query. |
|
# pattern optional glob pattern to restrict retrieval |
|
# |
|
# Results: |
|
# value value associated with the key given. |
|
|
|
proc ::struct::graph::__node_getall {name node {pattern *}} { |
|
CheckMissingNode $name $node |
|
|
|
variable ${name}::nodeAttr |
|
if {![info exists nodeAttr($node)]} { |
|
# No attributes ... |
|
return {} |
|
} |
|
|
|
upvar ${name}::$nodeAttr($node) data |
|
return [array get data $pattern] |
|
} |
|
|
|
# ::struct::graph::__node_keys -- |
|
# |
|
# Get a list of keys from a node in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to query. |
|
# pattern optional glob pattern to restrict retrieval |
|
# |
|
# Results: |
|
# value value associated with the key given. |
|
|
|
proc ::struct::graph::__node_keys {name node {pattern *}} { |
|
CheckMissingNode $name $node |
|
|
|
variable ${name}::nodeAttr |
|
if {![info exists nodeAttr($node)]} { |
|
# No attributes ... |
|
return {} |
|
} |
|
|
|
upvar ${name}::$nodeAttr($node) data |
|
return [array names data $pattern] |
|
} |
|
|
|
# ::struct::graph::__node_keyexists -- |
|
# |
|
# Test for existence of a given key for a node in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to query. |
|
# key key to lookup |
|
# |
|
# Results: |
|
# 1 if the key exists, 0 else. |
|
|
|
proc ::struct::graph::__node_keyexists {name node key} { |
|
CheckMissingNode $name $node |
|
|
|
variable ${name}::nodeAttr |
|
if {![info exists nodeAttr($node)]} { |
|
# No attribute data for this node, key cannot exist. |
|
return 0 |
|
} |
|
|
|
upvar ${name}::$nodeAttr($node) data |
|
return [info exists data($key)] |
|
} |
|
|
|
# ::struct::graph::__node_insert -- |
|
# |
|
# Add a node to a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# args node to insert; must be unique. If none is given, |
|
# the routine will generate a unique node name. |
|
# |
|
# Results: |
|
# node The name of the new node. |
|
|
|
proc ::struct::graph::__node_insert {name args} { |
|
if {[llength $args] == 0} { |
|
# No node name was given; generate a unique one |
|
set args [list [__generateUniqueNodeName $name]] |
|
} else { |
|
# seen is used to catch duplicate nodes in the args |
|
array set seen {} |
|
foreach node $args { |
|
if {[info exists seen($node)]} { |
|
return -code error "node \"$node\" already exists in graph \"$name\"" |
|
} |
|
CheckDuplicateNode $name $node |
|
set seen($node) . |
|
} |
|
} |
|
|
|
variable ${name}::inArcs |
|
variable ${name}::outArcs |
|
|
|
foreach node $args { |
|
# Set up the new node |
|
set inArcs($node) {} |
|
set outArcs($node) {} |
|
} |
|
|
|
return $args |
|
} |
|
|
|
# ::struct::graph::__node_opposite -- |
|
# |
|
# Retrieve node opposite to the specified one, along the arc. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to look up. |
|
# arc arc to look up. |
|
# |
|
# Results: |
|
# nodex Node opposite to <node,arc> |
|
|
|
proc ::struct::graph::__node_opposite {name node arc} { |
|
CheckMissingNode $name $node |
|
CheckMissingArc $name $arc |
|
|
|
variable ${name}::arcNodes |
|
|
|
# Node must be connected to at least one end of the arc. |
|
|
|
if {[string equal $node [lindex $arcNodes($arc) 0]]} { |
|
set result [lindex $arcNodes($arc) 1] |
|
} elseif {[string equal $node [lindex $arcNodes($arc) 1]]} { |
|
set result [lindex $arcNodes($arc) 0] |
|
} else { |
|
return -code error "node \"$node\" and arc \"$arc\" are not connected\ |
|
in graph \"$name\"" |
|
} |
|
|
|
return $result |
|
} |
|
|
|
# ::struct::graph::__node_set -- |
|
# |
|
# Set or get a value for a node in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to modify or query. |
|
# key attribute to modify or query |
|
# args ?value? |
|
# |
|
# Results: |
|
# val value associated with the given key of the given node |
|
|
|
proc ::struct::graph::__node_set {name node key args} { |
|
if { [llength $args] > 1 } { |
|
return -code error "wrong # args: should be \"$name node set node key ?value?\"" |
|
} |
|
CheckMissingNode $name $node |
|
|
|
if { [llength $args] > 0 } { |
|
# Setting the value. This may have to create |
|
# the attribute array for this particular |
|
# node |
|
|
|
variable ${name}::nodeAttr |
|
if {![info exists nodeAttr($node)]} { |
|
# No attribute data for this node, |
|
# so create it as we need it now. |
|
GenAttributeStorage $name node $node |
|
} |
|
upvar ${name}::$nodeAttr($node) data |
|
|
|
return [set data($key) [lindex $args end]] |
|
} else { |
|
# Getting a value |
|
return [__node_get $name $node $key] |
|
} |
|
} |
|
|
|
# ::struct::graph::__node_append -- |
|
# |
|
# Append a value for a node in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to modify or query. |
|
# args key value |
|
# |
|
# Results: |
|
# val value associated with the given key of the given node |
|
|
|
proc ::struct::graph::__node_append {name node key value} { |
|
CheckMissingNode $name $node |
|
|
|
variable ${name}::nodeAttr |
|
if {![info exists nodeAttr($node)]} { |
|
# No attribute data for this node, |
|
# so create it as we need it. |
|
GenAttributeStorage $name node $node |
|
} |
|
|
|
upvar ${name}::$nodeAttr($node) data |
|
return [append data($key) $value] |
|
} |
|
|
|
# ::struct::graph::__node_attr -- |
|
# |
|
# Return attribute data for one key and multiple nodes, possibly all. |
|
# |
|
# Arguments: |
|
# name Name of the graph object. |
|
# key Name of the attribute to retrieve. |
|
# |
|
# Results: |
|
# children Dictionary mapping nodes to attribute data. |
|
|
|
proc ::struct::graph::__node_attr {name key args} { |
|
# Syntax: |
|
# |
|
# t attr key |
|
# t attr key -nodes {nodelist} |
|
# t attr key -glob nodepattern |
|
# t attr key -regexp nodepattern |
|
|
|
variable ${name}::nodeAttr |
|
|
|
set usage "wrong # args: should be \"[list $name] node attr key ?-nodes list|-glob pattern|-regexp pattern?\"" |
|
if {([llength $args] != 0) && ([llength $args] != 2)} { |
|
return -code error $usage |
|
} elseif {[llength $args] == 0} { |
|
# This automatically restricts the list |
|
# to nodes which can have the attribute |
|
# in question. |
|
|
|
set nodes [array names nodeAttr] |
|
} else { |
|
# Determine a list of nodes to look at |
|
# based on the chosen restriction. |
|
|
|
foreach {mode value} $args break |
|
switch -exact -- $mode { |
|
-nodes { |
|
# This is the only branch where we have to |
|
# perform an explicit restriction to the |
|
# nodes which have attributes. |
|
set nodes {} |
|
foreach n $value { |
|
if {![info exists nodeAttr($n)]} continue |
|
lappend nodes $n |
|
} |
|
} |
|
-glob { |
|
set nodes [array names nodeAttr $value] |
|
} |
|
-regexp { |
|
set nodes {} |
|
foreach n [array names nodeAttr] { |
|
if {![regexp -- $value $n]} continue |
|
lappend nodes $n |
|
} |
|
} |
|
default { |
|
return -code error "bad type \"$mode\": must be -glob, -nodes, or -regexp" |
|
} |
|
} |
|
} |
|
|
|
# Without possibly matching nodes |
|
# the result has to be empty. |
|
|
|
if {![llength $nodes]} { |
|
return {} |
|
} |
|
|
|
# Now locate matching keys and their values. |
|
|
|
set result {} |
|
foreach n $nodes { |
|
upvar ${name}::$nodeAttr($n) data |
|
if {[info exists data($key)]} { |
|
lappend result $n $data($key) |
|
} |
|
} |
|
|
|
return $result |
|
} |
|
|
|
# ::struct::graph::__node_lappend -- |
|
# |
|
# lappend a value for a node in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to modify or query. |
|
# args key value |
|
# |
|
# Results: |
|
# val value associated with the given key of the given node |
|
|
|
proc ::struct::graph::__node_lappend {name node key value} { |
|
CheckMissingNode $name $node |
|
|
|
variable ${name}::nodeAttr |
|
if {![info exists nodeAttr($node)]} { |
|
# No attribute data for this node, |
|
# so create it as we need it. |
|
GenAttributeStorage $name node $node |
|
} |
|
|
|
upvar ${name}::$nodeAttr($node) data |
|
return [lappend data($key) $value] |
|
} |
|
|
|
# ::struct::graph::__node_unset -- |
|
# |
|
# Remove a keyed value from a node. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node to modify. |
|
# key attribute to remove |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::__node_unset {name node key} { |
|
CheckMissingNode $name $node |
|
|
|
variable ${name}::nodeAttr |
|
if {![info exists nodeAttr($node)]} { |
|
# No attribute data for this node, |
|
# nothing to do. |
|
return |
|
} |
|
|
|
upvar ${name}::$nodeAttr($node) data |
|
catch {unset data($key)} |
|
|
|
if {[array size data] == 0} { |
|
# No attributes stored for this node, squash the whole array. |
|
unset nodeAttr($node) |
|
unset data |
|
} |
|
return |
|
} |
|
|
|
# ::struct::graph::_nodes -- |
|
# |
|
# Return a list of all nodes in a graph satisfying some restriction. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# args list of options and nodes specifying the restriction. |
|
# |
|
# Results: |
|
# nodes list of nodes |
|
|
|
proc ::struct::graph::_nodes {name args} { |
|
|
|
CheckE $name nodes $args |
|
|
|
switch -exact -- $cond { |
|
none {set nodes [NodesNONE $name]} |
|
in {set nodes [NodesIN $name $condNodes]} |
|
out {set nodes [NodesOUT $name $condNodes]} |
|
adj {set nodes [NodesADJ $name $condNodes]} |
|
inner {set nodes [NodesINN $name $condNodes]} |
|
embedding {set nodes [NodesEMB $name $condNodes]} |
|
default {return -code error "Can't happen, panic"} |
|
} |
|
|
|
# |
|
# We have a list of nodes that match the relation to the nodes. |
|
# Now filter according to -key and -value. |
|
# |
|
|
|
if {$haveKey && $haveValue} { |
|
set nodes [NodesKV $name $key $value $nodes] |
|
} elseif {$haveKey} { |
|
set nodes [NodesK $name $key $nodes] |
|
} |
|
|
|
# |
|
# Apply the general filter command, if specified. |
|
# |
|
|
|
if {$haveFilter} { |
|
lappend fcmd $name |
|
set nodes [uplevel 1 [list ::struct::list filter $nodes $fcmd]] |
|
} |
|
|
|
return $nodes |
|
} |
|
|
|
proc ::struct::graph::NodesIN {name cn} { |
|
# nodes -in. |
|
# "Neighbours with arcs going into the node set" |
|
# |
|
# NODES/in (NS) := { source(a) | a in ARC/in (NS) } |
|
|
|
# Result is all nodes with at least one arc going to |
|
# at least one node in the list of arguments. |
|
|
|
variable ${name}::inArcs |
|
variable ${name}::arcNodes |
|
|
|
set nodes {} |
|
array set coll {} |
|
|
|
foreach node $cn { |
|
foreach e $inArcs($node) { |
|
set n [lindex $arcNodes($e) 0] |
|
if {[info exists coll($n)]} {continue} |
|
lappend nodes $n |
|
set coll($n) . |
|
} |
|
} |
|
return $nodes |
|
} |
|
|
|
proc ::struct::graph::NodesOUT {name cn} { |
|
# nodes -out. |
|
# "Neighbours with arcs coming from the node set" |
|
# |
|
# NODES/out (NS) := { target(a) | a in ARC/out (NS) } |
|
|
|
# Result is all nodes with at least one arc coming from |
|
# at least one node in the list of arguments. |
|
|
|
variable ${name}::outArcs |
|
variable ${name}::arcNodes |
|
|
|
set nodes {} |
|
array set coll {} |
|
|
|
foreach node $cn { |
|
foreach e $outArcs($node) { |
|
set n [lindex $arcNodes($e) 1] |
|
if {[info exists coll($n)]} {continue} |
|
lappend nodes $n |
|
set coll($n) . |
|
} |
|
} |
|
return $nodes |
|
} |
|
|
|
proc ::struct::graph::NodesADJ {name cn} { |
|
# nodes -adj. |
|
# "Neighbours of the node set" |
|
# |
|
# NODES/adj (NS) := NODES/in (NS) + NODES/out (NS) |
|
|
|
# Result is all nodes with at least one arc coming from |
|
# or going to at least one node in the list of arguments. |
|
|
|
return [struct::set union \ |
|
[NodesIN $name $cn] \ |
|
[NodesOUT $name $cn]] |
|
if 0 { |
|
# Alternate implementation using arrays, |
|
# implementing the set union directly, |
|
# intertwined with the data retrieval. |
|
|
|
foreach node $cn { |
|
foreach e $inArcs($node) { |
|
set n [lindex $arcNodes($e) 0] |
|
if {[info exists coll($n)]} {continue} |
|
lappend nodes $n |
|
set coll($n) . |
|
} |
|
foreach e $outArcs($node) { |
|
set n [lindex $arcNodes($e) 1] |
|
if {[info exists coll($n)]} {continue} |
|
lappend nodes $n |
|
set coll($n) . |
|
} |
|
} |
|
} |
|
} |
|
|
|
proc ::struct::graph::NodesINN {name cn} { |
|
# nodes -adj. |
|
# "Inner node of the node set" |
|
# |
|
# NODES/inner (NS) := NODES/adj (NS) * NS |
|
|
|
# Result is all nodes from the set with at least one arc coming |
|
# from or going to at least one node in the set. |
|
# |
|
# I.e the adjacent nodes also in the set. |
|
|
|
return [struct::set intersect \ |
|
[NodesADJ $name $cn] $cn] |
|
|
|
if 0 { |
|
# Alternate implementation using arrays, |
|
# implementing the set intersect/union |
|
# directly, intertwined with the data retrieval. |
|
|
|
array set group {} |
|
foreach node $cn { |
|
set group($node) . |
|
} |
|
|
|
foreach node $cn { |
|
foreach e $inArcs($node) { |
|
set n [lindex $arcNodes($e) 0] |
|
if {![info exists group($n)]} {continue} |
|
if { [info exists coll($n)]} {continue} |
|
lappend nodes $n |
|
set coll($n) . |
|
} |
|
foreach e $outArcs($node) { |
|
set n [lindex $arcNodes($e) 1] |
|
if {![info exists group($n)]} {continue} |
|
if { [info exists coll($n)]} {continue} |
|
lappend nodes $n |
|
set coll($n) . |
|
} |
|
} |
|
} |
|
} |
|
|
|
proc ::struct::graph::NodesEMB {name cn} { |
|
# nodes -embedding. |
|
# "Embedding nodes for the node set" |
|
# |
|
# NODES/emb (NS) := NODES/adj (NS) - NS |
|
|
|
# Result is all nodes with at least one arc coming from or going |
|
# to at least one node in the set, but not in the set itself |
|
# |
|
# I.e the adjacent nodes not in the set. |
|
|
|
# Result is all nodes from the set with at least one arc coming |
|
# from or going to at least one node in the set. |
|
# I.e the adjacent nodes still in the set. |
|
|
|
return [struct::set difference \ |
|
[NodesADJ $name $cn] $cn] |
|
|
|
if 0 { |
|
# Alternate implementation using arrays, |
|
# implementing the set diff/union directly, |
|
# intertwined with the data retrieval. |
|
|
|
array set group {} |
|
foreach node $cn { |
|
set group($node) . |
|
} |
|
|
|
foreach node $cn { |
|
foreach e $inArcs($node) { |
|
set n [lindex $arcNodes($e) 0] |
|
if {[info exists group($n)]} {continue} |
|
if {[info exists coll($n)]} {continue} |
|
lappend nodes $n |
|
set coll($n) . |
|
} |
|
foreach e $outArcs($node) { |
|
set n [lindex $arcNodes($e) 1] |
|
if {[info exists group($n)]} {continue} |
|
if {[info exists coll($n)]} {continue} |
|
lappend nodes $n |
|
set coll($n) . |
|
} |
|
} |
|
} |
|
} |
|
|
|
proc ::struct::graph::NodesNONE {name} { |
|
variable ${name}::inArcs |
|
return [array names inArcs] |
|
} |
|
|
|
proc ::struct::graph::NodesKV {name key value nodes} { |
|
set filteredNodes {} |
|
foreach node $nodes { |
|
catch { |
|
set nval [__node_get $name $node $key] |
|
if {$nval == $value} { |
|
lappend filteredNodes $node |
|
} |
|
} |
|
} |
|
return $filteredNodes |
|
} |
|
|
|
proc ::struct::graph::NodesK {name key nodes} { |
|
set filteredNodes {} |
|
foreach node $nodes { |
|
catch { |
|
__node_get $name $node $key |
|
lappend filteredNodes $node |
|
} |
|
} |
|
return $filteredNodes |
|
} |
|
|
|
# ::struct::graph::__node_rename -- |
|
# |
|
# Rename a node in place. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node Name of the node to rename |
|
# newname The new name of the node. |
|
# |
|
# Results: |
|
# The new name of the node. |
|
|
|
proc ::struct::graph::__node_rename {name node newname} { |
|
CheckMissingNode $name $node |
|
CheckDuplicateNode $name $newname |
|
|
|
set oldname $node |
|
|
|
# Perform the rename in the internal |
|
# data structures. |
|
|
|
# - graphAttr - not required, node independent. |
|
# - arcAttr - not required, node independent. |
|
# - counters - not required |
|
|
|
variable ${name}::nodeAttr |
|
variable ${name}::inArcs |
|
variable ${name}::outArcs |
|
variable ${name}::arcNodes |
|
|
|
# Node relocation |
|
|
|
set inArcs($newname) [set in $inArcs($oldname)] |
|
unset inArcs($oldname) |
|
set outArcs($newname) [set out $outArcs($oldname)] |
|
unset outArcs($oldname) |
|
|
|
if {[info exists nodeAttr($oldname)]} { |
|
set nodeAttr($newname) $nodeAttr($oldname) |
|
unset nodeAttr($oldname) |
|
} |
|
|
|
# Update all relevant arcs. |
|
# 8.4: lset ... |
|
|
|
foreach a $in { |
|
set arcNodes($a) [list [lindex $arcNodes($a) 0] $newname] |
|
} |
|
foreach a $out { |
|
set arcNodes($a) [list $newname [lindex $arcNodes($a) 1]] |
|
} |
|
|
|
return $newname |
|
} |
|
|
|
# ::struct::graph::_serialize -- |
|
# |
|
# Serialize a graph object (partially) into a transportable value. |
|
# If only a subset of nodes is serialized the result will be a sub- |
|
# graph in the mathematical sense of the word: These nodes and all |
|
# arcs which are only between these nodes. No arcs to modes outside |
|
# of the listed set. |
|
# |
|
# Arguments: |
|
# name Name of the graph. |
|
# args list of nodes to place into the serialized graph |
|
# |
|
# Results: |
|
# A list structure describing the part of the graph which was serialized. |
|
|
|
proc ::struct::graph::_serialize {name args} { |
|
|
|
# all - boolean flag - set if and only if the all nodes of the |
|
# graph are chosen for serialization. Because if that is true we |
|
# can skip the step finding the relevant arcs and simply take all |
|
# arcs. |
|
|
|
variable ${name}::arcNodes |
|
variable ${name}::arcWeight |
|
variable ${name}::inArcs |
|
|
|
set all 0 |
|
if {[llength $args] > 0} { |
|
set nodes [luniq $args] |
|
foreach n $nodes {CheckMissingNode $name $n} |
|
if {[llength $nodes] == [array size inArcs]} { |
|
set all 1 |
|
} |
|
} else { |
|
set nodes [array names inArcs] |
|
set all 1 |
|
} |
|
|
|
if {$all} { |
|
set arcs [array names arcNodes] |
|
} else { |
|
set arcs [eval [linsert $nodes 0 _arcs $name -inner]] |
|
} |
|
|
|
variable ${name}::nodeAttr |
|
variable ${name}::arcAttr |
|
variable ${name}::graphAttr |
|
|
|
set na {} |
|
set aa {} |
|
array set np {} |
|
|
|
# node indices, attribute data ... |
|
set i 0 |
|
foreach n $nodes { |
|
set np($n) [list $i] |
|
incr i 3 |
|
|
|
if {[info exists nodeAttr($n)]} { |
|
upvar ${name}::$nodeAttr($n) data |
|
lappend np($n) [array get data] |
|
} else { |
|
lappend np($n) {} |
|
} |
|
} |
|
|
|
# arc dictionary |
|
set arcdata {} |
|
foreach a $arcs { |
|
foreach {src dst} $arcNodes($a) break |
|
# Arc information |
|
|
|
set arc [list $a] |
|
lappend arc [lindex $np($dst) 0] |
|
if {[info exists arcAttr($a)]} { |
|
upvar ${name}::$arcAttr($a) data |
|
lappend arc [array get data] |
|
} else { |
|
lappend arc {} |
|
} |
|
|
|
# Add weight information, if there is any. |
|
|
|
if {[info exists arcWeight($a)]} { |
|
lappend arc $arcWeight($a) |
|
} |
|
|
|
# Add the information to the node |
|
# indices ... |
|
|
|
lappend np($src) $arc |
|
} |
|
|
|
# Combine the transient data into one result. |
|
|
|
set result [list] |
|
foreach n $nodes { |
|
lappend result $n |
|
lappend result [lindex $np($n) 1] |
|
lappend result [lrange $np($n) 2 end] |
|
} |
|
lappend result [array get graphAttr] |
|
|
|
return $result |
|
} |
|
|
|
# ::struct::graph::_set -- |
|
# |
|
# Set or get a keyed value from the graph itself |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# key attribute to modify or query |
|
# args ?value? |
|
# |
|
# Results: |
|
# value value associated with the key given. |
|
|
|
proc ::struct::graph::_set {name key args} { |
|
if { [llength $args] > 1 } { |
|
return -code error "wrong # args: should be \"$name set key ?value?\"" |
|
} |
|
if { [llength $args] > 0 } { |
|
variable ${name}::graphAttr |
|
return [set graphAttr($key) [lindex $args end]] |
|
} else { |
|
# Getting a value |
|
return [_get $name $key] |
|
} |
|
} |
|
|
|
# ::struct::graph::_swap -- |
|
# |
|
# Swap two nodes in a graph. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node1 first node to swap. |
|
# node2 second node to swap. |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::_swap {name node1 node2} { |
|
# Can only swap two real nodes |
|
CheckMissingNode $name $node1 |
|
CheckMissingNode $name $node2 |
|
|
|
# Can't swap a node with itself |
|
if { [string equal $node1 $node2] } { |
|
return -code error "cannot swap node \"$node1\" with itself" |
|
} |
|
|
|
# Swapping nodes means swapping their labels, values and arcs |
|
variable ${name}::outArcs |
|
variable ${name}::inArcs |
|
variable ${name}::arcNodes |
|
variable ${name}::nodeAttr |
|
|
|
# Redirect arcs to the new nodes. |
|
|
|
foreach e $inArcs($node1) {lset arcNodes($e) end $node2} |
|
foreach e $inArcs($node2) {lset arcNodes($e) end $node1} |
|
foreach e $outArcs($node1) {lset arcNodes($e) 0 $node2} |
|
foreach e $outArcs($node2) {lset arcNodes($e) 0 $node1} |
|
|
|
# Swap arc lists |
|
|
|
set tmp $inArcs($node1) |
|
set inArcs($node1) $inArcs($node2) |
|
set inArcs($node2) $tmp |
|
|
|
set tmp $outArcs($node1) |
|
set outArcs($node1) $outArcs($node2) |
|
set outArcs($node2) $tmp |
|
|
|
# Swap the values |
|
# More complicated now with the possibility that nodes do not have |
|
# attribute storage associated with them. But also |
|
# simpler as we just have to swap/move the array |
|
# reference |
|
|
|
if { |
|
[set ia [info exists nodeAttr($node1)]] || |
|
[set ib [info exists nodeAttr($node2)]] |
|
} { |
|
# At least one of the nodes has attribute data. We simply swap |
|
# the references to the arrays containing them. No need to |
|
# copy the actual data around. |
|
|
|
if {$ia && $ib} { |
|
set tmp $nodeAttr($node1) |
|
set nodeAttr($node1) $nodeAttr($node2) |
|
set nodeAttr($node2) $tmp |
|
} elseif {$ia} { |
|
set nodeAttr($node2) $nodeAttr($node1) |
|
unset nodeAttr($node1) |
|
} elseif {$ib} { |
|
set nodeAttr($node1) $nodeAttr($node2) |
|
unset nodeAttr($node2) |
|
} else { |
|
return -code error "Impossible condition." |
|
} |
|
} ; # else: No attribute storage => Nothing to do {} |
|
|
|
return |
|
} |
|
|
|
# ::struct::graph::_unset -- |
|
# |
|
# Remove a keyed value from the graph itself |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# key attribute to remove |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::_unset {name key} { |
|
variable ${name}::graphAttr |
|
if {[info exists graphAttr($key)]} { |
|
unset graphAttr($key) |
|
} |
|
return |
|
} |
|
|
|
# ::struct::graph::_walk -- |
|
# |
|
# Walk a graph using a pre-order depth or breadth first |
|
# search. Pre-order DFS is the default. At each node that is visited, |
|
# a command will be called with the name of the graph and the node. |
|
# |
|
# Arguments: |
|
# name name of the graph. |
|
# node node at which to start. |
|
# args additional args: ?-order pre|post|both? ?-type {bfs|dfs}? |
|
# -command cmd |
|
# |
|
# Results: |
|
# None. |
|
|
|
proc ::struct::graph::_walk {name node args} { |
|
set usage "$name walk node ?-dir forward|backward?\ |
|
?-order pre|post|both? ?-type bfs|dfs? -command cmd" |
|
|
|
if {[llength $args] < 2} { |
|
return -code error "wrong # args: should be \"$usage\"" |
|
} |
|
|
|
CheckMissingNode $name $node |
|
|
|
# Set defaults |
|
set type dfs |
|
set order pre |
|
set cmd "" |
|
set dir forward |
|
|
|
# Process specified options |
|
for {set i 0} {$i < [llength $args]} {incr i} { |
|
set flag [lindex $args $i] |
|
switch -glob -- $flag { |
|
"-type" { |
|
incr i |
|
if { $i >= [llength $args] } { |
|
return -code error "value for \"$flag\" missing: should be \"$usage\"" |
|
} |
|
set type [string tolower [lindex $args $i]] |
|
} |
|
"-order" { |
|
incr i |
|
if { $i >= [llength $args] } { |
|
return -code error "value for \"$flag\" missing: should be \"$usage\"" |
|
} |
|
set order [string tolower [lindex $args $i]] |
|
} |
|
"-command" { |
|
incr i |
|
if { $i >= [llength $args] } { |
|
return -code error "value for \"$flag\" missing: should be \"$usage\"" |
|
} |
|
set cmd [lindex $args $i] |
|
} |
|
"-dir" { |
|
incr i |
|
if { $i >= [llength $args] } { |
|
return -code error "value for \"$flag\" missing: should be \"$usage\"" |
|
} |
|
set dir [string tolower [lindex $args $i]] |
|
} |
|
default { |
|
return -code error "unknown option \"$flag\": should be \"$usage\"" |
|
} |
|
} |
|
} |
|
|
|
# Make sure we have a command to run, otherwise what's the point? |
|
if { [string equal $cmd ""] } { |
|
return -code error "no command specified: should be \"$usage\"" |
|
} |
|
|
|
# Validate that the given type is good |
|
switch -glob -- $type { |
|
"dfs" { |
|
set type "dfs" |
|
} |
|
"bfs" { |
|
set type "bfs" |
|
} |
|
default { |
|
return -code error "bad search type \"$type\": must be bfs or dfs" |
|
} |
|
} |
|
|
|
# Validate that the given order is good |
|
switch -glob -- $order { |
|
"both" { |
|
set order both |
|
} |
|
"pre" { |
|
set order pre |
|
} |
|
"post" { |
|
set order post |
|
} |
|
default { |
|
return -code error "bad search order \"$order\": must be both,\ |
|
pre, or post" |
|
} |
|
} |
|
|
|
# Validate that the given direction is good |
|
switch -glob -- $dir { |
|
"forward" { |
|
set dir -out |
|
} |
|
"backward" { |
|
set dir -in |
|
} |
|
default { |
|
return -code error "bad search direction \"$dir\": must be\ |
|
backward or forward" |
|
} |
|
} |
|
|
|
# Do the walk |
|
|
|
set st [list ] |
|
lappend st $node |
|
array set visited {} |
|
|
|
if { [string equal $type "dfs"] } { |
|
if { [string equal $order "pre"] } { |
|
# Pre-order Depth-first search |
|
|
|
while { [llength $st] > 0 } { |
|
set node [lindex $st end] |
|
ldelete st end |
|
|
|
# Skip all nodes already visited via some other path |
|
# through the graph. |
|
if {[info exists visited($node)]} continue |
|
|
|
# Evaluate the command at this node |
|
set cmdcpy $cmd |
|
lappend cmdcpy enter $name $node |
|
uplevel 1 $cmdcpy |
|
|
|
set visited($node) . |
|
|
|
# Add this node's neighbours (according to direction) |
|
# Have to add them in reverse order |
|
# so that they will be popped left-to-right |
|
|
|
set next [_nodes $name $dir $node] |
|
set len [llength $next] |
|
|
|
for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} { |
|
set nextnode [lindex $next $i] |
|
if {[info exists visited($nextnode)]} { |
|
# Skip nodes already visited |
|
continue |
|
} |
|
lappend st $nextnode |
|
} |
|
} |
|
} elseif { [string equal $order "post"] } { |
|
# Post-order Depth-first search |
|
|
|
while { [llength $st] > 0 } { |
|
set node [lindex $st end] |
|
|
|
if {[info exists visited($node)]} { |
|
# Second time we are here, pop it, |
|
# then evaluate the command. |
|
|
|
ldelete st end |
|
# Bug 2420330. Note: The visited node may be |
|
# multiple times on the stack (neighbour of more |
|
# than one node). Remove all occurences. |
|
while {[set index [lsearch -exact $st $node]] != -1} { |
|
set st [lreplace $st $index $index] |
|
} |
|
|
|
# Evaluate the command at this node |
|
set cmdcpy $cmd |
|
lappend cmdcpy leave $name $node |
|
uplevel 1 $cmdcpy |
|
} else { |
|
# First visit. Remember it. |
|
set visited($node) . |
|
|
|
# Add this node's neighbours. |
|
set next [_nodes $name $dir $node] |
|
set len [llength $next] |
|
|
|
for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} { |
|
set nextnode [lindex $next $i] |
|
if {[info exists visited($nextnode)]} { |
|
# Skip nodes already visited |
|
continue |
|
} |
|
lappend st $nextnode |
|
} |
|
} |
|
} |
|
} else { |
|
# Both-order Depth-first search |
|
|
|
while { [llength $st] > 0 } { |
|
set node [lindex $st end] |
|
|
|
if {[info exists visited($node)]} { |
|
# Second time we are here, pop it, |
|
# then evaluate the command. |
|
|
|
ldelete st end |
|
|
|
# Evaluate the command at this node |
|
set cmdcpy $cmd |
|
lappend cmdcpy leave $name $node |
|
uplevel 1 $cmdcpy |
|
} else { |
|
# First visit. Remember it. |
|
set visited($node) . |
|
|
|
# Evaluate the command at this node |
|
set cmdcpy $cmd |
|
lappend cmdcpy enter $name $node |
|
uplevel 1 $cmdcpy |
|
|
|
# Add this node's neighbours. |
|
set next [_nodes $name $dir $node] |
|
set len [llength $next] |
|
|
|
for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} { |
|
set nextnode [lindex $next $i] |
|
if {[info exists visited($nextnode)]} { |
|
# Skip nodes already visited |
|
continue |
|
} |
|
lappend st $nextnode |
|
} |
|
} |
|
} |
|
} |
|
|
|
} else { |
|
if { [string equal $order "pre"] } { |
|
# Pre-order Breadth first search |
|
while { [llength $st] > 0 } { |
|
set node [lindex $st 0] |
|
ldelete st 0 |
|
# Evaluate the command at this node |
|
set cmdcpy $cmd |
|
lappend cmdcpy enter $name $node |
|
uplevel 1 $cmdcpy |
|
|
|
set visited($node) . |
|
|
|
# Add this node's neighbours. |
|
foreach child [_nodes $name $dir $node] { |
|
if {[info exists visited($child)]} { |
|
# Skip nodes already visited |
|
continue |
|
} |
|
lappend st $child |
|
} |
|
} |
|
} else { |
|
# Post-order Breadth first search |
|
# Both-order Breadth first search |
|
# Haven't found anything in Knuth |
|
# and unable to define something |
|
# consistent for myself. Leave it |
|
# out. |
|
|
|
return -code error "unable to do a ${order}-order breadth first walk" |
|
} |
|
} |
|
return |
|
} |
|
|
|
# ::struct::graph::Union -- |
|
# |
|
# Return a list which is the union of the elements |
|
# in the specified lists. |
|
# |
|
# Arguments: |
|
# args list of lists representing sets. |
|
# |
|
# Results: |
|
# set list representing the union of the argument lists. |
|
|
|
proc ::struct::graph::Union {args} { |
|
switch -- [llength $args] { |
|
0 { |
|
return {} |
|
} |
|
1 { |
|
return [lindex $args 0] |
|
} |
|
default { |
|
foreach set $args { |
|
foreach e $set { |
|
set tmp($e) . |
|
} |
|
} |
|
return [array names tmp] |
|
} |
|
} |
|
} |
|
|
|
# ::struct::graph::GenAttributeStorage -- |
|
# |
|
# Create an array to store the attributes of a node in. |
|
# |
|
# Arguments: |
|
# name Name of the graph containing the node |
|
# type Type of object for the attribute |
|
# obj Name of the node or arc which got attributes. |
|
# |
|
# Results: |
|
# none |
|
|
|
proc ::struct::graph::GenAttributeStorage {name type obj} { |
|
variable ${name}::nextAttr |
|
upvar ${name}::${type}Attr attribute |
|
|
|
set attr "a[incr nextAttr]" |
|
set attribute($obj) $attr |
|
return |
|
} |
|
|
|
proc ::struct::graph::CheckMissingArc {name arc} { |
|
if {![__arc_exists $name $arc]} { |
|
return -code error "arc \"$arc\" does not exist in graph \"$name\"" |
|
} |
|
} |
|
|
|
proc ::struct::graph::CheckMissingNode {name node {prefix {}}} { |
|
if {![__node_exists $name $node]} { |
|
return -code error "${prefix}node \"$node\" does not exist in graph \"$name\"" |
|
} |
|
} |
|
|
|
proc ::struct::graph::CheckDuplicateArc {name arc} { |
|
if {[__arc_exists $name $arc]} { |
|
return -code error "arc \"$arc\" already exists in graph \"$name\"" |
|
} |
|
} |
|
|
|
proc ::struct::graph::CheckDuplicateNode {name node} { |
|
if {[__node_exists $name $node]} { |
|
return -code error "node \"$node\" already exists in graph \"$name\"" |
|
} |
|
} |
|
|
|
proc ::struct::graph::CheckE {name what arguments} { |
|
|
|
# Discriminate between conditions and nodes |
|
|
|
upvar 1 haveCond haveCond ; set haveCond 0 |
|
upvar 1 haveKey haveKey ; set haveKey 0 |
|
upvar 1 key key ; set key {} |
|
upvar 1 haveValue haveValue ; set haveValue 0 |
|
upvar 1 value value ; set value {} |
|
upvar 1 haveFilter haveFilter ; set haveFilter 0 |
|
upvar 1 fcmd fcmd ; set fcmd {} |
|
upvar 1 cond cond ; set cond "none" |
|
upvar 1 condNodes condNodes ; set condNodes {} |
|
|
|
set wa_usage "wrong # args: should be \"$name $what ?-key key? ?-value value? ?-filter cmd? ?-in|-out|-adj|-inner|-embedding node node...?\"" |
|
set seenodes 0 |
|
|
|
for {set i 0} {$i < [llength $arguments]} {incr i} { |
|
set arg [lindex $arguments $i] |
|
switch -glob -- $arg { |
|
-in - |
|
-out - |
|
-adj - |
|
-inner - |
|
-embedding { |
|
if {$haveCond} { |
|
return -code error "invalid restriction:\ |
|
illegal multiple use of\ |
|
\"-in\"|\"-out\"|\"-adj\"|\"-inner\"|\"-embedding\"" |
|
} |
|
|
|
set haveCond 1 |
|
set cond [string range $arg 1 end] |
|
set seenodes 1 |
|
} |
|
-key { |
|
if {($i + 1) == [llength $arguments]} { |
|
return -code error $wa_usage |
|
} |
|
if {$haveKey} { |
|
return -code error {invalid restriction: illegal multiple use of "-key"} |
|
} |
|
|
|
incr i |
|
set key [lindex $arguments $i] |
|
set haveKey 1 |
|
set seenodes 0 |
|
} |
|
-value { |
|
if {($i + 1) == [llength $arguments]} { |
|
return -code error $wa_usage |
|
} |
|
if {$haveValue} { |
|
return -code error {invalid restriction: illegal multiple use of "-value"} |
|
} |
|
|
|
incr i |
|
set value [lindex $arguments $i] |
|
set haveValue 1 |
|
set seenodes 0 |
|
} |
|
-filter { |
|
if {($i + 1) == [llength $arguments]} { |
|
return -code error $wa_usage |
|
} |
|
if {$haveFilter} { |
|
return -code error {invalid restriction: illegal multiple use of "-filter"} |
|
} |
|
|
|
incr i |
|
set fcmd [lindex $arguments $i] |
|
set haveFilter 1 |
|
set seenodes 0 |
|
} |
|
-* { |
|
if {$seenodes} { |
|
lappend condNodes $arg |
|
} else { |
|
return -code error "bad restriction \"$arg\": must be -adj, -embedding,\ |
|
-filter, -in, -inner, -key, -out, or -value" |
|
} |
|
} |
|
default { |
|
lappend condNodes $arg |
|
} |
|
} |
|
} |
|
|
|
# Validate that there are nodes to use in the restriction. |
|
# otherwise what's the point? |
|
if {$haveCond} { |
|
if {[llength $condNodes] == 0} { |
|
return -code error $wa_usage |
|
} |
|
|
|
# Remove duplicates. Note: lsort -unique is not present in Tcl |
|
# 8.2, thus not usable here. |
|
|
|
array set nx {} |
|
foreach c $condNodes {set nx($c) .} |
|
set condNodes [array names nx] |
|
unset nx |
|
|
|
# Make sure that the specified nodes exist! |
|
foreach node $condNodes {CheckMissingNode $name $node} |
|
} |
|
|
|
if {$haveValue && !$haveKey} { |
|
return -code error {invalid restriction: use of "-value" without "-key"} |
|
} |
|
|
|
return |
|
} |
|
|
|
proc ::struct::graph::CheckSerialization {ser gavar navar aavar inavar outavar arcnvar arcwvar} { |
|
upvar 1 \ |
|
$gavar graphAttr \ |
|
$navar nodeAttr \ |
|
$aavar arcAttr \ |
|
$inavar inArcs \ |
|
$outavar outArcs \ |
|
$arcnvar arcNodes \ |
|
$arcwvar arcWeight |
|
|
|
array set nodeAttr {} |
|
array set arcAttr {} |
|
array set inArcs {} |
|
array set outArcs {} |
|
array set arcNodes {} |
|
array set arcWeight {} |
|
|
|
# Overall length ok ? |
|
if {[llength $ser] % 3 != 1} { |
|
return -code error \ |
|
"error in serialization: list length not 1 mod 3." |
|
} |
|
|
|
# Attribute length ok ? Dictionary! |
|
set graphAttr [lindex $ser end] |
|
if {[llength $graphAttr] % 2} { |
|
return -code error \ |
|
"error in serialization: malformed graph attribute dictionary." |
|
} |
|
|
|
# Basic decoder pass |
|
|
|
foreach {node attr narcs} [lrange $ser 0 end-1] { |
|
if {![info exists inArcs($node)]} { |
|
set inArcs($node) [list] |
|
} |
|
set outArcs($node) [list] |
|
|
|
# Attribute length ok ? Dictionary! |
|
if {[llength $attr] % 2} { |
|
return -code error \ |
|
"error in serialization: malformed node attribute dictionary." |
|
} |
|
# Remember attribute data only for non-empty nodes |
|
if {[llength $attr]} { |
|
set nodeAttr($node) $attr |
|
} |
|
|
|
foreach arcd $narcs { |
|
if { |
|
([llength $arcd] != 3) && |
|
([llength $arcd] != 4) |
|
} { |
|
return -code error \ |
|
"error in serialization: arc information length not 3 or 4." |
|
} |
|
|
|
foreach {arc dst aattr} $arcd break |
|
|
|
if {[info exists arcNodes($arc)]} { |
|
return -code error \ |
|
"error in serialization: duplicate definition of arc \"$arc\"." |
|
} |
|
|
|
# Attribute length ok ? Dictionary! |
|
if {[llength $aattr] % 2} { |
|
return -code error \ |
|
"error in serialization: malformed arc attribute dictionary." |
|
} |
|
# Remember attribute data only for non-empty nodes |
|
if {[llength $aattr]} { |
|
set arcAttr($arc) $aattr |
|
} |
|
|
|
# Remember weight data if it was specified. |
|
if {[llength $arcd] == 4} { |
|
set arcWeight($arc) [lindex $arcd 3] |
|
} |
|
|
|
# Destination reference ok ? |
|
if { |
|
![string is integer -strict $dst] || |
|
($dst % 3) || |
|
($dst < 0) || |
|
($dst >= [llength $ser]) |
|
} { |
|
return -code error \ |
|
"error in serialization: bad arc destination reference \"$dst\"." |
|
} |
|
|
|
# Get destination and reconstruct the |
|
# various relationships. |
|
|
|
set dstnode [lindex $ser $dst] |
|
|
|
set arcNodes($arc) [list $node $dstnode] |
|
lappend inArcs($dstnode) $arc |
|
lappend outArcs($node) $arc |
|
} |
|
} |
|
|
|
# Duplicate node names ? |
|
|
|
if {[array size outArcs] < ([llength $ser] / 3)} { |
|
return -code error \ |
|
"error in serialization: duplicate node names." |
|
} |
|
|
|
# Ok. The data is now ready for the caller. |
|
return |
|
} |
|
|
|
########################## |
|
# Private functions follow |
|
# |
|
# Do a compatibility version of [lset] for pre-8.4 versions of Tcl. |
|
# This version does not do multi-arg [lset]! |
|
|
|
proc ::struct::graph::K { x y } { set x } |
|
|
|
if { [package vcompare [package provide Tcl] 8.4] < 0 } { |
|
proc ::struct::graph::lset { var index arg } { |
|
upvar 1 $var list |
|
set list [::lreplace [K $list [set list {}]] $index $index $arg] |
|
} |
|
} |
|
|
|
proc ::struct::graph::ldelete {var index {end {}}} { |
|
upvar 1 $var list |
|
if {$end == {}} {set end $index} |
|
set list [lreplace [K $list [set list {}]] $index $end] |
|
return |
|
} |
|
|
|
proc ::struct::graph::luniq {list} { |
|
array set _ {} |
|
set result [list] |
|
foreach e $list { |
|
if {[info exists _($e)]} {continue} |
|
lappend result $e |
|
set _($e) . |
|
} |
|
return $result |
|
} |
|
|
|
# ### ### ### ######### ######### ######### |
|
## Ready |
|
|
|
namespace eval ::struct { |
|
# Put 'graph::graph' into the general structure namespace |
|
# for pickup by the main management. |
|
|
|
namespace import -force graph::graph_tcl |
|
} |
|
|
|
|