Skip to content

Commit

Permalink
Add AdjacencyList data structure (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
bwetherfield authored May 27, 2019
1 parent 7233878 commit d699d35
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 0 deletions.
165 changes: 165 additions & 0 deletions Sources/DataStructures/AdjacencyList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//
// AdjacencyList.swift
// DataStructures
//
// Created by Benjamin Wetherfield on 5/23/19.
//

// Adjacency list representation of a graph
public struct AdjacencyList<Node: Hashable> {

// MARK: - Instance Properties

// Stores the set of nodes adjacent to each node
public let adjacencies: [Node: Set<Node>]

// MARK: - Initializers

public init(_ adjacencies: [Node: Set<Node>]) {
self.adjacencies = adjacencies
}

// Safe initializer that ensures there are no hanging nodes inside of a "value" `Set` that are not within
// the "keys" of `adjancencies`.
public init(safe adjacencies: [Node: Set<Node>]) {
self.adjacencies = adjacencies.reduce(into: [Node: Set<Node>]()) { completed, pair in
let (node, neighbors) = pair
completed[node] = neighbors
neighbors.forEach { neighbor in
if !completed.keys.contains(neighbor) { completed[neighbor] = [] }
}
}
}
}

extension AdjacencyList {

// MARK: - Instance Methods

// Tarjan's algorithm to find strongly connected components
func getStronglyConnectedComponent ()
-> (Node) -> Set<Node> {

func reducer(
_ map: inout [Node: Set<Node>],
_ pair: (key: Node, value: Set<Node>)
) -> () {

func strongConnect (
_ map: inout [Node: Set<Node>],
_ indexAt: inout [Node: Int],
_ lowLinkAt: inout [Node: Int],
_ counter: inout Int,
_ stack: inout Stack<Node>,
_ stackSet: inout Set<Node>,
_ pair: (key: Node, value: Set<Node>)
) -> () {
let (cursor, neighbors) = pair
indexAt[cursor] = counter
lowLinkAt[cursor] = counter
counter += 1
stack.push(cursor)
stackSet.insert(cursor)

neighbors.forEach { neighbor in
if !indexAt.keys.contains(neighbor) {
strongConnect(&map,&indexAt, &lowLinkAt, &counter, &stack, &stackSet,
(key: neighbor, value: adjacencies[neighbor]!))
lowLinkAt[cursor] = min(lowLinkAt[cursor]!, lowLinkAt[neighbor]!)
} else if stackSet.contains(neighbor) {
lowLinkAt[cursor] = min(lowLinkAt[cursor]!, indexAt[neighbor]!)
}
}

if indexAt[cursor]! == lowLinkAt[cursor]! {
var tempSet: Set<Node> = []
while stack.top! != cursor {
let next = stack.pop()!
tempSet.insert(next)
stackSet.remove(next)
}
tempSet.insert(stack.pop()!)
stackSet.remove(cursor)
tempSet.forEach { node in map[node] = tempSet }
}
}

var indexAt: [Node: Int] = [:]
var lowLinkAt: [Node: Int] = [:]
var counter: Int = 0
var stack: Stack<Node> = []
var stackSet: Set<Node> = []

let _ = strongConnect(&map, &indexAt, &lowLinkAt, &counter, &stack, &stackSet, pair)
}

let map = adjacencies.reduce(into: [:], reducer)
return { map[$0]! }
}

// Group nodes according to the set-forming function `nodeClumper` and return the resulting
// `AdjacencyList`, removing self-loops that arise.
func clumpify (via nodeClumper: @escaping (Node) -> Set<Node>) -> AdjacencyList<Set<Node>> {
return AdjacencyList<Set<Node>>(
adjacencies.reduce(into: [Set<Node>: Set<Set<Node>>]()) { list, adjacencyPair in
let (node, adjacentNodes) = adjacencyPair
let clump = nodeClumper(node)
let adjacentClumps = Set(adjacentNodes.map(nodeClumper))
if let existingSet = list[clump] {
list[clump] = existingSet.union(adjacentClumps)
} else {
list[clump] = adjacentClumps
}
list[clump]!.remove(clump)
})
}

// Group nodes according to the function that sends a node to its strongly connected component, as found
// by the implementation of Tarjan's algorithm, hence forming a Directed Acyclic Graph (DAG) version of
// original `AdjacencyList` (`self`).
public func DAGify () -> AdjacencyList<Set<Node>> {
return clumpify (via: getStronglyConnectedComponent())
}

// Determines whether the graph represented by the `AdjacencyList` contains a cycle.
public func containsCycle () -> Bool {

var flag = false

func cycleSearch(_ visited: inout Set<Node>, _ keyValue: (key: Node, value: Set<Node>)) {

func depthFirstSearch (
_ visited: inout Set<Node>,
_ active: inout Set<Node>,
_ keyValue: (key: Node, value: Set<Node>)
) -> Bool
{
let (node, neighbors) = keyValue
if visited.contains(node) { return false }
visited.insert(node)
active.insert(node)
let foundCycle = neighbors.reduce(false) { outcome, neighbor in
if active.contains(neighbor) {
return true
} else if !visited.contains(neighbor) {
return outcome ||
depthFirstSearch(&visited, &active, (neighbor, adjacencies[neighbor]!))
} else {
return outcome
}
}
active.remove(node)
return foundCycle
}

var active: Set<Node> = []
if flag == true { return }
if depthFirstSearch(&visited, &active, keyValue) {
flag = true
}
}

let _ = adjacencies.reduce(into: [], cycleSearch)
return flag
}
}
80 changes: 80 additions & 0 deletions Tests/DataStructuresTests/AdjacencyListTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// AdjacencyListTests.swift
// DataStructuresTests
//
// Created by Benjamin Wetherfield on 5/23/19.
//

import XCTest
import DataStructures

class AdjacencyListTests: XCTestCase {

func testSafeInitializer() {
let adjacencyList = AdjacencyList<Int>(safe: [1:[2,3,4]])
XCTAssertEqual(adjacencyList.adjacencies[1],[2,3,4])
XCTAssertEqual(adjacencyList.adjacencies[2],[])
XCTAssertEqual(adjacencyList.adjacencies[3],[])
XCTAssertEqual(adjacencyList.adjacencies[4],[])
}

func testSimpleCycle() {
let adjacencyList = AdjacencyList<Int>([1:[1]])
XCTAssertTrue(adjacencyList.containsCycle())
}

func testSimpleNonCycle() {
let adjacencyList = AdjacencyList<Int>([1:[]])
XCTAssertFalse(adjacencyList.containsCycle())
}

func testSimpleCycleTwoNodes() {
let adjacencyList = AdjacencyList<Int>([1:[2], 2:[1]])
XCTAssertTrue(adjacencyList.containsCycle())
}

func testSimpleCycleThreeNodesMixed() {
let adjacencyList = AdjacencyList<Int>([1:[2], 2:[1], 3: [1,2]])
XCTAssertTrue(adjacencyList.containsCycle())
}

func testSimpleCycleDisjoint() {
let adjacencyList = AdjacencyList<Int>([1:[], 2:[3], 3:[4], 4:[2]])
XCTAssertTrue(adjacencyList.containsCycle())
}

func testSimpleNoCycleDisjoint() {
let adjacencyList = AdjacencyList<Int>([1:[2], 2:[], 3:[4], 4:[5], 5:[]])
XCTAssertFalse(adjacencyList.containsCycle())
}

func testDAGifySimpleCycle() {
let adjacencyList = AdjacencyList<Int>([1:[1]])
XCTAssertFalse(adjacencyList.DAGify().containsCycle())
}

func testDAGifySimpleNonCycle() {
let adjacencyList = AdjacencyList<Int>([1:[]])
XCTAssertFalse(adjacencyList.DAGify().containsCycle())
}

func testDAGifySimpleCycleTwoNodes() {
let adjacencyList = AdjacencyList<Int>([1:[2], 2:[1]])
XCTAssertFalse(adjacencyList.DAGify().containsCycle())
}

func testDAGifySimpleCycleThreeNodesMixed() {
let adjacencyList = AdjacencyList<Int>([1:[2], 2:[1], 3: [1,2]])
XCTAssertFalse(adjacencyList.DAGify().containsCycle())
}

func testDAGifySimpleCycleDisjoint() {
let adjacencyList = AdjacencyList<Int>([1:[], 2:[3], 3:[4], 4:[2]])
XCTAssertFalse(adjacencyList.DAGify().containsCycle())
}

func testDAGifySimpleNoCycleDisjoint() {
let adjacencyList = AdjacencyList<Int>([1:[2], 2:[], 3:[4], 4:[5], 5:[]])
XCTAssertFalse(adjacencyList.DAGify().containsCycle())
}
}
19 changes: 19 additions & 0 deletions Tests/DataStructuresTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ extension AVLTreeTests {
]
}

