Skip to content

Commit

Permalink
Merge 7a319a3 into bf0407a
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabacman committed Jul 10, 2018
2 parents bf0407a + 7a319a3 commit b4bc593
Show file tree
Hide file tree
Showing 13 changed files with 807 additions and 243 deletions.
49 changes: 38 additions & 11 deletions docs/Mole.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Each graph can understand the following messages:
### Accessing

- `neighborsOf: aVertex` Returns the vertices that are connected to `aVertex`
- `edgesIncidentTo: aVertex` Returns the edges that are incident to `aVertex`
- `order` The number of vertices
- `size` The number of edges

Expand All @@ -22,17 +23,17 @@ Each graph can understand the following messages:

Undirected graphs, graphs in which the two endpoints of each edge are not distinguished from each other, are modeled in `UndirectedGraph`.

The easier way to create an undirected graph it's to use the builder: `UndirectedGraphBuilder`.
The easier way to create an undirected graph it's to use the builder: `GraphBuilder` then request an undirected graph using `buildUndirected`.
For example:
```smalltalk
UndirectedGraphBuilder new
relate: 1 to: 2;
selfRelate: 1;
relate: 4 to: 3;
GraphBuilder new
connect: 1 to: 2;
loopOn: 1;
connect: 4 to: 3;
addVertex: 8;
build
buildUndirected
```
will create an undirected graph with 5 vertices {1, 2, 3, 4, 8} and the following edges: { (1,2), (1,1), (4,3)}. The builder will take care of creating the right type of edge and adding the vertices included in edges automatically.
will create an undirected graph with 5 vertices {1, 2, 3, 4, 8} and the following edges: {(1,2), (1,1), (4,3)}. The builder will take care of creating the right type of edge and adding the vertices included in the edges automatically.

In addition to the messages common to all graphs, undirected graphs also understand:

Expand All @@ -43,16 +44,16 @@ In addition to the messages common to all graphs, undirected graphs also underst

Directed graphs, graphs where all the edges are directed from one vertex to another, are modeled in `DirectedGraph`. A directed graph is sometimes called a digraph or a directed network.

The easier way to create an directed graph it's to use the builder: `DirectedGraphBuilder`.
The easier way to create an directed graph it's to use the builder: `GraphBuilder` then request a directed graph using `buildDirected`.
For example:
```smalltalk
DirectedGraphBuilder new
GraphBuilder new
connect: 1 to: 2;
connect: 4 to: 3;
addVertex: 5;
build
buildDirected
```
will create an directed graph with 5 vertices {1, 2, 3, 4, 5} and the following edges: { 1 -> 2, 4 -> 3}.
will create a directed graph with 5 vertices {1, 2, 3, 4, 5} and the following edges: { 1 -> 2, 4 -> 3}.

In addition to the messages common to all graphs, directed graphs also understand:

Expand All @@ -68,6 +69,32 @@ In addition to the messages common to all graphs, directed graphs also understan
- `isAcyclic` Returns true if the graph is acyclic
- `isCyclic` Returns true if the graph is cyclic

## Weights and labels

Edges in both directed and undirected graphs can have additional information embedded into them. This is generally referred to as `labeling`. A label can be just a name to identify the edge, a weight to indicate the cost of traversing it, or even complex objects like functions.

To add a label, use the alternative messages in the builder: `connect:to:labeled:` and `loopOn:labeled:`:
```smalltalk
GraphBuilder new
connect: 1 to: 2 labeled: 'Simple edge';
connect: 4 to: 3 labeled: 8;
loopOn: 3 labeled: [:arguments | self logEdgeMovement: arguments ];
buildDirected
```
will create a directed graph with and edge {1 -> 2} that is named Simple edge, an edge {4 -> 3} with weight 8, and a loop {4 -> 4} with a logging function attached.

To use the information in an edge label, send the message `withLabelDo:ifUnlabeled:`, for example:
```smalltalk
"Write information from the graph to aStream"
anEdge withLabelDo: [:name | aStream nextPutAll: name ] ifUnlabeled: [ aStream nextPutAll: 'N/A']
"Sum the distances represented by the weights in a path"
anEdge withLabelDo: [:weight | totalDistance := totalDistance + weight ] ifUnlabeled: [ ]
"Evaluate a function related to the edge traversed"
anEdge withLabelDo: [:function | function value: evaluationContext ] ifUnlabeled: [ Error signal: 'All edges are expected to have a function']
```

## Future Work
- Traversal algorithms
- Walks and paths
Expand Down
190 changes: 181 additions & 9 deletions source/Mole-Tests/DirectedEdgeTest.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,196 @@ Class {
}

