-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathOptionSet.swift
372 lines (357 loc) · 15 KB
/
OptionSet.swift
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
//===--- OptionSet.swift --------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
/// A type that presents a mathematical set interface to a bit set.
///
/// You use the `OptionSet` protocol to represent bitset types, where
/// individual bits represent members of a set. Adopting this protocol in
/// your custom types lets you perform set-related operations such as
/// membership tests, unions, and intersections on those types. What's more,
/// when implemented using specific criteria, adoption of this protocol
/// requires no extra work on your part.
///
/// When creating an option set, include a `rawValue` property in your type
/// declaration. For your type to automatically receive default implementations
/// for set-related operations, the `rawValue` property must be of a type that
/// conforms to the `FixedWidthInteger` protocol, such as `Int` or `UInt8`.
/// Next, create unique options as static properties of your custom type using
/// unique powers of two (1, 2, 4, 8, 16, and so forth) for each individual
/// property's raw value so that each property can be represented by a single
/// bit of the type's raw value.
///
/// For example, consider a custom type called `ShippingOptions` that is an
/// option set of the possible ways to ship a customer's purchase.
/// `ShippingOptions` includes a `rawValue` property of type `Int` that stores
/// the bit mask of available shipping options. The static members `nextDay`,
/// `secondDay`, `priority`, and `standard` are unique, individual options.
///
/// struct ShippingOptions: OptionSet {
/// let rawValue: Int
///
/// static let nextDay = ShippingOptions(rawValue: 1 << 0)
/// static let secondDay = ShippingOptions(rawValue: 1 << 1)
/// static let priority = ShippingOptions(rawValue: 1 << 2)
/// static let standard = ShippingOptions(rawValue: 1 << 3)
///
/// static let express: ShippingOptions = [.nextDay, .secondDay]
/// static let all: ShippingOptions = [.express, .priority, .standard]
/// }
///
/// Declare additional preconfigured option set values as static properties
/// initialized with an array literal containing other option values. In the
/// example, because the `express` static property is assigned an array
/// literal with the `nextDay` and `secondDay` options, it will contain those
/// two elements.
///
/// Using an Option Set Type
/// ========================
///
/// When you need to create an instance of an option set, assign one of the
/// type's static members to your variable or constant. Alternatively, to
/// create an option set instance with multiple members, assign an array
/// literal with multiple static members of the option set. To create an empty
/// instance, assign an empty array literal to your variable.
///
/// let singleOption: ShippingOptions = .priority
/// let multipleOptions: ShippingOptions = [.nextDay, .secondDay, .priority]
/// let noOptions: ShippingOptions = []
///
/// Use set-related operations to check for membership and to add or remove
/// members from an instance of your custom option set type. The following
/// example shows how you can determine free shipping options based on a
/// customer's purchase price:
///
/// let purchasePrice = 87.55
///
/// var freeOptions: ShippingOptions = []
/// if purchasePrice > 50 {
/// freeOptions.insert(.priority)
/// }
///
/// if freeOptions.contains(.priority) {
/// print("You've earned free priority shipping!")
/// } else {
/// print("Add more to your cart for free priority shipping!")
/// }
/// // Prints "You've earned free priority shipping!"
public protocol OptionSet : SetAlgebra, RawRepresentable {
// We can't constrain the associated Element type to be the same as
// Self, but we can do almost as well with a default and a
// constrained extension
/// The element type of the option set.
///
/// To inherit all the default implementations from the `OptionSet` protocol,
/// the `Element` type must be `Self`, the default.
associatedtype Element = Self
// FIXME: This initializer should just be the failable init from
// RawRepresentable. Unfortunately, current language limitations
// that prevent non-failable initializers from forwarding to
// failable ones would prevent us from generating the non-failing
// default (zero-argument) initializer. Since OptionSet's main
// purpose is to create convenient conformances to SetAlgebra,
// we opt for a non-failable initializer.
/// Creates a new option set from the given raw value.
///
/// This initializer always succeeds, even if the value passed as `rawValue`
/// exceeds the static properties declared as part of the option set. This
/// example creates an instance of `ShippingOptions` with a raw value beyond
/// the highest element, with a bit mask that effectively contains all the
/// declared static members.
///
/// let extraOptions = ShippingOptions(rawValue: 255)
/// print(extraOptions.isStrictSuperset(of: .all))
/// // Prints "true"
///
/// - Parameter rawValue: The raw value of the option set to create. Each bit
/// of `rawValue` potentially represents an element of the option set,
/// though raw values may include bits that are not defined as distinct
/// values of the `OptionSet` type.
init(rawValue: RawValue)
}
/// `OptionSet` requirements for which default implementations
/// are supplied.
///
/// - Note: A type conforming to `OptionSet` can implement any of
/// these initializers or methods, and those implementations will be
/// used in lieu of these defaults.
extension OptionSet {
/// Returns a new option set of the elements contained in this set, in the
/// given set, or in both.
///
/// This example uses the `union(_:)` method to add two more shipping options
/// to the default set.
///
/// let defaultShipping = ShippingOptions.standard
/// let memberShipping = defaultShipping.union([.secondDay, .priority])
/// print(memberShipping.contains(.priority))
/// // Prints "true"
///
/// - Parameter other: An option set.
/// - Returns: A new option set made up of the elements contained in this
/// set, in `other`, or in both.
@inlinable // generic-performance
public func union(_ other: Self) -> Self {
var r: Self = Self(rawValue: self.rawValue)
r.formUnion(other)
return r
}
/// Returns a new option set with only the elements contained in both this
/// set and the given set.
///
/// This example uses the `intersection(_:)` method to limit the available
/// shipping options to what can be used with a PO Box destination.
///
/// // Can only ship standard or priority to PO Boxes
/// let poboxShipping: ShippingOptions = [.standard, .priority]
/// let memberShipping: ShippingOptions =
/// [.standard, .priority, .secondDay]
///
/// let availableOptions = memberShipping.intersection(poboxShipping)
/// print(availableOptions.contains(.priority))
/// // Prints "true"
/// print(availableOptions.contains(.secondDay))
/// // Prints "false"
///
/// - Parameter other: An option set.
/// - Returns: A new option set with only the elements contained in both this
/// set and `other`.
@inlinable // generic-performance
public func intersection(_ other: Self) -> Self {
var r = Self(rawValue: self.rawValue)
r.formIntersection(other)
return r
}
/// Returns a new option set with the elements contained in this set or in
/// the given set, but not in both.
///
/// - Parameter other: An option set.
/// - Returns: A new option set with only the elements contained in either
/// this set or `other`, but not in both.
@inlinable // generic-performance
public func symmetricDifference(_ other: Self) -> Self {
var r = Self(rawValue: self.rawValue)
r.formSymmetricDifference(other)
return r
}
}
/// `OptionSet` requirements for which default implementations are
/// supplied when `Element == Self`, which is the default.
///
/// - Note: A type conforming to `OptionSet` can implement any of
/// these initializers or methods, and those implementations will be
/// used in lieu of these defaults.
extension OptionSet where Element == Self {
/// Returns a Boolean value that indicates whether a given element is a
/// member of the option set.
///
/// This example uses the `contains(_:)` method to check whether next-day
/// shipping is in the `availableOptions` instance.
///
/// let availableOptions = ShippingOptions.express
/// if availableOptions.contains(.nextDay) {
/// print("Next day shipping available")
/// }
/// // Prints "Next day shipping available"
///
/// - Parameter member: The element to look for in the option set.
/// - Returns: `true` if the option set contains `member`; otherwise,
/// `false`.
@inlinable // generic-performance
public func contains(_ member: Self) -> Bool {
return self.isSuperset(of: member)
}
/// Adds the given element to the option set if it is not already a member.
///
/// In the following example, the `.secondDay` shipping option is added to
/// the `freeOptions` option set if `purchasePrice` is greater than 50.0. For
/// the `ShippingOptions` declaration, see the `OptionSet` protocol
/// discussion.
///
/// let purchasePrice = 87.55
///
/// var freeOptions: ShippingOptions = [.standard, .priority]
/// if purchasePrice > 50 {
/// freeOptions.insert(.secondDay)
/// }
/// print(freeOptions.contains(.secondDay))
/// // Prints "true"
///
/// - Parameter newMember: The element to insert.
/// - Returns: `(true, newMember)` if `newMember` was not contained in
/// `self`. Otherwise, returns `(false, oldMember)`, where `oldMember` is
/// the member of the set equal to `newMember`.
@inlinable // generic-performance
@discardableResult
public mutating func insert(
_ newMember: Element
) -> (inserted: Bool, memberAfterInsert: Element) {
let oldMember = self.intersection(newMember)
let shouldInsert = oldMember != newMember
let result = (
inserted: shouldInsert,
memberAfterInsert: shouldInsert ? newMember : oldMember)
if shouldInsert {
self.formUnion(newMember)
}
return result
}
/// Removes the given element and all elements subsumed by it.
///
/// In the following example, the `.priority` shipping option is removed from
/// the `options` option set. Attempting to remove the same shipping option
/// a second time results in `nil`, because `options` no longer contains
/// `.priority` as a member.
///
/// var options: ShippingOptions = [.secondDay, .priority]
/// let priorityOption = options.remove(.priority)
/// print(priorityOption == .priority)
/// // Prints "true"
///
/// print(options.remove(.priority))
/// // Prints "nil"
///
/// In the next example, the `.express` element is passed to `remove(_:)`.
/// Although `.express` is not a member of `options`, `.express` subsumes
/// the remaining `.secondDay` element of the option set. Therefore,
/// `options` is emptied and the intersection between `.express` and
/// `options` is returned.
///
/// let expressOption = options.remove(.express)
/// print(expressOption == .express)
/// // Prints "false"
/// print(expressOption == .secondDay)
/// // Prints "true"
///
/// - Parameter member: The element of the set to remove.
/// - Returns: The intersection of `[member]` and the set, if the
/// intersection was nonempty; otherwise, `nil`.
@inlinable // generic-performance
@discardableResult
public mutating func remove(_ member: Element) -> Element? {
let r = isSuperset(of: member) ? Optional(member) : nil
self.subtract(member)
return r
}
/// Inserts the given element into the set.
///
/// If `newMember` is not contained in the set but subsumes current members
/// of the set, the subsumed members are returned.
///
/// var options: ShippingOptions = [.secondDay, .priority]
/// let replaced = options.update(with: .express)
/// print(replaced == .secondDay)
/// // Prints "true"
///
/// - Returns: The intersection of `[newMember]` and the set if the
/// intersection was nonempty; otherwise, `nil`.
@inlinable // generic-performance
@discardableResult
public mutating func update(with newMember: Element) -> Element? {
let r = self.intersection(newMember)
self.formUnion(newMember)
return r.isEmpty ? nil : r
}
}
/// `OptionSet` requirements for which default implementations are
/// supplied when `RawValue` conforms to `FixedWidthInteger`,
/// which is the usual case. Each distinct bit of an option set's
/// `.rawValue` corresponds to a disjoint value of the `OptionSet`.
///
/// - `union` is implemented as a bitwise "or" (`|`) of `rawValue`s
/// - `intersection` is implemented as a bitwise "and" (`&`) of
/// `rawValue`s
/// - `symmetricDifference` is implemented as a bitwise "exclusive or"
/// (`^`) of `rawValue`s
///
/// - Note: A type conforming to `OptionSet` can implement any of
/// these initializers or methods, and those implementations will be
/// used in lieu of these defaults.
extension OptionSet where RawValue : FixedWidthInteger {
/// Creates an empty option set.
///
/// This initializer creates an option set with a raw value of zero.
@inlinable // generic-performance
public init() {
self.init(rawValue: 0)
}
/// Inserts the elements of another set into this option set.
///
/// This method is implemented as a `|` (bitwise OR) operation on the
/// two sets' raw values.
///
/// - Parameter other: An option set.
@inlinable // generic-performance
public mutating func formUnion(_ other: Self) {
self = Self(rawValue: self.rawValue | other.rawValue)
}
/// Removes all elements of this option set that are not
/// also present in the given set.
///
/// This method is implemented as a `&` (bitwise AND) operation on the
/// two sets' raw values.
///
/// - Parameter other: An option set.
@inlinable // generic-performance
public mutating func formIntersection(_ other: Self) {
self = Self(rawValue: self.rawValue & other.rawValue)
}
/// Replaces this set with a new set containing all elements
/// contained in either this set or the given set, but not in both.
///
/// This method is implemented as a `^` (bitwise XOR) operation on the two
/// sets' raw values.
///
/// - Parameter other: An option set.
@inlinable // generic-performance
public mutating func formSymmetricDifference(_ other: Self) {
self = Self(rawValue: self.rawValue ^ other.rawValue)
}
}