extension AdjacencyListTests {
static let __allTests = [
("testDAGifySimpleCycle", testDAGifySimpleCycle),
("testDAGifySimpleCycleDisjoint", testDAGifySimpleCycleDisjoint),
("testDAGifySimpleCycleThreeNodesMixed", testDAGifySimpleCycleThreeNodesMixed),
("testDAGifySimpleCycleTwoNodes", testDAGifySimpleCycleTwoNodes),
("testDAGifySimpleNoCycleDisjoint", testDAGifySimpleNoCycleDisjoint),
("testDAGifySimpleNonCycle", testDAGifySimpleNonCycle),
("testSafeInitializer", testSafeInitializer),
("testSimpleCycle", testSimpleCycle),
("testSimpleCycleDisjoint", testSimpleCycleDisjoint),
("testSimpleCycleThreeNodesMixed", testSimpleCycleThreeNodesMixed),
("testSimpleCycleTwoNodes", testSimpleCycleTwoNodes),
("testSimpleNoCycleDisjoint", testSimpleNoCycleDisjoint),
("testSimpleNonCycle", testSimpleNonCycle),
]
}

extension ArrayExtensionsTests {
static let __allTests = [
("testInsertingAtIndexAtBeginning", testInsertingAtIndexAtBeginning),
Expand Down Expand Up @@ -573,6 +591,7 @@ extension ZipToLongestTests {
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(AVLTreeTests.__allTests),
testCase(AdjacencyListTests.__allTests),
testCase(ArrayExtensionsTests.__allTests),
testCase(BimapTests.__allTests),
testCase(BinaryHeapTests.__allTests),
Expand Down

0 comments on commit d699d35

Please sign in to comment.