From 45a1204e9a3d33f9080a3441b3ff3e3d596b19d4 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Mon, 15 Jun 2020 13:01:46 -0700 Subject: [PATCH 01/15] Begin adding a union-edges composite graph, but being forced into defining either to avoid reimplementing ConcatenatedCollection a bunch of times. --- Sources/PenguinGraphs/GraphCopying.swift | 24 ++++ .../PenguinGraphs/GraphTransformations.swift | 75 ++++++++++++ .../ConcatenatedCollection.swift | 113 ++++++++++++++++++ Sources/PenguinStructures/Either.swift | 50 ++++++++ .../ConcatenatedCollectionTests.swift | 44 +++++++ .../XCTestManifests.swift | 1 + 6 files changed, 307 insertions(+) create mode 100644 Sources/PenguinStructures/ConcatenatedCollection.swift create mode 100644 Sources/PenguinStructures/Either.swift create mode 100644 Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift diff --git a/Sources/PenguinGraphs/GraphCopying.swift b/Sources/PenguinGraphs/GraphCopying.swift index 5a37d683..5fee7009 100644 --- a/Sources/PenguinGraphs/GraphCopying.swift +++ b/Sources/PenguinGraphs/GraphCopying.swift @@ -165,3 +165,27 @@ extension MutablePropertyGraph where Self: DefaultInitializable { edgeProperties: InternalEdgePropertyMap(for: other)) } } + +extension IncidenceGraph where Self: MutableGraph & VertexListGraph { + /// Adds all edges from `other` into `self`. + public mutating func addEdges(from other: Other) + where Other.VertexId == VertexId { + for v in vertices { + for e in other.edges(from: v) { + _ = addEdge(from: v, to: other.destination(of: e)) + } + } + } +} + +extension IncidenceGraph where Self: MutablePropertyGraph & VertexListGraph { + /// Adds all edges from `other` into `self`. + public mutating func addEdges(from other: Other) + where Other.VertexId == VertexId, Other.Edge == Edge { + for v in vertices { + for e in other.edges(from: v) { + _ = addEdge(from: v, to: other.destination(of: e), storing: other[edge: e]) + } + } + } +} \ No newline at end of file diff --git a/Sources/PenguinGraphs/GraphTransformations.swift b/Sources/PenguinGraphs/GraphTransformations.swift index 602bbd59..349d4039 100644 --- a/Sources/PenguinGraphs/GraphTransformations.swift +++ b/Sources/PenguinGraphs/GraphTransformations.swift @@ -415,3 +415,78 @@ extension BidirectionalGraph { TransposeGraph(self) } } + +// TODO: consider generalizing to EdgeListGraph's. + +/// A graph containing all the vertices and edges of a base graph, augmented with all the edges of +/// a second graph data structure. +/// +/// A `UnionEdgesGraph` allows overlaying the edges of one graph onto the edges of another graph. +/// This operation can be useful when viewing the same set of vertices in multiple ways. The +/// `UnionEdgesGraph` does not modify or even copy either of the two underlying graphs, and all +/// operations on the `UnionEdgesGraph` occur with identical complexity to the underlying graphs' +/// operations. +/// +/// Note: If many operations will be done on the resulting graph, it can sometimes be more efficient +/// to flatten the extra edges into the base graph (so long as `Base` is a `MutableGraph`) by +/// calling `base.addEdges(from: extraEdges)`. +/// +/// Beware: it is up to the user to ensure that all edges in `ExtraEdges` point to valid vertices in +/// `Base`, and that `ExtraEdges.edges(from:)` can be called with every valid vertex in `Base`. +// public struct UnionEdgesGraph: IncidenceGraph +// where Base.VertexId == ExtraEdges.VertexId { +// /// The name of a vertex in `self`. +// public typealias VertexId = Base.VertexId +// /// The name of an edge in `self`. +// public enum EdgeId: Equatable, Comparable { +// case base(Base.EdgeId) +// case extra(ExtraEdges.EdgeId) + +// /// Returns true if `lhs` should be ordered before `rhs`. +// public static func < (lhs: Self, rhs: Self) -> Bool { +// switch (lhs, rhs) { +// case (.base(let lhs), .base(let rhs)): return lhs < rhs +// case (.base, _): return true +// case (.extra(let lhs), .extra(let rhs)): return lhs < rhs +// default: return false +// } +// } +// } + +// /// The base graph. +// private var base: Base +// /// A graph containing extra edges. +// private var extraEdges: ExtraEdges + +// /// Creates a graph based on `base`, augmented with edges in `extraEdges`. +// public init(_ base: Base, _ extraEdges: ExtraEdges) { +// self.base = base +// self.extraEdges = extraEdges +// } + +// /// A collection of edges from a single vertex. +// public struct VertexEdgeCollection: Collection { +// let base: Base.VertexEdgeCollection +// let extra: ExtraEdges.VertexEdgeCollection + +// public enum Index { + +// } + +// public var startIndex: +// } +// public func edges(from vertex: VertexId) -> VertexEdgeCollection { +// VertexEdgeCollection(base: base.edges(from: vertex), extra: extraEdges.edges(from: vertex)) +// } +// } + +// extension UnionEdgesGraph.EdgeId: Hashable +// where BaseGraph.EdgeId: Hashable, ExtraEdges.EdgeId: Hashable { +// /// Hashes `self` into `hasher`. +// public func hash(into hasher: inout Hasher) { +// switch self { +// case .base(let edge): edge.hash(into: &hasher) +// case .extra(let edge): edge.hash(into: &hasher) +// } +// } +// } \ No newline at end of file diff --git a/Sources/PenguinStructures/ConcatenatedCollection.swift b/Sources/PenguinStructures/ConcatenatedCollection.swift new file mode 100644 index 00000000..7f462ca6 --- /dev/null +++ b/Sources/PenguinStructures/ConcatenatedCollection.swift @@ -0,0 +1,113 @@ +// Copyright 2020 Penguin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/// A collection that is all the elements of one collection followed by all the elements of a second +/// collection. +public struct ConcatenatedCollection: Collection where +First.Element == Second.Element { + /// The elements in `self`. + public typealias Element = First.Element + /// The collection whose elements appear first. + private var first: First + /// The collection whose elements appear second. + private var second: Second + + /// Concatenates `first` with `second`. + public init(_ first: First, _ second: Second) { + self.first = first + self.second = second + } + + /// A handle into elements in `self`. + public enum Index: Equatable, Comparable { + case first(First.Index) + case second(Second.Index) + + /// Returns true if `lhs` should be ordered before `rhs`. + public static func < (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.first(let lhs), .first(let rhs)): return lhs < rhs + case (.first, _): return true + case (.second(let lhs), .second(let rhs)): return lhs < rhs + default: return false + } + } + } + + /// The first valid index into `self`. + public var startIndex: Index { .first(first.startIndex) } + /// One beyond the last valid index into `self`. + public var endIndex: Index { .second(second.endIndex) } + /// Returns the next valid index after `index`. + public func index(after index: Index) -> Index { + switch index { + case .first(let index): + let newIndex = first.index(after: index) + guard newIndex != first.endIndex else { return .second(second.startIndex) } + return .first(newIndex) + case .second(let index): + return .second(second.index(after: index)) + } + } + /// Accesses element at `index`. + public subscript(index: Index) -> Element { + switch index { + case .first(let index): return first[index] + case .second(let index): return second[index] + } + } + /// The number of elements in `self`. + public var count: Int { first.count + second.count } + /// True if `self` contains no elements. + public var isEmpty: Bool { first.isEmpty && second.isEmpty } +} + +extension ConcatenatedCollection.Index: Hashable where First.Index: Hashable, Second.Index: Hashable { + /// Hashes `self` into `hasher`. + public func hash(into hasher: inout Hasher) { + switch self { + case .first(let i): i.hash(into: &hasher) + case .second(let i): i.hash(into: &hasher) + } + } +} + +extension ConcatenatedCollection: BidirectionalCollection +where First: BidirectionalCollection, Second: BidirectionalCollection { + /// Returns the next valid index before `index`. + public func index(before index: Index) -> Index { + switch index { + case .first(let index): return .first(first.index(before: index)) + case .second(let index): + if index == second.startIndex { + return .first(first.index(before: first.endIndex)) + } + return .second(second.index(before: index)) + } + } +} + +// TODO: Add RandomAccessCollection conformance. +// TODO: Add MutableCollection conformance. + +extension Collection { + /// Returns a new collection where all the elements of `self` appear before all the elements of + /// `other`. + public func concatenated(with other: Other) + -> ConcatenatedCollection + where Other.Element == Element { + return ConcatenatedCollection(self, other) + } +} diff --git a/Sources/PenguinStructures/Either.swift b/Sources/PenguinStructures/Either.swift new file mode 100644 index 00000000..05ee99f0 --- /dev/null +++ b/Sources/PenguinStructures/Either.swift @@ -0,0 +1,50 @@ +// Copyright 2020 Penguin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Represents one of two possible choices. +public enum Either { + case a(A) + case b(B) +} + +extension Either: Equatable where A: Equatable, B: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.a(let lhs), .a(let rhs)): return lhs == rhs + case (.b(let lhs), .b(let rhs)): return lhs == rhs + default: return false + } + } +} + +extension Either: Comparable where A: Comparable, B: Comparable { + public static func < (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.a(let lhs), .a(let rhs)): return lhs < rhs + case (.a, _): return true + case (.b(let lhs), .b(let rhs)): return lhs < rhs + default: return false + } + } +} + + +extension Either: Hashable where A: Hashable, B: Hashable { + public func hash(into hasher: inout Hasher) { + switch self { + case .a(let a): a.hash(into: &hasher) + case .b(let b): b.hash(into: &hasher) + } + } +} diff --git a/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift b/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift new file mode 100644 index 00000000..12cbc0d2 --- /dev/null +++ b/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift @@ -0,0 +1,44 @@ +// Copyright 2020 Penguin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import PenguinStructures +import XCTest + +final class ConcatenatedCollectionTests: XCTestCase { + + func testConcatenateSetAndList() { + let c = Set(["1", "2", "3"]).concatenated(with: ["10", "11", "12"]) + XCTAssertFalse(c.isEmpty) + XCTAssertEqual(6, c.count) + XCTAssertEqual(Set(["1", "2", "3"]), Set(c.prefix(3))) + XCTAssertEqual(["10", "11", "12"], Array(c.suffix(3))) + } + + func testConcatenateRanges() { + let c = (0..<3).concatenated(with: 3...6) + XCTAssertEqual(Array(0...6), Array(c)) + } + + func testConcatenateBidirectionalOperations() { + let c = (0..<3).concatenated(with: [10, 11, 12]) + XCTAssertEqual(6, c.count) + XCTAssertEqual([12, 11, 10, 2, 1, 0], Array(c.reversed())) + } + + static var allTests = [ + ("testConcatenateSetAndList", testConcatenateSetAndList), + ("testConcatenateRanges", testConcatenateRanges), + ("testConcatenateBidirectionalOperations", testConcatenateBidirectionalOperations), + ] +} diff --git a/Tests/PenguinStructuresTests/XCTestManifests.swift b/Tests/PenguinStructuresTests/XCTestManifests.swift index 8d1aae2d..c45ed78b 100644 --- a/Tests/PenguinStructuresTests/XCTestManifests.swift +++ b/Tests/PenguinStructuresTests/XCTestManifests.swift @@ -23,6 +23,7 @@ import XCTest testCase(ArrayStorageExtensionTests.allTests), testCase(ArrayStorageTests.allTests), testCase(CollectionAlgorithmTests.allTests), + testCase(ConcatenatedCollectionTests.allTests), testCase(DequeTests.allTests), testCase(DoubleEndedBufferTests.allTests), testCase(FactoryInitializableTests.allTests), From 1c3882ac40c16ce08b637fb4768787fda3ac3c05 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Mon, 15 Jun 2020 13:24:36 -0700 Subject: [PATCH 02/15] Augment `addEdges` to take a callback and add tests. --- Sources/PenguinGraphs/GraphCopying.swift | 43 +++++++++++++++---- .../PenguinGraphTests/GraphCopyingTests.swift | 31 +++++++++++++ 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/Sources/PenguinGraphs/GraphCopying.swift b/Sources/PenguinGraphs/GraphCopying.swift index 5fee7009..e949d289 100644 --- a/Sources/PenguinGraphs/GraphCopying.swift +++ b/Sources/PenguinGraphs/GraphCopying.swift @@ -167,12 +167,15 @@ extension MutablePropertyGraph where Self: DefaultInitializable { } extension IncidenceGraph where Self: MutableGraph & VertexListGraph { - /// Adds all edges from `other` into `self`. - public mutating func addEdges(from other: Other) - where Other.VertexId == VertexId { + /// Adds all edges from `other` into `self`, calling `edgeCreationListener` with every new EdgeId. + public mutating func addEdges( + from other: Other, + _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } + ) where Other.VertexId == VertexId { for v in vertices { for e in other.edges(from: v) { - _ = addEdge(from: v, to: other.destination(of: e)) + let edgeId = addEdge(from: v, to: other.destination(of: e)) + edgeCreationListener(edgeId, &self) } } } @@ -180,12 +183,36 @@ extension IncidenceGraph where Self: MutableGraph & VertexListGraph { extension IncidenceGraph where Self: MutablePropertyGraph & VertexListGraph { /// Adds all edges from `other` into `self`. - public mutating func addEdges(from other: Other) - where Other.VertexId == VertexId, Other.Edge == Edge { + public mutating func addEdges( + from other: Other, + _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } + ) where Other.VertexId == VertexId, Other.Edge == Edge { + addEdges(from: other, storing: InternalEdgePropertyMap(for: other), edgeCreationListener) + } + + /// Adds all edges from `other` into `self` storing the corresponding edge property from + /// `edgeProperties`. + public mutating func addEdges< + Other: IncidenceGraph, + EdgeProperties: PropertyMap + >( + from other: Other, + storing edgeProperties: EdgeProperties, + _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } + ) where + Other.VertexId == VertexId, + EdgeProperties.Graph == Other, + EdgeProperties.Key == Other.EdgeId, + EdgeProperties.Value == Edge + { for v in vertices { for e in other.edges(from: v) { - _ = addEdge(from: v, to: other.destination(of: e), storing: other[edge: e]) + let edgeId = addEdge( + from: v, + to: other.destination(of: e), + storing: edgeProperties.get(e, in: other)) + edgeCreationListener(edgeId, &self) } } } -} \ No newline at end of file +} diff --git a/Tests/PenguinGraphTests/GraphCopyingTests.swift b/Tests/PenguinGraphTests/GraphCopyingTests.swift index e7b57af5..d8054ff3 100644 --- a/Tests/PenguinGraphTests/GraphCopyingTests.swift +++ b/Tests/PenguinGraphTests/GraphCopyingTests.swift @@ -111,9 +111,40 @@ final class GraphCopyingTests: XCTestCase { XCTAssertEqual(2, dst.inDegree(of: 2)) } + func testAddingEdges() { + var src = DirectedAdjacencyList() + _ = src.addVertex() + _ = src.addVertex() + _ = src.addVertex() + + _ = src.addEdge(from: 1, to: 2, storing: "1->2 (src)") + + var dst = UndirectedAdjacencyList() + _ = dst.addVertex() + _ = dst.addVertex() + _ = dst.addVertex() + + _ = dst.addEdge(from: 0, to: 1, storing: "0->1 (dst)") + var edgeCallback = false + dst.addEdges(from: src) { e, g in + XCTAssertFalse(edgeCallback) + edgeCallback = true + XCTAssertEqual(1, g.source(of: e)) + XCTAssertEqual(2, g.destination(of: e)) + } + XCTAssert(edgeCallback) + + let edges = dst.edges(from: 2) + XCTAssertEqual(1, edges.count) + XCTAssertEqual(2, dst.source(of: edges[0])) + XCTAssertEqual(1, dst.destination(of: edges[0])) + XCTAssertEqual("1->2 (src)", dst[edge: edges[0]]) + } + static var allTests = [ ("testUndirectedCopying", testUndirectedCopying), ("testProperyGraphCopying", testProperyGraphCopying), ("testProperyGraphCopyingBidirectional", testProperyGraphCopyingBidirectional), + ("testAddingEdges", testAddingEdges), ] } From 055d23f445a04353991ace91a8179d342203ebfa Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Mon, 15 Jun 2020 14:53:34 -0700 Subject: [PATCH 03/15] Add either tests. --- .../PenguinStructuresTests/EitherTests.swift | 39 +++++++++++++++++++ .../XCTestManifests.swift | 1 + 2 files changed, 40 insertions(+) create mode 100644 Tests/PenguinStructuresTests/EitherTests.swift diff --git a/Tests/PenguinStructuresTests/EitherTests.swift b/Tests/PenguinStructuresTests/EitherTests.swift new file mode 100644 index 00000000..9a5654b4 --- /dev/null +++ b/Tests/PenguinStructuresTests/EitherTests.swift @@ -0,0 +1,39 @@ +//****************************************************************************** +// Copyright 2020 Penguin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import XCTest +import PenguinStructures + + +class EitherTests: XCTestCase { + + func testEquality() { + XCTAssertEqual(Either.a(1), .a(1)) + XCTAssertNotEqual(Either.a(1), .b(1)) + XCTAssertNotEqual(Either.a(1), .b("1")) + XCTAssertEqual(Either.b("0"), .b("0")) + } + + func testComparable() { + typealias E = Either + XCTAssert(E.a(1) < .b(0)) + XCTAssert(E.b(0) > .a(100000)) + } + + static var allTests = [ + ("testEquality", testEquality), + ("testComparable", testComparable), + ] +} diff --git a/Tests/PenguinStructuresTests/XCTestManifests.swift b/Tests/PenguinStructuresTests/XCTestManifests.swift index c45ed78b..4436f429 100644 --- a/Tests/PenguinStructuresTests/XCTestManifests.swift +++ b/Tests/PenguinStructuresTests/XCTestManifests.swift @@ -26,6 +26,7 @@ import XCTest testCase(ConcatenatedCollectionTests.allTests), testCase(DequeTests.allTests), testCase(DoubleEndedBufferTests.allTests), + testCase(EitherTests.allTests), testCase(FactoryInitializableTests.allTests), testCase(FixedSizeArrayTests.allTests), testCase(TupleTests.allTests), From d7580c830857338664754a0d8d24afb92f1a1b64 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Mon, 15 Jun 2020 17:01:04 -0700 Subject: [PATCH 04/15] Implement `UnionEdgeGraph` which supports combining the edges of two graphs together without making a copy. Also along with this change includes: 1. A collection which concatenates two heterogeneous collections, so long as their element types are identical. 2. DefaultInitializable conformances for a variety of fixed-width numerical types. 3. An implementation of `Either` which is used to implement `UnionEdgeGraph` as well as `ConcatenatedCollection`. 4. Tests for the above. Note: The `UnionEdgeGraph` demonstrates why an `Either` type is necessary (assuming we want to avoid duplicating the implementation of ConcatenatedCollection across both the `VertexEdgeCollection` and `VertexInEdgeCollection` types). --- .../PenguinGraphs/GraphTransformations.swift | 251 ++++++++++++++---- .../ConcatenatedCollection.swift | 54 ++-- .../DefaultInitializable.swift | 6 + Sources/PenguinStructures/Either.swift | 5 +- .../GraphTransformationsTests.swift | 48 ++++ Tests/PenguinGraphTests/XCTestManifests.swift | 1 + .../ConcatenatedCollectionTests.swift | 7 + 7 files changed, 275 insertions(+), 97 deletions(-) create mode 100644 Tests/PenguinGraphTests/GraphTransformationsTests.swift diff --git a/Sources/PenguinGraphs/GraphTransformations.swift b/Sources/PenguinGraphs/GraphTransformations.swift index 349d4039..eae1be28 100644 --- a/Sources/PenguinGraphs/GraphTransformations.swift +++ b/Sources/PenguinGraphs/GraphTransformations.swift @@ -416,8 +416,6 @@ extension BidirectionalGraph { } } -// TODO: consider generalizing to EdgeListGraph's. - /// A graph containing all the vertices and edges of a base graph, augmented with all the edges of /// a second graph data structure. /// @@ -433,60 +431,195 @@ extension BidirectionalGraph { /// /// Beware: it is up to the user to ensure that all edges in `ExtraEdges` point to valid vertices in /// `Base`, and that `ExtraEdges.edges(from:)` can be called with every valid vertex in `Base`. -// public struct UnionEdgesGraph: IncidenceGraph -// where Base.VertexId == ExtraEdges.VertexId { -// /// The name of a vertex in `self`. -// public typealias VertexId = Base.VertexId -// /// The name of an edge in `self`. -// public enum EdgeId: Equatable, Comparable { -// case base(Base.EdgeId) -// case extra(ExtraEdges.EdgeId) - -// /// Returns true if `lhs` should be ordered before `rhs`. -// public static func < (lhs: Self, rhs: Self) -> Bool { -// switch (lhs, rhs) { -// case (.base(let lhs), .base(let rhs)): return lhs < rhs -// case (.base, _): return true -// case (.extra(let lhs), .extra(let rhs)): return lhs < rhs -// default: return false -// } -// } -// } - -// /// The base graph. -// private var base: Base -// /// A graph containing extra edges. -// private var extraEdges: ExtraEdges - -// /// Creates a graph based on `base`, augmented with edges in `extraEdges`. -// public init(_ base: Base, _ extraEdges: ExtraEdges) { -// self.base = base -// self.extraEdges = extraEdges -// } - -// /// A collection of edges from a single vertex. -// public struct VertexEdgeCollection: Collection { -// let base: Base.VertexEdgeCollection -// let extra: ExtraEdges.VertexEdgeCollection - -// public enum Index { - -// } - -// public var startIndex: -// } -// public func edges(from vertex: VertexId) -> VertexEdgeCollection { -// VertexEdgeCollection(base: base.edges(from: vertex), extra: extraEdges.edges(from: vertex)) -// } -// } - -// extension UnionEdgesGraph.EdgeId: Hashable -// where BaseGraph.EdgeId: Hashable, ExtraEdges.EdgeId: Hashable { -// /// Hashes `self` into `hasher`. -// public func hash(into hasher: inout Hasher) { -// switch self { -// case .base(let edge): edge.hash(into: &hasher) -// case .extra(let edge): edge.hash(into: &hasher) -// } -// } -// } \ No newline at end of file +/// +/// - SeeAlso: `UnionEdgesGraphVertexPropertyMapAdapter`. +public struct UnionEdgesGraph: IncidenceGraph +where Base.VertexId == ExtraEdges.VertexId { + /// The name of a vertex in `self`. + public typealias VertexId = Base.VertexId + /// The name of a vertex in `self`. + public typealias EdgeId = Either + + /// The base graph. + fileprivate var base: Base + /// A graph containing extra edges. + fileprivate var extraEdges: ExtraEdges + + /// Creates a graph based on `base`, augmented with edges in `extraEdges`. + public init(_ base: Base, _ extraEdges: ExtraEdges) { + self.base = base + self.extraEdges = extraEdges + } + + /// A collection of edges from a single vertex in `self`. + public typealias VertexEdgeCollection = + ConcatenatedCollection< + LazyMapCollection, + LazyMapCollection> + // public typealias VertexEdgeCollection = + // ConcatenatedCollection<[EdgeId], [EdgeId]> + + /// A collection of edges from a single vertex. + public func edges(from vertex: VertexId) -> VertexEdgeCollection { + base.edges(from: vertex).lazy.map(EdgeId.a).concatenated(with: + extraEdges.edges(from: vertex).lazy.map(EdgeId.b)) + } + + /// Returns the source vertex of `edge`. + public func source(of edge: EdgeId) -> VertexId { + switch edge { + case .a(let edge): return base.source(of: edge) + case .b(let edge): return extraEdges.source(of: edge) + } + } + + /// Returns the destionation vertex of `edge`. + public func destination(of edge: EdgeId) -> VertexId { + switch edge { + case .a(let edge): return base.destination(of: edge) + case .b(let edge): return extraEdges.destination(of: edge) + } + } +} + +extension UnionEdgesGraph: BidirectionalGraph where Base: BidirectionalGraph, ExtraEdges: BidirectionalGraph { + /// A collection of edges to a vertex. + public typealias VertexInEdgeCollection = ConcatenatedCollection< + LazyMapCollection, + LazyMapCollection> + + /// Returns a collection of all edges in `self` whose destination is `vertex`. + public func edges(to vertex: VertexId) -> VertexInEdgeCollection { + base.edges(to: vertex).lazy.map(EdgeId.a).concatenated(with: + extraEdges.edges(to: vertex).lazy.map(EdgeId.b)) + } +} + +extension UnionEdgesGraph: PropertyGraph +where Base: PropertyGraph, ExtraEdges: PropertyGraph, ExtraEdges.Edge == Base.Edge { + /// Arbitrary data asssociated with every vertex. + public typealias Vertex = Base.Vertex + + /// Arbitrary data associated with every edge. + public typealias Edge = Base.Edge + + /// Accesses the data associated with `vertex`. + public subscript(vertex vertex: VertexId) -> Vertex { + get { base[vertex: vertex] } + _modify { yield &base[vertex: vertex] } + } + + /// Access the data associated with `edge`. + public subscript(edge edge: EdgeId) -> Edge { + get { + switch edge { + case .a(let edge): return base[edge: edge] + case .b(let edge): return extraEdges[edge: edge] + } + } + + _modify { + switch edge { + case .a(let edge): yield &base[edge: edge] + case .b(let edge): yield &extraEdges[edge: edge] + } + } + } +} + +extension UnionEdgesGraph: VertexListGraph where Base: VertexListGraph { + /// The number of vertices in `self`. + public var vertexCount: Int { base.vertexCount } + /// The collection of all vertices in `self`. + public var vertices: Base.VertexCollection { base.vertices } +} + +extension UnionEdgesGraph: EdgeListGraph where Base: EdgeListGraph, ExtraEdges: EdgeListGraph { + public typealias EdgeCollection = ConcatenatedCollection< + LazyMapCollection, + LazyMapCollection> + + /// All edges in `self`. + public var edges: EdgeCollection { + base.edges.lazy.map(EdgeId.a).concatenated(with: extraEdges.edges.lazy.map(EdgeId.b)) + } +} + +extension UnionEdgesGraph: SearchDefaultsGraph where Base: SearchDefaultsGraph { + /// Makes a default color map where every vertex is set to `color`. + public func makeDefaultColorMap(repeating color: VertexColor) -> UnionEdgesGraphVertexPropertyMapAdapter { + UnionEdgesGraphVertexPropertyMapAdapter(base.makeDefaultColorMap(repeating: color)) + } + + /// Makes a default int map for every vertex. + public func makeDefaultVertexIntMap(repeating value: Int) -> UnionEdgesGraphVertexPropertyMapAdapter { + UnionEdgesGraphVertexPropertyMapAdapter(base.makeDefaultVertexIntMap(repeating: value)) + } + + /// Makes a default vertex property map mapping vertices. + public func makeDefaultVertexVertexMap(repeating vertex: VertexId) -> UnionEdgesGraphVertexPropertyMapAdapter { + UnionEdgesGraphVertexPropertyMapAdapter(base.makeDefaultVertexVertexMap(repeating: vertex)) + } +} + +/// Adapts a property map for a graph to be used with the graph unioned with extra edges. +/// +/// - SeeAlso: `UnionEdgesGraph` +public struct UnionEdgesGraphVertexPropertyMapAdapter< + Underlying: PropertyMap, + ExtraEdges: IncidenceGraph +>: PropertyMap +where + Underlying.Graph: IncidenceGraph, + Underlying.Graph.VertexId == ExtraEdges.VertexId, + Underlying.Key == Underlying.Graph.VertexId +{ + /// The graph this property map operates upon. + public typealias Graph = UnionEdgesGraph + /// The identifier used to access data. + public typealias Key = Graph.VertexId + /// The value of data stored in `self`. + public typealias Value = Underlying.Value + + /// The underlying property map. + private var underlying: Underlying + + /// Wraps `underlying` for use with a transposed version of its graph. + public init(_ underlying: Underlying) { + self.underlying = underlying + } + + /// Wraps `underlying` for use with `graph`. (`graph` is taken to help type inference along.) + public init(_ underlying: Underlying, for graph: __shared Graph) { + self.init(underlying) + } + + /// Retrieves the property value for `key` in `graph`. + public func get(_ key: Key, in graph: Graph) -> Value { + underlying.get(key, in: graph.base) + } + + /// Sets the property `newValue` for `key` in `graph`. + public mutating func set(_ key: Key, in graph: inout Graph, to newValue: Value) { + underlying.set(key, in: &graph.base, to: newValue) + } +} + +extension UnionEdgesGraphVertexPropertyMapAdapter: ExternalPropertyMap where Underlying: ExternalPropertyMap { + /// Accesses the `Value` for a given `Key`. + public subscript(key: Key) -> Value { + get { underlying[key] } + set { underlying[key] = newValue } + } +} + +extension IncidenceGraph { + /// Returns a new graph containing all the vertices in `self`, augmented with edges from `other`. + /// + /// - Complexity: O(1) + public func unionEdges(with other: Other) -> UnionEdgesGraph + where Other.VertexId == VertexId + { + UnionEdgesGraph(self, other) + } +} \ No newline at end of file diff --git a/Sources/PenguinStructures/ConcatenatedCollection.swift b/Sources/PenguinStructures/ConcatenatedCollection.swift index 7f462ca6..56dc76e1 100644 --- a/Sources/PenguinStructures/ConcatenatedCollection.swift +++ b/Sources/PenguinStructures/ConcatenatedCollection.swift @@ -31,41 +31,31 @@ First.Element == Second.Element { } /// A handle into elements in `self`. - public enum Index: Equatable, Comparable { - case first(First.Index) - case second(Second.Index) - - /// Returns true if `lhs` should be ordered before `rhs`. - public static func < (lhs: Self, rhs: Self) -> Bool { - switch (lhs, rhs) { - case (.first(let lhs), .first(let rhs)): return lhs < rhs - case (.first, _): return true - case (.second(let lhs), .second(let rhs)): return lhs < rhs - default: return false - } - } - } + public typealias Index = Either /// The first valid index into `self`. - public var startIndex: Index { .first(first.startIndex) } + public var startIndex: Index { + if first.startIndex != first.endIndex { return .a(first.startIndex) } + return .b(second.startIndex) + } /// One beyond the last valid index into `self`. - public var endIndex: Index { .second(second.endIndex) } + public var endIndex: Index { .b(second.endIndex) } /// Returns the next valid index after `index`. public func index(after index: Index) -> Index { switch index { - case .first(let index): + case .a(let index): let newIndex = first.index(after: index) - guard newIndex != first.endIndex else { return .second(second.startIndex) } - return .first(newIndex) - case .second(let index): - return .second(second.index(after: index)) + guard newIndex != first.endIndex else { return .b(second.startIndex) } + return .a(newIndex) + case .b(let index): + return .b(second.index(after: index)) } } /// Accesses element at `index`. public subscript(index: Index) -> Element { switch index { - case .first(let index): return first[index] - case .second(let index): return second[index] + case .a(let index): return first[index] + case .b(let index): return second[index] } } /// The number of elements in `self`. @@ -74,27 +64,17 @@ First.Element == Second.Element { public var isEmpty: Bool { first.isEmpty && second.isEmpty } } -extension ConcatenatedCollection.Index: Hashable where First.Index: Hashable, Second.Index: Hashable { - /// Hashes `self` into `hasher`. - public func hash(into hasher: inout Hasher) { - switch self { - case .first(let i): i.hash(into: &hasher) - case .second(let i): i.hash(into: &hasher) - } - } -} - extension ConcatenatedCollection: BidirectionalCollection where First: BidirectionalCollection, Second: BidirectionalCollection { /// Returns the next valid index before `index`. public func index(before index: Index) -> Index { switch index { - case .first(let index): return .first(first.index(before: index)) - case .second(let index): + case .a(let index): return .a(first.index(before: index)) + case .b(let index): if index == second.startIndex { - return .first(first.index(before: first.endIndex)) + return .a(first.index(before: first.endIndex)) } - return .second(second.index(before: index)) + return .b(second.index(before: index)) } } } diff --git a/Sources/PenguinStructures/DefaultInitializable.swift b/Sources/PenguinStructures/DefaultInitializable.swift index 3a3c769a..e9ea43d9 100644 --- a/Sources/PenguinStructures/DefaultInitializable.swift +++ b/Sources/PenguinStructures/DefaultInitializable.swift @@ -21,3 +21,9 @@ public protocol DefaultInitializable { extension Array: DefaultInitializable {} extension Dictionary: DefaultInitializable {} extension String: DefaultInitializable {} +extension Int: DefaultInitializable {} +extension UInt: DefaultInitializable {} +extension Int32: DefaultInitializable {} +extension UInt32: DefaultInitializable {} +extension Float: DefaultInitializable {} +extension Double: DefaultInitializable {} diff --git a/Sources/PenguinStructures/Either.swift b/Sources/PenguinStructures/Either.swift index 05ee99f0..10bacc93 100644 --- a/Sources/PenguinStructures/Either.swift +++ b/Sources/PenguinStructures/Either.swift @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Represents one of two possible choices. +/// Represents one of two possible cases. public enum Either { case a(A) case b(B) } extension Either: Equatable where A: Equatable, B: Equatable { + /// True if `lhs` is equivalent to `rhs`. public static func == (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { case (.a(let lhs), .a(let rhs)): return lhs == rhs @@ -29,6 +30,7 @@ extension Either: Equatable where A: Equatable, B: Equatable { } extension Either: Comparable where A: Comparable, B: Comparable { + /// True iff `lhs` is less than `rhs`. public static func < (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { case (.a(let lhs), .a(let rhs)): return lhs < rhs @@ -41,6 +43,7 @@ extension Either: Comparable where A: Comparable, B: Comparable { extension Either: Hashable where A: Hashable, B: Hashable { + /// Hashes `self` into `hasher`. public func hash(into hasher: inout Hasher) { switch self { case .a(let a): a.hash(into: &hasher) diff --git a/Tests/PenguinGraphTests/GraphTransformationsTests.swift b/Tests/PenguinGraphTests/GraphTransformationsTests.swift new file mode 100644 index 00000000..eae8c204 --- /dev/null +++ b/Tests/PenguinGraphTests/GraphTransformationsTests.swift @@ -0,0 +1,48 @@ +// Copyright 2020 Penguin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import PenguinStructures +import PenguinGraphs +import XCTest + +final class GraphTransformationsTests: XCTestCase { + + func testUnionEdges() { + var g1 = BidirectionalAdjacencyList() + _ = g1.addVertex(storing: 10) + _ = g1.addVertex(storing: 11) + _ = g1.addVertex(storing: 12) + _ = g1.addEdge(from: 0, to: 1, storing: "0->1 (g1)") + + var g2 = BidirectionalAdjacencyList() + _ = g2.addVertex() + _ = g2.addVertex() + _ = g2.addVertex() + _ = g2.addEdge(from: 1, to: 2, storing: "1->2 (g2)") + + var g = g1.unionEdges(with: g2) + + XCTAssertEqual(3, g.vertexCount) + XCTAssertEqual(Array(0..<3), Array(g.vertices)) + XCTAssertEqual(Array(10..<13), g.vertices.map { g[vertex: $0] }) + + var recorder = TablePredecessorRecorder(for: g) + g.breadthFirstSearch(startingAt: 0) { recorder.record($0, graph: $1) } + XCTAssertEqual([0, 1, 2], Array(recorder.path(to: 2)!)) + } + + static var allTests = [ + ("testUnionEdges", testUnionEdges), + ] +} diff --git a/Tests/PenguinGraphTests/XCTestManifests.swift b/Tests/PenguinGraphTests/XCTestManifests.swift index 6c25b412..f8c7e81c 100644 --- a/Tests/PenguinGraphTests/XCTestManifests.swift +++ b/Tests/PenguinGraphTests/XCTestManifests.swift @@ -26,6 +26,7 @@ import XCTest testCase(EdgeInfoTests.allTests), testCase(GraphCopyingTests.allTests), testCase(GraphGeneratorsTests.allTests), + testCase(GraphTransformationsTests.allTests), testCase(InfiniteGridTests.allTests), testCase(InternalPropertyMapTests.allTests), testCase(ParallelExpanderTests.allTests), diff --git a/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift b/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift index 12cbc0d2..ee58b7e3 100644 --- a/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift +++ b/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift @@ -36,9 +36,16 @@ final class ConcatenatedCollectionTests: XCTestCase { XCTAssertEqual([12, 11, 10, 2, 1, 0], Array(c.reversed())) } + func testConcatenatingEmpty() { + let c = (0..<0).concatenated(with: [1, 2, 3]) + XCTAssertEqual(3, c.count) + XCTAssertEqual([1, 2, 3], Array(c)) + } + static var allTests = [ ("testConcatenateSetAndList", testConcatenateSetAndList), ("testConcatenateRanges", testConcatenateRanges), ("testConcatenateBidirectionalOperations", testConcatenateBidirectionalOperations), + ("testConcatenatingEmpty", testConcatenatingEmpty), ] } From 78a0787c8a924fb76403015ca83344fae31e6523 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Mon, 15 Jun 2020 17:26:06 -0700 Subject: [PATCH 05/15] Cleanups. --- Sources/PenguinGraphs/GraphTransformations.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/PenguinGraphs/GraphTransformations.swift b/Sources/PenguinGraphs/GraphTransformations.swift index eae1be28..450e9389 100644 --- a/Sources/PenguinGraphs/GraphTransformations.swift +++ b/Sources/PenguinGraphs/GraphTransformations.swift @@ -456,8 +456,6 @@ where Base.VertexId == ExtraEdges.VertexId { ConcatenatedCollection< LazyMapCollection, LazyMapCollection> - // public typealias VertexEdgeCollection = - // ConcatenatedCollection<[EdgeId], [EdgeId]> /// A collection of edges from a single vertex. public func edges(from vertex: VertexId) -> VertexEdgeCollection { @@ -622,4 +620,4 @@ extension IncidenceGraph { { UnionEdgesGraph(self, other) } -} \ No newline at end of file +} From 303fcc6fd8163324ed9d0ee58d7a87becfb17143 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Fri, 19 Jun 2020 13:02:14 -0700 Subject: [PATCH 06/15] WIP: Respond to comments --- Sources/PenguinGraphs/GraphCopying.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/PenguinGraphs/GraphCopying.swift b/Sources/PenguinGraphs/GraphCopying.swift index e949d289..e7ac945d 100644 --- a/Sources/PenguinGraphs/GraphCopying.swift +++ b/Sources/PenguinGraphs/GraphCopying.swift @@ -168,6 +168,8 @@ extension MutablePropertyGraph where Self: DefaultInitializable { extension IncidenceGraph where Self: MutableGraph & VertexListGraph { /// Adds all edges from `other` into `self`, calling `edgeCreationListener` with every new EdgeId. + /// + /// - Precondition: (not checked) all of `other`'s VertexId's must be valid VertexId's in `self`. public mutating func addEdges( from other: Other, _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } @@ -183,6 +185,8 @@ extension IncidenceGraph where Self: MutableGraph & VertexListGraph { extension IncidenceGraph where Self: MutablePropertyGraph & VertexListGraph { /// Adds all edges from `other` into `self`. + /// + /// - Precondition: (not checked) all of `other`'s VertexId's must be valid VertexId's in `self`. public mutating func addEdges( from other: Other, _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } @@ -192,6 +196,8 @@ extension IncidenceGraph where Self: MutablePropertyGraph & VertexListGraph { /// Adds all edges from `other` into `self` storing the corresponding edge property from /// `edgeProperties`. + /// + /// - Precondition: (not checked) all of `other`'s VertexId's must be valid VertexId's in `self`. public mutating func addEdges< Other: IncidenceGraph, EdgeProperties: PropertyMap From 3489977a64c58b0240c8f31093b1c42a2aaa4f13 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Sat, 20 Jun 2020 15:03:31 -0700 Subject: [PATCH 07/15] Update Sources/PenguinStructures/ConcatenatedCollection.swift Co-authored-by: Dave Abrahams --- Sources/PenguinStructures/ConcatenatedCollection.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PenguinStructures/ConcatenatedCollection.swift b/Sources/PenguinStructures/ConcatenatedCollection.swift index 56dc76e1..bfa30eed 100644 --- a/Sources/PenguinStructures/ConcatenatedCollection.swift +++ b/Sources/PenguinStructures/ConcatenatedCollection.swift @@ -33,7 +33,7 @@ First.Element == Second.Element { /// A handle into elements in `self`. public typealias Index = Either - /// The first valid index into `self`. + /// The position of the first element, or `endIndex` if `self.isEmpty` public var startIndex: Index { if first.startIndex != first.endIndex { return .a(first.startIndex) } return .b(second.startIndex) From bbe3d2e2aee58f2eb0fe72a958c670ef6c021470 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Sat, 20 Jun 2020 15:05:59 -0700 Subject: [PATCH 08/15] Apply suggestions from code review Co-authored-by: Dave Abrahams --- Sources/PenguinStructures/ConcatenatedCollection.swift | 6 +++--- Sources/PenguinStructures/Either.swift | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/PenguinStructures/ConcatenatedCollection.swift b/Sources/PenguinStructures/ConcatenatedCollection.swift index bfa30eed..93904d14 100644 --- a/Sources/PenguinStructures/ConcatenatedCollection.swift +++ b/Sources/PenguinStructures/ConcatenatedCollection.swift @@ -51,8 +51,8 @@ First.Element == Second.Element { return .b(second.index(after: index)) } } - /// Accesses element at `index`. - public subscript(index: Index) -> Element { + /// Accesses the element at `i`. + public subscript(i: Index) -> Element { switch index { case .a(let index): return first[index] case .b(let index): return second[index] @@ -60,7 +60,7 @@ First.Element == Second.Element { } /// The number of elements in `self`. public var count: Int { first.count + second.count } - /// True if `self` contains no elements. + /// True iff `self` contains no elements. public var isEmpty: Bool { first.isEmpty && second.isEmpty } } diff --git a/Sources/PenguinStructures/Either.swift b/Sources/PenguinStructures/Either.swift index 10bacc93..10890b0e 100644 --- a/Sources/PenguinStructures/Either.swift +++ b/Sources/PenguinStructures/Either.swift @@ -16,6 +16,10 @@ public enum Either { case a(A) case b(B) + var a: A? { if case .a(let x) = self { return x } else {return nil} } + var b: B? { if case .b(let x) = self { return x } else {return nil} } + init(_ x: A) { self = .a(x) } + init(_ x: B) { self = .b(x) } } extension Either: Equatable where A: Equatable, B: Equatable { From 1240d2b9a14bd1e96fb7a58ab8d2a9915c44b1cc Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Sat, 20 Jun 2020 15:06:16 -0700 Subject: [PATCH 09/15] Make mapping extensible, remove UnionEdges graph combinator. --- Sources/PenguinGraphs/GraphCopying.swift | 57 +++-- .../PenguinGraphs/GraphTransformations.swift | 206 ------------------ 2 files changed, 38 insertions(+), 225 deletions(-) diff --git a/Sources/PenguinGraphs/GraphCopying.swift b/Sources/PenguinGraphs/GraphCopying.swift index e7ac945d..feaede47 100644 --- a/Sources/PenguinGraphs/GraphCopying.swift +++ b/Sources/PenguinGraphs/GraphCopying.swift @@ -166,44 +166,61 @@ extension MutablePropertyGraph where Self: DefaultInitializable { } } -extension IncidenceGraph where Self: MutableGraph & VertexListGraph { - /// Adds all edges from `other` into `self`, calling `edgeCreationListener` with every new EdgeId. - /// - /// - Precondition: (not checked) all of `other`'s VertexId's must be valid VertexId's in `self`. - public mutating func addEdges( +extension IncidenceGraph where Self: MutableGraph { + /// Adds all edges from `other` into `self`, using `vertexMapper` to map vertices, and calling + /// `edgeCreationListener` with every new EdgeId. + public mutating func addEdges( from other: Other, + mappingVertices vertexMapper: (Other, Other.VertexId, Self) -> VertexId, _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } - ) where Other.VertexId == VertexId { - for v in vertices { - for e in other.edges(from: v) { - let edgeId = addEdge(from: v, to: other.destination(of: e)) + ) { + for vSrc in other.vertices { + let v = vertexMapper(other, vSrc, self) + for e in other.edges(from: vSrc) { + let d = vertexMapper(other, other.destination(of: e), self) + let edgeId = addEdge(from: v, to: d) edgeCreationListener(edgeId, &self) } } } + + /// Adds all edges from `other` into `self`, and calling `edgeCreationListener` with every new + /// EdgeId. + /// + /// - Precondition: (not checked) all of `other`'s VertexId's must be valid VertexId's in `self`. + public mutating func addEdges( + from other: Other, + _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } + ) where Other.VertexId == VertexId { + addEdges(from: other, mappingVertices: { _, v, _ in v }, edgeCreationListener) + } } extension IncidenceGraph where Self: MutablePropertyGraph & VertexListGraph { - /// Adds all edges from `other` into `self`. + /// Adds all edges from `other` into `self`, calling `edgeCreationListener` with every new + /// `EdgeId`. /// - /// - Precondition: (not checked) all of `other`'s VertexId's must be valid VertexId's in `self`. - public mutating func addEdges( + /// - Precondition: all of `other`'s VertexId's must be valid VertexId's in `self`. + public mutating func addEdges( from other: Other, _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } ) where Other.VertexId == VertexId, Other.Edge == Edge { - addEdges(from: other, storing: InternalEdgePropertyMap(for: other), edgeCreationListener) + addEdges( + from: other, + storing: InternalEdgePropertyMap(for: other), + mappingVertices: { _, v, _ in v }, + edgeCreationListener) } /// Adds all edges from `other` into `self` storing the corresponding edge property from /// `edgeProperties`. - /// - /// - Precondition: (not checked) all of `other`'s VertexId's must be valid VertexId's in `self`. public mutating func addEdges< - Other: IncidenceGraph, + Other: IncidenceGraph & VertexListGraph, EdgeProperties: PropertyMap >( from other: Other, storing edgeProperties: EdgeProperties, + mappingVertices vertexMapper: (Other, Other.VertexId, Self) -> VertexId, _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } ) where Other.VertexId == VertexId, @@ -211,11 +228,13 @@ extension IncidenceGraph where Self: MutablePropertyGraph & VertexListGraph { EdgeProperties.Key == Other.EdgeId, EdgeProperties.Value == Edge { - for v in vertices { - for e in other.edges(from: v) { + for vSrc in other.vertices { + let v = vertexMapper(other, vSrc, self) + for e in other.edges(from: vSrc) { + let d = vertexMapper(other, other.destination(of: e), self) let edgeId = addEdge( from: v, - to: other.destination(of: e), + to: d, storing: edgeProperties.get(e, in: other)) edgeCreationListener(edgeId, &self) } diff --git a/Sources/PenguinGraphs/GraphTransformations.swift b/Sources/PenguinGraphs/GraphTransformations.swift index 450e9389..602bbd59 100644 --- a/Sources/PenguinGraphs/GraphTransformations.swift +++ b/Sources/PenguinGraphs/GraphTransformations.swift @@ -415,209 +415,3 @@ extension BidirectionalGraph { TransposeGraph(self) } } - -/// A graph containing all the vertices and edges of a base graph, augmented with all the edges of -/// a second graph data structure. -/// -/// A `UnionEdgesGraph` allows overlaying the edges of one graph onto the edges of another graph. -/// This operation can be useful when viewing the same set of vertices in multiple ways. The -/// `UnionEdgesGraph` does not modify or even copy either of the two underlying graphs, and all -/// operations on the `UnionEdgesGraph` occur with identical complexity to the underlying graphs' -/// operations. -/// -/// Note: If many operations will be done on the resulting graph, it can sometimes be more efficient -/// to flatten the extra edges into the base graph (so long as `Base` is a `MutableGraph`) by -/// calling `base.addEdges(from: extraEdges)`. -/// -/// Beware: it is up to the user to ensure that all edges in `ExtraEdges` point to valid vertices in -/// `Base`, and that `ExtraEdges.edges(from:)` can be called with every valid vertex in `Base`. -/// -/// - SeeAlso: `UnionEdgesGraphVertexPropertyMapAdapter`. -public struct UnionEdgesGraph: IncidenceGraph -where Base.VertexId == ExtraEdges.VertexId { - /// The name of a vertex in `self`. - public typealias VertexId = Base.VertexId - /// The name of a vertex in `self`. - public typealias EdgeId = Either - - /// The base graph. - fileprivate var base: Base - /// A graph containing extra edges. - fileprivate var extraEdges: ExtraEdges - - /// Creates a graph based on `base`, augmented with edges in `extraEdges`. - public init(_ base: Base, _ extraEdges: ExtraEdges) { - self.base = base - self.extraEdges = extraEdges - } - - /// A collection of edges from a single vertex in `self`. - public typealias VertexEdgeCollection = - ConcatenatedCollection< - LazyMapCollection, - LazyMapCollection> - - /// A collection of edges from a single vertex. - public func edges(from vertex: VertexId) -> VertexEdgeCollection { - base.edges(from: vertex).lazy.map(EdgeId.a).concatenated(with: - extraEdges.edges(from: vertex).lazy.map(EdgeId.b)) - } - - /// Returns the source vertex of `edge`. - public func source(of edge: EdgeId) -> VertexId { - switch edge { - case .a(let edge): return base.source(of: edge) - case .b(let edge): return extraEdges.source(of: edge) - } - } - - /// Returns the destionation vertex of `edge`. - public func destination(of edge: EdgeId) -> VertexId { - switch edge { - case .a(let edge): return base.destination(of: edge) - case .b(let edge): return extraEdges.destination(of: edge) - } - } -} - -extension UnionEdgesGraph: BidirectionalGraph where Base: BidirectionalGraph, ExtraEdges: BidirectionalGraph { - /// A collection of edges to a vertex. - public typealias VertexInEdgeCollection = ConcatenatedCollection< - LazyMapCollection, - LazyMapCollection> - - /// Returns a collection of all edges in `self` whose destination is `vertex`. - public func edges(to vertex: VertexId) -> VertexInEdgeCollection { - base.edges(to: vertex).lazy.map(EdgeId.a).concatenated(with: - extraEdges.edges(to: vertex).lazy.map(EdgeId.b)) - } -} - -extension UnionEdgesGraph: PropertyGraph -where Base: PropertyGraph, ExtraEdges: PropertyGraph, ExtraEdges.Edge == Base.Edge { - /// Arbitrary data asssociated with every vertex. - public typealias Vertex = Base.Vertex - - /// Arbitrary data associated with every edge. - public typealias Edge = Base.Edge - - /// Accesses the data associated with `vertex`. - public subscript(vertex vertex: VertexId) -> Vertex { - get { base[vertex: vertex] } - _modify { yield &base[vertex: vertex] } - } - - /// Access the data associated with `edge`. - public subscript(edge edge: EdgeId) -> Edge { - get { - switch edge { - case .a(let edge): return base[edge: edge] - case .b(let edge): return extraEdges[edge: edge] - } - } - - _modify { - switch edge { - case .a(let edge): yield &base[edge: edge] - case .b(let edge): yield &extraEdges[edge: edge] - } - } - } -} - -extension UnionEdgesGraph: VertexListGraph where Base: VertexListGraph { - /// The number of vertices in `self`. - public var vertexCount: Int { base.vertexCount } - /// The collection of all vertices in `self`. - public var vertices: Base.VertexCollection { base.vertices } -} - -extension UnionEdgesGraph: EdgeListGraph where Base: EdgeListGraph, ExtraEdges: EdgeListGraph { - public typealias EdgeCollection = ConcatenatedCollection< - LazyMapCollection, - LazyMapCollection> - - /// All edges in `self`. - public var edges: EdgeCollection { - base.edges.lazy.map(EdgeId.a).concatenated(with: extraEdges.edges.lazy.map(EdgeId.b)) - } -} - -extension UnionEdgesGraph: SearchDefaultsGraph where Base: SearchDefaultsGraph { - /// Makes a default color map where every vertex is set to `color`. - public func makeDefaultColorMap(repeating color: VertexColor) -> UnionEdgesGraphVertexPropertyMapAdapter { - UnionEdgesGraphVertexPropertyMapAdapter(base.makeDefaultColorMap(repeating: color)) - } - - /// Makes a default int map for every vertex. - public func makeDefaultVertexIntMap(repeating value: Int) -> UnionEdgesGraphVertexPropertyMapAdapter { - UnionEdgesGraphVertexPropertyMapAdapter(base.makeDefaultVertexIntMap(repeating: value)) - } - - /// Makes a default vertex property map mapping vertices. - public func makeDefaultVertexVertexMap(repeating vertex: VertexId) -> UnionEdgesGraphVertexPropertyMapAdapter { - UnionEdgesGraphVertexPropertyMapAdapter(base.makeDefaultVertexVertexMap(repeating: vertex)) - } -} - -/// Adapts a property map for a graph to be used with the graph unioned with extra edges. -/// -/// - SeeAlso: `UnionEdgesGraph` -public struct UnionEdgesGraphVertexPropertyMapAdapter< - Underlying: PropertyMap, - ExtraEdges: IncidenceGraph ->: PropertyMap -where - Underlying.Graph: IncidenceGraph, - Underlying.Graph.VertexId == ExtraEdges.VertexId, - Underlying.Key == Underlying.Graph.VertexId -{ - /// The graph this property map operates upon. - public typealias Graph = UnionEdgesGraph - /// The identifier used to access data. - public typealias Key = Graph.VertexId - /// The value of data stored in `self`. - public typealias Value = Underlying.Value - - /// The underlying property map. - private var underlying: Underlying - - /// Wraps `underlying` for use with a transposed version of its graph. - public init(_ underlying: Underlying) { - self.underlying = underlying - } - - /// Wraps `underlying` for use with `graph`. (`graph` is taken to help type inference along.) - public init(_ underlying: Underlying, for graph: __shared Graph) { - self.init(underlying) - } - - /// Retrieves the property value for `key` in `graph`. - public func get(_ key: Key, in graph: Graph) -> Value { - underlying.get(key, in: graph.base) - } - - /// Sets the property `newValue` for `key` in `graph`. - public mutating func set(_ key: Key, in graph: inout Graph, to newValue: Value) { - underlying.set(key, in: &graph.base, to: newValue) - } -} - -extension UnionEdgesGraphVertexPropertyMapAdapter: ExternalPropertyMap where Underlying: ExternalPropertyMap { - /// Accesses the `Value` for a given `Key`. - public subscript(key: Key) -> Value { - get { underlying[key] } - set { underlying[key] = newValue } - } -} - -extension IncidenceGraph { - /// Returns a new graph containing all the vertices in `self`, augmented with edges from `other`. - /// - /// - Complexity: O(1) - public func unionEdges(with other: Other) -> UnionEdgesGraph - where Other.VertexId == VertexId - { - UnionEdgesGraph(self, other) - } -} From cc98e87b175d562e7f0081bfb85d897e683592bb Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Sat, 20 Jun 2020 15:09:01 -0700 Subject: [PATCH 10/15] Get everything compiling again. --- .../ConcatenatedCollection.swift | 2 +- .../GraphTransformationsTests.swift | 48 ------------------- Tests/PenguinGraphTests/XCTestManifests.swift | 1 - 3 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 Tests/PenguinGraphTests/GraphTransformationsTests.swift diff --git a/Sources/PenguinStructures/ConcatenatedCollection.swift b/Sources/PenguinStructures/ConcatenatedCollection.swift index 93904d14..37243dd0 100644 --- a/Sources/PenguinStructures/ConcatenatedCollection.swift +++ b/Sources/PenguinStructures/ConcatenatedCollection.swift @@ -53,7 +53,7 @@ First.Element == Second.Element { } /// Accesses the element at `i`. public subscript(i: Index) -> Element { - switch index { + switch i { case .a(let index): return first[index] case .b(let index): return second[index] } diff --git a/Tests/PenguinGraphTests/GraphTransformationsTests.swift b/Tests/PenguinGraphTests/GraphTransformationsTests.swift deleted file mode 100644 index eae8c204..00000000 --- a/Tests/PenguinGraphTests/GraphTransformationsTests.swift +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2020 Penguin Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import PenguinStructures -import PenguinGraphs -import XCTest - -final class GraphTransformationsTests: XCTestCase { - - func testUnionEdges() { - var g1 = BidirectionalAdjacencyList() - _ = g1.addVertex(storing: 10) - _ = g1.addVertex(storing: 11) - _ = g1.addVertex(storing: 12) - _ = g1.addEdge(from: 0, to: 1, storing: "0->1 (g1)") - - var g2 = BidirectionalAdjacencyList() - _ = g2.addVertex() - _ = g2.addVertex() - _ = g2.addVertex() - _ = g2.addEdge(from: 1, to: 2, storing: "1->2 (g2)") - - var g = g1.unionEdges(with: g2) - - XCTAssertEqual(3, g.vertexCount) - XCTAssertEqual(Array(0..<3), Array(g.vertices)) - XCTAssertEqual(Array(10..<13), g.vertices.map { g[vertex: $0] }) - - var recorder = TablePredecessorRecorder(for: g) - g.breadthFirstSearch(startingAt: 0) { recorder.record($0, graph: $1) } - XCTAssertEqual([0, 1, 2], Array(recorder.path(to: 2)!)) - } - - static var allTests = [ - ("testUnionEdges", testUnionEdges), - ] -} diff --git a/Tests/PenguinGraphTests/XCTestManifests.swift b/Tests/PenguinGraphTests/XCTestManifests.swift index f8c7e81c..6c25b412 100644 --- a/Tests/PenguinGraphTests/XCTestManifests.swift +++ b/Tests/PenguinGraphTests/XCTestManifests.swift @@ -26,7 +26,6 @@ import XCTest testCase(EdgeInfoTests.allTests), testCase(GraphCopyingTests.allTests), testCase(GraphGeneratorsTests.allTests), - testCase(GraphTransformationsTests.allTests), testCase(InfiniteGridTests.allTests), testCase(InternalPropertyMapTests.allTests), testCase(ParallelExpanderTests.allTests), From da60e789f55a7de17427feca3b033c9b2cb3dba6 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Sat, 4 Jul 2020 11:34:29 -0700 Subject: [PATCH 11/15] [WIP]: Intermediate commit to switch to Linux for easier debugging. --- Sources/PenguinGraphs/GraphCopying.swift | 1 - .../ConcatenatedCollection.swift | 93 ---------- Sources/PenguinStructures/Concatenation.swift | 166 ++++++++++++++++++ Sources/PenguinStructures/Either.swift | 139 ++++++++++++++- .../PenguinGraphTests/GraphCopyingTests.swift | 24 +-- .../ConcatenatedCollectionTests.swift | 28 ++- .../PenguinStructuresTests/EitherTests.swift | 15 +- .../StdlibProtocolTests.swift | 39 ++++ 8 files changed, 366 insertions(+), 139 deletions(-) delete mode 100644 Sources/PenguinStructures/ConcatenatedCollection.swift create mode 100644 Sources/PenguinStructures/Concatenation.swift diff --git a/Sources/PenguinGraphs/GraphCopying.swift b/Sources/PenguinGraphs/GraphCopying.swift index feaede47..8ab8b408 100644 --- a/Sources/PenguinGraphs/GraphCopying.swift +++ b/Sources/PenguinGraphs/GraphCopying.swift @@ -120,7 +120,6 @@ extension MutablePropertyGraph where Self: DefaultInitializable { } } - /// Initializes `self` as a copy of the incidences and properties of `other`. /// /// - Complexity: O(|V| + |E|) diff --git a/Sources/PenguinStructures/ConcatenatedCollection.swift b/Sources/PenguinStructures/ConcatenatedCollection.swift deleted file mode 100644 index 37243dd0..00000000 --- a/Sources/PenguinStructures/ConcatenatedCollection.swift +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2020 Penguin Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -/// A collection that is all the elements of one collection followed by all the elements of a second -/// collection. -public struct ConcatenatedCollection: Collection where -First.Element == Second.Element { - /// The elements in `self`. - public typealias Element = First.Element - /// The collection whose elements appear first. - private var first: First - /// The collection whose elements appear second. - private var second: Second - - /// Concatenates `first` with `second`. - public init(_ first: First, _ second: Second) { - self.first = first - self.second = second - } - - /// A handle into elements in `self`. - public typealias Index = Either - - /// The position of the first element, or `endIndex` if `self.isEmpty` - public var startIndex: Index { - if first.startIndex != first.endIndex { return .a(first.startIndex) } - return .b(second.startIndex) - } - /// One beyond the last valid index into `self`. - public var endIndex: Index { .b(second.endIndex) } - /// Returns the next valid index after `index`. - public func index(after index: Index) -> Index { - switch index { - case .a(let index): - let newIndex = first.index(after: index) - guard newIndex != first.endIndex else { return .b(second.startIndex) } - return .a(newIndex) - case .b(let index): - return .b(second.index(after: index)) - } - } - /// Accesses the element at `i`. - public subscript(i: Index) -> Element { - switch i { - case .a(let index): return first[index] - case .b(let index): return second[index] - } - } - /// The number of elements in `self`. - public var count: Int { first.count + second.count } - /// True iff `self` contains no elements. - public var isEmpty: Bool { first.isEmpty && second.isEmpty } -} - -extension ConcatenatedCollection: BidirectionalCollection -where First: BidirectionalCollection, Second: BidirectionalCollection { - /// Returns the next valid index before `index`. - public func index(before index: Index) -> Index { - switch index { - case .a(let index): return .a(first.index(before: index)) - case .b(let index): - if index == second.startIndex { - return .a(first.index(before: first.endIndex)) - } - return .b(second.index(before: index)) - } - } -} - -// TODO: Add RandomAccessCollection conformance. -// TODO: Add MutableCollection conformance. - -extension Collection { - /// Returns a new collection where all the elements of `self` appear before all the elements of - /// `other`. - public func concatenated(with other: Other) - -> ConcatenatedCollection - where Other.Element == Element { - return ConcatenatedCollection(self, other) - } -} diff --git a/Sources/PenguinStructures/Concatenation.swift b/Sources/PenguinStructures/Concatenation.swift new file mode 100644 index 00000000..9d7e73b0 --- /dev/null +++ b/Sources/PenguinStructures/Concatenation.swift @@ -0,0 +1,166 @@ +// Copyright 2020 Penguin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/// A collection that is all the elements of one collection followed by all the elements of a second +/// collection. +public struct Concatenation: Collection where +First.Element == Second.Element { + /// The elements in `self`. + public typealias Element = First.Element + /// The collection whose elements appear first. + @usableFromInline + var first: First + /// The collection whose elements appear second. + @usableFromInline + var second: Second + + /// Concatenates `first` with `second`. + public init(_ first: First, _ second: Second) { + self.first = first + self.second = second + } + + /// A position in a `Concatenation`. + public struct Index: Comparable { + /// A position into one of the two underlying collections. + @usableFromInline + var position: Either + + /// Creates a new index into the first underlying collection. + @usableFromInline + internal init(first i: First.Index) { + self.position = .a(i) + } + + /// Creates a new index into the first underlying collection. + @usableFromInline + internal init(second i: Second.Index) { + self.position = .b(i) + } + + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.position < rhs.position + } + } + + /// The position of the first element, or `endIndex` if `self.isEmpty` + @inlinable + public var startIndex: Index { + if !first.isEmpty { return Index(first: first.startIndex) } + return Index(second: second.startIndex) + } + + /// The collection’s “past the end” position—that is, the position one greater than the last valid + /// subscript argument. + @inlinable + public var endIndex: Index { Index(second: second.endIndex) } + + /// Returns the next index after `i`. + public func index(after i: Index) -> Index { + switch i.position { + case .a(let index): + let newIndex = first.index(after: index) + guard newIndex != first.endIndex else { return Index(second: second.startIndex) } + return Index(first: newIndex) + case .b(let index): + return Index(second: second.index(after: index)) + } + } + + /// Accesses the element at `i`. + @inlinable + public subscript(i: Index) -> Element { + switch i.position { + case .a(let index): return first[index] + case .b(let index): return second[index] + } + } + + /// The number of elements in `self`. + @inlinable + public var count: Int { first.count + second.count } + + /// True iff `self` contains no elements. + @inlinable + public var isEmpty: Bool { first.isEmpty && second.isEmpty } +} + +extension Concatenation: BidirectionalCollection +where First: BidirectionalCollection, Second: BidirectionalCollection { + /// Returns the next valid index before `i`. + @inlinable + public func index(before i: Index) -> Index { + switch i.position { + case .a(let index): return Index(first: first.index(before: index)) + case .b(let index): + if index == second.startIndex { + return Index(first: first.index(before: first.endIndex)) + } + return Index(second: second.index(before: index)) + } + } + + /// Returns the distance between two indices. + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + switch (start.position, end.position) { + case (.a(let start), .a(let end)): + return first.distance(from: start, to: end) + case (.a(let start), .b(let end)): + return first.distance(from: start, to: first.endIndex) + second.distance(from: second.startIndex, to: end) + case (.b(let start), .a(let end)): + return second.distance(from: start, to: second.startIndex) + first.distance(from: first.endIndex, to: end) + case (.b(let start), .b(let end)): + return second.distance(from: start, to: end) + } + } +} + +extension Concatenation: RandomAccessCollection + where First: RandomAccessCollection, Second: RandomAccessCollection +{ + @inlinable + public func index(_ i: Index, offsetBy n: Int) -> Index { + if n == 0 { return i } + if n < 0 { return offsetBackward(i, by: n) } + return offsetForward(i, by: n) + } + + @usableFromInline + func offsetForward(_ i: Index, by n: Int) -> Index { + // TODO: IMPLEMENT ME! + fatalError() + } + + @usableFromInline + func offsetBackward(_ i: Index, by n: Int) -> Index { + // TODO: IMPLEMENT ME! + fatalError() + } +} + +// TODO: Add RandomAccessCollection conformance. +// TODO: Add MutableCollection conformance. + +extension Collection { + /// Returns a new collection where all the elements of `self` appear before all the elements of + /// `other`. + @inlinable + public func concatenated(with other: Other) + -> Concatenation + where Other.Element == Element { + return Concatenation(self, other) + } +} diff --git a/Sources/PenguinStructures/Either.swift b/Sources/PenguinStructures/Either.swift index 10890b0e..b23984b3 100644 --- a/Sources/PenguinStructures/Either.swift +++ b/Sources/PenguinStructures/Either.swift @@ -12,14 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Represents one of two possible cases. +// TODO: Consider adding `ABiasedEither` and `BBiasedEither` for biased projection wrapper types of +// `Either`. + +/// An unbiased [tagged union or sum type](https://en.wikipedia.org/wiki/Tagged_union) of exactly +/// two possible cases. +/// +/// **When _NOT_ to use Either**: if there are asymmetrical semantics (e.g. `A` is special in some +/// manner), or when there are better names (i.e. meaning) that can be attached to the cases, a +/// domain-specific `enum` often results in more maintainable code and easier to use APIs. +/// +/// **When to use Either**: good applications of `Either` come up in generic programming where there +/// are no defined semantics or information that can be gained from naming or biasing one of the two +/// cases. public enum Either { case a(A) case b(B) - var a: A? { if case .a(let x) = self { return x } else {return nil} } - var b: B? { if case .b(let x) = self { return x } else {return nil} } - init(_ x: A) { self = .a(x) } - init(_ x: B) { self = .b(x) } + /// An `A` if `self` represents the `a` case, `nil` otherwise. + var a: A? { if case .a(let x) = self { return x } else {return nil } } + /// A `B` if `self` represents the `b` case, `nil` otherwise. + var b: B? { if case .b(let x) = self { return x } else {return nil } } + /// Creates an `Either` from `x` (`a` case). + public init(_ x: A, or other: Type) { self = .a(x) } + /// Creates an `Either` from `x` (`a` case). + public init(_ x: A) { self = .a(x) } + /// Creates an `Either` from `x` (`b` case). + public init(_ x: B) { self = .b(x) } } extension Either: Equatable where A: Equatable, B: Equatable { @@ -33,8 +51,11 @@ extension Either: Equatable where A: Equatable, B: Equatable { } } +// Note: while there are other possible orderings that could sense, until Swift has reasonable +// rules and tools to resolve typeclass incoherency, we define a single broadly applicable ordering +// here. extension Either: Comparable where A: Comparable, B: Comparable { - /// True iff `lhs` is less than `rhs`. + /// True iff `lhs` is less than `rhs` such that all `a`s are ordered before all `b`s. public static func < (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { case (.a(let lhs), .a(let rhs)): return lhs < rhs @@ -55,3 +76,109 @@ extension Either: Hashable where A: Hashable, B: Hashable { } } } + +extension Either: CustomStringConvertible { + /// A textual representation of `self`. + public var description: String { + switch self { + case .a(let a): return "A(\(a))" + case .b(let b): return "B(\(b))" + } + } +} + +/// A sequence of one of two sequence types. +/// +/// An `EitherSequence` can sometimes be used an alternative to `AnySequence`. Advantages of +/// `EitherSequence` include higher performance, as more information is available at compile time, +/// enabling more effective static optimizations. +/// +/// Tip: if code uses `AnySequence`, but most of the time is used with a particular collection type +/// `T` (e.g. `Array`), consider using an `EitherSequence`. +public typealias EitherSequence = Either + where A.Element == B.Element + +extension EitherSequence { + /// A type that provides the sequence’s iteration interface and encapsulates its iteration state. + public struct Iterator: IteratorProtocol { + // Note: although we would ideally use `var underlying = Either`, this + // would result in accidentally quadratic behavior due to the extra copies required. (Enums + // cannot be modified in-place, resulting in a lot of extra copies.) + // + // Future optimization: avoid needing to reserve space for both `a` and `b` iterators. + + /// An iterator for the `A` collection. + var a: A.Iterator? + /// An iterator for the `A` collection. + var b: B.Iterator? + + /// The type of element traversed by the iterator. + public typealias Element = A.Element + + /// Advances to the next element and returns it, or `nil` if no next element exists. + public mutating func next() -> Element? { + a?.next() ?? b?.next() + } + } +} + +extension EitherSequence: Sequence { + /// A type representing the sequence’s elements. + public typealias Element = A.Element + + /// Returns an iterator over the elements of this sequence. + public func makeIterator() -> Iterator { + switch self { + case .a(let a): return Iterator(a: a.makeIterator(), b: nil) + case .b(let b): return Iterator(a: nil, b: b.makeIterator()) + } + } +} + +/// A collection of one of two collection types. +/// +/// - SeeAlso: `EitherSequence`. +public typealias EitherCollection = Either + where A.Element == B.Element + +extension EitherCollection: Collection { + /// A type that represents a position in the collection. + public typealias Index = Either + + /// The position of the first element in a nonempty collection. + public var startIndex: Index { + switch self { + case .a(let c): return Index(c.startIndex) + case .b(let c): return Index(c.startIndex) + } + } + + /// The collection’s “past the end” position—that is, the position one greater than the last valid + /// subscript argument. + public var endIndex: Index { + switch self { + case .a(let c): return Index(c.endIndex) + case .b(let c): return Index(c.endIndex) + } + } + + /// Returns the position immediately after the given index. + public func index(after i: Index) -> Index { + switch (i, self) { + case (.a(let i), .a(let c)): return Index(c.index(after: i)) + case (.b(let i), .b(let c)): return Index(c.index(after: i)) + default: fatalError("Invalid index \(i) used with \(self).") + } + } + + /// Accesses the element at the specified position. + public subscript(position: Index) -> Element { + switch (position, self) { + case (.a(let i), .a(let c)): return c[i] + case (.b(let i), .b(let c)): return c[i] + default: fatalError("Invalid index \(position) used with \(self).") + } + } +} + +// TODO: Bidirectional & RandomAccess conformances & verification tests! diff --git a/Tests/PenguinGraphTests/GraphCopyingTests.swift b/Tests/PenguinGraphTests/GraphCopyingTests.swift index d8054ff3..ea40a5da 100644 --- a/Tests/PenguinGraphTests/GraphCopyingTests.swift +++ b/Tests/PenguinGraphTests/GraphCopyingTests.swift @@ -114,30 +114,30 @@ final class GraphCopyingTests: XCTestCase { func testAddingEdges() { var src = DirectedAdjacencyList() _ = src.addVertex() - _ = src.addVertex() - _ = src.addVertex() + let srcV1 = src.addVertex() + let srcV2 = src.addVertex() - _ = src.addEdge(from: 1, to: 2, storing: "1->2 (src)") + _ = src.addEdge(from: srcV1, to: srcV2, storing: "1->2 (src)") var dst = UndirectedAdjacencyList() - _ = dst.addVertex() - _ = dst.addVertex() - _ = dst.addVertex() + let dstV0 = dst.addVertex() + let dstV1 = dst.addVertex() + let dstV2 = dst.addVertex() - _ = dst.addEdge(from: 0, to: 1, storing: "0->1 (dst)") + _ = dst.addEdge(from: dstV0, to: dstV1, storing: "0->1 (dst)") var edgeCallback = false dst.addEdges(from: src) { e, g in XCTAssertFalse(edgeCallback) edgeCallback = true - XCTAssertEqual(1, g.source(of: e)) - XCTAssertEqual(2, g.destination(of: e)) + XCTAssertEqual(srcV1, g.source(of: e)) + XCTAssertEqual(srcV2, g.destination(of: e)) } XCTAssert(edgeCallback) - let edges = dst.edges(from: 2) + let edges = dst.edges(from: dstV2) XCTAssertEqual(1, edges.count) - XCTAssertEqual(2, dst.source(of: edges[0])) - XCTAssertEqual(1, dst.destination(of: edges[0])) + XCTAssertEqual(dstV2, dst.source(of: edges[dstV0])) + XCTAssertEqual(dstV1, dst.destination(of: edges[0])) XCTAssertEqual("1->2 (src)", dst[edge: edges[0]]) } diff --git a/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift b/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift index ee58b7e3..ad7864a7 100644 --- a/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift +++ b/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift @@ -18,34 +18,30 @@ import XCTest final class ConcatenatedCollectionTests: XCTestCase { func testConcatenateSetAndList() { - let c = Set(["1", "2", "3"]).concatenated(with: ["10", "11", "12"]) - XCTAssertFalse(c.isEmpty) - XCTAssertEqual(6, c.count) - XCTAssertEqual(Set(["1", "2", "3"]), Set(c.prefix(3))) - XCTAssertEqual(["10", "11", "12"], Array(c.suffix(3))) + let s = Set(["1", "2", "3"]) + let c = s.concatenated(with: ["10", "11", "12"]) + c.checkCollectionSemantics(expectedValues: Array(s) + ["10", "11", "12"]) } func testConcatenateRanges() { let c = (0..<3).concatenated(with: 3...6) - XCTAssertEqual(Array(0...6), Array(c)) + c.checkRandomAccessCollectionSemantics(expectedValues: 0...6) } - func testConcatenateBidirectionalOperations() { - let c = (0..<3).concatenated(with: [10, 11, 12]) - XCTAssertEqual(6, c.count) - XCTAssertEqual([12, 11, 10, 2, 1, 0], Array(c.reversed())) + func testConcatenatingEmptyPrefix() { + let c = (0..<0).concatenated(with: [1, 2, 3]) + c.checkRandomAccessCollectionSemantics(expectedValues: 1...3) } - func testConcatenatingEmpty() { - let c = (0..<0).concatenated(with: [1, 2, 3]) - XCTAssertEqual(3, c.count) - XCTAssertEqual([1, 2, 3], Array(c)) + func testConcatenatingEmptySuffix() { + let c = (1...3).concatenated(with: 1000..<1000) + c.checkRandomAccessCollectionSemantics(expectedValues: [1, 2, 3]) } static var allTests = [ ("testConcatenateSetAndList", testConcatenateSetAndList), ("testConcatenateRanges", testConcatenateRanges), - ("testConcatenateBidirectionalOperations", testConcatenateBidirectionalOperations), - ("testConcatenatingEmpty", testConcatenatingEmpty), + ("testConcatenatingEmptyPrefix", testConcatenatingEmptyPrefix), + ("testConcatenatingEmptySuffix", testConcatenatingEmptySuffix), ] } diff --git a/Tests/PenguinStructuresTests/EitherTests.swift b/Tests/PenguinStructuresTests/EitherTests.swift index 9a5654b4..dee30093 100644 --- a/Tests/PenguinStructuresTests/EitherTests.swift +++ b/Tests/PenguinStructuresTests/EitherTests.swift @@ -19,21 +19,14 @@ import PenguinStructures class EitherTests: XCTestCase { - func testEquality() { - XCTAssertEqual(Either.a(1), .a(1)) - XCTAssertNotEqual(Either.a(1), .b(1)) - XCTAssertNotEqual(Either.a(1), .b("1")) - XCTAssertEqual(Either.b("0"), .b("0")) - } - func testComparable() { - typealias E = Either - XCTAssert(E.a(1) < .b(0)) - XCTAssert(E.b(0) > .a(100000)) + Either.checkComparableSemantics(.a(1), .a(2), .a(1000), .b(-3), .b(0), .b(5)) + + Either.checkComparableSemantics( + Either(""), Either("abc"), Either("xyz"), Either(-4), Either(0), Either(10)) } static var allTests = [ - ("testEquality", testEquality), ("testComparable", testComparable), ] } diff --git a/Tests/PenguinStructuresTests/StdlibProtocolTests.swift b/Tests/PenguinStructuresTests/StdlibProtocolTests.swift index 1aa76bb2..febf85b0 100644 --- a/Tests/PenguinStructuresTests/StdlibProtocolTests.swift +++ b/Tests/PenguinStructuresTests/StdlibProtocolTests.swift @@ -307,3 +307,42 @@ extension MutableCollection where Element: Equatable { } } +extension Comparable { + /// XCTests `Self`'s semantic conformance to `Comparable` using the `orderedExamples`. + /// + /// - Parameter orderedExamples: a collection of instances of `Self` in ascending order. + static func checkComparableSemantics(_ orderedExamples: Self...) { + precondition(!orderedExamples.isEmpty, "Must have non-empty set of orderedExamples.") + + for i in orderedExamples.indices { + XCTAssert(orderedExamples[i] == orderedExamples[i], "\(orderedExamples[i])") + XCTAssert(orderedExamples[i] <= orderedExamples[i], "\(orderedExamples[i])") + XCTAssert(orderedExamples[i] >= orderedExamples[i], "\(orderedExamples[i])") + XCTAssertFalse(orderedExamples[i] < orderedExamples[i], "\(orderedExamples[i])") + XCTAssertFalse(orderedExamples[i] > orderedExamples[i], "\(orderedExamples[i])") + XCTAssertFalse(orderedExamples[i] != orderedExamples[i], "\(orderedExamples[i])") + + for j in orderedExamples[orderedExamples.index(after: i)...].indices { + // Equality checks. + XCTAssertFalse(orderedExamples[i] == orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") + XCTAssert(orderedExamples[i] != orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") + + // Comparison checks. + XCTAssertFalse(orderedExamples[i] > orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") + XCTAssertFalse(orderedExamples[i] >= orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") + XCTAssert(orderedExamples[i] < orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") + XCTAssert(orderedExamples[i] <= orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") + + // Commutative equality checks. + XCTAssert(orderedExamples[j] != orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") + XCTAssertFalse(orderedExamples[j] == orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") + + // Commutative comparison checks. + XCTAssert(orderedExamples[j] > orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") + XCTAssert(orderedExamples[j] >= orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") + XCTAssertFalse(orderedExamples[j] < orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") + XCTAssertFalse(orderedExamples[j] <= orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") + } + } + } +} From 21f57919590418f0a99e81a16acd7acc362b2ed1 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Sat, 4 Jul 2020 15:59:47 -0700 Subject: [PATCH 12/15] Finish `Concatenation` implementation to properly adhere to relevant collection conformances. --- Sources/PenguinStructures/Concatenation.swift | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/Sources/PenguinStructures/Concatenation.swift b/Sources/PenguinStructures/Concatenation.swift index 9d7e73b0..025ca0eb 100644 --- a/Sources/PenguinStructures/Concatenation.swift +++ b/Sources/PenguinStructures/Concatenation.swift @@ -95,6 +95,21 @@ First.Element == Second.Element { /// True iff `self` contains no elements. @inlinable public var isEmpty: Bool { first.isEmpty && second.isEmpty } + + /// Returns the distance between two indices. + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + switch (start.position, end.position) { + case (.a(let start), .a(let end)): + return first.distance(from: start, to: end) + case (.a(let start), .b(let end)): + return first.distance(from: start, to: first.endIndex) + second.distance(from: second.startIndex, to: end) + case (.b(let start), .a(let end)): + return second.distance(from: start, to: second.startIndex) + first.distance(from: first.endIndex, to: end) + case (.b(let start), .b(let end)): + return second.distance(from: start, to: end) + } + } } extension Concatenation: BidirectionalCollection @@ -111,21 +126,6 @@ where First: BidirectionalCollection, Second: BidirectionalCollection { return Index(second: second.index(before: index)) } } - - /// Returns the distance between two indices. - @inlinable - public func distance(from start: Index, to end: Index) -> Int { - switch (start.position, end.position) { - case (.a(let start), .a(let end)): - return first.distance(from: start, to: end) - case (.a(let start), .b(let end)): - return first.distance(from: start, to: first.endIndex) + second.distance(from: second.startIndex, to: end) - case (.b(let start), .a(let end)): - return second.distance(from: start, to: second.startIndex) + first.distance(from: first.endIndex, to: end) - case (.b(let start), .b(let end)): - return second.distance(from: start, to: end) - } - } } extension Concatenation: RandomAccessCollection @@ -140,14 +140,32 @@ extension Concatenation: RandomAccessCollection @usableFromInline func offsetForward(_ i: Index, by n: Int) -> Index { - // TODO: IMPLEMENT ME! - fatalError() + switch i.position { + case .a(let index): + let d = first.distance(from: index, to: first.endIndex) + if n < d { + return Index(first: first.index(index, offsetBy: n)) + } else { + return Index(second: second.index(second.startIndex, offsetBy: n - d)) + } + case .b(let index): + return Index(second: second.index(index, offsetBy: n)) + } } @usableFromInline func offsetBackward(_ i: Index, by n: Int) -> Index { - // TODO: IMPLEMENT ME! - fatalError() + switch i.position { + case .a(let index): + return Index(first: first.index(index, offsetBy: n)) + case .b(let index): + let d = second.distance(from: second.startIndex, to: index) + if -n <= d { + return Index(second: second.index(index, offsetBy: n)) + } else { + return Index(first: first.index(first.endIndex, offsetBy: n + d)) + } + } } } From 973ae4bd132010fbc6b435b4deedc9afe703a5bd Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Tue, 11 Aug 2020 13:38:45 -0700 Subject: [PATCH 13/15] Remove everything but Either/Concatenated. --- Sources/PenguinGraphs/GraphCopying.swift | 77 +------------------ .../DefaultInitializable.swift | 6 -- .../PenguinGraphTests/GraphCopyingTests.swift | 31 -------- .../StdlibProtocolTests.swift | 39 ---------- 4 files changed, 1 insertion(+), 152 deletions(-) diff --git a/Sources/PenguinGraphs/GraphCopying.swift b/Sources/PenguinGraphs/GraphCopying.swift index 8ab8b408..5a37d683 100644 --- a/Sources/PenguinGraphs/GraphCopying.swift +++ b/Sources/PenguinGraphs/GraphCopying.swift @@ -120,6 +120,7 @@ extension MutablePropertyGraph where Self: DefaultInitializable { } } + /// Initializes `self` as a copy of the incidences and properties of `other`. /// /// - Complexity: O(|V| + |E|) @@ -164,79 +165,3 @@ extension MutablePropertyGraph where Self: DefaultInitializable { edgeProperties: InternalEdgePropertyMap(for: other)) } } - -extension IncidenceGraph where Self: MutableGraph { - /// Adds all edges from `other` into `self`, using `vertexMapper` to map vertices, and calling - /// `edgeCreationListener` with every new EdgeId. - public mutating func addEdges( - from other: Other, - mappingVertices vertexMapper: (Other, Other.VertexId, Self) -> VertexId, - _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } - ) { - for vSrc in other.vertices { - let v = vertexMapper(other, vSrc, self) - for e in other.edges(from: vSrc) { - let d = vertexMapper(other, other.destination(of: e), self) - let edgeId = addEdge(from: v, to: d) - edgeCreationListener(edgeId, &self) - } - } - } - - /// Adds all edges from `other` into `self`, and calling `edgeCreationListener` with every new - /// EdgeId. - /// - /// - Precondition: (not checked) all of `other`'s VertexId's must be valid VertexId's in `self`. - public mutating func addEdges( - from other: Other, - _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } - ) where Other.VertexId == VertexId { - addEdges(from: other, mappingVertices: { _, v, _ in v }, edgeCreationListener) - } -} - -extension IncidenceGraph where Self: MutablePropertyGraph & VertexListGraph { - /// Adds all edges from `other` into `self`, calling `edgeCreationListener` with every new - /// `EdgeId`. - /// - /// - Precondition: all of `other`'s VertexId's must be valid VertexId's in `self`. - public mutating func addEdges( - from other: Other, - _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } - ) where Other.VertexId == VertexId, Other.Edge == Edge { - addEdges( - from: other, - storing: InternalEdgePropertyMap(for: other), - mappingVertices: { _, v, _ in v }, - edgeCreationListener) - } - - /// Adds all edges from `other` into `self` storing the corresponding edge property from - /// `edgeProperties`. - public mutating func addEdges< - Other: IncidenceGraph & VertexListGraph, - EdgeProperties: PropertyMap - >( - from other: Other, - storing edgeProperties: EdgeProperties, - mappingVertices vertexMapper: (Other, Other.VertexId, Self) -> VertexId, - _ edgeCreationListener: (EdgeId, inout Self) -> Void = { _, _ in } - ) where - Other.VertexId == VertexId, - EdgeProperties.Graph == Other, - EdgeProperties.Key == Other.EdgeId, - EdgeProperties.Value == Edge - { - for vSrc in other.vertices { - let v = vertexMapper(other, vSrc, self) - for e in other.edges(from: vSrc) { - let d = vertexMapper(other, other.destination(of: e), self) - let edgeId = addEdge( - from: v, - to: d, - storing: edgeProperties.get(e, in: other)) - edgeCreationListener(edgeId, &self) - } - } - } -} diff --git a/Sources/PenguinStructures/DefaultInitializable.swift b/Sources/PenguinStructures/DefaultInitializable.swift index e9ea43d9..3a3c769a 100644 --- a/Sources/PenguinStructures/DefaultInitializable.swift +++ b/Sources/PenguinStructures/DefaultInitializable.swift @@ -21,9 +21,3 @@ public protocol DefaultInitializable { extension Array: DefaultInitializable {} extension Dictionary: DefaultInitializable {} extension String: DefaultInitializable {} -extension Int: DefaultInitializable {} -extension UInt: DefaultInitializable {} -extension Int32: DefaultInitializable {} -extension UInt32: DefaultInitializable {} -extension Float: DefaultInitializable {} -extension Double: DefaultInitializable {} diff --git a/Tests/PenguinGraphTests/GraphCopyingTests.swift b/Tests/PenguinGraphTests/GraphCopyingTests.swift index ea40a5da..e7b57af5 100644 --- a/Tests/PenguinGraphTests/GraphCopyingTests.swift +++ b/Tests/PenguinGraphTests/GraphCopyingTests.swift @@ -111,40 +111,9 @@ final class GraphCopyingTests: XCTestCase { XCTAssertEqual(2, dst.inDegree(of: 2)) } - func testAddingEdges() { - var src = DirectedAdjacencyList() - _ = src.addVertex() - let srcV1 = src.addVertex() - let srcV2 = src.addVertex() - - _ = src.addEdge(from: srcV1, to: srcV2, storing: "1->2 (src)") - - var dst = UndirectedAdjacencyList() - let dstV0 = dst.addVertex() - let dstV1 = dst.addVertex() - let dstV2 = dst.addVertex() - - _ = dst.addEdge(from: dstV0, to: dstV1, storing: "0->1 (dst)") - var edgeCallback = false - dst.addEdges(from: src) { e, g in - XCTAssertFalse(edgeCallback) - edgeCallback = true - XCTAssertEqual(srcV1, g.source(of: e)) - XCTAssertEqual(srcV2, g.destination(of: e)) - } - XCTAssert(edgeCallback) - - let edges = dst.edges(from: dstV2) - XCTAssertEqual(1, edges.count) - XCTAssertEqual(dstV2, dst.source(of: edges[dstV0])) - XCTAssertEqual(dstV1, dst.destination(of: edges[0])) - XCTAssertEqual("1->2 (src)", dst[edge: edges[0]]) - } - static var allTests = [ ("testUndirectedCopying", testUndirectedCopying), ("testProperyGraphCopying", testProperyGraphCopying), ("testProperyGraphCopyingBidirectional", testProperyGraphCopyingBidirectional), - ("testAddingEdges", testAddingEdges), ] } diff --git a/Tests/PenguinStructuresTests/StdlibProtocolTests.swift b/Tests/PenguinStructuresTests/StdlibProtocolTests.swift index 992d0d61..7bd9a06a 100644 --- a/Tests/PenguinStructuresTests/StdlibProtocolTests.swift +++ b/Tests/PenguinStructuresTests/StdlibProtocolTests.swift @@ -463,42 +463,3 @@ extension MutableCollection where Element: Equatable { } } -extension Comparable { - /// XCTests `Self`'s semantic conformance to `Comparable` using the `orderedExamples`. - /// - /// - Parameter orderedExamples: a collection of instances of `Self` in ascending order. - static func checkComparableSemantics(_ orderedExamples: Self...) { - precondition(!orderedExamples.isEmpty, "Must have non-empty set of orderedExamples.") - - for i in orderedExamples.indices { - XCTAssert(orderedExamples[i] == orderedExamples[i], "\(orderedExamples[i])") - XCTAssert(orderedExamples[i] <= orderedExamples[i], "\(orderedExamples[i])") - XCTAssert(orderedExamples[i] >= orderedExamples[i], "\(orderedExamples[i])") - XCTAssertFalse(orderedExamples[i] < orderedExamples[i], "\(orderedExamples[i])") - XCTAssertFalse(orderedExamples[i] > orderedExamples[i], "\(orderedExamples[i])") - XCTAssertFalse(orderedExamples[i] != orderedExamples[i], "\(orderedExamples[i])") - - for j in orderedExamples[orderedExamples.index(after: i)...].indices { - // Equality checks. - XCTAssertFalse(orderedExamples[i] == orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") - XCTAssert(orderedExamples[i] != orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") - - // Comparison checks. - XCTAssertFalse(orderedExamples[i] > orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") - XCTAssertFalse(orderedExamples[i] >= orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") - XCTAssert(orderedExamples[i] < orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") - XCTAssert(orderedExamples[i] <= orderedExamples[j], "\(orderedExamples[i]), \(orderedExamples[j])") - - // Commutative equality checks. - XCTAssert(orderedExamples[j] != orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") - XCTAssertFalse(orderedExamples[j] == orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") - - // Commutative comparison checks. - XCTAssert(orderedExamples[j] > orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") - XCTAssert(orderedExamples[j] >= orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") - XCTAssertFalse(orderedExamples[j] < orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") - XCTAssertFalse(orderedExamples[j] <= orderedExamples[i], "\(orderedExamples[i]), \(orderedExamples[j])") - } - } - } -} From 4160e4656b4d2ebaf5141969af41886a92bce63a Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Tue, 11 Aug 2020 15:12:54 -0700 Subject: [PATCH 14/15] Make Either ready for prime time --- Sources/PenguinStructures/Either.swift | 45 +++++++-------- .../PenguinStructuresTests/EitherTests.swift | 57 +++++++++++++++++-- .../XCTestManifests.swift | 1 + 3 files changed, 74 insertions(+), 29 deletions(-) diff --git a/Sources/PenguinStructures/Either.swift b/Sources/PenguinStructures/Either.swift index b23984b3..cb51c3e2 100644 --- a/Sources/PenguinStructures/Either.swift +++ b/Sources/PenguinStructures/Either.swift @@ -16,7 +16,7 @@ // `Either`. /// An unbiased [tagged union or sum type](https://en.wikipedia.org/wiki/Tagged_union) of exactly -/// two possible cases. +/// two possible cases, `.a` and `.b`, having types `A` and `B` respectively. /// /// **When _NOT_ to use Either**: if there are asymmetrical semantics (e.g. `A` is special in some /// manner), or when there are better names (i.e. meaning) that can be attached to the cases, a @@ -28,20 +28,16 @@ public enum Either { case a(A) case b(B) - /// An `A` if `self` represents the `a` case, `nil` otherwise. - var a: A? { if case .a(let x) = self { return x } else {return nil } } - /// A `B` if `self` represents the `b` case, `nil` otherwise. - var b: B? { if case .b(let x) = self { return x } else {return nil } } - /// Creates an `Either` from `x` (`a` case). - public init(_ x: A, or other: Type) { self = .a(x) } - /// Creates an `Either` from `x` (`a` case). - public init(_ x: A) { self = .a(x) } - /// Creates an `Either` from `x` (`b` case). - public init(_ x: B) { self = .b(x) } + + /// `x` iff the value of `self` is `.a(x)` for some `x`; `nil` otherwise. + public var a: A? { if case .a(let x) = self { return x } else {return nil } } + + /// `x` iff the value of `self` is `.b(x)` for some `x`; `nil` otherwise. + public var b: B? { if case .b(let x) = self { return x } else {return nil } } } extension Either: Equatable where A: Equatable, B: Equatable { - /// True if `lhs` is equivalent to `rhs`. + /// True iff `lhs` is equivalent to `rhs`. public static func == (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { case (.a(let lhs), .a(let rhs)): return lhs == rhs @@ -51,11 +47,12 @@ extension Either: Equatable where A: Equatable, B: Equatable { } } -// Note: while there are other possible orderings that could sense, until Swift has reasonable +// Note: while there are other possible orderings that could make sense, until Swift has reasonable // rules and tools to resolve typeclass incoherency, we define a single broadly applicable ordering // here. extension Either: Comparable where A: Comparable, B: Comparable { - /// True iff `lhs` is less than `rhs` such that all `a`s are ordered before all `b`s. + /// True iff `lhs` comes before `rhs` in an ordering where every `.a(x)`s is ordered before any + /// `.b(y)`, `.a(x)`s are ordered by increasing `x`, and `.b(y)`s are ordered by increasing `y`. public static func < (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { case (.a(let lhs), .a(let rhs)): return lhs < rhs @@ -81,13 +78,13 @@ extension Either: CustomStringConvertible { /// A textual representation of `self`. public var description: String { switch self { - case .a(let a): return "A(\(a))" - case .b(let b): return "B(\(b))" + case .a(let x): return "Either.a(\(x))" + case .b(let x): return "Either.b(\(x))" } } } -/// A sequence of one of two sequence types. +/// A sequence backed by one of two sequence types. /// /// An `EitherSequence` can sometimes be used an alternative to `AnySequence`. Advantages of /// `EitherSequence` include higher performance, as more information is available at compile time, @@ -148,8 +145,8 @@ extension EitherCollection: Collection { /// The position of the first element in a nonempty collection. public var startIndex: Index { switch self { - case .a(let c): return Index(c.startIndex) - case .b(let c): return Index(c.startIndex) + case .a(let c): return .a(c.startIndex) + case .b(let c): return .b(c.startIndex) } } @@ -157,16 +154,16 @@ extension EitherCollection: Collection { /// subscript argument. public var endIndex: Index { switch self { - case .a(let c): return Index(c.endIndex) - case .b(let c): return Index(c.endIndex) + case .a(let c): return .a(c.endIndex) + case .b(let c): return .b(c.endIndex) } } /// Returns the position immediately after the given index. public func index(after i: Index) -> Index { switch (i, self) { - case (.a(let i), .a(let c)): return Index(c.index(after: i)) - case (.b(let i), .b(let c)): return Index(c.index(after: i)) + case (.a(let i), .a(let c)): return .a(c.index(after: i)) + case (.b(let i), .b(let c)): return .b(c.index(after: i)) default: fatalError("Invalid index \(i) used with \(self).") } } @@ -181,4 +178,4 @@ extension EitherCollection: Collection { } } -// TODO: Bidirectional & RandomAccess conformances & verification tests! +// TODO: Bidirectional & RandomAccess conformances diff --git a/Tests/PenguinStructuresTests/EitherTests.swift b/Tests/PenguinStructuresTests/EitherTests.swift index dee30093..c8556207 100644 --- a/Tests/PenguinStructuresTests/EitherTests.swift +++ b/Tests/PenguinStructuresTests/EitherTests.swift @@ -18,15 +18,62 @@ import PenguinStructures class EitherTests: XCTestCase { - - func testComparable() { - Either.checkComparableSemantics(.a(1), .a(2), .a(1000), .b(-3), .b(0), .b(5)) + let sorted0: [Either] = [.a(1), .a(2), .a(1000), .b(-3), .b(0), .b(5)] + let sorted1: [Either] = [.a(""), .a("abc"), .a("xyz"), .b(-4), .b(0), .b(10)] - Either.checkComparableSemantics( - Either(""), Either("abc"), Either("xyz"), Either(-4), Either(0), Either(10)) + func testComparable() { + // Also tests Equatable semantics + do { + for (a, (b, c)) in zip(sorted0, zip(sorted0.dropFirst(), sorted0.dropFirst(2))) { + a.checkComparableSemantics(equal: a, greater: b, greaterStill: c) + } + } + + do { + for (a, (b, c)) in zip(sorted1, zip(sorted1.dropFirst(), sorted1.dropFirst(2))) { + a.checkComparableSemantics(equal: a, greater: b, greaterStill: c) + } + } } + func testProperties() { + let isA = Either.a("ayy") + let isB = Either.b(3) + XCTAssert(isA.a == "ayy") + XCTAssert(isA.b == nil) + XCTAssert(isB.a == nil) + XCTAssert(isB.b == 3) + } + + func testHashable() { + // Also tests Equatable semantics + for x in sorted0 { x.checkHashableSemantics() } + for y in sorted1 { y.checkHashableSemantics() } + } + static var allTests = [ ("testComparable", testComparable), + ("testProperties", testProperties), + ("testHashable", testHashable), + ] +} + +class EitherCollectionTests: XCTestCase { + func testCollection() { + typealias X = ClosedRange + typealias Y = ReversedCollection> + let x: X = 0...10 + let y: Y = x.reversed() + + // Also tests Sequence semantics + Either.a(x).checkCollectionSemantics(expectedValues: x) + Either.b(y).checkCollectionSemantics(expectedValues: y) + + Either.b(x).checkCollectionSemantics(expectedValues: x) + Either.a(y).checkCollectionSemantics(expectedValues: y) + } + + static var allTests = [ + ("testCollection", testCollection), ] } diff --git a/Tests/PenguinStructuresTests/XCTestManifests.swift b/Tests/PenguinStructuresTests/XCTestManifests.swift index 0da1402b..eb5b2fcd 100644 --- a/Tests/PenguinStructuresTests/XCTestManifests.swift +++ b/Tests/PenguinStructuresTests/XCTestManifests.swift @@ -26,6 +26,7 @@ import XCTest testCase(ConcatenatedCollectionTests.allTests), testCase(DequeTests.allTests), testCase(DoubleEndedBufferTests.allTests), + testCase(EitherCollectionTests.allTests), testCase(EitherTests.allTests), testCase(FactoryInitializableTests.allTests), testCase(FixedSizeArrayTests.allTests), From 97968ddd7af88983afe56249e8eca3660fd2387f Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Tue, 11 Aug 2020 15:14:39 -0700 Subject: [PATCH 15/15] Eliminate concatenated collection (next PR). --- Sources/PenguinStructures/Concatenation.swift | 184 ------------------ .../ConcatenatedCollectionTests.swift | 47 ----- .../XCTestManifests.swift | 1 - 3 files changed, 232 deletions(-) delete mode 100644 Sources/PenguinStructures/Concatenation.swift delete mode 100644 Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift diff --git a/Sources/PenguinStructures/Concatenation.swift b/Sources/PenguinStructures/Concatenation.swift deleted file mode 100644 index 025ca0eb..00000000 --- a/Sources/PenguinStructures/Concatenation.swift +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2020 Penguin Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -/// A collection that is all the elements of one collection followed by all the elements of a second -/// collection. -public struct Concatenation: Collection where -First.Element == Second.Element { - /// The elements in `self`. - public typealias Element = First.Element - /// The collection whose elements appear first. - @usableFromInline - var first: First - /// The collection whose elements appear second. - @usableFromInline - var second: Second - - /// Concatenates `first` with `second`. - public init(_ first: First, _ second: Second) { - self.first = first - self.second = second - } - - /// A position in a `Concatenation`. - public struct Index: Comparable { - /// A position into one of the two underlying collections. - @usableFromInline - var position: Either - - /// Creates a new index into the first underlying collection. - @usableFromInline - internal init(first i: First.Index) { - self.position = .a(i) - } - - /// Creates a new index into the first underlying collection. - @usableFromInline - internal init(second i: Second.Index) { - self.position = .b(i) - } - - public static func < (lhs: Self, rhs: Self) -> Bool { - return lhs.position < rhs.position - } - } - - /// The position of the first element, or `endIndex` if `self.isEmpty` - @inlinable - public var startIndex: Index { - if !first.isEmpty { return Index(first: first.startIndex) } - return Index(second: second.startIndex) - } - - /// The collection’s “past the end” position—that is, the position one greater than the last valid - /// subscript argument. - @inlinable - public var endIndex: Index { Index(second: second.endIndex) } - - /// Returns the next index after `i`. - public func index(after i: Index) -> Index { - switch i.position { - case .a(let index): - let newIndex = first.index(after: index) - guard newIndex != first.endIndex else { return Index(second: second.startIndex) } - return Index(first: newIndex) - case .b(let index): - return Index(second: second.index(after: index)) - } - } - - /// Accesses the element at `i`. - @inlinable - public subscript(i: Index) -> Element { - switch i.position { - case .a(let index): return first[index] - case .b(let index): return second[index] - } - } - - /// The number of elements in `self`. - @inlinable - public var count: Int { first.count + second.count } - - /// True iff `self` contains no elements. - @inlinable - public var isEmpty: Bool { first.isEmpty && second.isEmpty } - - /// Returns the distance between two indices. - @inlinable - public func distance(from start: Index, to end: Index) -> Int { - switch (start.position, end.position) { - case (.a(let start), .a(let end)): - return first.distance(from: start, to: end) - case (.a(let start), .b(let end)): - return first.distance(from: start, to: first.endIndex) + second.distance(from: second.startIndex, to: end) - case (.b(let start), .a(let end)): - return second.distance(from: start, to: second.startIndex) + first.distance(from: first.endIndex, to: end) - case (.b(let start), .b(let end)): - return second.distance(from: start, to: end) - } - } -} - -extension Concatenation: BidirectionalCollection -where First: BidirectionalCollection, Second: BidirectionalCollection { - /// Returns the next valid index before `i`. - @inlinable - public func index(before i: Index) -> Index { - switch i.position { - case .a(let index): return Index(first: first.index(before: index)) - case .b(let index): - if index == second.startIndex { - return Index(first: first.index(before: first.endIndex)) - } - return Index(second: second.index(before: index)) - } - } -} - -extension Concatenation: RandomAccessCollection - where First: RandomAccessCollection, Second: RandomAccessCollection -{ - @inlinable - public func index(_ i: Index, offsetBy n: Int) -> Index { - if n == 0 { return i } - if n < 0 { return offsetBackward(i, by: n) } - return offsetForward(i, by: n) - } - - @usableFromInline - func offsetForward(_ i: Index, by n: Int) -> Index { - switch i.position { - case .a(let index): - let d = first.distance(from: index, to: first.endIndex) - if n < d { - return Index(first: first.index(index, offsetBy: n)) - } else { - return Index(second: second.index(second.startIndex, offsetBy: n - d)) - } - case .b(let index): - return Index(second: second.index(index, offsetBy: n)) - } - } - - @usableFromInline - func offsetBackward(_ i: Index, by n: Int) -> Index { - switch i.position { - case .a(let index): - return Index(first: first.index(index, offsetBy: n)) - case .b(let index): - let d = second.distance(from: second.startIndex, to: index) - if -n <= d { - return Index(second: second.index(index, offsetBy: n)) - } else { - return Index(first: first.index(first.endIndex, offsetBy: n + d)) - } - } - } -} - -// TODO: Add RandomAccessCollection conformance. -// TODO: Add MutableCollection conformance. - -extension Collection { - /// Returns a new collection where all the elements of `self` appear before all the elements of - /// `other`. - @inlinable - public func concatenated(with other: Other) - -> Concatenation - where Other.Element == Element { - return Concatenation(self, other) - } -} diff --git a/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift b/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift deleted file mode 100644 index ad7864a7..00000000 --- a/Tests/PenguinStructuresTests/ConcatenatedCollectionTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 Penguin Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import PenguinStructures -import XCTest - -final class ConcatenatedCollectionTests: XCTestCase { - - func testConcatenateSetAndList() { - let s = Set(["1", "2", "3"]) - let c = s.concatenated(with: ["10", "11", "12"]) - c.checkCollectionSemantics(expectedValues: Array(s) + ["10", "11", "12"]) - } - - func testConcatenateRanges() { - let c = (0..<3).concatenated(with: 3...6) - c.checkRandomAccessCollectionSemantics(expectedValues: 0...6) - } - - func testConcatenatingEmptyPrefix() { - let c = (0..<0).concatenated(with: [1, 2, 3]) - c.checkRandomAccessCollectionSemantics(expectedValues: 1...3) - } - - func testConcatenatingEmptySuffix() { - let c = (1...3).concatenated(with: 1000..<1000) - c.checkRandomAccessCollectionSemantics(expectedValues: [1, 2, 3]) - } - - static var allTests = [ - ("testConcatenateSetAndList", testConcatenateSetAndList), - ("testConcatenateRanges", testConcatenateRanges), - ("testConcatenatingEmptyPrefix", testConcatenatingEmptyPrefix), - ("testConcatenatingEmptySuffix", testConcatenatingEmptySuffix), - ] -} diff --git a/Tests/PenguinStructuresTests/XCTestManifests.swift b/Tests/PenguinStructuresTests/XCTestManifests.swift index eb5b2fcd..0523eddd 100644 --- a/Tests/PenguinStructuresTests/XCTestManifests.swift +++ b/Tests/PenguinStructuresTests/XCTestManifests.swift @@ -23,7 +23,6 @@ import XCTest testCase(ArrayStorageExtensionTests.allTests), testCase(ArrayStorageTests.allTests), testCase(CollectionAlgorithmTests.allTests), - testCase(ConcatenatedCollectionTests.allTests), testCase(DequeTests.allTests), testCase(DoubleEndedBufferTests.allTests), testCase(EitherCollectionTests.allTests),