Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UnionEdgeGraph, Either, ConcatenatedCollection and MutableGraph.addEdges #84

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion Sources/PenguinGraphs/GraphCopying.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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|)
Expand Down Expand Up @@ -165,3 +164,79 @@ 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<Other: IncidenceGraph & VertexListGraph>(
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<Other: IncidenceGraph & VertexListGraph>(
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<Other: IncidenceGraph & PropertyGraph & VertexListGraph>(
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)
}
}
}
}
184 changes: 184 additions & 0 deletions Sources/PenguinStructures/Concatenation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// 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<First: Collection, Second: Collection>: 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<First.Index, Second.Index>

/// 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<Other: Collection>(with other: Other)
-> Concatenation<Self, Other>
where Other.Element == Element {
return Concatenation(self, other)
}
}
6 changes: 6 additions & 0 deletions Sources/PenguinStructures/DefaultInitializable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Loading