Given I've got an array in Swift such as [1,2,3,4], a method pairs() will transform it in to the array of tuples: [(1,2), (2,3), (3,4)].
Here are some more examples of how pairs() should behave:
pairs([]) should return [] as it has no pairs.
pairs([1]) should also return [], as it has no pairs.
pairs([1,2]) should be [(1,2)]. It has just one pair.
I can write code to do this for Array, but I'd like to have pairs() available as an extension on Sequence, so that it returns a Sequence of the pairs. This would make it useable on any sequence, and compatible with methods such as map, reduce, filter, etc.
How do I go about creating a Sequence like this? And how do I write the method to transform any Sequence in this way so that it can be used as flexibly as possible?
We can use zip() and dropFirst() if we define an extension
on the Collection type:
extension Collection {
func pairs() -> AnySequence<(Element, Element)> {
return AnySequence(zip(self, self.dropFirst()))
}
}
Example:
let array = [1, 2, 3, 4]
for p in array.pairs() {
print(p)
}
Output:
(1, 2)
(2, 3)
(3, 4)
More examples:
print(Array("abc".pairs()))
// [("a", "b"), ("b", "c")]
print([1, 2, 3, 4, 5].pairs().map(+))
// [3, 5, 7, 9]
print([3, 1, 4, 1, 5, 9, 2].pairs().filter(<))
// [(1, 4), (1, 5), (5, 9)]
(Unlike I wrote in the first version of this answer ...) this
approach is not safe when applied to a Sequence, because it is
not guaranteed that a sequence can be traversed multiple times
non-destructively.
Here is a direct implementation with a custom iterator type
which works on sequences as well:
struct PairSequence<S: Sequence>: IteratorProtocol, Sequence {
var it: S.Iterator
var last: S.Element?
init(seq: S) {
it = seq.makeIterator()
last = it.next()
}
mutating func next() -> (S.Element, S.Element)? {
guard let a = last, let b = it.next() else { return nil }
last = b
return (a, b)
}
}
extension Sequence {
func pairs() -> PairSequence<Self> {
return PairSequence(seq: self)
}
}
Example:
print(Array([1, 2, 3, 4].pairs().pairs()))
// [((1, 2), (2, 3)), ((2, 3), (3, 4))]
Related
I'm working with a sorting function that takes an array of Ints already sorted in descending order and places a new Int in its correct spot. (i.e if my sorted array was [10, 7, 2] and the new int was 5, the function would return [10, 7, 5, 2]). The function for doing this, once it has found the correct spot for the new Int, slices the original array into the items before the new Ints spot and those after, and then combines the slices with the new Int.
The problem I'm running into is that this won't give me an array but rather an array slice.
Code:
func addToSorted(sorted: [Int], new: Int) -> [Int] {
if sorted.count == 0 {
return [new]
} else {
for index in 0..<sorted.count {
let item = sorted[index]
if new > item {
return sorted[..<index] + [new] + sorted[index...]
}
}
}
}
let result = addToSorted(sorted: [10, 7, 2], new: 5)
print(result) // expected [10, 7, 5, 2]
This is a more generic (and efficient) alternative which uses binary search
extension RandomAccessCollection where Element : Comparable {
func descendingInsertionIndex(of value: Element) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value > slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.endIndex
}
}
And use it
var array = [10, 7, 5, 2]
let index = array.descendingInsertionIndex(of: 4)
array.insert(4, at: index)
print(array) // [10, 7, 5, 4, 2]
For ascending order replace if value > slice[middle] with if value < slice[middle] and return slice.endIndex with return slice.startIndex
If you use the Swift Algorithms, this insertion is a one-liner:
var arr = [10, 7, 2]
arr.insert(5, at: arr.partitioningIndex {$0 < 5})
print (arr) // [10, 7, 5, 2]
This is very efficient — O(log n) — because your array is already partitioned (sorted) and therefore it uses a binary search.
You would have to promote the slices to arrays:
return Array(sorted[..<index]) + [new] + Array(sorted[index...])
A few other points:
You should make a habit out of using sorted.isEmpty over sorted.count == 0, it's much faster for some collections that don't store their count, such as lazy collections or even String (IIRC).
A better approach would be to just use Array.insert(_:at:):
var sorted = sorted // Make a local mutable copy
sorted.insert(new, at: index)
BTW after your for loop, you need insert at the end of your array (this also removes the need for checking the empty case):
return sorted + [new]
Since this works even when sorted is empty, you can remove that special case.
Since you know your data structure is already sorted, you can use binary search instead of linear search to find the insertion index faster.
arrayOfTuples = [(4, 4, "id1"), (3, 6, "id2"), (3, 6, "id3")]
How to remove the item with the id2 string?
You can use RangeReplaceableCollection method removeAll(where:) and pass a predicate:
var arrayOfTuples = [(4, 4, "id1"), (3, 6, "id2"), (3, 6, "id3")]
arrayOfTuples.removeAll(where: {$2 == "id2"})
print(arrayOfTuples) // [(4, 4, "id1"), (3, 6, "id3")]
If you would like to remove only the first occurrence where the third element of your tuple is equal to "id2" you can use Collection's method firstIndex(where:):
if let index = arrayOfTuples.firstIndex(where: {$2 == "id2"}) {
arrayOfTuples.remove(at: index)
}
Hi I have the following
class MyClass {
var myString: String?
}
var myClassList = [String: MyClass]()
I would like to sort this array alphabetically by the myString variable in Swift 3 any pointers?
As mentioned above, you have a dictionary, not tuples.
However, Dictionaries do indeed have a sorted(by:) method that you can use to sort an array of Key/Value pair tuples. Here's an example:
var m: [String: Int] = ["a": 1]
let n = m.sorted(by: { (first: (key: String, value: Int), second: (key: String, value: Int)) -> Bool in
return first.value > second.value
})
That's expanded to show the full signature of the closure, however easily shorthanded to:
let n = m.sorted(by: {
return $0.value > $1.value
})
Additionally, you can also perform other enumerations over Dictionaries
m.forEach { (element: (key: String, value: Int)) in
print($0.value)
}
All of this is due to the Collection and sequence protocol hierarchies in Swift, they're some pretty nice abstractions.
Cool problem! Though i'd like to point out first that [String: MyClass] is a Dictionary and not at Tupule.
Swift does, however, support Tupules. The syntax for your tupule would look like so:
var tupule: (String, MyClass) = (foo, bar)
You would then need to make an Array of them:
var tupules:[(String, MyClass)] = [(foo, bar), (up, dog)]
Then you could sort that array:
tupules.sort({ $0[1].myString > $1[1].myString })
though you should probably define a more robust sort mechanism.
These are the contents of the sort closure:
$0 is the one of the objects for which needs to be compared, $1 is the other.
$0[1] and $1[1] accesses the objects' 1 index, in this case, as defined in your tupule, it is your custom object MyClass
Hope this helps.
Swift version 5.2.4
let arr = [(0, 5), (1, 2), (2, 4), (3, 1), (4, 3)] // -> [(3, 1), (1, 2), (4, 3), (2, 4), (0, 5)]
let sorted = arr.sorted{ $0.1 < $1.1 }
This question already has answers here:
In Swift, an efficient function that separates an array into 2 arrays based on a predicate
(7 answers)
Closed 6 months ago.
Problem
Given an array of values how can I split it into sub-arrays made of elements that are equal?
Example
Given this array
let numbers = [1, 1, 1, 3, 3, 4]
I want this output
[[1,1,1], [3, 3], [4]]
What I am NOT looking for
A possible way of solving this would be creating some sort of index to indicate the occurrences of each element like this.
let indexes = [1:3, 3:2, 4:1]
And finally use the index to rebuild the output array.
let subsequences = indexes.sort { $0.0.0 < $0.1.0 }.reduce([Int]()) { (res, elm) -> [Int] in
return res + [Int](count: elm.1, repeatedValue: elm.0)
}
However with this solution I am losing the original values. Of course in this case it's not a big problem (an Int value is still and Inteven if recreated) but I would like to apply this solution to more complex data structures like this
struct Starship: Equatable {
let name: String
let warpSpeed: Int
}
func ==(left:Starship, right:Starship) -> Bool {
return left.warpSpeed == right.warpSpeed
}
Final considerations
The function I am looking for would be some kind of reverse of flatten(), infact
let subsequences: [[Int]] = [[1,1,1], [3, 3], [4]]
print(Array(subsequences.flatten())) // [1, 1, 1, 3, 3, 4]
I hope I made myself clear, let me know should you need further details.
// extract unique numbers using a set, then
// map sub-arrays of the original arrays with a filter on each distinct number
let numbers = [1, 1, 1, 3, 3, 4]
let numberGroups = Set(numbers).map{ value in return numbers.filter{$0==value} }
print(numberGroups)
[EDIT] changed to use Set Initializer as suggested by Hamish
[EDIT2] Swift 4 added an initializer to Dictionary that will do this more efficiently:
let numberGroups = Array(Dictionary(grouping:numbers){$0}.values)
For a list of objects to be grouped by one of their properties:
let objectGroups = Array(Dictionary(grouping:objects){$0.property}.values)
If you could use CocoaPods/Carthage/Swift Package Manager/etc. you could use packages like oisdk/SwiftSequence which provides the group() method:
numbers.lazy.group()
// should return a sequence that generates [1, 1, 1], [3, 3], [4].
or UsrNameu1/TraverSwift which provides groupBy:
groupBy(SequenceOf(numbers), ==)
If you don't want to add external dependencies, you could always write an algorithm like:
func group<S: SequenceType where S.Generator.Element: Equatable>(seq: S) -> [[S.Generator.Element]] {
var result: [[S.Generator.Element]] = []
var current: [S.Generator.Element] = []
for element in seq {
if current.isEmpty || element == current[0] {
current.append(element)
} else {
result.append(current)
current = [element]
}
}
result.append(current)
return result
}
group(numbers)
// returns [[1, 1, 1], [3, 3], [4]].
Let's assume that you have an unsorted array of items. You will need to sort the initial array then you will have something like this:
[1, 1, 1, 3, 3, 4]
After that you will initialize two arrays: one for storing arrays and another one to use it as a current array.
Loop through the initial array and:
if the current value isn't different from the last one, push it to the current array
otherwise push the current array to the first one then empty the current array.
Hope it helps!
Worth mentioning, using Swift Algorithms this is now a one-liner:
import Algorithms
let numbers = [1, 1, 1, 3, 3, 4]
let chunks: [[Int]] = numbers.chunked(by: ==).map { .init($0) }
print(chunks) // [[1, 1, 1], [3, 3], [4]]
I'm trying to add a tuple (e.g., 2-item tuple) to an array.
var myStringArray: (String,Int)[]? = nil
myStringArray += ("One", 1)
What I'm getting is:
Could not find an overload for '+=' that accepts the supplied
arguments
Hint: I tried to do an overload of the '+=' per reference book:
#assignment func += (inout left: (String,Int)[], right: (String,Int)[]) {
left = (left:String+right:String, left:Int+right+Int)
}
...but haven't got it right.
Any ideas? ...solution?
Since this is still the top answer on google for adding tuples to an array, its worth noting that things have changed slightly in the latest release. namely:
when declaring/instantiating arrays; the type is now nested within the braces:
var stuff:[(name: String, value: Int)] = []
the compound assignment operator, +=, is now used for concatenating arrays; if adding a single item, it needs to be nested in an array:
stuff += [(name: "test 1", value: 1)]
it also worth noting that when using append() on an array containing named tuples, you can provide each property of the tuple you're adding as an argument to append():
stuff.append((name: "test 2", value: 2))
You have two issues. First problem, you're not creating an "array of tuples", you're creating an "optional array of tuples". To fix that, change this line:
var myStringArray: (String,Int)[]? = nil
to:
var myStringArray: (String,Int)[]
Second, you're creating a variable, but not giving it a value. You have to create a new array and assign it to the variable. To fix that, add this line after the first one:
myStringArray = []
...or you can just change the first line to this:
var myStringArray: (String,Int)[] = []
After that, this line works fine and you don't have to worry about overloading operators or other craziness. You're done!
myStringArray += ("One", 1)
Here's the complete solution. A whopping two lines and one wasn't even changed:
var myStringArray: (String,Int)[] = []
myStringArray += ("One", 1)
Swift 4 solution:
// init empty tuple array
var myTupleArray: [(String, Int)] = []
// append a value
myTupleArray.append(("One", 1))
If you remove the optional, it works fine, otherwise you'll have to do this:
var myStringArray: (String,Int)[]? = nil
if !myStringArray {
myStringArray = []
}
var array = myStringArray!
array += ("One", 1)
myStringArray = array
You can never append an empty array, so you'll have to initialize it at some point. You'll see in the overload operator below that we sort of lazy load it to make sure that it is never nil.
You could condense this into a '+=' operator:
#assignment func += (inout left: Array<(String, Int)>?, right: (String, Int)) {
if !left {
left = []
}
var array = left!
array.append(right.0, right.1)
left = array
}
Then call:
var myStringArray: (String,Int)[]? = nil
myStringArray += ("one", 1)
I've ended up here multiple times due to this issue. Still not as easy as i'd like with appending onto an array of tuples. Here is an example of how I do it now.
Set an alias for the Tuple - key point
typealias RegionDetail = (regionName:String, constraintDetails:[String:String]?)
Empty array
var allRegionDetails = [RegionDetail]()
Easy to add now
var newRegion = RegionDetail(newRegionName, constraints)
allRegionDetails.append(newRegion)
var anotherNewRegion = RegionDetail("Empty Thing", nil)
allRegionDetails.append(anotherNewRegion)
Note:
It's not work anymore
if you do:
array += tuple
you will get error
what you need is :
array += [tuple]
I think apple change to this representation because it's more logical
#assignment func += (inout left: Array<(String, Int)>?, right: (String, Int)) {
if !left {
left = []
}
if left {
var array = left!
array.append(right.0, right.1)
left = array
}
}
var myStringArray: (String,Int)[]? = nil
myStringArray += ("x",1)
Thanks to comments:
import UIKit
#assignment func += (inout left: Array<(String, Int)>?, right: (String, Int)) {
if !left {
left = []
}
if left {
var array = left!
array.append(right.0, right.1)
left = array
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
println("interestingNumbers: \(interestingNumbers)\n")
var largest = 0
var myStringArray: (String,Int)[]? = nil
myStringArray += ("One", 1)
var x = 0
for (kind, numbers) in interestingNumbers {
println(kind)
for number in numbers {
if number > largest {
largest = number
}
x++
println("\(x)) Number: \(number)")
myStringArray += (kind,number)
} // end Number
} // end Kind
println("myStringArray: \(myStringArray)")
}
}
The Output:
interestingNumbers: [Square: [1, 4, 9, 16, 25], Prime: [2, 3, 5, 7,
11, 13], Fibonacci: [1, 1, 2, 3, 5, 8]]
Square 1) Number: 1 2) Number: 4 3) Number: 9 4)
Number: 16 5) Number: 25 Prime 6) Number: 2 7)
Number: 3 8) Number: 5 9) Number: 7 10) Number: 11
11) Number: 13 Fibonacci 12) Number: 1 13) Number:
1 14) Number: 2 15) Number: 3 16) Number: 5 17)
Number: 8
Array of tupules:
myStringArray: [(One, 1), (Square, 1), (Square, 4), (Square, 9),
(Square, 16), (Square, 25), (Prime, 2), (Prime, 3), (Prime, 5),
(Prime, 7), (Prime, 11), (Prime, 13), (Fibonacci, 1), (Fibonacci, 1),
(Fibonacci, 2), (Fibonacci, 3), (Fibonacci, 5), (Fibonacci, 8)]