Initial commit
This commit is contained in:
commit
8aa42b400c
|
@ -0,0 +1,4 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
|
@ -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.
|
|
@ -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"]),
|
||||
]
|
||||
)
|
|
@ -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.
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
import XCTest
|
||||
|
||||
import ElementaryCyclesSearchTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += ElementaryCyclesSearchTests.allTests()
|
||||
tests += ElementaryCyclesTests.allTests()
|
||||
XCTMain(tests)
|
Loading…
Reference in New Issue