Sort an array on three variables [duplicate] - ios

This question already has answers here:
Swift - Sort array of objects with multiple criteria
(8 answers)
Closed 5 years ago.
I have an array with playing cards. I would like to sort these on value, color and playability.
I have 4 colors; 1, 2, 3, 4.
I have 3 playability options; 1, 2, 3.
I have 5 values; 1, 2, 3, 4, 5.
I am able to sort the array on color and value. So if the color is the same, the array is sorted on value.
example color-value pairs:
1-2, 1-4, 1-5, 2-2, 3-1, 3-2, 3-5
Code is,
playerCards.sort {
if $0.colorSorter == $1.colorSorter {
return $0.value < $1.value
}
return $0.colorSorter < $1.colorSorter
}
How do I add the third paramater to sort on playability additionally?
What I would like to see (playability-color-value triplets):
1-1-2, 1-1-4, 1-2-2, 1-3-1, 2-1-5, 2-3-1, 2-3-2, 3-3-5
1: sort on playability
2: sort on color
3: sort on value.
Thanks!

Assuming this is your struct
struct Card {
let color:Int
let value:Int
let playability:Int
}
and this is your array
let cards:[Card] = ...
You can sort cards by
playability
color
value
writing
let sorted = cards.sorted { (left, right) -> Bool in
guard left.playability == right.playability else { return left.playability < right.playability }
guard left.color == right.color else { return left.color < right.color }
return left.value < right.value
}

Related

Remove matching entry from an array

I have 2 sections in my tableview. Section 0 has data from an array and section 1 has data from an api. Now I want to check if the data from section 0 has a matching data to that of section 1 then I want to remove that particular entry from section 1. Not sure how to do that…
I have 2 arrays which populate data in each of the sections:
filteredSelectedProductList —> Section 0
productsList —> Section 1
This is the code I have:
if self.filteredSelectedProductList.isEmpty == false {
for prod in self.filteredSelectedProductList { //section 0
for filtProd in productsList { //section 1
if prod.productId == filtProd.productId {
//Here I want to remove the matching entry from productsList array. How can I do that..?
}
}
}
}
You can use filter where not contains,
something like this should work,
var arr1 = [1,3,4,5,6] // array 1 section 0
var arr2 = [2,34,5,6] // array 2 section 1
print(arr2.filter{!arr1.contains($0)}) // [2,34]
In your case you are using custom model, you can confirm to Equatable and do the following with it, then you can simple use it as i showed you above.
struct MyCustomObject {
var id: Int // some unique id you can compare things to
}
extension MyCustomObject: Equatable {
static func == (lhs: MyCustomObject, rhs: MyCustomObject) -> Bool {
return lhs.id == rhs.id
}
}
Usage :
var arra1: [MyCustomObject] = [] // section 0
var arra2: [MyCustomObject] = [] // section 1
print(arra2.filter{!arra1.contains($0)})
Swift 4.1
You can do by this way, You just have to put your array instead of this
var array1 = ["45","34","67"] //section 0
var array2 = ["45", "23"] // section 1
array2.removeAll(where: { array1.contains($0) }) // finalArray = ["23"]
You can use removeAll:
productsList.removeAll(where: { filteredSelectedProductList.contains($0) })
To use the contains, your models must conform to Equatable, otherwise you should do like this:
productsList.removeAll(where: { item in filteredSelectedProductList.contains(where: { $0.productId == item.productId }) })
Array.contains is an O(n) operation. Meaning that each element in the first array, you may have to search all of the second array. Resulting in O(m * n), m and n being the counts of the arrays, which is not efficient.
A better way would be to:
build a dictionary of the occurrences of elements in the first array,
then loop through the second array,
Append the elements in the second array that don't belong to the first one.
Here a function that does the above steps with a time complexity of O(m + n):
func intersect<T: Hashable>(_ a1: [T], with a2: [T]) -> [T] {
var result = [T]()
result.reserveCapacity(a2.count)
var dict1: [T: Int] = [T: Int].init(minimumCapacity: a1.count)
for x in a1 {
dict1[x, default: 0] += 1
}
for y in a2 {
if dict1[y] == nil || dict1[y] == 0 {
result.append(y)
} else {
dict1[y]! -= 1
}
}
return result
}
Note that the elements of the arrays have to conform to Hashable in order to use a dictionary. If an element occurs in the second array more than in the first, the extra duplicates are the ones kept. Which is not handled in all other answers
Here are some use cases :
let array1 = [1, 2, 3, 4, 5, 6]
let array2 = [1, 2, 2, 4, 7, 6]
intersect(array1, with: array2) //[2, 7]
struct MyStruct: Hashable { var id: Int }
let array3 = [MyStruct(id: 0), MyStruct(id: 2), MyStruct(id: 3)]
let array4 = [MyStruct(id: 1), MyStruct(id: 2), MyStruct(id: 2)]
intersect(array3, with: array4) //[{id 1}, {id 2}]