{ #category : #tests }
DirectedEdgeTest >> testAccessing [
DirectedEdgeTest >> testConvergesTo [

| edge |

edge := DirectedEdge from: 1 to: 2.

self
assert: (edge convergesTo: 2);
deny: (edge convergesTo: 1);
deny: (edge convergesTo: 3)
]

{ #category : #tests }
DirectedEdgeTest >> testEquals [

| edge equivalentEdge differentEdge |

edge := DirectedEdge from: 1 to: 2.
equivalentEdge := DirectedEdge from: 1 to: 2.
differentEdge := DirectedEdge from: 2 to: 1.
self
assert: edge equals: edge;
assert: edge equals: equivalentEdge;
assert: equivalentEdge equals: edge;
deny: edge = differentEdge;
deny: differentEdge = edge.

edge := DirectedEdge from: 1 to: 2 labeled: 'Some labeling object'.
equivalentEdge := DirectedEdge from: 1 to: 2 labeled: 'Some labeling object'.
differentEdge := DirectedEdge from: 1 to: 2 labeled: 'A different labeling object'.
self
assert: edge equals: edge;
assert: edge equals: equivalentEdge;
assert: equivalentEdge equals: edge;
deny: edge = differentEdge;
deny: differentEdge = edge
]

{ #category : #tests }
DirectedEdgeTest >> testFromTo [

| edge isUnlabeled |

edge := DirectedEdge from: 1 to: 2.
isUnlabeled := false.
self
assert: edge source equals: 1;
assert: edge target equals: 2.

edge withLabelDo: [ :label | self fail ] ifUnlabeled: [ isUnlabeled := true ].
self assert: isUnlabeled
]

{ #category : #tests }
DirectedEdgeTest >> testFromToLabeled [

| edge |

edge := DirectedEdge
from: 1
to: 2
labeled: 'I could be a complext function, it''s not just for text'.
self
assert: edge source equals: 1;
assert: edge target equals: 2.

edge
withLabelDo:
[ :label | self assert: label equals: 'I could be a complext function, it''s not just for text' ]
ifUnlabeled: [ self fail ]
]

{ #category : #tests }
DirectedEdgeTest >> testGoesFromTo [

| edge |

edge := DirectedEdge from: 1 to: 2.

self
assert: (DirectedEdge from: 1 to: 2) source equals: 1;
assert: (DirectedEdge from: 1 to: 2) target equals: 2
assert: (edge goesFrom: 1 to: 2);
deny: (edge goesFrom: 2 to: 1);
deny: (edge goesFrom: 1 to: 3);
deny: (edge goesFrom: 3 to: 2)
]

{ #category : #tests }
DirectedEdgeTest >> testComparing [
DirectedEdgeTest >> testHash [

| edge equivalentEdge differentEdge |

edge := DirectedEdge from: 1 to: 2.
equivalentEdge := DirectedEdge from: 1 to: 2.
differentEdge := DirectedEdge from: 1 to: 3.
self
assert: (DirectedEdge from: 1 to: 2) equals: (DirectedEdge from: 1 to: 2);
assert: (DirectedEdge from: 1 to: 2) hash equals: (DirectedEdge from: 1 to: 2) hash;
deny: (DirectedEdge from: 1 to: 2) = (DirectedEdge from: 2 to: 1)
assert: edge hash equals: equivalentEdge hash;
deny: edge hash = differentEdge hash.

edge := DirectedEdge from: 1 to: 2 labeled: 'Some labeling object'.
equivalentEdge := DirectedEdge from: 1 to: 2 labeled: 'A different labeling object'.
differentEdge := DirectedEdge from: 1 to: 3 labeled: 'A different labeling object'.
self
assert: edge hash equals: equivalentEdge hash;
deny: edge hash = differentEdge hash
]

{ #category : #tests }
DirectedEdgeTest >> testPrintString [
DirectedEdgeTest >> testIsDirected [

self assert: (DirectedEdge from: 1 to: 2) isDirected
]

self assert: (DirectedEdge from: 1 to: 2) printString equals: '1 -> 2'
{ #category : #tests }
DirectedEdgeTest >> testIsIncidentTo [

| edge |

edge := DirectedEdge from: 1 to: 2.

self
assert: (edge isIncidentTo: 1);
assert: (edge isIncidentTo: 2);
deny: (edge isIncidentTo: 3)
]

{ #category : #tests }
DirectedEdgeTest >> testIsSelfLoop [

self deny: (DirectedEdge from: 1 to: 2) isSelfLoop.

self assert: (DirectedEdge from: 1 to: 1) isSelfLoop
]

{ #category : #tests }
DirectedEdgeTest >> testPrintOn [

self
assert: (DirectedEdge from: 1 to: 2) printString equals: '1 -> 2';
assert: (DirectedEdge from: 1 to: 1) printString equals: '1 -> 1'
]

{ #category : #tests }
DirectedEdgeTest >> testSource [

self assert: (DirectedEdge from: 1 to: 2) source equals: 1
]

{ #category : #tests }
DirectedEdgeTest >> testStartsOn [

| edge |

edge := DirectedEdge from: 1 to: 2.

self
assert: (edge startsOn: 1);
deny: (edge startsOn: 2);
deny: (edge startsOn: 3)
]

{ #category : #tests }
DirectedEdgeTest >> testTarget [

self assert: (DirectedEdge from: 1 to: 2) target equals: 2
]

{ #category : #tests }
DirectedEdgeTest >> testWithLabelDoIfUnlabeled [

| edges stream edgeFunction |

stream := WriteStream on: String new.
edgeFunction := [ :source :target |
stream
nextPutAll: ('From <1s> to <2s>' expandMacrosWith: source with: target);
cr ].
edges := OrderedCollection new
add: (DirectedEdge from: 'Home' to: 'Work' labeled: edgeFunction);
add: (DirectedEdge from: 'Work' to: 'Party' labeled: edgeFunction);
yourself.

edges
do: [ :edge |
edge
withLabelDo: [ :function | function value: edge source value: edge target ]
ifUnlabeled: [ self fail ] ].
self
assert: stream contents
equals:
'From Home to Work
From Work to Party
'
]

0 comments on commit b4bc593

Please sign in to comment.