Created
November 10, 2024 15:52
-
-
Save ivanopcode/8251fce762c6206157ef6f545062b219 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License | |
// | |
// Copyright (c) 2024 Ivan Oparin | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
/// # OrderedIDSet | |
/// | |
/// `OrderedIDSet` представляет собой упорядоченное множество элементов, где уникальность элементов определяется их идентификатором `id`. Эта структура данных решает проблему, когда необходимо хранить коллекцию элементов с сохранением порядка вставки, обеспечивая при этом уникальность на основе бизнес-идентификатора, а не хэш-значения элемента. | |
/// | |
/// В стандартной библиотеке Swift `Set` и `OrderedSet`(в пакете Swift Collections) обеспечивают уникальность элементов на основе их хэш-значения, что может быть нежелательно, если требуется уникальность по определенному свойству, например, идентификатору. `OrderedIDSet` использует `Identifiable` протокол для определения уникальности элементов по их `id`, обеспечивая быстрый доступ и модификацию элементов. | |
/// | |
/// ## Основные характеристики: | |
/// - **Упорядоченность**: Элементы сохраняют порядок вставки. | |
/// - **Уникальность по `id`**: Элементы уникальны на основе их идентификатора. | |
/// - **Быстрый доступ**: Использует `OrderedDictionary` для обеспечения эффективного доступа к элементам. | |
/// | |
/// ## Пример использования: | |
/// ```swift | |
/// struct User: Identifiable, Hashable { | |
/// let id: Int | |
/// let name: String | |
/// } | |
/// | |
/// var users = OrderedIDSet<User>() | |
/// users.insert(User(id: 1, name: "Alice")) | |
/// users.insert(User(id: 2, name: "Bob")) | |
/// ``` | |
/// | |
public struct OrderedIDSet<Element: Identifiable & Hashable> { | |
/// Внутреннее хранилище элементов в виде упорядоченного словаря. | |
@usableFromInline | |
internal private(set) var _elements: OrderedDictionary<Element.ID, Element> = [:] | |
/// Количество элементов в множестве. | |
/// | |
/// - Complexity: O(1) | |
public var count: Int { | |
_elements.count | |
} | |
/// Проверяет, является ли множество пустым. | |
/// | |
/// - Complexity: O(1) | |
public var isEmpty: Bool { | |
_elements.isEmpty | |
} | |
/// Представление содержимого этой коллекции только для чтения в виде значения массива. | |
/// | |
/// - Complexity: O(1) | |
public var elements: Array<Element> { | |
_elements.values.elements | |
} | |
/// Возвращает последний элемент упорядоченного множества. | |
/// | |
/// Если упорядоченное множество пусто, возвращает nil. | |
/// | |
/// - Complexity: O(1) | |
public var last: Element? { | |
_elements.values.last | |
} | |
/// Инициализатор с перечислением элементов. | |
/// | |
/// - Parameter elements: Элементы для добавления в множество. | |
/// | |
/// - Complexity: O(*n*), где *n* — количество элементов. | |
public init(elements: Element...) { | |
for element in elements { | |
_elements[element.id] = element | |
} | |
} | |
/// Инициализатор с массивом элементов. | |
/// | |
/// - Parameter elements: Массив элементов для добавления в множество. | |
/// | |
/// - Complexity: O(*n*), где *n* — количество элементов. | |
public init(elements: [Element]) { | |
for element in elements { | |
_elements[element.id] = element | |
} | |
} | |
/// Вставляет элемент в множество. | |
/// | |
/// - Parameter element: Элемент для вставки. | |
/// - Returns: Кортеж, содержащий флаг успешной вставки и элемент после вставки. | |
/// | |
/// - Complexity: Амортизированная O(1) | |
@discardableResult | |
public mutating func insert(_ element: Element) -> (inserted: Bool, memberAfterInsert: Element) { | |
if let existingElement = _elements[element.id] { | |
return (false, existingElement) | |
} else { | |
_elements[element.id] = element | |
return (true, element) | |
} | |
} | |
/// Обновляет элемент в множестве. | |
/// | |
/// - Parameter newMember: Новый элемент для обновления. | |
/// - Returns: Старый элемент, если он существовал. | |
/// | |
/// - Complexity: Амортизированная O(1) | |
// periphery:ignore | |
@discardableResult | |
public mutating func update(with newMember: Element) -> Element? { | |
let oldMember = _elements.updateValue(newMember, forKey: newMember.id) | |
return oldMember | |
} | |
/// Обновляет множество с помощью последовательности элементов. | |
/// | |
/// - Parameter elements: Последовательность элементов для обновления. | |
/// | |
/// - Complexity: O(*n*), где *n* — количество элементов в последовательности. | |
public mutating func update<S: Sequence>(with elements: S) where S.Element == Element { | |
for item in elements { | |
_elements.updateValue(item, forKey: item.id) | |
} | |
} | |
/// Удаляет элемент из множества. | |
/// | |
/// - Parameter member: Элемент для удаления. | |
/// - Returns: Удаленный элемент, если он существовал. | |
/// | |
/// - Complexity: Амортизированная O(1) | |
@discardableResult | |
public mutating func remove(_ member: Element) -> Element? { | |
return _elements.removeValue(forKey: member.id) | |
} | |
/// Удаляет элемент из множества. | |
/// | |
/// - Parameter member: Элемент для удаления. | |
/// - Returns: Удаленный элемент, если он существовал. | |
/// | |
/// - Complexity: Амортизированная O(1) | |
@discardableResult | |
public mutating func remove(byId memberId: Element.ID) -> Element? { | |
return _elements.removeValue(forKey: memberId) | |
} | |
/// Удаляет элементы из коллекции по предоставленному массиву идентификаторов. | |
/// | |
/// - Parameter ids: Массив идентификаторов элементов, которые необходимо удалить. | |
/// | |
/// - Complexity: O(k), где k - количество идентификаторов в массиве `ids`. | |
/// Для каждого идентификатора операция удаления выполняется за O(1). | |
public mutating func removeItems(withIDs ids: [Element.ID]) { | |
for id in ids { | |
_elements.removeValue(forKey: id) | |
} | |
} | |
/// Удаляет все элементы, удовлетворяющие заданному условию. | |
/// | |
/// - Parameter shouldBeRemoved: Замыкание, определяющее, должен ли элемент быть удален. | |
/// | |
/// - Complexity: O(*n*), где *n* — количество элементов в множестве. | |
public mutating func removeAll(where shouldBeRemoved: (Element) -> Bool) { | |
for key in _elements.keys { | |
if let element = _elements[key], shouldBeRemoved(element) { | |
_elements.removeValue(forKey: key) | |
} | |
} | |
} | |
/// Удаляет все элементы из множества. | |
/// | |
/// После вызова этого метода множество становится пустым. | |
/// | |
/// - Complexity: O(1) | |
public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { | |
_elements.removeAll(keepingCapacity: keepCapacity) | |
} | |
/// Проверяет, содержит ли множество заданный элемент. | |
/// | |
/// - Parameter element: Элемент для проверки. | |
/// - Returns: `true`, если элемент существует в множестве, иначе `false`. | |
/// | |
/// - Complexity: O(1) | |
public func contains(_ element: Element) -> Bool { | |
_elements.keys.contains(element.id) | |
} | |
} | |
extension OrderedIDSet { | |
/// Сортирует элементы коллекции на месте в соответствии с предоставленным замыканием сравнения. | |
/// | |
/// - Parameter areInIncreasingOrder: Замыкание, которое определяет порядок сортировки. | |
/// Возвращает `true`, если первый элемент должен располагаться перед вторым | |
/// в отсортированной последовательности. | |
/// | |
/// - Complexity: O(n log n), где n - количество элементов в коллекции. | |
// periphery:ignore | |
mutating func sort(by areInIncreasingOrder: (Element, Element) -> Bool) { | |
let sortedElements = self.elements.sorted(by: areInIncreasingOrder) | |
self = OrderedIDSet(elements: sortedElements) | |
} | |
} | |
extension OrderedIDSet { | |
/// Инициализатор для создания пустого множества | |
/// | |
/// Этот инициализатор эквивалентен инициализации с помощью пустого литерала массива. | |
/// | |
/// - Complexity: O(1) | |
@inlinable | |
public init() { | |
_elements = [:] | |
} | |
} | |
extension OrderedIDSet { | |
// periphery:ignore | |
/// Группирует элементы по заданному ключу. | |
/// | |
/// - Parameter keyForValue: Функция, возвращающая ключ для группировки каждого элемента. | |
/// - Returns: Словарь, где ключи — результаты функции `keyForValue`, а значения — массивы элементов. | |
/// | |
/// - Complexity: O(*n*), где *n* — количество элементов в множестве. | |
public func grouping<Key: Hashable>(by keyForValue: (Element) -> Key) -> [Key: [Element]] { | |
var groupedDictionary: [Key: [Element]] = [:] | |
for element in self { | |
let key = keyForValue(element) | |
groupedDictionary[key, default: []].append(element) | |
} | |
return groupedDictionary | |
} | |
} | |
extension OrderedIDSet { | |
/// Предоставляет доступ к элементу коллекции по его идентификатору. | |
/// | |
/// - Parameter id: Идентификатор искомого элемента. | |
/// - Returns: Элемент с указанным идентификатором, если он существует; в противном случае `nil`. | |
/// | |
/// - Complexity: O(1) | |
// periphery:ignore | |
public subscript(id id: Element.ID) -> Element? { | |
return _elements[id] | |
} | |
} | |
internal extension OrderedIDSet { | |
mutating func updateElements(_ elements: OrderedDictionary<Element.ID, Element>) { | |
_elements = elements | |
} | |
mutating func updateElementById(_ elementId: Element.ID, element: Element) { | |
_elements[elementId] = element | |
} | |
} | |
extension OrderedIDSet: Equatable where Element: Equatable {} | |
extension OrderedIDSet: Hashable where Element: Hashable {} | |
// | |
// OrderedIDSet+Collection.swift | |
import OrderedCollections | |
extension OrderedIDSet: Collection { | |
/// Тип индекса для коллекции. | |
public typealias Index = OrderedDictionary<Element.ID, Element>.Index | |
/// Начальный индекс коллекции. | |
public var startIndex: Index { | |
_elements.elements.startIndex | |
} | |
/// Конечный индекс коллекции. | |
public var endIndex: Index { | |
_elements.elements.endIndex | |
} | |
/// Возвращает индекс после указанного. | |
/// | |
/// - Parameter i: Текущий индекс. | |
/// - Returns: Следующий индекс в последовательности. | |
public func index(after i: Index) -> Index { | |
_elements.elements.index(after: i) | |
} | |
/// Возвращает элемент по индексу. | |
/// | |
/// - Parameter position: Индекс элемента. | |
/// - Returns: Элемент по указанному индексу. | |
public subscript(position: Index) -> Element { | |
_elements.elements[position].value | |
} | |
} | |
extension OrderedIDSet: ExpressibleByArrayLiteral { | |
/// Инициализатор для создания множества из литерала массива. | |
/// | |
/// - Parameter elements: Элементы для добавления. | |
/// | |
/// - Complexity: O(*n*), где *n* — количество элементов. | |
public init(arrayLiteral elements: Element...) { | |
for element in elements { | |
updateElementById(element.id, element: element) | |
} | |
} | |
} | |
import OrderedCollections | |
/// Расширение OrderedIDSet для реализации протокола RandomAccessCollection. | |
/// RandomAccessCollection позволяет осуществлять произвольный доступ к элементам коллекции | |
/// с константным временем O(1). | |
extension OrderedIDSet: RandomAccessCollection { | |
/// Определяет тип индексов, используемый для доступа к элементам коллекции. | |
public typealias Indices = OrderedDictionary<Element.ID, Element>.Values.Indices | |
/// Возвращает индекс, непосредственно предшествующий указанному индексу. | |
/// | |
/// - Parameter i: Действительный индекс коллекции. | |
/// - Returns: Индекс, находящийся непосредственно перед `i`. | |
/// | |
/// - Complexity: O(1) | |
public func index(before i: Index) -> Index { | |
_elements.values.index(before: i) | |
} | |
/// Возвращает индекс, смещенный на указанное расстояние от заданного индекса. | |
/// | |
/// - Parameters: | |
/// - i: Действительный индекс коллекции. | |
/// - distance: Величина смещения от индекса `i`. | |
/// - Returns: Индекс, смещенный на расстояние `distance` от индекса `i`. | |
/// | |
/// - Complexity: O(1) | |
public func index(_ i: Index, offsetBy distance: Int) -> Index { | |
_elements.values.index(i, offsetBy: distance) | |
} | |
/// Возвращает расстояние между двумя индексами. | |
/// | |
/// - Parameters: | |
/// - start: Начальный индекс. | |
/// - end: Конечный индекс. | |
/// - Returns: Количество элементов между индексами `start` и `end`. | |
/// | |
/// - Complexity: O(1) | |
public func distance(from start: Index, to end: Index) -> Int { | |
_elements.values.distance(from: start, to: end) | |
} | |
} | |
// | |
// OrderedIDSet+RangeReplaceableCollection.swift | |
import OrderedCollections | |
extension OrderedIDSet: RangeReplaceableCollection { | |
/// Заменяет элементы в указанном диапазоне новыми элементами. | |
/// | |
/// - Parameters: | |
/// - subrange: Диапазон для замены. | |
/// - newElements: Новые элементы для вставки. | |
/// | |
/// - Complexity: O(*m*) в среднем, где *m* — общее количество элементов в диапазоне и новых элементах. | |
public mutating func replaceSubrange<C: Collection>( | |
_ subrange: Range<Index>, | |
with newElements: C | |
) where C.Element == Element { | |
var updatedDict = OrderedDictionary<Element.ID, Element>() | |
// Копируем элементы до subrange | |
for index in startIndex..<subrange.lowerBound { | |
let element = self[index] | |
updatedDict[element.id] = element | |
} | |
// Вставляем новые элементы | |
for element in newElements { | |
updatedDict[element.id] = element | |
} | |
// Копируем элементы после subrange | |
for index in subrange.upperBound..<endIndex { | |
let element = self[index] | |
updatedDict[element.id] = element | |
} | |
updateElements(updatedDict) | |
} | |
} | |
import Foundation | |
extension OrderedIDSet: Sequence { | |
/// Создает итератор для перебора элементов множества. | |
/// | |
/// - Returns: Итератор по элементам множества. | |
/// | |
/// - Complexity: O(1) | |
public func makeIterator() -> AnyIterator<Element> { | |
var iterator = _elements.values.makeIterator() | |
return AnyIterator { | |
return iterator.next() | |
} | |
} | |
} | |
// OrderedIDSetTests.swift | |
import XCTest | |
@testable import WBCommon | |
final class OrderedIDSetTests: XCTestCase { | |
struct TestElement: Identifiable, Hashable { | |
let id: Int | |
let value: String | |
} | |
func testEmptyInitialization() { | |
let emptySet = OrderedIDSet<TestElement>() | |
XCTAssertTrue(emptySet.isEmpty) | |
XCTAssertEqual(emptySet.count, 0) | |
} | |
func testInitializationWithElements() { | |
let element1 = TestElement(id: 1, value: "One") | |
let element2 = TestElement(id: 2, value: "Two") | |
let set = OrderedIDSet(elements: element1, element2) | |
XCTAssertEqual(set.count, 2) | |
XCTAssertEqual(set.elements, [element1, element2]) | |
} | |
func testInsert() { | |
var set = OrderedIDSet<TestElement>() | |
let element1 = TestElement(id: 1, value: "One") | |
// вставка нового | |
let result1 = set.insert(element1) | |
XCTAssertTrue(result1.inserted) | |
XCTAssertEqual(set.count, 1) | |
// вставка дубликата | |
let result2 = set.insert(element1) | |
XCTAssertFalse(result2.inserted) | |
XCTAssertEqual(set.count, 1) | |
// вставка еще одного нового | |
let element2 = TestElement(id: 2, value: "Two") | |
let result3 = set.insert(element2) | |
XCTAssertTrue(result3.inserted) | |
XCTAssertEqual(set.count, 2) | |
} | |
func testUpdate() { | |
var set = OrderedIDSet<TestElement>() | |
let element1 = TestElement(id: 1, value: "One") | |
let element2 = TestElement(id: 1, value: "Uno") // дубликат по id | |
// обнолвние в пустом сете | |
let oldElement1 = set.update(with: element1) | |
XCTAssertNil(oldElement1) | |
XCTAssertEqual(set.count, 1) | |
// обновление существуюшего | |
let oldElement2 = set.update(with: element2) | |
XCTAssertEqual(oldElement2?.value, "One") | |
XCTAssertEqual(set.count, 1) | |
XCTAssertEqual(set[id: element1.id]?.value, "Uno") | |
} | |
func testUpdateWithSequence() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two") | |
] | |
set.update(with: elements) | |
XCTAssertEqual(set.count, 2) | |
XCTAssertEqual(set.elements, elements) | |
let newElements = [ | |
TestElement(id: 2, value: "Deux"), | |
TestElement(id: 3, value: "Three") | |
] | |
set.update(with: newElements) | |
XCTAssertEqual(set.count, 3) | |
XCTAssertEqual(set[id: 2]?.value, "Deux") | |
XCTAssertNotNil(set[id: 3]) | |
} | |
func testRemove() { | |
var set = OrderedIDSet<TestElement>() | |
let element1 = TestElement(id: 1, value: "One") | |
let element2 = TestElement(id: 2, value: "Two") | |
set.insert(element1) | |
set.insert(element2) | |
// удаление существующего | |
let removedElement = set.remove(element1) | |
XCTAssertEqual(removedElement, element1) | |
XCTAssertEqual(set.count, 1) | |
XCTAssertFalse(set.contains(element1)) | |
// удаление не существующего | |
let removedElementNil = set.remove(element1) | |
XCTAssertNil(removedElementNil) | |
XCTAssertEqual(set.count, 1) | |
} | |
func testRemoveByID() { | |
var set = OrderedIDSet<TestElement>() | |
let element = TestElement(id: 1, value: "One") | |
set.insert(element) | |
let removedElement = set.remove(byId: 1) | |
XCTAssertEqual(removedElement, element) | |
XCTAssertEqual(set.count, 0) | |
} | |
func testRemoveItemsWithIDs() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two"), | |
TestElement(id: 3, value: "Three") | |
] | |
set.update(with: elements) | |
set.removeItems(withIDs: [1, 3]) | |
XCTAssertEqual(set.count, 1) | |
XCTAssertNil(set[id: 1]) | |
XCTAssertNotNil(set[id: 2]) | |
XCTAssertNil(set[id: 3]) | |
} | |
func testRemoveAllWhere() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two"), | |
TestElement(id: 3, value: "Three") | |
] | |
set.update(with: elements) | |
set.removeAll { $0.id % 2 == 0 } // удалить четные | |
XCTAssertEqual(set.count, 2) | |
XCTAssertNotNil(set[id: 1]) | |
XCTAssertNil(set[id: 2]) | |
XCTAssertNotNil(set[id: 3]) | |
} | |
func testContains() { | |
var set = OrderedIDSet<TestElement>() | |
let element = TestElement(id: 1, value: "One") | |
set.insert(element) | |
XCTAssertTrue(set.contains(element)) | |
let otherElement = TestElement(id: 2, value: "Two") | |
XCTAssertFalse(set.contains(otherElement)) | |
} | |
func testSubscriptByID() { | |
var set = OrderedIDSet<TestElement>() | |
let element = TestElement(id: 1, value: "One") | |
set.insert(element) | |
let retrievedElement = set[id: element.id] | |
XCTAssertEqual(retrievedElement, element) | |
let nonExistentElement = set[id: 2] | |
XCTAssertNil(nonExistentElement) | |
} | |
func testIteration() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two"), | |
TestElement(id: 3, value: "Three") | |
] | |
set.update(with: elements) | |
var iteratedElements: [TestElement] = [] | |
for element in set { | |
iteratedElements.append(element) | |
} | |
XCTAssertEqual(iteratedElements, elements) | |
} | |
func testSorting() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 3, value: "Three"), | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two") | |
] | |
set.update(with: elements) | |
set.sort { $0.id < $1.id } | |
XCTAssertEqual(set.elements.map { $0.id }, [1, 2, 3]) | |
} | |
func testRandomAccessCollectionConformance() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two"), | |
TestElement(id: 3, value: "Three") | |
] | |
set.update(with: elements) | |
XCTAssertEqual(set.startIndex, 0) | |
XCTAssertEqual(set.endIndex, 3) | |
XCTAssertEqual(set[0], elements[0]) | |
XCTAssertEqual(set[1], elements[1]) | |
XCTAssertEqual(set[2], elements[2]) | |
let index = set.index(set.startIndex, offsetBy: 2) | |
XCTAssertEqual(set[index], elements[2]) | |
} | |
func testEquatableConformance() { | |
let element1 = TestElement(id: 1, value: "One") | |
let element2 = TestElement(id: 2, value: "Two") | |
var set1 = OrderedIDSet<TestElement>() | |
set1.insert(element1) | |
set1.insert(element2) | |
var set2 = OrderedIDSet<TestElement>() | |
set2.insert(element1) | |
set2.insert(element2) | |
XCTAssertEqual(set1, set2) | |
set2.remove(element2) | |
XCTAssertNotEqual(set1, set2) | |
} | |
func testHashableConformance() { | |
let element1 = TestElement(id: 1, value: "One") | |
let element2 = TestElement(id: 2, value: "Two") | |
var set1 = OrderedIDSet<TestElement>() | |
set1.insert(element1) | |
set1.insert(element2) | |
var set2 = OrderedIDSet<TestElement>() | |
set2.insert(element1) | |
set2.insert(element2) | |
let dict: [OrderedIDSet<TestElement>: String] = [set1: "First"] | |
XCTAssertEqual(dict[set2], "First") | |
} | |
func testExpressibleByArrayLiteral() { | |
let element1 = TestElement(id: 1, value: "One") | |
let element2 = TestElement(id: 2, value: "Two") | |
let set: OrderedIDSet<TestElement> = [element1, element2] | |
XCTAssertEqual(set.count, 2) | |
XCTAssertEqual(set.elements, [element1, element2]) | |
} | |
func testGrouping() { | |
let elements = [ | |
TestElement(id: 1, value: "Odd"), | |
TestElement(id: 2, value: "Even"), | |
TestElement(id: 3, value: "Odd"), | |
TestElement(id: 4, value: "Even") | |
] | |
let set = OrderedIDSet(elements: elements) | |
let grouped = set.grouping { $0.id % 2 == 0 ? "Even" : "Odd" } | |
XCTAssertEqual(grouped["Even"]?.count, 2) | |
XCTAssertEqual(grouped["Odd"]?.count, 2) | |
} | |
func testRemoveAll() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two") | |
] | |
set.update(with: elements) | |
set.removeAll() | |
XCTAssertTrue(set.isEmpty) | |
XCTAssertEqual(set.count, 0) | |
} | |
func testLastProperty() { | |
var set = OrderedIDSet<TestElement>() | |
XCTAssertNil(set.last) | |
let element1 = TestElement(id: 1, value: "One") | |
let element2 = TestElement(id: 2, value: "Two") | |
set.insert(element1) | |
set.insert(element2) | |
XCTAssertEqual(set.last, element2) | |
} | |
func testIndexing() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two"), | |
TestElement(id: 3, value: "Three") | |
] | |
set.update(with: elements) | |
let index = set.firstIndex { $0.id == 2 } | |
XCTAssertEqual(index, 1) | |
XCTAssertEqual(set[index!], elements[1]) | |
} | |
func testReplaceSubrange() { | |
var set = OrderedIDSet<TestElement>() | |
let elements = [ | |
TestElement(id: 1, value: "One"), | |
TestElement(id: 2, value: "Two"), | |
TestElement(id: 3, value: "Three") | |
] | |
set.update(with: elements) | |
let newElements = [ | |
TestElement(id: 2, value: "Deux"), | |
TestElement(id: 4, value: "Four") | |
] | |
set.replaceSubrange(1...2, with: newElements) | |
XCTAssertEqual(set.count, 3) | |
XCTAssertEqual(set.elements.map { $0.id }, [1, 2, 4]) | |
XCTAssertEqual(set[id: 2]?.value, "Deux") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment