Skip to content

Commit

Permalink
Implement conflict detection
Browse files Browse the repository at this point in the history
  • Loading branch information
ladvoc committed Sep 11, 2024
1 parent 7603066 commit 6eb46cf
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 0 deletions.
86 changes: 86 additions & 0 deletions Sources/BijectiveDictionary/BijectiveDictionary+Conflict.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// =============================================================
// File: BijectiveDictionary+Conflict.swift
// Project: BijectiveDictionary
// -------------------------------------------------------------
// Created by Jacob Gelman on 09/11/2024
// Copyright © 2024 Jacob Gelman. All rights reserved.
// =============================================================

extension BijectiveDictionary {

/// The result of a conflict check.
@frozen
public enum Conflict: Hashable {

/// The left value is already present.
case left

/// The right value is already present.
case right

/// Both the left and right values are already present in the same pair.
case pair

/// Both the left and right values are already present across two different pairs.
case both(otherLeft: Left, otherRight: Right)
}

/// Check if a conflict exists between the given pair and the contents of the dictionary that, if inserted,
/// would override an existing pair or break the bijective property.
///
/// - Parameter pair: The left-right pair to perform the conflict check against.
/// - Returns: The conflict, if one exists, or `nil`, indicating neither the left nor right value already
/// exist in the dictionary.
/// - Complexity: O(1)
///
/// The following example demonstrates creating a dictionary mapping element symbols
/// to their atomic numbers and checking to see if a conflict exists:
/// ```swift
/// let dict: BijectiveDictionary = ["Ti": 22, "Si": 14, "He": 2]
///
/// guard let conflict = dict.conflict(for: ("Ti", 2)) else {
/// print("No conflict")
/// return
/// }
/// switch conflict {
/// case .left:
/// print("Symbol already present")
/// case .right:
/// print("Atomic number already present")
/// case .pair:
/// print("Pair already exists")
/// case .both(let otherSymbol, let otherNumber):
/// print("Other symbol: \(otherSymbol), other number: \(otherNumber)")
/// }
/// // prints "Other symbol: He, other number: 22"
/// ```
@inlinable
public func conflict(with pair: Element) -> Conflict? {
let existing = (findByLeft(pair.left), findByRight(pair.right))
return switch existing {
case (nil, nil): nil
case (nil, .some): .right
case (.some, nil): .left
case (.some(let byLeft), .some(let byRight)):
if byLeft.left != byRight.left || byLeft.right != byRight.right {
.both(otherLeft: byRight.left, otherRight: byLeft.right)
} else {
.pair
}
}
}

/// Find a pair in the dictionary by left value.
@inlinable
internal func findByLeft(_ leftValue: Left) -> Element? {

This comment has been minimized.

Copy link
@DandyLyons

DandyLyons Sep 11, 2024

Collaborator

Might be clearer if we named this findPairByLeft(_:)

Also, is this unsafe to use outside the library? Perhaps we should make this public. I don't see a reason not to.

guard let rightValue = self[left: leftValue] else { return nil }
return (leftValue, rightValue)
}

/// Find a pair in the dictionary by right value.
@inlinable
internal func findByRight(_ rightValue: Right) -> Element? {
guard let leftValue = self[right: rightValue] else { return nil }
return (leftValue, rightValue)
}
}
10 changes: 10 additions & 0 deletions Tests/BijectiveDictionaryTests/BijectiveDictionaryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,14 @@ func testAThousand() {
#expect(bijectiveDict[right: rightV] == leftV)
}
}

@Test
func conflict() {
let dict: BijectiveDictionary = ["A": 1, "B": 2, "C": 3]
#expect(dict.conflict(with: ("D", 4)) == nil)
#expect(dict.conflict(with: ("A", 0)) == .left)
#expect(dict.conflict(with: ("E", 1)) == .right)
#expect(dict.conflict(with: ("A", 1)) == .pair)
#expect(dict.conflict(with: ("A", 3)) == .both(otherLeft: "C", otherRight: 1))
}
#endif

0 comments on commit 6eb46cf

Please sign in to comment.