/
ArrayExtensions.swift
318 lines (258 loc) · 10.1 KB
/
ArrayExtensions.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
//
// ArrayExtensions.swift
// EZSwiftExtensions
//
// Created by Goktug Yilmaz on 15/07/15.
// Copyright (c) 2015 Goktug Yilmaz. All rights reserved.
//
import Foundation
public func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
switch (lhs, rhs) {
case let (lhs?, rhs?):
return lhs == rhs
case (.none, .none):
return true
default:
return false
}
}
extension Array {
///EZSE: Get a sub array from range of index
public func get(at range: ClosedRange<Int>) -> Array {
let halfOpenClampedRange = Range(range)
return Array(self[halfOpenClampedRange])
}
/// EZSE: Checks if array contains at least 1 item which type is same with given element's type
public func containsType<T>(of element: T) -> Bool {
let elementType = type(of: element)
return contains { type(of: $0) == elementType}
}
/// EZSE: Decompose an array to a tuple with first element and the rest
public func decompose() -> (head: Iterator.Element, tail: SubSequence)? {
return (count > 0) ? (self[0], self[1..<count]) : nil
}
/// EZSE: Iterates on each element of the array with its index. (Index, Element)
public func forEachEnumerated(_ body: @escaping (_ offset: Int, _ element: Element) -> Void) {
enumerated().forEach(body)
}
/// EZSE: Gets the object at the specified index, if it exists.
public func get(at index: Int) -> Element? {
guard index >= 0 && index < count else { return nil }
return self[index]
}
/// EZSE: Prepends an object to the array.
public mutating func insertFirst(_ newElement: Element) {
insert(newElement, at: 0)
}
/// EZSE: Returns a random element from the array.
public func random() -> Element? {
guard count > 0 else { return nil }
let index = Int(arc4random_uniform(UInt32(count)))
return self[index]
}
/// EZSE: Reverse the given index. i.g.: reverseIndex(2) would be 2 to the last
public func reverseIndex(_ index: Int) -> Int? {
guard index >= 0 && index < count else { return nil }
return Swift.max(count - 1 - index, 0)
}
/// EZSE: Shuffles the array in-place using the Fisher-Yates-Durstenfeld algorithm.
public mutating func shuffle() {
guard count > 1 else { return }
var j: Int
for i in 0..<(count-2) {
j = Int(arc4random_uniform(UInt32(count - i)))
if i != i+j { self.swapAt(i, i+j) }
}
}
/// EZSE: Shuffles copied array using the Fisher-Yates-Durstenfeld algorithm, returns shuffled array.
public func shuffled() -> Array {
var result = self
result.shuffle()
return result
}
/// EZSE: Returns an array with the given number as the max number of elements.
public func takeMax(_ n: Int) -> Array {
return Array(self[0..<Swift.max(0, Swift.min(n, count))])
}
/// EZSE: Checks if test returns true for all the elements in self
public func testAll(_ body: @escaping (Element) -> Bool) -> Bool {
return !contains { !body($0) }
}
/// EZSE: Checks if all elements in the array are true or false
public func testAll(is condition: Bool) -> Bool {
return testAll { ($0 as? Bool) ?? !condition == condition }
}
}
extension Array where Element: Equatable {
/// EZSE: Checks if the main array contains the parameter array
public func contains(_ array: [Element]) -> Bool {
return array.testAll { self.index(of: $0) ?? -1 >= 0 }
}
/// EZSE: Checks if self contains a list of items.
public func contains(_ elements: Element...) -> Bool {
return elements.testAll { self.index(of: $0) ?? -1 >= 0 }
}
/// EZSE: Returns the indexes of the object
public func indexes(of element: Element) -> [Int] {
return enumerated().flatMap { ($0.element == element) ? $0.offset : nil }
}
/// EZSE: Returns the last index of the object
public func lastIndex(of element: Element) -> Int? {
return indexes(of: element).last
}
/// EZSE: Removes the first given object
public mutating func removeFirst(_ element: Element) {
guard let index = index(of: element) else { return }
self.remove(at: index)
}
/// EZSE: Removes all occurrences of the given object(s), at least one entry is needed.
public mutating func removeAll(_ firstElement: Element?, _ elements: Element...) {
var removeAllArr = [Element]()
if let firstElementVal = firstElement {
removeAllArr.append(firstElementVal)
}
elements.forEach({element in removeAllArr.append(element)})
removeAll(removeAllArr)
}
/// EZSE: Removes all occurrences of the given object(s)
public mutating func removeAll(_ elements: [Element]) {
// COW ensures no extra copy in case of no removed elements
self = filter { !elements.contains($0) }
}
/// EZSE: Difference of self and the input arrays.
public func difference(_ values: [Element]...) -> [Element] {
var result = [Element]()
elements: for element in self {
for value in values {
// if a value is in both self and one of the values arrays
// jump to the next iteration of the outer loop
if value.contains(element) {
continue elements
}
}
// element it's only in self
result.append(element)
}
return result
}
/// EZSE: Intersection of self and the input arrays.
public func intersection(_ values: [Element]...) -> Array {
var result = self
var intersection = Array()
for (i, value) in values.enumerated() {
// the intersection is computed by intersecting a couple per loop:
// self n values[0], (self n values[0]) n values[1], ...
if i > 0 {
result = intersection
intersection = Array()
}
// find common elements and save them in first set
// to intersect in the next loop
value.forEach { (item: Element) -> Void in
if result.contains(item) {
intersection.append(item)
}
}
}
return intersection
}
/// EZSE: Union of self and the input arrays.
public func union(_ values: [Element]...) -> Array {
var result = self
for array in values {
for value in array {
if !result.contains(value) {
result.append(value)
}
}
}
return result
}
/// EZSE: Returns an array consisting of the unique elements in the array
public func unique() -> Array {
return reduce([]) { $0.contains($1) ? $0 : $0 + [$1] }
}
}
extension Array where Element: Hashable {
/// EZSE: Removes all occurrences of the given object(s)
public mutating func removeAll(_ elements: [Element]) {
let elementsSet = Set(elements)
// COW ensures no extra copy in case of no removed elements
self = filter { !elementsSet.contains($0) }
}
}
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
public subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
// MARK: - Deprecated 1.8
extension Array {
/// EZSE: Checks if array contains at least 1 instance of the given object type
@available(*, deprecated: 1.8, renamed: "containsType(of:)")
public func containsInstanceOf<T>(_ element: T) -> Bool {
return containsType(of: element)
}
/// EZSE: Gets the object at the specified index, if it exists.
@available(*, deprecated: 1.8, renamed: "get(at:)")
public func get(_ index: Int) -> Element? {
return get(at: index)
}
/// EZSE: Checks if all elements in the array are true of false
@available(*, deprecated: 1.8, renamed: "testAll(is:)")
public func testIfAllIs(_ condition: Bool) -> Bool {
return testAll(is: condition)
}
}
extension Array where Element: Equatable {
/// EZSE: Removes the first given object
@available(*, deprecated: 1.8, renamed: "removeFirst(_:)")
public mutating func removeFirstObject(_ object: Element) {
removeFirst(object)
}
}
// MARK: - Deprecated 1.7
extension Array {
/// EZSE: Prepends an object to the array.
@available(*, deprecated: 1.7, renamed: "insertFirst(_:)")
public mutating func insertAsFirst(_ newElement: Element) {
insertFirst(newElement)
}
}
extension Array where Element: Equatable {
/// EZSE: Checks if the main array contains the parameter array
@available(*, deprecated: 1.7, renamed: "contains(_:)")
public func containsArray(_ array: [Element]) -> Bool {
return contains(array)
}
/// EZSE: Returns the indexes of the object
@available(*, deprecated: 1.7, renamed: "indexes(of:)")
public func indexesOf(_ object: Element) -> [Int] {
return indexes(of: object)
}
/// EZSE: Returns the last index of the object
@available(*, deprecated: 1.7, renamed: "lastIndex(_:)")
public func lastIndexOf(_ object: Element) -> Int? {
return lastIndex(of: object)
}
/// EZSE: Removes the first given object
@available(*, deprecated: 1.7, renamed: "removeFirstObject(_:)")
public mutating func removeObject(_ object: Element) {
removeFirstObject(object)
}
}
// MARK: - Deprecated 1.6
extension Array {
/// EZSE: Creates an array with values generated by running each value of self
/// through the mapFunction and discarding nil return values.
@available(*, deprecated: 1.6, renamed: "flatMap(_:)")
public func mapFilter<V>(mapFunction map: (Element) -> (V)?) -> [V] {
return flatMap { map($0) }
}
/// EZSE: Iterates on each element of the array with its index. (Index, Element)
@available(*, deprecated: 1.6, renamed: "forEachEnumerated(_:)")
public func each(_ call: @escaping (Int, Element) -> Void) {
forEachEnumerated(call)
}
}