Initial commit

This commit is contained in:
hectr 2018-09-24 03:18:11 +02:00 committed by hectr
commit 8aa42b400c
28 changed files with 1692 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
/.build
/Packages
/*.xcodeproj

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
(BSD-2 license)
Original work Copyright (c) 2012, Frank Meyer
All rights reserved.
Swift port Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

37
Package.swift Normal file
View File

@ -0,0 +1,37 @@
// swift-tools-version:4.2
import PackageDescription
let package = Package(
name: "ElementaryCycles",
products: [
.library(
name: "ElementaryCycles",
targets: ["ElementaryCycles"]),
.library(
name: "ElementaryCyclesSearch",
targets: ["ElementaryCyclesSearch"]),
.executable(
name: "ElementaryCyclesSearchExample",
targets: ["ElementaryCyclesSearchExample"]),
],
dependencies: [
],
targets: [
.target(
name: "ElementaryCyclesSearchExample",
dependencies: ["ElementaryCyclesSearch"]),
.target(
name: "ElementaryCyclesSearch",
dependencies: []),
.target(
name: "ElementaryCycles",
dependencies: ["ElementaryCyclesSearch"]),
.testTarget(
name: "ElementaryCyclesSearchTests",
dependencies: ["ElementaryCyclesSearch"]),
.testTarget(
name: "ElementaryCyclesTests",
dependencies: ["ElementaryCycles"]),
]
)

114
README.md Normal file
View File

@ -0,0 +1,114 @@
# ElementaryCycles
Swift port of an algorythm used to find all the cycles in a directed graph:
> This is an implementation of an algorithm by Donald B. Johnson to find all elementary cycles in a directed graph ([Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph. SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84](https://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF)).
>
> Original Java implementation: <http://normalisiert.de>
## Usage
### ElementaryCycles
```swift
import ElementaryCycles
// dictionary pairs represent node connections from the keys to their values
let graph = ["A": ["B", "C"], "B": ["A"], "C": ["D"], "D": ["C"]]
let cycles = ElementaryCycles.find(graph: graph, sort: { $0 < $1 })
// nodes order is determined by sort parameter
print(cycles) // [["A", "B"], ["C", "D"]]
```
- **Input**: *Directed graph*
```
┌─────────┐
┌───┐ ┌───┐ ┌───┐
┌> │ A │ ──> │ C │ ──> │ D │
│ └───┘ └───┘ └───┘
│ │
│ │
│ ┌───┐
└─ │ B │
└───┘
```
- **Output**: *Elementary circuits*
```
┌───┐ ┌───┐
│ A │ ──> │ B │
└───┘ └───┘
┌───┐ ┌───┐
│ C │ ──> │ D │
└───┘ └───┘
```
### ElementaryCyclesSearch
Alternatively, you can use **ElementaryCyclesSearch** library directly, which contains the actual algorythm implementation:
> The implementation is pretty much generic, all it needs is a adjacency-matrix of your graph and the objects of your nodes. Then you get back the sets of node-objects which build a cycle.
>
> Original Java implemnetation: <http://normalisiert.de>
```swift
import ElementaryCyclesSearch
let nodes = Vector(["A", "B", "C", "D"])
let matrix = AdjacencyMatrix(4, 4) { matrix in
matrix[0][1] = true
matrix[0][2] = true
matrix[1][0] = true
matrix[2][3] = true
matrix[3][2] = true
}
let cycles = getElementaryCycles(adjacencyMatrix: matrix, graphNodes: nodes)
```
- **Input**: *Node objects*
| **A** | **B** | **C** | **D** |
|---------|---------|---------|---------|
- **Input**: *Adjacency-matrix*
| | **A** | **B** | **C** | **D** |
|---------|---------|---------|---------|---------|
|**A** | `false` | `true` | `false` | `false` |
|**B** | `true` | `false` | `false` | `false` |
|**C** | `true` | `false` | `false` | `true` |
|**D** | `false` | `false` | `true` | `false` |
- **Output**: *Elementary circuits*
```
┌───┐ ┌───┐
│ A │ ──> │ B │
└───┘ └───┘
┌───┐ ┌───┐
│ C │ ──> │ D │
└───┘ └───┘
```
## License
> (BSD-2 license)
>
> Original work Copyright (c) 2012, Frank Meyer
>
> All rights reserved.
>
> Swift port Copyright (c) 2018, Hèctor Marquès
>
> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
>
> Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
> Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
>
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,36 @@
/*
(BSD-2 license)
Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
import ElementaryCyclesSearch
extension Matrix where Element == Bool {
enum Error: Swift.Error {
case indexNotFound(node: AnyHashable, nodes: [AnyHashable])
}
static func getAdjacencyMatrix<Node: Hashable>(nodes: [Node], adjacencyDictionary: [Node: [Node]]) throws -> AdjacencyMatrix {
let matrix = AdjacencyMatrix(nodes.count, nodes.count)
for (offset, node) in nodes.enumerated() {
if let adjacentNodes = adjacencyDictionary[node] {
for adjacentNode in adjacentNodes {
guard let adjacentIndex = nodes.index(of: adjacentNode) else {
throw Error.indexNotFound(node: adjacentNode, nodes: nodes)
}
matrix[offset][adjacentIndex] = true
}
}
}
return matrix
}
}

View File

@ -0,0 +1,35 @@
/*
(BSD-2 license)
Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
import ElementaryCyclesSearch
extension Matrix where Element == Bool {
static func getNodes<Node: Hashable>(graph: [Node: [Node]], sort: ((Node, Node) -> Bool)?) -> [Node] {
var nodes = [Node]()
for (node, adjacentNodes) in graph {
if !nodes.contains(node) {
nodes.append(node)
}
for adjacentNode in adjacentNodes {
if !nodes.contains(adjacentNode) {
nodes.append(adjacentNode)
}
}
}
if let sort = sort {
nodes.sort(by: sort)
}
return nodes
}
}

View File

@ -0,0 +1,29 @@
/*
(BSD-2 license)
Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
import ElementaryCyclesSearch
extension ElementaryCyclesSearch {
static func toArray(elementaryCycles: Vector<Vector<Node>>) -> [[Node]] {
var cycles = [[Node]]()
for vector in elementaryCycles {
var cycle = [Node]()
for element in vector {
cycle.append(element)
}
cycles.append(cycle)
}
return cycles
}
}

View File

@ -0,0 +1,24 @@
/*
(BSD-2 license)
Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
import ElementaryCyclesSearch
public struct ElementaryCycles {
public static func find<Node: Hashable>(graph: [Node: [Node]], sort: ((Node, Node) -> Bool)? = nil) -> [[Node]] {
let nodes = AdjacencyMatrix.getNodes(graph: graph, sort: sort)
let adjacencyMatrix = try! AdjacencyMatrix.getAdjacencyMatrix(nodes: nodes, adjacencyDictionary: graph)
let elementaryCycles = ElementaryCyclesSearch.getElementaryCycles(adjacencyMatrix: adjacencyMatrix, graphNodes: Vector(array: nodes))
return ElementaryCyclesSearch.toArray(elementaryCycles: elementaryCycles)
}
}

View File

@ -0,0 +1,58 @@
/*
(BSD-2 license)
Original work Copyright (c) 2012, Frank Meyer
All rights reserved.
Swift port Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
/**
* Calculates the adjacency-list for a given adjacency-matrix.
*
*
* @author Frank Meyer, web@normalisiert.de
* @version 1.0, 26.08.2006
*
*/
extension Matrix where Element == Int {
/**
* Calculates a adjacency-list for a given array of an adjacency-matrix.
*
* @param adjacencyMatrix array with the adjacency-matrix that represents
* the graph
* @return int[][]-array of the adjacency-list of given nodes. The first
* dimension in the array represents the same node as in the given
* adjacency, the second dimension represents the indicies of those nodes,
* that are direct successornodes of the node.
*/
static func getAdjacencyList(adjacencyMatrix: AdjacencyMatrix) -> Matrix<Int> {
let list = Matrix<Int>(adjacencyMatrix.reservedLength)
for i in 0 ..< adjacencyMatrix.reservedLength {
let v = Vector<Int>()
for j in 0 ..< adjacencyMatrix[i].reservedLength {
if let isAdjacent = adjacencyMatrix[i]?[j], isAdjacent {
v.add(j)
}
}
list[i] = Vector(v.size)
for j in 0 ..< v.size {
let integer = v.get(j)
list[i][j] = integer
}
}
return list;
}
}

