-
Notifications
You must be signed in to change notification settings - Fork 400
Expand file tree
/
Copy pathCaseInsensitiveStringSet.swift
More file actions
224 lines (185 loc) · 7.07 KB
/
Copy pathCaseInsensitiveStringSet.swift
File metadata and controls
224 lines (185 loc) · 7.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Collections open source project
//
// Copyright (c) 2026 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
//
// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
//
//===----------------------------------------------------------------------===//
/**
Case-insensitive ordered set of `String`.
This file defines `CaseInsensitiveStringSet`,
a lightweight wrapper around `SkipListedSortedSet` that compares and
stores `String` values using a case-insensitive collation.
Membership, ordering, and set operations all use case-insensitive comparisons.
For example, the strings "apple" and "APPLE" are considered equivalent and
the set will contain at most one of them.
The underlying storage is a `SortedSet` parameterized by a custom `Orderable`
implementation that performs localized,
case-insensitive comparisons with `String.compare(_:options:locale:)` using
the `.caseInsensitive` option.
### Examples
```swift
var set = CaseInsensitiveStringSet()
set.insert("apple")
set.insert("APPLE") // Not added as a distinct element
set.insert("Banana")
// Membership is case-insensitive
set.contains("apple") // true
set.contains("APPLE") // true
set.contains("banana") // true
set.contains("BANANA") // true
// Iteration yields case-insensitive ascending order
// ["apple", "Banana"] (actual stored casing depends on first insertion)
let elements = Array(set)
```
*/
import Foundation
/// A set of unique `String` values compared case-insensitively and kept in
/// sorted order.
///
/// `CaseInsensitiveStringSet` behaves like a regular set,
/// but all equality and ordering checks are performed without regard to
/// letter case.
/// This means inserting any casing variant of an existing element will not
/// increase the set's count,
/// and iteration yields elements in case-insensitive ascending order.
///
/// ### Examples
/// ```swift
/// // Create from a sequence
/// let s1 = CaseInsensitiveStringSet(["a", "B", "b"]) // contains "a", "B"
///
/// // Create from an array literal
/// let s2: CaseInsensitiveStringSet = ["Hello", "WORLD", "world"]
/// // s2.count == 2
///
/// // Insertion returns whether a new element was inserted
/// var s3: CaseInsensitiveStringSet = []
/// let result1 = s3.insert("Swift")
/// result1.inserted // true
/// let result2 = s3.insert("swift")
/// result2.inserted // false (equivalent element already present)
///
/// // Set algebra operations
/// let a: CaseInsensitiveStringSet = ["red", "GREEN"]
/// let b: CaseInsensitiveStringSet = ["Green", "BLUE"]
/// let u = a.union(b) // ["BLUE", "GREEN", "red"]
/// let i = a.intersection(b) // ["GREEN"]
/// let d = a.subtracting(b) // ["red"]
/// let x = a.symmetricDifference(b) // ["BLUE", "red"]
/// ```
public struct CaseInsensitiveStringSet {
/// The element type stored by the set. Always `String`.
public typealias Element = _Ordering.Element
/// Creates a set by wrapping an existing sorted-set implementation.
///
/// - Parameter implementation: The underlying storage configured with the
/// case-insensitive ordering used by this type.
///
/// - Postcondition: This set will have the same elements as `implementation`.
/// - Note: This initializer is internal and primarily intended for bridging
/// with the underlying `SortedSet`.
init(wrapping implementation: _Inner) {
self.inner = implementation
}
/// The underlying storage type.
public typealias _Inner = SkipListedSortedSet<_Ordering>
/// The wrapped storage instance implementing all set semantics.
var inner: _Inner
/// Case-insensitive ordering for `String` elements.
///
/// This `Orderable` implementation defines a total order and equivalence that
/// both use case-insensitive string comparison.
public enum _Ordering: Orderable {
public static func areDecreasing(_ lhs: Element, _ rhs: Element) -> Bool {
lhs.compare(rhs, options: .caseInsensitive, locale: nil)
== .orderedDescending
}
public static func areEquivalent(_ lhs: Element, _ rhs: Element) -> Bool {
lhs.compare(rhs, options: .caseInsensitive, locale: nil) == .orderedSame
}
public static func areIncreasing(_ lhs: Element, _ rhs: Element) -> Bool {
lhs.compare(rhs, options: .caseInsensitive, locale: nil)
== .orderedAscending
}
public typealias Element = String
}
}
extension CaseInsensitiveStringSet: Comparable, Sequence, SetAlgebra {
public static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.inner < rhs.inner
}
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.inner == rhs.inner
}
public func contains(_ member: Element) -> Bool {
return self.inner.contains(member)
}
public mutating func formIntersection(_ other: Self) {
self.inner.formIntersection(other.inner)
}
public mutating func formSymmetricDifference(_ other: __owned Self) {
self.inner.formSymmetricDifference(other.inner)
}
public mutating func formUnion(_ other: __owned Self) {
self.inner.formUnion(other.inner)
}
public init() {
self.init(wrapping: .init())
}
public init(arrayLiteral elements: Element...) {
self.init(elements)
}
public init(_ sequence: __owned some Sequence<Element>) {
self.init(wrapping: .init(sequence))
}
@discardableResult
public mutating func insert(_ newMember: __owned Element) -> (
inserted: Bool, memberAfterInsert: Element
) {
return self.inner.insert(newMember)
}
public func intersection(_ other: Self) -> Self {
return Self(wrapping: self.inner.intersection(other.inner))
}
public func isDisjoint(with other: Self) -> Bool {
return self.inner.isDisjoint(with: other.inner)
}
public var isEmpty: Bool { inner.isEmpty }
public func isSubset(of other: Self) -> Bool {
return self.inner.isSubset(of: other.inner)
}
public func isSuperset(of other: Self) -> Bool {
return self.inner.isSuperset(of: other.inner)
}
public typealias Iterator = _Inner.Iterator
public func makeIterator() -> Iterator {
return inner.makeIterator()
}
@discardableResult
public mutating func remove(_ member: Element) -> Element? {
return self.inner.remove(member)
}
public mutating func subtract(_ other: Self) {
self.inner.subtract(other.inner)
}
public func subtracting(_ other: Self) -> Self {
return Self(wrapping: self.inner.subtracting(other.inner))
}
public func symmetricDifference(_ other: __owned Self) -> Self {
return Self(wrapping: self.inner.symmetricDifference(other.inner))
}
public var underestimatedCount: Int { inner.underestimatedCount }
public func union(_ other: __owned Self) -> Self {
return Self(wrapping: self.inner.union(other.inner))
}
@discardableResult
public mutating func update(with newMember: __owned Element) -> Element? {
return self.inner.update(with: newMember)
}
}