From 6eb46cf2d699d901e7575f20f334e466ef66ecfc Mon Sep 17 00:00:00 2001 From: ladvoc <3182119+ladvoc@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:56:09 -0700 Subject: [PATCH] Implement conflict detection --- .../BijectiveDictionary+Conflict.swift | 86 +++++++++++++++++++ .../BijectiveDictionaryTests.swift | 10 +++ 2 files changed, 96 insertions(+) create mode 100644 Sources/BijectiveDictionary/BijectiveDictionary+Conflict.swift diff --git a/Sources/BijectiveDictionary/BijectiveDictionary+Conflict.swift b/Sources/BijectiveDictionary/BijectiveDictionary+Conflict.swift new file mode 100644 index 0000000..4a10c7a --- /dev/null +++ b/Sources/BijectiveDictionary/BijectiveDictionary+Conflict.swift @@ -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? { + 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) + } +} diff --git a/Tests/BijectiveDictionaryTests/BijectiveDictionaryTests.swift b/Tests/BijectiveDictionaryTests/BijectiveDictionaryTests.swift index 430a9c1..70a511d 100644 --- a/Tests/BijectiveDictionaryTests/BijectiveDictionaryTests.swift +++ b/Tests/BijectiveDictionaryTests/BijectiveDictionaryTests.swift @@ -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