View File

@ -0,0 +1,19 @@
/*
(BSD-2 license)
Original work Copyright (c) 2012, Frank Meyer
All rights reserved.
Swift port Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
typealias AdjacencyList = Matrix<Int>

View File

@ -0,0 +1,263 @@
/*
(BSD-2 license)
Original work Copyright (c) 2012, Frank Meyer
All rights reserved.
Swift port Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
/**
* This is a helpclass for the search of all elementary cycles in a graph
* with the algorithm of Johnson. For this it searches for strong connected
* components, using the algorithm of Tarjan. The constructor gets an
* adjacency-list of a graph. Based on this graph, it gets a nodenumber s,
* for which it calculates the subgraph, containing all nodes
* {s, s + 1, ..., n}, where n is the highest nodenumber in the original
* graph (e.g. it builds a subgraph with all nodes with higher or same
* nodenumbers like the given node s). It returns the strong connected
* component of this subgraph which contains the lowest nodenumber of all
* nodes in the subgraph.<br><br>
*
* For a description of the algorithm for calculating the strong connected
* components see:<br>
* Robert Tarjan: Depth-first search and linear graph algorithms. In: SIAM
* Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160.<br>
* For a description of the algorithm for searching all elementary cycles in
* a directed graph see:<br>
* Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph.
* SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84.<br><br>
*
* @author Frank Meyer, web_at_normalisiert_dot_de
* @version 1.1, 22.03.2009
*
*/
class StrongConnectedComponents {
class Result {
private var nodeIDsOfSCC: Set<Int>
private var adjacencyList: AdjacencyList
private var lowestNodeId = -1
fileprivate init(adjacencyList: AdjacencyList, lowestNodeId: Int) {
self.lowestNodeId = lowestNodeId
self.nodeIDsOfSCC = Set<Int>()
self.adjacencyList = adjacencyList;
for i in self.lowestNodeId ..< self.adjacencyList.reservedLength {
if let adj = adjacencyList[i], adj.size > 0 {
self.nodeIDsOfSCC.insert(i)
}
}
}
func getAdjList() -> AdjacencyList {
return adjacencyList
}
func getLowestNodeId() -> Int {
return lowestNodeId
}
}
/** Adjacency-list of original graph */
private var adjacencyListOriginal: AdjacencyList!
/** Adjacency-list of currently viewed subgraph */
private var adjacencyList: AdjacencyList!
/** Helpattribute for finding scc's */
private var visited: Vector<Bool>!
/** Helpattribute for finding scc's */
private var stack: Vector<Int>!
/** Helpattribute for finding scc's */
private var lowlink: Vector<Int>!
/** Helpattribute for finding scc's */
private var number: Vector<Int>!
/** Helpattribute for finding scc's */
private var strongConnectedComponentsCounter = 0;
/** Helpattribute for finding scc's */
private var currentStrongConnectedComponents: Vector<Vector<Int>>!
/**
* Constructor.
*
* @param adjacencyList adjacency-list of the graph
*/
init(adjacencyList: AdjacencyList) {
self.adjacencyListOriginal = adjacencyList;
}
/**
* This method returns the adjacency-structure of the strong connected
* component with the least vertex in a subgraph of the original graph
* induced by the nodes {s, s + 1, ..., n}, where s is a given node. Note
* that trivial strong connected components with just one node will not
* be returned.
*
* @param node node s
* @return SCCResult with adjacency-structure of the strong
* connected component; null, if no such component exists
*/
func getAdjacencyList(node: Int) -> Result? {
visited = Vector<Bool>(self.adjacencyListOriginal.reservedLength)
lowlink = Vector<Int>(self.adjacencyListOriginal.reservedLength)
number = Vector<Int>(self.adjacencyListOriginal.reservedLength)
visited = Vector<Bool>(self.adjacencyListOriginal.reservedLength)
stack = Vector<Int>()
currentStrongConnectedComponents = Vector<Vector<Int>>()
makeAdjacencyListSubgraph(node: node);
for i in node ..< self.adjacencyListOriginal.reservedLength {
let isVisited = visited[i] ?? false
if !isVisited {
getStrongConnectedComponents(root: i)
let lowestIdComponent = getLowestIdComponent()
if let nodes = lowestIdComponent, !nodes.contains(node), !nodes.contains(node + 1) {
return getAdjacencyList(node: node + 1);
} else {
if let adjacencyList = getAdjList(nodes: lowestIdComponent) {
for j in 0 ..< self.adjacencyListOriginal.reservedLength {
if adjacencyList[j].size > 0 {
return Result(adjacencyList: adjacencyList, lowestNodeId: j);
}
}
}
}
}
}
return nil;
}
/**
* Builds the adjacency-list for a subgraph containing just nodes
* >= a given index.
*
* @param node Node with lowest index in the subgraph
*/
private func makeAdjacencyListSubgraph(node: Int) {
adjacencyList = AdjacencyList(adjacencyListOriginal.reservedLength, 0)
for i in node ..< adjacencyList.reservedLength {
let successors = Vector<Int>()
for j in 0 ..< self.adjacencyListOriginal[i].reservedLength {
guard let original = adjacencyListOriginal[i]?[j] else { continue }
if original >= node {
successors.add(original)
}
}
if successors.size > 0 {
adjacencyList[i] = Vector(successors.size)
for j in 0 ..< successors.size {
let succ = successors.get(j)
adjacencyList[i][j] = succ
}
}
}
}
/**
* Calculates the strong connected component out of a set of scc's, that
* contains the node with the lowest index.
*
* @return Vector::Integer of the strongConnectedComponents containing the lowest nodenumber
*/
private func getLowestIdComponent() -> Vector<Int>? {
var min = adjacencyList.reservedLength;
var currScc: Vector<Int>?
for i in 0 ..< currentStrongConnectedComponents.size {
let strongConnectedComponents = currentStrongConnectedComponents.get(i)
for j in 0 ..< strongConnectedComponents.size {
let node = strongConnectedComponents.get(j)
if node < min {
currScc = strongConnectedComponents
min = node
}
}
}
return currScc;
}
/**
* @return Vector[]::Integer representing the adjacency-structure of the
* strong connected component with least vertex in the currently viewed
* subgraph
*/
private func getAdjList(nodes: Vector<Int>?) -> AdjacencyList? {
guard let nodes = nodes else { return nil }
let lowestIdAdjacencyList = AdjacencyList(adjacencyList.reservedLength)
for i in 0 ..< lowestIdAdjacencyList.reservedLength {
lowestIdAdjacencyList[i] = Vector<Int>()
}
for i in 0 ..< nodes.size {
let node = nodes.get(i)
guard let adjListNode = adjacencyList[node] else { continue }
for j in 0 ..< adjListNode.reservedLength {
guard let succ = adjacencyList[node]?[j] else { continue }
if nodes.contains(succ) {
lowestIdAdjacencyList[node].add(succ)
}
}
}
return lowestIdAdjacencyList;
}
/**
* Searchs for strong connected components reachable from a given node.
*
* @param root node to start from.
*/
private func getStrongConnectedComponents(root: Int) {
strongConnectedComponentsCounter += 1
lowlink[root] = strongConnectedComponentsCounter
number[root] = strongConnectedComponentsCounter
visited[root] = true;
stack.add(root);
for i in 0 ..< adjacencyList[root].reservedLength {
guard let w = adjacencyList[root]?[i] else { continue }
if !(visited[w] ?? false) {
getStrongConnectedComponents(root: w);
lowlink[root] = min(lowlink[root]!, lowlink[w]!)
} else if let wNumber = number[w], wNumber < number[root]! {
if stack.contains(w) {
lowlink[root] = min(lowlink[root]!, number[w]!)
}
}
}
// found strongConnectedComponents
if (lowlink[root] == number[root]) && (stack.size > 0) {
var next = -1;
let strongConnectedComponents = Vector<Int>()
repeat {
next = stack.get(stack.size - 1)
stack.remove(at: stack.size - 1)
strongConnectedComponents.add(next)
} while number[next]! > number[root]!
// simple scc's with just one node will not be added
if strongConnectedComponents.size > 1 {
currentStrongConnectedComponents.add(strongConnectedComponents);
}
}
}
}