Compare array to reference array and remove duplicates [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I can't understand how to rewrite this expression in more "swift" and efficient way:
for result in results {
var isExists = false
for ref in referenceArray {
if result.id == ref.id {
isExists = true
break
}
}
if isExists == false {
filteredResults.append(result)
}
}
I tried this:
filteredResults = results.filter { result in
referenceArray.contains { $0.id != result.id }
}
But it gives me empty array.
Thanks.
Correct me if I'm wrong but it sounds like you want to do something like this:
Given a set of items A and set of items B, create a set of items C with only the items that are in B and not in A.
To paraphrase, I think you're looking for the "new" things in B that don't already exist in A.
If this is what you're trying to do you can use a Set. This is a trivial example with Ints, but hopefully it'll help:
let setA = Set([1,2,3,4,5,6,7,8,9,10])
let setB = Set([2,4,6,8,10,13])
// Only the values that overlap both sets
let evens = setA.intersection(setB) // {6, 10, 2, 4, 8}
// Only the values that do not overlap both sets
let odds = setA.symmetricDifference(setB) // {9, 5, 7, 3, 1, 13}
// All unique elements in both sets
let uniqueToBoth = setA.union(setB) // {13, 10, 2, 4, 9, 5, 6, 7, 3, 1, 8}
// Only elements unique to B
let uniqueToB = setB.subtracting(setA) // {13}

'didSet' certain elements inside Arrays - Swift [duplicate]

This question already has an answer here:
Swift didSet get index of array
(1 answer)
Closed 5 years ago.
I have an array, with multiple values. I want to detect if one of those values is changed, something like this:
var array =
[
1,
2,
3,
4 { didSet{ print("Value Changed")}},
5,
6
]
Is that possible, in any way?
Thanks
Swift 3.0
You can do like below to Observer which index of array is changed.
var oldArray: [Int] = []
var array = [ 1,2,3,4,5,6] {
willSet {
// Set old array value for compare
oldArray = array
}
didSet {
let changedIndex = zip(array, oldArray).map{$0 != $1}.enumerated().filter{$1}.map{$0.0}
print("index: \(changedIndex)")
}
}
// Now change value of index 4 of array
array[4] = 10 //index: [4]

How to remove item in Array? [duplicate]

This question already has answers here:
RemoveAtIndex crash from swift array
(5 answers)
Closed 6 years ago.
I am coding with Swift, and confuse with one problem.
I encountered Index out of Range Error when I am trying to remove one item from array during the array's enumeration.
Here is my error codes:
var array :[Int] = [0,1,2,3,4,5]
for (index, number) in array.enumerate() {
if array[index] == 2 {
array.removeAtIndex(index) // Fatal error: Index out of range
}
}
Does that means array.enumerate() not be called during each for loop?
I have to change my codes like that:
for number in array {
if number == 2 || number == 5 {
array.removeAtIndex(array.indexOf(number)!)
}
}
Or
var index = 0
repeat {
if array[index] == 2 || array[index] == 4 {
array.removeAtIndex(index)
}
index += 1
} while(index < array.count)
You are removing item at the same time when you are enumerating same array. Use filter instead:
var array: [Int] = [0,1,2,3,4,5]
array = array.filter{$0 != 2}
or, for multiple values, use Set:
let unwantedValues: Set<Int> = [2, 4, 5]
array = array.filter{!unwantedValues.contains($0)}
Same in one line:
array = array.filter{!Set([2, 4, 5]).contains($0)}

Replacement for C-style loop in Swift 2.2

Swift 2.2 deprecated the C-style loop. However in some cases, the new range operator just doesn't work the same.
for var i = 0; i < -1; ++i { ... }
and
for i in 0..<-1 { ... }
The later one will fail at run-time. I can wrap the loop with an if, but it's a bit cluttered. Sometimes this kind of loop is useful.
Any thoughts?
Use cases
You need to enumerate all elements of an array, except the last one.
You need to enumerate all whole integer numbers in a decimal range, but the range can be like [0.5, 0.9] and so there's no integers (after some maths), which results in an empty loop.
Although it's not as "pretty", you can use stride:
for var i in 0.stride(to: -1, by: -1) {
print(i)
}
Mimicking the "C-style loop"
Not entirely pretty, but you can wrap the range:s upper bound with a max(0, ..) to ascertain it never takes negative values.
let foo : [Int] = []
for i in 0..<max(0,foo.count-1) {
print(i)
}
I'd prefer, however, the from.stride(to:by) solution (that has already been mentioned in the other answers, see e.g. Michael:s answer).
I think it's valuable to explicitly point out, however, that from.stride(to:by) neatly returns an empty StrideTo (or, if converted to an array: an empty array) if attempting to stride to a number that is less than from but by a positive stride. E.g., striding from 0 to -42 by 1 will not attempt to stride all the way through "∞ -> -∞ -> -42" (i.e., an error case), but simply returns an empty StrideTo (as it should):
Array(0.stride(to: -42, by: 1)) // []
// -> equivalent to your C loop:
for i in 0.stride(to: foo.count-1, by: 1) {
print(i)
}
Use case 1: enumerate all but the last element of an array
For this specific use case, a simple solution is using dropLast() (as described by Sulthan in the comments to your question) followed by forEach.
let foo = Array(1...5)
foo.dropLast().forEach { print($0) } // 1 2 3 4
Or, if you need more control over what to drop out, apply a filter to your array
let foo = Array(1...5)
foo.filter { $0 < foo.count }.forEach { print($0) } // 1 2 3 4
Use case 2: enumerate all integers in a decimal range, allowing this enumeration to be empty
For your decimal/double closed interval example ([0.6, 0.9]; an interval rather than a range in the context of Swift syntax), you can convert the closed interval to an integer range (using ceil function) and apply a forEach over the latter
let foo : (ClosedInterval<Double>) -> () = {
(Int(ceil($0.start))..<Int(ceil($0.end)))
.forEach { print($0) }
}
foo(0.5...1.9) // 1
foo(0.5...0.9) // nothing
Or, if you specifically want to enumerate the (possible) integers contained in this interval; use as en extension fit to your purpose:
protocol MyDoubleBounds {
func ceilToInt() -> Int
}
extension Double: MyDoubleBounds {
func ceilToInt() -> Int {
return Int(ceil(self)) // no integer bounds check in this simple example
}
}
extension ClosedInterval where Bound: MyDoubleBounds {
func enumerateIntegers() -> EnumerateSequence<(Range<Int>)> {
return (self.start.ceilToInt()
..< self.end.ceilToInt())
.enumerate()
}
}
Example usage:
for (i, intVal) in (1.3...3.2).enumerateIntegers() {
print(i, intVal)
} /* 0 2
1 3 */
for (i, intVal) in (0.6...0.9).enumerateIntegers() {
print(i, intVal)
} /* nothing */
For reference:
In swift 3.0 stride is now defined globally which makes for loop look more natural:
for i in stride(from: 10, to: 0, by: -1){
print(i)
} /* 10 9 8 7 6 5 4 3 2 1 */
For Swift 3 and need to change the "index"
for var index in stride(from: 0, to: 10, by: 1){}

Resources