I have a tableView with its style being Right Detail. I therefore have 2 arrays, one is for the textLabels data, and the other is for detailTextLabel.
There will be 2 "sort by" options. One will be sort by the textLabels data, and the second will sort by the detailTextlabels data. So when I sort the first array (textLabels array), the second array (detailTextLables array) will also have to get sorted based on the firstarray`.
I know how to sort arrays, but how can I sort one array based on another?
Here's how I sorted the array: (it's an array of Dates.
firstArray.sort({ (a, b) -> Bool in
a.earlierDate(b) == a
})
First, why not have two arrays? Because you only have one array of UITableViewCells and you want to keep all the data associated with a particular table view cell together. And it makes the need to try to coordinate the sorting of multiple arrays (what you are asking to do) unnecessary.
But if you really want to do that:
var array1 = ["1", "3", "2"]
var array2 = ["One", "Three", "Two"]
let sortedFoo = zip(array1, array2).sort { $0.0 < $1.0 }
array1 = sortedFoo.map { $0.0 }
array2 = sortedFoo.map { $0.1 }
The idea with the above code is that it combines the two arrays into one array of tuples, and then sorts them based on the elements in the first array, then breaks that single array back out into two separate arrays.
In other words, since you have to combine the two arrays into one to do the sort anyway, you might as well make them in a single array in the first place. :-)
It's a bit messy, but you can use enumerate to work with indices and elements at the same time:
array1.enumerate().sort {
return $0.element < $1.element
}.map {$0.element}
array1.enumerate().sort {
return array2[$0.index] < array2[$1.index]
}.map {$0.element}
But it's really much simpler/easier with one array.
struct Item {
let prop1: Int
let prop2: String
}
var array = [
Item(prop1: 1, prop2: "c"),
Item(prop1: 2, prop2: "b"),
Item(prop1: 3, prop2: "a")
]
array.sort { $0.prop1 < $1.prop1 }
array.sort { $0.prop2 < $1.prop2 }
How about using using positionOf on the sorted array to find the corresponding index in the I sorted array?
Related
I am currently having a big issue sorting my Data alphabetically in a 2D array. I'm going to try to give you every detail to be as clear as possible.
Currently, I am fetching my contacts with the CNContactStore. This all works fine. I am able to retrieve all the data I want out of my contacts.
Now, I created the following struct:
struct FavoritableContact {
let contact: CNContact
var hasFavorited: Bool
}
With this, I declared and initialized the following array:
var favoritableContacts = [FavoritableContact]()
Once I retrieved my contacts, I simply appended them to favoritableContacts;
try store.enumerateContacts(with: request, usingBlock: { (contact, stopPointerIfYouWantToStopEnumerating) in
favoritableContacts.append(FavoritableContact(contact: contact, hasFavorited: false))
})
To sort them in alphabetical order in the same array, I simply did the following:
var sortedContacts = favoritableContacts.sorted { $0.contact.familyName < $1.contact.familyName }
Now if possible, I want to create the following 2D array,
var 2D = [
[FavoritableContact] //"A"
[FavoritableContact], //"B"
[FavoritableContact], //"C"
[FavoritableContact], //"D"
...
]
I am just not sure how to take my sortedContacts array and separate alphabetically.
I am very new here, If I forgot something, or I didn't do somethign right please let me know.
As was pointed out in the comments, a dictionary with first letters as keys is probably the better way to go as it is much easier to access, though perhaps you have a reason for wanting to use a 2d array instead. To achieve that you could do something like this:
//Create an empty array filled with 26 arrays of FavorableContact
var array2d = Array<[FavoritableContact]>(repeating: [FavoritableContact](), count: 26)
//Find the ascii value for "A" to use as your base
let aAscii = Int("A".unicodeScalars.filter({ $0.isASCII }).map({ $0.value })[0]) //This returns 65, btw, so you could also just hardcode
//Go through your original array, find the first letter of each contact, and append to the correct array
favoritableContacts.forEach { (contact) in
//Get the ascii value for the first letter
let firstLetter = Int(contact.contact.familyName.prefix(1).uppercased().unicodeScalars.filter({ $0.isASCII }).map({ $0.value })[0])
//Append to the array for this letter by subtracting the ascii value for "A" from the ascii value for the uppercased version of this letter.
array2d[firstLetter - aAscii].append(contact)
}
This is not the cleanest thing in the world, and it assumes standard English language alphabet with no diacritics, symbols, numbers or anything else. Assuming that is true it gets the job done.
Could use something like this.
var contactsLeftToSort : [FavoritableContact] = []
var doubleArray : [[FavoritableContact]?] = [[FavoritableContact]?]()
var index : Int = 0
for char in "ABCDEFGHIJKLMNOPQRSTUV" {
doubleArray.append(nil)
var i = 0
while i < contactsLeftToSort.count {
let contact = contactsLeftToSort[i]
if contact.name.first == char {
doubleArray[index] == nil ? doubleArray[index] = [contact] : doubleArray[index]!.append(contact)
contactsLeftToSort.remove(at: i)
}
//assuming original list is alphabetized.. if not, delete this line.
if contact.name.first! > char { break }
i += 1
}
index += 1
}
As I wrote in the comments above, I think you can achieve this in a much more elegant way by using a dictionary instead of an array.
SWIFT 4
let sortedContacts: [FavoritableContact] = ... // An array of FavoritableContact objects, they should be sorted
let groupedContacts = Dictionary(grouping: contacts, by { $0.familyName.first! })
You now have a dictionary of all your contacts where the keys are the alphabetical letters (ie. A-Z) and the values are arrays of sorted FavoritableContact objects (assuming you sorted the big array of FavoritableContacts before creating the dictionary).
If you wanted to use this as the datasource for your tableview, you would make the number of sections all the possible first letters of family names. For the number of rows in each section, you return the count of the array for the key like so:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
let letterForSection = letterForSection() // Custom method to get the section of the letter
return contactsDict[letterForSection].count
}
The rest of the datasource methods would work in a similar way.
Man, all of these answers are really over-complicating this. All you need is something along the lines of:
let groupedContacts = Dictionary(grouping: contacts, by: { $0.contact.firstName.first! })
for initial, contacts in groupedContacts.lazy.sorted().{ $0.key < $1.key} {
print("#################", initial)
contacts.forEach{ print($0) }
}
Hope you are having a great day.
I'm trying to understand what is the fastest approach to do the following:
Assuming I have these two Arrays:
var firstArray = ["a","b","c"]
var secondArray = ["a","d","e"]
I'd like to get as an output:
1)Array of objects that inside firstArray but no in secondArray .
1)Array of objects that inside secondArray but no in firstArray .
3)Array of the common objects between firstArray and secondArray.
So basically the output would be:
1) ["b","c"]
2) ["d","e"]
3) ["a"]
The main issue here is to understand what is the most efficient way to do so. Thank you very much!
If your arrays are sorted and the items are unique in each array, the fastest way would be to handle each of the items only once. Start by comparing the first items in each array; if they are equal, put that into the common array and then move on to the second items. If one item is less than another, it goes into the unique array of the lesser item and you move on to the next item in the lesser array. Continue this process until you run out of items for one array, then put the remaining items of the second array into the unique items array for that array.
var i = 0
var j = 0
let a = ["a", "b", "c"]
let b = ["a", "d", "e"]
var aUnique = [String]()
var bUnique = [String]()
var common = [String]()
while i < a.count && j < b.count {
if a[i] == b[j] {
common.append(a[i])
i += 1
j += 1
} else if a[i] < b[j] {
aUnique.append(a[i])
i += 1
} else {
bUnique.append(b[j])
j += 1
}
}
if i < a.count {
// put remaining items into aUnique
aUnique += a[i ..< a.count]
} else if j < b.count {
// put remaining items into bUnique
bUnique += b[j ..< b.count]
}
print(common) // ["a"]
print(aUnique) // ["b", "c"]
print(bUnique) // ["d", "e"]
Analysis
This algorithm appends one item to one of the arrays each time through the loop. It will loop at most a.count + b.count - 1 times if both arrays are unique relative to each other, or only their last item is common.
If both arrays are identical, it will loop only a.count times.
If all elements of array b are greater than the elements of array a, it will loop only a.count times. If all elements of array a are greater than the elements of array b, it will loop only b.count times.
I will assume that the elements of your arrays are Equatable.
If they are also Hashable, and if the order of the elements is not important for you, and if (as in your example), all elements are unique, you might want to consider using set algebra rather than the an ordered collection type such as Array. E.g. using Set in Swift allows you to use the subtract(_:) or subtracting(_:) mutating/non-methods for 1) and 2), and intersection(_:)/formIntersection(_:) for 3), which all use O(1) (amortized) lookup for comparing elements between the sets (as compared to e.g. using O(n) contains(_:) of Array (with Equatable elements) to lookup existance of some specified element).
For additional details, see the language reference for Set as well as the thread linked to by vadian:
Set operations (union, intersection) on Swift array?
If the elements in each array is not unique, and you want to keep multiples as well as the order between the elements, you could use the Set representation of one of the arrays while filtering the other one.
E.g., for:
var firstArray = ["a","b","c"]
var secondArray = ["a","d","e"]
A) in O(n):
let excludeElements = Set(secondArray) // O(n)
secondArray = secondArray
.filter { !excludeElements.contains($0) } // O(n) due to O(1) (amortized) .contains lookup
B) in O(n):
let excludeElements = Set(firstArray) // O(n)
secondArray = secondArray
.filter { !excludeElements.contains($0) } // O(n) due to O(1) (amortized) .contains lookup
C) in O(n), using order and duplicates as they occur in firstArray:
let includeElements = Set(secondArray) // O(n)
let commonElements = firstArray
.filter(includeElements.contains) // O(n) due to O(1) (amortized) .contains lookup
C) in O(n), using order and duplicates as they occur in secondArray:
let includeElements = Set(firstArray) // O(n)
let commonElements = secondArray
.filter(includeElements.contains) // O(n) due to O(1) (amortized) .contains lookup
Performance?
The above only looks at asymptotic time complexity, and doesn't take into account any actual benchmarking. Generally the functional methods such as filter are slower than just a for or while loop, so if performance becomes an issue for your app, you should consider, at that point, performing profiling as well as custom bench-marking possible bottlenecks in your algorithms.
Also, if your arrays are known to be sorted, there are more efficient ways to traverse them and filter out the result. See e.g. the following thread (C language, but the logic is the important part):
Finding common elements in two arrays of different size
I'm doing some operations like sorting, filter and grouped by some attributes of arrays object.
I'm adding objects of a filtered array in to another array like:
arrGroup.append(contentsOf: filteredArray)
My question is: will all of the objects maintain the same sorted order in the array every time, with 100% certainty?
Logically, will it add the object like
for object in filteredArray {
arrGroup.append(object)
}
or
for index in 0...filteredArray.count {
let object = filteredArray[index]
arrGroup.append(object)
}
For me, all are same, just difference in CPU cycle at run time. But my friend says that I should go with last option. Technically I'm getting same result for all three every time I debug my code.
Your suggestion please.
Yes, when you add an Array to another Array it will maintain the order as it is.
But yes if you are using Set then you might not get same order as it is not ordered collection but Array is ordered collection which maintains it's ordering until you changes it manually.
here is the code example :
var arr1 = ["1", "2" , "3"] // Print arr1 : ["1", "2", "3"]
let arr2 = ["4", "5" , "6"] // Print arr2 : ["4", "5", "6"]
arr1.append(contentsOf: arr2) // Print arr1 : ["1", "2", "3", "4", "5", "6"]
Array preserves whatever ordering you give it.
Array.append(contentsOf:) appends all items of the second array to the end of the first array, in order. Here's roughly what that algorithm would look like:
extension Array {
mutating func myAppend(contentsOf other: [Element]) {
reserveCapacity(self.count + other.count)
for element in other {
self.append(element)
}
}
}
Techniques for iterating an array
If you only need the elements
The preferred method
The preferred way to iterate the items of a Sequence is to use a typical for-in loop:
for element in array { // most preferred!
// use the element
}
The discouraged method
for i in 0 ..< array.count {
let element = array[i] // Avoid this!
// use the element
}
I highly advise against this technique. The reason is because it's very easy to fall victim to an off-by-one-error. In fact, your very own example has it!
for index in 0...filteredArray.count {
let object = filteredArray[index] // when index is filteredArray.count ... đź’Ł
arrGroup.append(object)
}
Don't use this! Any array of n elements has indices 0 ..< n, not 0 ... n. Attempting to access array[array.count] will crash your program.
Another valid but discouraged method
for i in array.indices {
let element = array[i] // Avoid this!
// use the element
}
If you only need the indices
for i in array.indices {
// use the index i
}
If you need both the indices and the elements
for (i, element) in array.enumerated() {
// use the index i and the element.
}
The 2 for loops you have above are doing the same thing in terms of they will iterate from object #0 to the last object.
The first one is called fast enumeration and hence faster and more effective than the second.
And to answer your question. Yes the order will remain the same.
Is there any built-in way to create an ordered map in Swift 2? Arrays [T] are sorted by the order that objects are appended to it, but dictionaries [K : V] aren't ordered.
For example
var myArray: [String] = []
myArray.append("val1")
myArray.append("val2")
myArray.append("val3")
//will always print "val1, val2, val3"
print(myArray)
var myDictionary: [String : String] = [:]
myDictionary["key1"] = "val1"
myDictionary["key2"] = "val2"
myDictionary["key3"] = "val3"
//Will print "[key1: val1, key3: val3, key2: val2]"
//instead of "[key1: val1, key2: val2, key3: val3]"
print(myDictionary)
Are there any built-in ways to create an ordered key : value map that is ordered in the same way that an array is, or will I have to create my own class?
I would like to avoid creating my own class if at all possible, because whatever is included by Swift would most likely be more efficient.
You can order them by having keys with type Int.
var myDictionary: [Int: [String: String]]?
or
var myDictionary: [Int: (String, String)]?
I recommend the first one since it is a more common format (JSON for example).
Just use an array of tuples instead. Sort by whatever you like. All "built-in".
var array = [(name: String, value: String)]()
// add elements
array.sort() { $0.name < $1.name }
// or
array.sort() { $0.0 < $1.0 }
"If you need an ordered collection of key-value pairs and don’t need the fast key lookup that Dictionary provides, see the DictionaryLiteral type for an alternative." - https://developer.apple.com/reference/swift/dictionary
You can use KeyValuePairs,
from documentation:
Use a KeyValuePairs instance when you need an ordered collection of key-value pairs and don’t require the fast key lookup that the Dictionary type provides.
let pairs: KeyValuePairs = ["john": 1,"ben": 2,"bob": 3,"hans": 4]
print(pairs.first!)
//prints (key: "john", value: 1)
if your keys confirm to Comparable, you can create a sorted dictionary from your unsorted dictionary as follows
let sortedDictionary = unsortedDictionary.sorted() { $0.key > $1.key }
As Matt says, dictionaries (and sets) are unordered collections in Swift (and in Objective-C). This is by design.
If you want you can create an array of your dictionary's keys and sort that into any order you want, and then use it to fetch items from your dictionary.
NSDictionary has a method allKeys that gives you all the keys of your dictionary in an array. I seem to remember something similar for Swift Dictionary objects, but I'm not sure. I'm still learning the nuances of Swift.
EDIT:
For Swift Dictionaries it's someDictionary.keys
You can use the official OrderedDictionary from the original Swift Repo
The ordered collections currently contain:
Ordered Dictionary (That you are looking for)
Ordered Set
They said it is going to be merged in the Swift itself soon (in WWDC21)
Swift does not include any built-in ordered dictionary capability, and as far as I know, Swift 2 doesn't either
Then you shall create your own. You can check out these tutorials for help:
http://timekl.com/blog/2014/06/02/learning-swift-ordered-dictionaries/
http://www.raywenderlich.com/82572/swift-generics-tutorial
I know i am l8 to the party but did you look into NSMutableOrderedSet ?
https://developer.apple.com/reference/foundation/nsorderedset
You can use ordered sets as an alternative to arrays when the order of
elements is important and performance in testing whether an object is
contained in the set is a consideration—testing for membership of an
array is slower than testing for membership of a set.
var orderedDictionary = [(key:String, value:String)]()
As others have said, there's no built in support for this type of structure. It's possible they will add an implementation to the standard library at some point, but given it's relatively rare for it to be the best solution in most applications, so I wouldn't hold your breath.
One alternative is the OrderedDictionary project. Since it adheres to BidirectionalCollection you get most of the same APIs you're probably used to using with other Collection Types, and it appears to be (currently) reasonably well maintained.
Here's what I did, pretty straightforward:
let array = [
["foo": "bar"],
["foo": "bar"],
["foo": "bar"],
["foo": "bar"],
["foo": "bar"],
["foo": "bar"]
]
// usage
for item in array {
let key = item.keys.first!
let value = item.values.first!
print(key, value)
}
Keys aren't unique as this isn't a Dictionary but an Array but you can use the array keys.
use Dictionary.enumerated()
example:
let dict = [
"foo": 1,
"bar": 2,
"baz": 3,
"hoge": 4,
"qux": 5
]
for (offset: offset, element: (key: key, value: value)) in dict.enumerated() {
print("\(offset): '\(key)':\(value)")
}
// Prints "0: 'bar':2"
// Prints "1: 'hoge':4"
// Prints "2: 'qux':5"
// Prints "3: 'baz':3"
// Prints "4: 'foo':1"
I want to enumerate through an array in Swift, and remove certain items. I'm wondering if this is safe to do, and if not, how I'm supposed to achieve this.
Currently, I'd be doing this:
for (index, aString: String) in enumerate(array) {
//Some of the strings...
array.removeAtIndex(index)
}
In Swift 2 this is quite easy using enumerate and reverse.
var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
a.removeAtIndex(i)
}
print(a)
You might consider filter way:
var theStrings = ["foo", "bar", "zxy"]
// Filter only strings that begins with "b"
theStrings = theStrings.filter { $0.hasPrefix("b") }
The parameter of filter is just a closure that takes an array type instance (in this case String) and returns a Bool. When the result is true it keeps the element, otherwise the element is filtered out.
In Swift 3 and 4, this would be:
With numbers, according to Johnston's answer:
var a = [1,2,3,4,5,6]
for (i,num) in a.enumerated().reversed() {
a.remove(at: i)
}
print(a)
With strings as the OP's question:
var b = ["a", "b", "c", "d", "e", "f"]
for (i,str) in b.enumerated().reversed()
{
if str == "c"
{
b.remove(at: i)
}
}
print(b)
However, now in Swift 4.2 or later, there is even a better, faster way that was recommended by Apple in WWDC2018:
var c = ["a", "b", "c", "d", "e", "f"]
c.removeAll(where: {$0 == "c"})
print(c)
This new way has several advantages:
It is faster than implementations with filter.
It does away with the need of reversing arrays.
It removes items in-place, and thus it updates the original array instead of allocating and returning a new array.
When an element at a certain index is removed from an array, all subsequent elements will have their position (and index) changed, because they shift back by one position.
So the best way is to navigate the array in reverse order - and in this case I suggest using a traditional for loop:
for var index = array.count - 1; index >= 0; --index {
if condition {
array.removeAtIndex(index)
}
}
However in my opinion the best approach is by using the filter method, as described by #perlfly in his answer.
No it's not safe to mutate arrays during enumaration, your code will crash.
If you want to delete only a few objects you can use the filter function.
Either create a mutable array to store the items to be deleted and then, after the enumeration, remove those items from the original. Or, create a copy of the array (immutable), enumerate that and remove the objects (not by index) from the original while enumerating.
The traditional for loop could be replaced with a simple while loop, useful if you also need to perform some other operations on each element prior to removal.
var index = array.count-1
while index >= 0 {
let element = array[index]
//any operations on element
array.remove(at: index)
index -= 1
}
I recommend to set elements to nil during enumeration, and after completing remove all empty elements using arrays filter() method.
Just to add, if you have multiple arrays and each element in index N of array A is related to the index N of array B, then you can still use the method reversing the enumerated array (like the past answers). But remember that when accessing and deleting the elements of the other arrays, no need to reverse them.
Like so, (one can copy and paste this on Playground)
var a = ["a", "b", "c", "d"]
var b = [1, 2, 3, 4]
var c = ["!", "#", "#", "$"]
// remove c, 3, #
for (index, ch) in a.enumerated().reversed() {
print("CH: \(ch). INDEX: \(index) | b: \(b[index]) | c: \(c[index])")
if ch == "c" {
a.remove(at: index)
b.remove(at: index)
c.remove(at: index)
}
}
print("-----")
print(a) // ["a", "b", "d"]
print(b) // [1, 2, 4]
print(c) // ["!", "#", "$"]