View File

@ -0,0 +1,26 @@
/*
(BSD-2 license)
Original work Copyright (c) 2012, Frank Meyer
All rights reserved.
Swift port Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
public typealias AdjacencyMatrix = Matrix<Bool>
extension Matrix where Element == Bool {
public convenience init(_ reservedRows: Int, _ reservedColumns: Int = 0, builder: (AdjacencyMatrix) -> Void) {
self.init(reservedRows, reservedColumns)
builder(self)
}
}

View File

@ -0,0 +1,191 @@
/*
(BSD-2 license)
Original work Copyright (c) 2012, Frank Meyer
All rights reserved.
Swift port Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
/**
* Searchs all elementary cycles in a given directed graph. The implementation
* is independent from the concrete objects that represent the graphnodes, it
* just needs an array of the objects representing the nodes the graph
* and an adjacency-matrix of type boolean, representing the edges of the
* graph. It then calculates based on the adjacency-matrix the elementary
* cycles and returns a list, which contains lists itself with the objects of the
* concrete graphnodes-implementation. Each of these lists represents an
* elementary cycle.<br><br>
*
* The implementation uses the algorithm of Donald B. Johnson for the search of
* the elementary cycles. For a description of the algorithm see:<br>
* Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph.
* SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84.<br><br>
*
* The algorithm of Johnson is based on the search for strong connected
* components in a graph. For a description of this part see:<br>
* Robert Tarjan: Depth-first search and linear graph algorithms. In: SIAM
* Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160.<br>
*
* @author Frank Meyer, web_at_normalisiert_dot_de
* @version 1.2, 22.03.2009
*
*/
public class ElementaryCyclesSearch<Node> {
/** List of cycles */
private var cycles: Vector<Vector<Node>>
/** Adjacency-list of graph */
private var adjacencyList: AdjacencyList
/** Graphnodes */
private var graphNodes: Vector<Node>
/** Blocked nodes, used by the algorithm of Johnson */
private var blocked: Vector<Bool>
/** B-Lists, used by the algorithm of Johnson */
private var B: Matrix<Int>
/** Stack for nodes, used by the algorithm of Johnson */
private var stack: Vector<Int>
/**
* Returns List::List::Object with the Lists of nodes of all elementary
* cycles in the graph.
*
* @param adjacencyMatrix adjacency-matrix of the graph
* @param graphNodes array of the graphnodes of the graph; this is used to
* build sets of the elementary cycles containing the objects of the original
* graph-representation
*
* @return List::List::Object with the Lists of the elementary cycles.
*/
public static func getElementaryCycles(adjacencyMatrix: AdjacencyMatrix, graphNodes: Vector<Node>) -> Vector<Vector<Node>> {
let ecs = ElementaryCyclesSearch(adjacencyMatrix: adjacencyMatrix, graphNodes: graphNodes)
return ecs.getElementaryCycles()
}
/**
* Constructor.
*
* @param adjacencyMatrix adjacency-matrix of the graph
* @param graphNodes array of the graphnodes of the graph; this is used to
* build sets of the elementary cycles containing the objects of the original
* graph-representation
*/
private init(adjacencyMatrix: AdjacencyMatrix, graphNodes: Vector<Node>) {
self.graphNodes = graphNodes;
self.adjacencyList = AdjacencyList.getAdjacencyList(adjacencyMatrix: adjacencyMatrix)
cycles = Vector<Vector<Node>>()
blocked = Vector<Bool>(adjacencyList.reservedLength)
B = Matrix<Int>(adjacencyList.reservedLength)
stack = Vector<Int>()
}
/**
* Returns List::List::Object with the Lists of nodes of all elementary
* cycles in the graph.
*
* @return List::List::Object with the Lists of the elementary cycles.
*/
private func getElementaryCycles() -> Vector<Vector<Node>> {
let sccs = StrongConnectedComponents(adjacencyList: adjacencyList)
var s = 0
while true {
if let sccResult = sccs.getAdjacencyList(node: s) {
let strongConnectedComponents = sccResult.getAdjList()
s = sccResult.getLowestNodeId()
for j in 0 ..< strongConnectedComponents.reservedLength {
if (strongConnectedComponents[j] != nil) && (strongConnectedComponents[j].size > 0) {
blocked[j] = false
B[j] = Vector<Int>()
}
}
_ = findCycles(v: s, s: s, adjacencyList: strongConnectedComponents)
s += 1
} else {
break
}
}
return cycles;
}
/**
* Calculates the cycles containing a given node in a strongly connected
* component. The method calls itself recursivly.
*
* @param v
* @param s
* @param adjacencyList adjacency-list with the subgraph of the strongly
* connected component s is part of.
* @return true, if cycle found; false otherwise
*/
private func findCycles(v: Int, s: Int, adjacencyList: AdjacencyList) -> Bool {
var f = false
stack.add(v)
blocked[v] = true
for i in 0 ..< adjacencyList[v].size {
let w = adjacencyList[v].get(i)
// found cycle
if w == s {
let cycle = Vector<Node>()
for j in 0 ..< stack.size {
let index = stack.get(j)
guard let node = graphNodes[index] else { continue }
cycle.add(node)
}
cycles.add(cycle)
f = true
} else if !(blocked[w] ?? false) {
if findCycles(v: w, s: s, adjacencyList: adjacencyList) {
f = true
}
}
}
if f {
unblock(node: v)
} else {
for i in 0 ..< adjacencyList[v].size {
let w = adjacencyList[v].get(i)
if !B[w].contains(v) {
B[w].add(v)
}
}
}
stack.remove(element: v)
return f
}
/**
* Unblocks recursivly all blocked nodes, starting with a given node.
*
* @param node node to unblock
*/
private func unblock(node: Int) {
blocked[node] = false
guard let Bnode = B[node] else { return }
while Bnode.size > 0 {
let w = Bnode.get(0)
Bnode.remove(at: 0)
if let isBlocked = blocked[w], isBlocked {
unblock(node: w)
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
(BSD-2 license)
Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
public struct MatrixIterator<Element>: IteratorProtocol {
private let matrix: Matrix<Element>
private var currentIndex = -1
private var currentIterator: VectorIterator<Element>?
fileprivate init(matrix: Matrix<Element>) {
self.matrix = matrix
}
mutating public func next() -> Element? {
if let element = currentIterator?.next() {
return element
}
currentIndex += 1
guard matrix.reservedLength > currentIndex else { return nil }
let vector = matrix[currentIndex]
currentIterator = vector?.makeIterator()
return next()
}
}
extension Matrix: Sequence {
public func makeIterator() -> MatrixIterator<Element> {
return MatrixIterator(matrix: self)
}
}

View File

@ -0,0 +1,71 @@
/*
(BSD-2 license)
Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
public final class Matrix<Element> {
private var vector: Vector<Vector<Element>>
public subscript(_ row: Int) -> Vector<Element>! {
get {
return vector[row]
}
set(newValue) {
vector[row] = newValue
}
}
public init(_ reservedRows: Int, _ reservedColumns: Int = 0) {
self.vector = Vector<Vector<Element>>(reservedRows)
for i in 0 ..< reservedLength {
vector[i] = Vector<Element>(reservedColumns)
}
}
public convenience init(rows: [[Element]]) {
var reservedColumns = 0
for row in rows {
reservedColumns = Swift.max(reservedColumns, row.count)
}
self.init(rows.count, reservedColumns)
for (offset, row) in rows.enumerated() {
self[offset] = Vector(array: row, reservedLength: reservedColumns)
}
}
public var reservedLength: Int {
return vector.reservedLength
}
}
extension Matrix: CustomDebugStringConvertible {
public var debugDescription: String {
return vector.debugDescription
}
}
extension Matrix: CustomStringConvertible {
public var description: String {
return vector.description
}
}
extension Matrix: Equatable where Element: Equatable {
public static func == (lhs: Matrix<Element>, rhs: Matrix<Element>) -> Bool {
guard lhs.reservedLength == rhs.reservedLength else { return false }
for index in 0 ..< lhs.reservedLength {
guard lhs[index] == rhs[index] else { return false }
}
return true
}
}

View File

@ -0,0 +1,36 @@
/*
(BSD-2 license)
Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
public struct VectorIterator<Element>: IteratorProtocol {
private let vector: Vector<Element>
private var currentIndex = -1
fileprivate init(vector: Vector<Element>) {
self.vector = vector
}
mutating public func next() -> Element? {
currentIndex += 1
guard vector.size > currentIndex else { return nil }
guard let element = vector[currentIndex] else { return next() }
return element
}
}
extension Vector: Sequence {
public func makeIterator() -> VectorIterator<Element> {
return VectorIterator(vector: self)
}
}

View File

@ -0,0 +1,115 @@
/*
(BSD-2 license)
Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
public final class Vector<Element> {
private var array: [Element?] {
didSet {
reservedLength = Swift.max(reservedLength, array.count)
}
}
public private(set) var reservedLength: Int {
didSet {
assert(reservedLength >= size)
}
}
public subscript(_ index: Int) -> Element? {
get {
guard array.count > index || index >= reservedLength else { return nil }
return array[index]
}
set(newValue) {
while index >= array.count && index < reservedLength {
array.append(nil)
}
array[index] = newValue
}
}
public init(array: [Element], reservedLength: Int? = nil) {
self.array = array
self.reservedLength = Swift.max(reservedLength ?? 0, array.count)
}
public init(_ reservedLength: Int = 0) {
self.array = [Element?]()
self.reservedLength = reservedLength
}
public var size: Int {
return array.count
}
public func get(_ index: Int) -> Element {
guard let element = self[index] else { fatalError("\(#function): index (\(index)) out of bounds (\(reservedLength))") }
return element
}
public func add(_ newElement: Element) {
array.append(newElement)
}
@discardableResult
public func remove(at index: Int) -> Element? {
var removedObject: Element?
if index < array.count {
removedObject = array[index]
array.remove(at: index)
} else if index >= reservedLength {
preconditionFailure("Index out of range: \(index) is beyond count (\(array.count) and reservedLength \(reservedLength)")
}
reservedLength -= 1
return removedObject
}
}
extension Vector where Element: Equatable {
func contains(_ element: Element) -> Bool{
return array.contains(element)
}
@discardableResult
func remove(element: Element) -> Bool {
if let index = array.index(of: element) {
array.remove(at: -index.distance(to: 0))
return true
}
return false
}
}
extension Vector: CustomDebugStringConvertible {
public var debugDescription: String {
return array.debugDescription
}
}
extension Vector: CustomStringConvertible {
public var description: String {
return array.description
}
}
extension Vector: Equatable where Element: Equatable {
public static func == (lhs: Vector<Element>, rhs: Vector<Element>) -> Bool {
guard lhs.reservedLength == rhs.reservedLength else { return false }
guard lhs.size == rhs.size else { return false }
for index in 0 ..< lhs.size {
guard lhs[index] == rhs[index] else { return false }
}
return true
}
}

View File

@ -0,0 +1,84 @@
/*
(BSD-2 license)
Original work Copyright (c) 2012, Frank Meyer
All rights reserved.
Swift port Copyright (c) 2018, Hèctor Marquès
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Swift
import ElementaryCyclesSearch
/**
* Testfile for elementary cycle search.
*
* @author Frank Meyer
*
*/
typealias Node = String
private func printCycles(_ cycles: Vector<Vector<Node>>) {
for i in 0 ..< cycles.size {
let cycle = cycles.get(i)
for j in 0 ..< cycle.size {
let node = cycle.get(j)
if j < (cycle.size - 1) {
print(node + " -> ", terminator: "")
} else {
print(node, terminator: "")
}
}
print()
}
}
let nodes: Vector<Node> = {
let vector = Vector<Node>(10)
for i in 0 ..< 10 {
vector[i] = "Node \(i)"
}
return vector
}()
let adjMatrix: AdjacencyMatrix = {
let adjMatrix = AdjacencyMatrix(10, 10) { matrix in
/*matrix[0][1] = true
matrix[1][2] = true
matrix[2][0] = true
matrix[2][4] = true
matrix[1][3] = true
matrix[3][6] = true
matrix[6][5] = true
matrix[5][3] = true
matrix[6][7] = true
matrix[7][8] = true
matrix[7][9] = true
matrix[9][6] = true;*/
matrix[0][1] = true
matrix[1][2] = true
matrix[2][0] = true
matrix[2][6] = true
matrix[3][4] = true
matrix[4][5] = true
matrix[4][6] = true
matrix[5][3] = true
matrix[6][7] = true
matrix[7][8] = true
matrix[8][6] = true
matrix[6][1] = true
}
return adjMatrix
}()
let cycles = ElementaryCyclesSearch.getElementaryCycles(adjacencyMatrix: adjMatrix, graphNodes: nodes)
printCycles(cycles)

View File

@ -0,0 +1,37 @@
import XCTest
@testable import ElementaryCyclesSearch
final class AdjacencyListTests: XCTestCase {
static var allTests = [("testPerformance", testPerformance),
("test", test)]
let matrix = AdjacencyMatrix(5, 5) { matrix in
for i in 0 ..< 5 {
matrix[i][0] = true
matrix[i][1] = false
matrix[i][2] = true
matrix[i][3] = false
matrix[i][4] = true
}
}
var sut: ((AdjacencyMatrix) -> Matrix<Int>)!
override func setUp() {
super.setUp()
sut = AdjacencyList.getAdjacencyList
}
func testPerformance() {
self.measure {
_ = sut(matrix)
}
}
func test() {
let result = sut(matrix)
let expectedDescription = "[Optional([Optional(0), Optional(2), Optional(4)]), Optional([Optional(0), Optional(2), Optional(4)]), Optional([Optional(0), Optional(2), Optional(4)]), Optional([Optional(0), Optional(2), Optional(4)]), Optional([Optional(0), Optional(2), Optional(4)])]"
XCTAssertEqual(result.description, expectedDescription)
}
}

View File

@ -0,0 +1,49 @@
import XCTest
@testable import ElementaryCyclesSearch
final class AdjacencyMatrixTests: XCTestCase {
static var allTests = [("test", test),
("testConvenienceInit", testConvenienceInit),
("testSequence", testSequence)
]
func test() {
let sut = AdjacencyMatrix(5, 5)
for i in 0 ..< 5 {
sut[i][0] = true
sut[i][1] = false
sut[i][2] = true
sut[i][3] = false
sut[i][4] = true
}
let expectedDescription = "[Optional([Optional(true), Optional(false), Optional(true), Optional(false), Optional(true)]), Optional([Optional(true), Optional(false), Optional(true), Optional(false), Optional(true)]), Optional([Optional(true), Optional(false), Optional(true), Optional(false), Optional(true)]), Optional([Optional(true), Optional(false), Optional(true), Optional(false), Optional(true)]), Optional([Optional(true), Optional(false), Optional(true), Optional(false), Optional(true)])]"
XCTAssertEqual(sut.description, expectedDescription)
}
func testConvenienceInit() {
let rows = [["a"],[],["b", "c"]]
let matrix = Matrix<String>(rows: rows)
let expectedMatrix: Matrix<String> = {
let matrix = Matrix<String>(3, 2)
matrix[0][0] = "a"
matrix[2][0] = "b"
matrix[2][1] = "c"
return matrix
}()
XCTAssertEqual(matrix, expectedMatrix)
}
func testSequence() {
let matrix = Matrix<Int>(4, 4)
matrix[0][1] = 1
matrix[1][2] = 2
matrix[3][0] = 3
matrix[3][3] = 4
var elements = [Int]()
for element in matrix {
elements.append(element)
}
let expectedElements = [1,2,3, 4]
XCTAssertEqual(elements, expectedElements)
}
}

View File

@ -0,0 +1,91 @@
import XCTest
@testable import ElementaryCyclesSearch
final class ElementaryCyclesSearchTests: XCTestCase {
static var allTests = [("test", test)]
private typealias Node = String
private var nodes: Vector<Node> {
let nodes = Vector<Node>(10)
for i in 0 ..< 10 {
nodes[i] = "Node \(i)"
}
return nodes
}
private let adjacencyMatrix = AdjacencyMatrix(10, 10) { matrix in
matrix[0][1] = true
matrix[1][2] = true
matrix[2][0] = true
matrix[2][6] = true
matrix[3][4] = true
matrix[4][5] = true
matrix[4][6] = true
matrix[5][3] = true
matrix[6][7] = true
matrix[7][8] = true
matrix[8][6] = true
matrix[6][1] = true
}
private var sut: ((AdjacencyMatrix, Vector<Node>) -> Vector<Vector<Node>>)!
private func prettify(cycles: Vector<Vector<Node>>) -> String {
var description = ""
for i in 0 ..< cycles.size {
let cycle = cycles.get(i)
for j in 0 ..< cycle.size {
let node = cycle.get(j)
if j < (cycle.size - 1) {
description.append(node + " -> ")
} else {
description.append(node)
}
}
description.append("\n")
}
return description
}
override func setUp() {
super.setUp()
sut = ElementaryCyclesSearch.getElementaryCycles
}
func testPerformanceExample() {
self.measure {
_ = sut(adjacencyMatrix, nodes)
}
}
func test() {
let adjacencyMatrix: AdjacencyMatrix = {
let matrix = AdjacencyMatrix(10, 10)
matrix[0][1] = true
matrix[1][2] = true
matrix[2][0] = true
matrix[2][6] = true
matrix[3][4] = true
matrix[4][5] = true
matrix[4][6] = true
matrix[5][3] = true
matrix[6][7] = true
matrix[7][8] = true
matrix[8][6] = true
matrix[6][1] = true
return matrix
}()
let result = sut(adjacencyMatrix, nodes)
let description = prettify(cycles: result)
let expectedString =
"""
Node 0 -> Node 1 -> Node 2
Node 1 -> Node 2 -> Node 6
Node 3 -> Node 4 -> Node 5
Node 6 -> Node 7 -> Node 8
"""
XCTAssertEqual(description, expectedString)
}
}

View File

@ -0,0 +1,179 @@
import XCTest
@testable import ElementaryCyclesSearch
final class StrongConnectedComponentsTests: XCTestCase {
static var allTests = [("test1", test1),
("test2", test2)]
func test1() {
let adjMatrix = Matrix(10, 10) { matrix in
matrix[0][1] = true
matrix[1][2] = true
matrix[2][0] = true; matrix[2][6] = true
matrix[3][4] = true
matrix[4][5] = true; matrix[4][6] = true
matrix[5][3] = true
matrix[6][7] = true
matrix[7][8] = true
matrix[8][6] = true
matrix[6][1] = true
}
let adjacencyList: Matrix<Int> = AdjacencyList.getAdjacencyList(adjacencyMatrix: adjMatrix)
let sccs = StrongConnectedComponents(adjacencyList: adjacencyList)
var description = ""
for i in 0 ..< adjacencyList.reservedLength {
description.append("i: \(i)\n")
guard let r = sccs.getAdjacencyList(node: i) else { continue }
let al = r.getAdjList()
for j in i ..< al.reservedLength {
if al[j].size > 0 {
description.append("j: \(j)")
for k in 0 ..< al[j].size {
description.append(" _\(al[j].get(k))");
}
description.append("\n")
}
}
description.append("\n")
}
let expectedString =
"""
i: 0
j: 0 _1
j: 1 _2
j: 2 _0 _6
j: 6 _1 _7
j: 7 _8
j: 8 _6
i: 1
j: 1 _2
j: 2 _6
j: 6 _1 _7
j: 7 _8
j: 8 _6
i: 2
j: 3 _4
j: 4 _5
j: 5 _3
i: 3
j: 3 _4
j: 4 _5
j: 5 _3
i: 4
j: 6 _7
j: 7 _8
j: 8 _6
i: 5
j: 6 _7
j: 7 _8
j: 8 _6
i: 6
j: 6 _7
j: 7 _8
j: 8 _6
i: 7
i: 8
i: 9
"""
XCTAssertEqual(description, expectedString)
}
func test2() {
let adjMatrix: AdjacencyMatrix = Matrix(10, 10) { matrix in
matrix[0][1] = true
matrix[1][2] = true
matrix[2][0] = true
matrix[2][4] = true
matrix[1][3] = true
matrix[3][6] = true
matrix[6][5] = true
matrix[5][3] = true
matrix[6][7] = true
matrix[7][8] = true
matrix[7][9] = true
matrix[9][6] = true
}
let adjacencyList: Matrix<Int> = AdjacencyList.getAdjacencyList(adjacencyMatrix: adjMatrix)
let sccs = StrongConnectedComponents(adjacencyList: adjacencyList)
var description = ""
for i in 0 ..< adjacencyList.reservedLength {
description.append("i: \(i)\n")
guard let r = sccs.getAdjacencyList(node: i) else { continue }
let al = r.getAdjList()
for j in i ..< al.reservedLength {
if al[j].size > 0 {
description.append("j: \(j)")
for k in 0 ..< al[j].size {
description.append(" _\(al[j].get(k))");
}
description.append("\n")
}
}
description.append("\n")
}
let expectedString =
"""
i: 0
j: 0 _1
j: 1 _2
j: 2 _0
i: 1
j: 3 _6
j: 5 _3
j: 6 _5 _7
j: 7 _9
j: 9 _6
i: 2
j: 3 _6
j: 5 _3
j: 6 _5 _7
j: 7 _9
j: 9 _6
i: 3
j: 3 _6
j: 5 _3
j: 6 _5 _7
j: 7 _9
j: 9 _6
i: 4
j: 6 _7
j: 7 _9
j: 9 _6
i: 5
j: 6 _7
j: 7 _9
j: 9 _6
i: 6
j: 6 _7
j: 7 _9
j: 9 _6
i: 7
i: 8
i: 9
"""
XCTAssertEqual(description, expectedString)
}
}

View File

@ -0,0 +1,13 @@
import XCTest
import ElementaryCyclesSearch
#if !os(macOS)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(AdjacencyListTests.allTests),
testCase(ElementaryCyclesSearchTests.allTests),
testCase(AdjacencyMatrixTests.allTests),
testCase(StrongConnectedComponentsTests.allTests),
]
}
#endif

View File

@ -0,0 +1,45 @@
import XCTest
@testable import ElementaryCycles
@testable import ElementaryCyclesSearch
final class AdjacencyMatrixShould: XCTestCase {
static var allTests = [("test_build_adjacency_matrix_from_dictionary", test_build_adjacency_matrix_from_dictionary),
("test_build_nodes_vector_from_array", test_build_nodes_vector_from_array)]
func test_build_adjacency_matrix_from_dictionary() {
let nodes = ["ES", "PT", "FR", "IT"]
let adjacencies = ["ES": ["PT", "FR"],
"PT": ["ES"],
"FR": ["ES", "IT"],
"IT": ["FR"]]
let adjacencyMatrix = try! AdjacencyMatrix.getAdjacencyMatrix(nodes: nodes, adjacencyDictionary: adjacencies)
let expectedAdjacencyMatrix = AdjacencyMatrix(4, 4) { matrix in
matrix[0][1] = true
matrix[0][2] = true
matrix[1][0] = true
matrix[2][0] = true
matrix[2][3] = true
matrix[3][2] = true
}
XCTAssertEqual(adjacencyMatrix, expectedAdjacencyMatrix)
}
func test_build_nodes_vector_from_array() {
let graph = ["ES": ["PT", "FR"],
"PT": ["ES"],
"FR": ["ES", "IT"],
"IT": ["FR"]]
let nodes = AdjacencyMatrix.getNodes(graph: graph, sort: nil)
let adjacencyMatrix = try! AdjacencyMatrix.getAdjacencyMatrix(nodes: nodes, adjacencyDictionary: graph)
let es = nodes.index(of: "ES")!
let pt = nodes.index(of: "PT")!
let fr = nodes.index(of: "FR")!
let it = nodes.index(of: "IT")!
XCTAssertTrue(adjacencyMatrix[es][pt] ?? adjacencyMatrix[pt][es] ?? false)
XCTAssertTrue(adjacencyMatrix[es][fr] ?? adjacencyMatrix[fr][es] ?? false)
XCTAssertTrue(adjacencyMatrix[fr][it] ?? adjacencyMatrix[it][fr] ?? false)
XCTAssertFalse(adjacencyMatrix[es][it] ?? adjacencyMatrix[it][es] ?? false)
XCTAssertFalse(adjacencyMatrix[pt][it] ?? adjacencyMatrix[it][pt] ?? false)
XCTAssertFalse(adjacencyMatrix[fr][pt] ?? adjacencyMatrix[pt][fr] ?? false)
}
}

View File

@ -0,0 +1,38 @@
import XCTest
@testable import ElementaryCycles
@testable import ElementaryCyclesSearch
final class ElementaryCyclesSearchShould: XCTestCase {
static var allTests = [("test_map_cycles_to_array", test_map_cycles_to_array)]
private typealias Node = String
private var nodes: Vector<Node> {
let nodes = Vector<Node>(10)
for i in 0 ..< 10 {
nodes[i] = "Node \(i)"
}
return nodes
}
private var adjacencyMatrix = AdjacencyMatrix(10, 10) { matrix in
matrix[0][1] = true
matrix[1][2] = true
matrix[2][0] = true
matrix[2][4] = true
matrix[1][3] = true
matrix[3][6] = true
matrix[6][5] = true
matrix[5][3] = true
matrix[6][7] = true
matrix[7][8] = true
matrix[7][9] = true
matrix[9][6] = true
}
func test_map_cycles_to_array() {
let result = ElementaryCyclesSearch.toArray(elementaryCycles: ElementaryCyclesSearch.getElementaryCycles(adjacencyMatrix: adjacencyMatrix, graphNodes: nodes))
let expected = [["Node 0", "Node 1", "Node 2"], ["Node 3", "Node 6", "Node 5"], ["Node 6", "Node 7", "Node 9"]]
XCTAssertEqual(result, expected)
}
}

View File

@ -0,0 +1,24 @@
import XCTest
@testable import ElementaryCycles
@testable import ElementaryCyclesSearch
final class ElementaryCyclesShould: XCTestCase {
static var allTests = [("test_find_cycles", test_find_cycles),
("test_find_sorted_cycles", test_find_sorted_cycles)]
func test_find_cycles() {
let graph = ["0": ["1"], "1": ["2", "0"], "2": []]
let result = ElementaryCycles.find(graph: graph)
XCTAssertEqual(result.count, 1)
XCTAssertEqual(result.first?.count, 2)
XCTAssertEqual(result.first?.contains("0"), true)
XCTAssertEqual(result.first?.contains("1"), true)
}
func test_find_sorted_cycles() {
let graph = ["A": ["B", "C"], "B": ["A"], "C": ["D"], "D": ["C"]]
let result = ElementaryCycles.find(graph: graph, sort: { $0 < $1 })
let expected = [["A", "B"], ["C", "D"]]
XCTAssertEqual(result, expected)
}
}

View File

@ -0,0 +1,12 @@
import XCTest
import ElementaryCycles
#if !os(macOS)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(ElementaryCyclesShould.allTests),
testCase(ElementaryCyclesSearchShould.allTests),
testCase(AdjacencyMatrixShould.allTests),
]
}
#endif

8
Tests/LinuxMain.swift Normal file
View File

@ -0,0 +1,8 @@
import XCTest
import ElementaryCyclesSearchTests
var tests = [XCTestCaseEntry]()
tests += ElementaryCyclesSearchTests.allTests()
tests += ElementaryCyclesTests.allTests()
XCTMain(tests)