blob: 7329ecb757b3172442fb5ee7bb347a08d53cb1a7 [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2015 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// MARK: Diff application to RangeReplaceableCollection
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) // FIXME(availability-5.1)
extension CollectionDifference {
fileprivate func _fastEnumeratedApply(
_ consume: (Change) -> Void
) {
let totalRemoves = removals.count
let totalInserts = insertions.count
var enumeratedRemoves = 0
var enumeratedInserts = 0
while enumeratedRemoves < totalRemoves || enumeratedInserts < totalInserts {
let change: Change
if enumeratedRemoves < removals.count && enumeratedInserts < insertions.count {
let removeOffset = removals[enumeratedRemoves]._offset
let insertOffset = insertions[enumeratedInserts]._offset
if removeOffset - enumeratedRemoves <= insertOffset - enumeratedInserts {
change = removals[enumeratedRemoves]
} else {
change = insertions[enumeratedInserts]
}
} else if enumeratedRemoves < totalRemoves {
change = removals[enumeratedRemoves]
} else if enumeratedInserts < totalInserts {
change = insertions[enumeratedInserts]
} else {
// Not reached, loop should have exited.
preconditionFailure()
}
consume(change)
switch change {
case .remove(_, _, _):
enumeratedRemoves += 1
case .insert(_, _, _):
enumeratedInserts += 1
}
}
}
}
extension RangeReplaceableCollection {
/// Applies a difference to a collection.
///
/// - Parameter difference: The difference to be applied.
///
/// - Returns: An instance representing the state of the receiver with the
/// difference applied, or `nil` if the difference is incompatible with
/// the receiver's state.
///
/// - Complexity: O(*n* + *c*), where *n* is `self.count` and *c* is the
/// number of changes contained by the parameter.
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) // FIXME(availability-5.1)
public func applying(_ difference: CollectionDifference<Element>) -> Self? {
var result = Self()
var enumeratedRemoves = 0
var enumeratedInserts = 0
var enumeratedOriginals = 0
var currentIndex = self.startIndex
func append(
into target: inout Self,
contentsOf source: Self,
from index: inout Self.Index, count: Int
) {
let start = index
source.formIndex(&index, offsetBy: count)
target.append(contentsOf: source[start..<index])
}
difference._fastEnumeratedApply { change in
switch change {
case .remove(offset: let offset, element: _, associatedWith: _):
let origCount = offset - enumeratedOriginals
append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
enumeratedOriginals += origCount + 1 // Removal consumes an original element
currentIndex = self.index(after: currentIndex)
enumeratedRemoves += 1
case .insert(offset: let offset, element: let element, associatedWith: _):
let origCount = (offset + enumeratedRemoves - enumeratedInserts) - enumeratedOriginals
append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
result.append(element)
enumeratedOriginals += origCount
enumeratedInserts += 1
}
_internalInvariant(enumeratedOriginals <= self.count)
}
let origCount = self.count - enumeratedOriginals
append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
_internalInvariant(currentIndex == self.endIndex)
_internalInvariant(enumeratedOriginals + origCount == self.count)
_internalInvariant(result.count == self.count + enumeratedInserts - enumeratedRemoves)
return result
}
}
// MARK: Definition of API
extension BidirectionalCollection {
/// Returns the difference needed to produce the receiver's state from the
/// parameter's state, using the provided closure to establish equivalence
/// between elements.
///
/// This function does not infer moves.
///
/// - Parameters:
/// - other: The base state.
/// - areEquivalent: A closure that returns whether the two
/// parameters are equivalent.
///
/// - Returns: The difference needed to produce the reciever's state from
/// the parameter's state.
///
/// - Complexity: For pathological inputs, worst case performance is
/// O(`self.count` * `other.count`). Faster execution can be expected
/// when the collections share many common elements.
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) // FIXME(availability-5.1)
public func difference<C: BidirectionalCollection>(
from other: C,
by areEquivalent: (Element, C.Element) -> Bool
) -> CollectionDifference<Element>
where C.Element == Self.Element {
var rawChanges: [CollectionDifference<Element>.Change] = []
let source = _CountingIndexCollection(other)
let target = _CountingIndexCollection(self)
let result = _CollectionChanges(from: source, to: target, by: areEquivalent)
for change in result {
switch change {
case let .removed(r):
for i in source.indices[r] {
rawChanges.append(
.remove(
offset: i.offset!,
element: source[i],
associatedWith: nil))
}
case let .inserted(r):
for i in target.indices[r] {
rawChanges.append(
.insert(
offset: i.offset!,
element: target[i],
associatedWith: nil))
}
case .matched: break
}
}
return CollectionDifference<Element>(_validatedChanges: rawChanges)
}
}
extension BidirectionalCollection where Element : Equatable {
/// Returns the difference needed to produce the receiver's state from the
/// parameter's state, using equality to establish equivalence between
/// elements.
///
/// This function does not infer element moves, but they can be computed
/// using `CollectionDifference.inferringMoves()` if desired.
///
/// - Parameters:
/// - other: The base state.
///
/// - Returns: The difference needed to produce the reciever's state from
/// the parameter's state.
///
/// - Complexity: For pathological inputs, worst case performance is
/// O(`self.count` * `other.count`). Faster execution can be expected
/// when the collections share many common elements, or if `Element`
/// also conforms to `Hashable`.
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) // FIXME(availability-5.1)
public func difference<C: BidirectionalCollection>(
from other: C
) -> CollectionDifference<Element> where C.Element == Self.Element {
return difference(from: other, by: ==)
}
}
extension BidirectionalCollection {
/// Returns a pair of subsequences containing the initial elements that
/// `self` and `other` have in common.
fileprivate func _commonPrefix<Other: BidirectionalCollection>(
with other: Other,
by areEquivalent: (Element, Other.Element) -> Bool
) -> (SubSequence, Other.SubSequence)
where Element == Other.Element {
let (s1, s2) = (startIndex, other.startIndex)
let (e1, e2) = (endIndex, other.endIndex)
var (i1, i2) = (s1, s2)
while i1 != e1 && i2 != e2 {
if !areEquivalent(self[i1], other[i2]) { break }
formIndex(after: &i1)
other.formIndex(after: &i2)
}
return (self[s1..<i1], other[s2..<i2])
}
}
// MARK: Diff production from OrderedCollection (soon to be BidirectionalCollection)
/// A collection of changes between a source and target collection.
///
/// It can be used to traverse the [longest common subsequence][lcs] of
/// source and target:
///
/// let changes = CollectionChanges(from: source, to target)
/// for case let .match(s, t) in changes {
/// // use `s`, `t`
/// }
///
/// It can also be used to traverse the [shortest edit script][ses] of
/// remove and insert operations:
///
/// let changes = CollectionChanges(from: source, to target)
/// for c in changes {
/// switch c {
/// case let .removed(s):
/// // use `s`
/// case let .inserted(t):
/// // use `t`
/// case .matched: continue
/// }
/// }
///
/// [lcs]: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
/// [ses]: http://en.wikipedia.org/wiki/Edit_distance
///
/// - Note: `CollectionChanges` holds a reference to state used to run the
/// difference algorithm, which can be exponentially larger than the
/// changes themselves.
fileprivate struct _CollectionChanges<SourceIndex, TargetIndex>
where SourceIndex: Comparable, TargetIndex: Comparable {
fileprivate typealias Endpoint = (x: SourceIndex, y: TargetIndex)
/// An encoding of change elements as an array of index pairs stored in
/// `pathStorage[pathStartIndex...]`.
///
/// This encoding allows the same storage to be used to run the difference
/// algorithm, report the result, and repeat in place using
/// `formChanges`.
///
/// The collection of changes between XABCD and XYCD is:
///
/// [.match(0..<1, 0..<1), .remove(1..<3), .insert(1..<2),
/// .match(3..<5, 2..<4)]
///
/// Which gets encoded as:
///
/// [(0, 0), (1, 1), (3, 1), (3, 2), (5, 4)]
///
/// You can visualize it as a two-dimensional path composed of remove
/// (horizontal), insert (vertical), and match (diagonal) segments:
///
/// X A B C D
/// X \ _ _
/// Y |
/// C \
/// D \
///
private var pathStorage: [Endpoint]
/// The index in `pathStorage` of the first segment in the difference path.
private var pathStartIndex: Int
/// Creates a collection of changes from a difference path.
fileprivate init(
pathStorage: [Endpoint], pathStartIndex: Int
) {
self.pathStorage = pathStorage
self.pathStartIndex = pathStartIndex
}
/// Creates an empty collection of changes, i.e. the changes between two
/// empty collections.
private init() {
self.pathStorage = []
self.pathStartIndex = 0
}
}
extension _CollectionChanges {
/// A range of elements removed from the source, inserted in the target, or
/// that the source and target have in common.
fileprivate enum Element {
case removed(Range<SourceIndex>)
case inserted(Range<TargetIndex>)
case matched(Range<SourceIndex>, Range<TargetIndex>)
}
}
extension _CollectionChanges: RandomAccessCollection {
fileprivate typealias Index = Int
fileprivate var startIndex: Index {
return 0
}
fileprivate var endIndex: Index {
return Swift.max(0, pathStorage.endIndex - pathStartIndex - 1)
}
fileprivate func index(after i: Index) -> Index {
return i + 1
}
fileprivate subscript(position: Index) -> Element {
_internalInvariant((startIndex..<endIndex).contains(position))
let current = pathStorage[position + pathStartIndex]
let next = pathStorage[position + pathStartIndex + 1]
if current.x != next.x && current.y != next.y {
return .matched(current.x..<next.x, current.y..<next.y)
} else if current.x != next.x {
return .removed(current.x..<next.x)
} else { // current.y != next.y
return .inserted(current.y..<next.y)
}
}
}
extension _CollectionChanges: CustomStringConvertible {
fileprivate var description: String {
return _makeCollectionDescription()
}
}
extension _CollectionChanges {
/// Creates the collection of changes between `source` and `target`.
///
/// - Runtime: O(*n* * *d*), where *n* is `source.count + target.count` and
/// *d* is the minimal number of inserted and removed elements.
/// - Space: O(*d* * *d*), where *d* is the minimal number of inserted and
/// removed elements.
fileprivate
init<Source: BidirectionalCollection, Target: BidirectionalCollection>(
from source: Source,
to target: Target,
by areEquivalent: (Source.Element, Target.Element) -> Bool
) where
Source.Element == Target.Element,
Source.Index == SourceIndex,
Target.Index == TargetIndex
{
self.init()
formChanges(from: source, to: target, by: areEquivalent)
}
/// Replaces `self` with the collection of changes between `source`
/// and `target`.
///
/// - Runtime: O(*n* * *d*), where *n* is `source.count + target.count` and
/// *d* is the minimal number of inserted and removed elements.
/// - Space: O(*d*²), where *d* is the minimal number of inserted and
/// removed elements.
private mutating func formChanges<
Source: BidirectionalCollection,
Target: BidirectionalCollection
>(
from source: Source,
to target: Target,
by areEquivalent: (Source.Element, Target.Element) -> Bool
) where
Source.Element == Target.Element,
Source.Index == SourceIndex,
Target.Index == TargetIndex
{
let pathStart = (x: source.startIndex, y: target.startIndex)
let pathEnd = (x: source.endIndex, y: target.endIndex)
let matches = source._commonPrefix(with: target, by: areEquivalent)
let (x, y) = (matches.0.endIndex, matches.1.endIndex)
if pathStart == pathEnd {
pathStorage.removeAll(keepingCapacity: true)
pathStartIndex = 0
} else if x == pathEnd.x || y == pathEnd.y {
pathStorage.removeAll(keepingCapacity: true)
pathStorage.append(pathStart)
if pathStart != (x, y) && pathEnd != (x, y) {
pathStorage.append((x, y))
}
pathStorage.append(pathEnd)
pathStartIndex = 0
} else {
formChangesCore(from: source, to: target, x: x, y: y, by: areEquivalent)
}
}
/// The core difference algorithm.
///
/// - Precondition: There is at least one difference between `a` and `b`
/// - Runtime: O(*n* * *d*), where *n* is `a.count + b.count` and
/// *d* is the number of inserts and removes.
/// - Space: O(*d* * *d*), where *d* is the number of inserts and removes.
private mutating func formChangesCore<
Source: BidirectionalCollection,
Target: BidirectionalCollection
>(
from a: Source,
to b: Target,
x: Source.Index,
y: Target.Index,
by areEquivalent: (Source.Element, Target.Element) -> Bool
) where
Source.Element == Target.Element,
Source.Index == SourceIndex,
Target.Index == TargetIndex
{
// Written to correspond, as closely as possible, to the psuedocode in
// Myers, E. "An O(ND) Difference Algorithm and Its Variations".
//
// See "FIGURE 2: The Greedy LCS/SES Algorithm" on p. 6 of the [paper].
//
// Note the following differences from the psuedocode in FIGURE 2:
//
// 1. FIGURE 2 relies on both *A* and *B* being Arrays. In a generic
// context, it isn't true that *y = x - k*, as *x*, *y*, *k* could
// all be different types, so we store both *x* and *y* in *V*.
// 2. FIGURE 2 only reports the length of the LCS/SES. Reporting a
// solution path requires storing a copy of *V* (the search frontier)
// after each iteration of the outer loop.
// 3. FIGURE 2 stops the search after *MAX* iterations. We run the loop
// until a solution is found. We also guard against incrementing past
// the end of *A* and *B*, both to satisfy the termination condition
// and because that would violate preconditions on collection.
//
// [paper]: http://www.xmailserver.org/diff2.pdf
var (x, y) = (x, y)
let (n, m) = (a.endIndex, b.endIndex)
var v = _SearchState<Source.Index, Target.Index>(consuming: &pathStorage)
v.appendFrontier(repeating: (x, y))
var d = 1
var delta = 0
outer: while true {
v.appendFrontier(repeating: (n, m))
for k in stride(from: -d, through: d, by: 2) {
if k == -d || (k != d && v[d - 1, k - 1].x < v[d - 1, k + 1].x) {
(x, y) = v[d - 1, k + 1]
if y != m { b.formIndex(after: &y) }
} else {
(x, y) = v[d - 1, k - 1]
if x != n { a.formIndex(after: &x) }
}
let matches = a[x..<n]._commonPrefix(with: b[y..<m], by: areEquivalent)
(x, y) = (matches.0.endIndex, matches.1.endIndex)
v[d, k] = (x, y)
if x == n && y == m {
delta = k
break outer
}
}
d += 1
}
self = v.removeCollectionChanges(a: a, b: b, d: d, delta: delta)
}
}
/// The search paths being explored.
fileprivate struct _SearchState<
SourceIndex: Comparable,
TargetIndex: Comparable
> {
fileprivate typealias Endpoint = (x: SourceIndex, y: TargetIndex)
/// The search frontier for each iteration.
///
/// The nth iteration of the core algorithm requires storing n + 1 search
/// path endpoints. Thus, the shape of the storage required is a triangle.
private var endpoints = _LowerTriangularMatrix<Endpoint>()
/// Creates an instance, taking the capacity of `storage` for itself.
///
/// - Postcondition: `storage` is empty.
fileprivate init(consuming storage: inout [Endpoint]) {
storage.removeAll(keepingCapacity: true)
swap(&storage, &endpoints.storage)
}
/// Returns the endpoint of the search frontier for iteration `d` on
/// diagonal `k`.
fileprivate subscript(d: Int, k: Int) -> Endpoint {
get {
_internalInvariant((-d...d).contains(k))
_internalInvariant((d + k) % 2 == 0)
return endpoints[d, (d + k) / 2]
}
set {
_internalInvariant((-d...d).contains(k))
_internalInvariant((d + k) % 2 == 0)
endpoints[d, (d + k) / 2] = newValue
}
}
/// Adds endpoints initialized to `repeatedValue` for the search frontier of
/// the next iteration.
fileprivate mutating func appendFrontier(repeating repeatedValue: Endpoint) {
endpoints.appendRow(repeating: repeatedValue)
}
}
extension _SearchState {
/// Removes and returns `_CollectionChanges`, leaving `_SearchState` empty.
///
/// - Precondition: There is at least one difference between `a` and `b`
fileprivate mutating func removeCollectionChanges<
Source: BidirectionalCollection,
Target: BidirectionalCollection
>(
a: Source,
b: Target,
d: Int,
delta: Int
) -> _CollectionChanges<Source.Index, Target.Index>
where Source.Index == SourceIndex, Target.Index == TargetIndex
{
// Calculating the difference path is very similar to running the core
// algorithm in reverse:
//
// var k = delta
// for d in (1...d).reversed() {
// if k == -d || (k != d && self[d - 1, k - 1].x < self[d - 1, k + 1].x) {
// // insert of self[d - 1, k + 1].y
// k += 1
// } else {
// // remove of self[d - 1, k - 1].x
// k -= 1
// }
// }
//
// It is more complicated below because:
//
// 1. We want to include segments for matches
// 2. We want to coallesce consecutive like segments
// 3. We don't want to allocate, so we're overwriting the elements of
// endpoints.storage we've finished reading.
let pathStart = (a.startIndex, b.startIndex)
let pathEnd = (a.endIndex, b.endIndex)
// `endpoints.storage` may need space for an additional element in order
// to store the difference path when `d == 1`.
//
// `endpoints.storage` has `(d + 1) * (d + 2) / 2` elements stored,
// but a difference path requires up to `2 + d * 2` elements[^1].
//
// If `d == 1`:
//
// (1 + 1) * (1 + 2) / 2 < 2 + 1 * 2
// 3 < 4
//
// `d == 1` is the only special case because:
//
// - It's a precondition that `d > 0`.
// - Once `d >= 2` `endpoints.storage` will have sufficient space:
//
// (d + 1) * (d + 2) / 2 = 2 + d * 2
// d * d - d - 2 = 0
// (d - 2) * (d + 1) = 0
// d = 2; d = -1
//
// [1]: An endpoint for every remove, insert, and match segment. (Recall
// *d* is the minimal number of inserted and removed elements). If there
// are no consecutive removes or inserts and every remove or insert is
// sandwiched between matches, the path will need `2 + d * 2` elements.
_internalInvariant(d > 0, "Must be at least one difference between `a` and `b`")
if d == 1 {
endpoints.storage.append(pathEnd)
}
var i = endpoints.storage.endIndex - 1
// `isInsertion` tracks whether the element at `endpoints.storage[i]`
// is an insertion (`true`), a removal (`false`), or a match (`nil`).
var isInsertion: Bool? = nil
var k = delta
endpoints.storage[i] = pathEnd
for d in (1...d).reversed() {
if k == -d || (k != d && self[d - 1, k - 1].x < self[d - 1, k + 1].x) {
let (x, y) = self[d - 1, k + 1]
// There was match before this insert, so add a segment.
if x != endpoints.storage[i].x {
i -= 1; endpoints.storage[i] = (x, b.index(after: y))
isInsertion = nil
}
// If the previous segment is also an insert, overwrite it.
if isInsertion != .some(true) { i -= 1 }
endpoints.storage[i] = (x, y)
isInsertion = true
k += 1
} else {
let (x, y) = self[d - 1, k - 1]
// There was a match before this remove, so add a segment.
if y != endpoints.storage[i].y {
i -= 1; endpoints.storage[i] = (a.index(after: x), y)
isInsertion = nil
}
// If the previous segment is also a remove, overwrite it.
if isInsertion != .some(false) { i -= 1 }
endpoints.storage[i] = (x, y)
isInsertion = false
k -= 1
}
}
if pathStart != endpoints.storage[i] {
i -= 1; endpoints.storage[i] = pathStart
}
let pathStorage = endpoints.storage
endpoints.storage = []
return _CollectionChanges(pathStorage: pathStorage, pathStartIndex: i)
}
}
/// An index that counts its offset from the start of its collection.
private struct _CountingIndex<Base: Comparable>: Equatable {
/// The position in the underlying collection.
let base: Base
/// The offset from the start index of the collection or `nil` if `self` is
/// the end index.
let offset: Int?
}
extension _CountingIndex: Comparable {
fileprivate static func <(lhs: _CountingIndex, rhs: _CountingIndex) -> Bool {
return (lhs.base, lhs.offset ?? Int.max) < (rhs.base, rhs.offset ?? Int.max)
}
}
/// A collection that counts the offset of its indices from its start index.
///
/// You can use `_CountingIndexCollection` with algorithms on `Collection` to
/// calculate offsets of significance:
///
/// if let i = _CountingIndexCollection("Café").index(of: "f") {
/// print(i.offset)
/// }
/// // Prints "2"
///
/// - Note: The offset of `endIndex` is `nil`
private struct _CountingIndexCollection<Base: BidirectionalCollection> {
private let base: Base
fileprivate init(_ base: Base) {
self.base = base
}
}
extension _CountingIndexCollection : BidirectionalCollection {
fileprivate typealias Index = _CountingIndex<Base.Index>
fileprivate typealias Element = Base.Element
fileprivate var startIndex: Index {
return Index(base: base.startIndex, offset: base.isEmpty ? nil : 0)
}
fileprivate var endIndex: Index {
return Index(base: base.endIndex, offset: nil)
}
fileprivate func index(after i: Index) -> Index {
let next = base.index(after: i.base)
return Index(
base: next, offset: next == base.endIndex ? nil : i.offset! + 1)
}
fileprivate func index(before i: Index) -> Index {
let prev = base.index(before: i.base)
return Index(
base: prev, offset: prev == base.endIndex ? nil : i.offset! + 1)
}
fileprivate subscript(position: Index) -> Element {
return base[position.base]
}
}
/// Returns the nth [triangular number].
///
/// [triangular number]: https://en.wikipedia.org/wiki/Triangular_number
fileprivate func _triangularNumber(_ n: Int) -> Int {
return n * (n + 1) / 2
}
/// A square matrix that only provides subscript access to elements on, or
/// below, the main diagonal.
///
/// A [lower triangular matrix] can be dynamically grown:
///
/// var m = _LowerTriangularMatrix<Int>()
/// m.appendRow(repeating: 1)
/// m.appendRow(repeating: 2)
/// m.appendRow(repeating: 3)
///
/// assert(Array(m.rowMajorOrder) == [
/// 1,
/// 2, 2,
/// 3, 3, 3,
/// ])
///
/// [lower triangular matrix]: http://en.wikipedia.org/wiki/Triangular_matrix
fileprivate struct _LowerTriangularMatrix<Element> {
/// The matrix elements stored in [row major order][rmo].
///
/// [rmo]: http://en.wikipedia.org/wiki/Row-_and_column-major_order
fileprivate var storage: [Element] = []
/// The dimension of the matrix.
///
/// Being a square matrix, the number of rows and columns are equal.
fileprivate var dimension: Int = 0
fileprivate subscript(row: Int, column: Int) -> Element {
get {
_internalInvariant((0...row).contains(column))
return storage[_triangularNumber(row) + column]
}
set {
_internalInvariant((0...row).contains(column))
storage[_triangularNumber(row) + column] = newValue
}
}
fileprivate mutating func appendRow(repeating repeatedValue: Element) {
dimension += 1
storage.append(contentsOf: repeatElement(repeatedValue, count: dimension))
}
}
extension _LowerTriangularMatrix {
/// A collection that visits the elements in the matrix in [row major
/// order][rmo].
///
/// [rmo]: http://en.wikipedia.org/wiki/Row-_and_column-major_order
fileprivate struct RowMajorOrder : RandomAccessCollection {
private var base: _LowerTriangularMatrix
fileprivate init(base: _LowerTriangularMatrix) {
self.base = base
}
fileprivate var startIndex: Int {
return base.storage.startIndex
}
fileprivate var endIndex: Int {
return base.storage.endIndex
}
fileprivate func index(after i: Int) -> Int {
return i + 1
}
fileprivate func index(before i: Int) -> Int {
return i - 1
}
fileprivate subscript(position: Int) -> Element {
return base.storage[position]
}
}
fileprivate var rowMajorOrder: RowMajorOrder {
return RowMajorOrder(base: self)
}
fileprivate subscript(row r: Int) -> Slice<RowMajorOrder> {
return rowMajorOrder[_triangularNumber(r)..<_triangularNumber(r + 1)]
}
}
extension _LowerTriangularMatrix: CustomStringConvertible {
fileprivate var description: String {
var rows: [[Element]] = []
for row in 0..<dimension {
rows.append(Array(self[row: row]))
}
return String(describing: rows)
}
}