Sorry for the complex wording of the question. My main experience is with PHP and it has a command called array_multisort. The syntax is below:
bool array_multisort ( array &$array1 [, mixed $array1_sort_order = SORT_ASC [, mixed $array1_sort_flags = SORT_REGULAR [, mixed $... ]]] )
It lets you sort 1 array and the reorder multiple other arrays based on the key changes in the original.
Is there an equivalent command in Swift / Xcode 7.2?
I have currently have a set of arrays:
FirstName Age City Country Active
Active is an array of time in seconds that a user has been active within my app. I would like to order that descending or ascending and the other arrays to change to remain consistent.
-
2Why not define a struct type that will hold properties firstName, age, city, country, active. Then have an array of that structs, and sort that array the way you need?0x416e746f6e– 0x416e746f6e2016年02月08日 16:14:59 +00:00Commented Feb 8, 2016 at 16:14
4 Answers 4
You could create an array of indexes in sorted order and use it as a mapping:
var names = [ "Paul", "John", "David" ]
var ages = [ 35, 42, 27 ]
let newOrder = names.enumerate().sort({0ドル.1<1ドル.1}).map({0ドル.0})
names = newOrder.map({names[0ドル]})
ages = newOrder.map({ages[0ドル]})
[EDIT] Here's an improvement on the technique :
It's the same approach but does the sorting and assignment in one step. (can be reassigned to original arrays or to separate ones)
(firstNames,ages,cities,countries,actives) =
{(
0ドル.map{firstNames[0ドル]},
0ドル.map{ages[0ドル]},
0ドル.map{cities[0ドル]},
0ドル.map{countries[0ドル]},
0ドル.map{actives[0ドル]}
)}
(firstNames.enumerated().sorted{0ドル.1<1ドル.1}.map{0ドル.0})
[EDIT2] and an Array extension to make it even easier to use if you are sorting in place:
extension Array where Element:Comparable
{
func ordering(by order:(Element,Element)->Bool) -> [Int]
{ return self.enumerated().sorted{order(0ドル.1,1ドル.1)}.map{0ドル.0} }
}
extension Array
{
func reorder<T>(_ otherArray:inout [T]) -> [Element]
{
otherArray = self.map{otherArray[0ドル as! Int]}
return self
}
}
firstNames.ordering(by: <)
.reorder(&firstNames)
.reorder(&ages)
.reorder(&cities)
.reorder(&countries)
.reorder(&actives)
combining the previous two:
extension Array
{
func reordered<T>(_ otherArray:[T]) -> [T]
{
return self.map{otherArray[0ドル as! Int]}
}
}
(firstNames,ages,cities,countries,actives) =
{(
0ドル.reordered(firstNames),
0ドル.reordered(ages),
0ドル.reordered(cities),
0ドル.reordered(countries),
0ドル.reordered(actives)
)}
(firstNames.ordering(by:<))
-
Cool solution. Let me see if I understand how it works.
enumerate()
maps the original names array to an array of tuples of the form (index, object). Then you sort those tuples, where 0ドル.1 means first object being compared, 2nd entry in the tuple (name string). You map the result of the sort to return an array of the index entries of each tuple.Duncan C– Duncan C2016年02月08日 16:29:37 +00:00Commented Feb 8, 2016 at 16:29 -
Thank you Alain T. Your code worked perfectly for my needs. A single line solution is great.PlatformDating– PlatformDating2016年02月08日 23:34:04 +00:00Commented Feb 8, 2016 at 23:34
-
A follow up question: What is the "type" of newOrder?PlatformDating– PlatformDating2016年02月09日 00:16:55 +00:00Commented Feb 9, 2016 at 0:16
-
It's an array of indices so: [Int]Alain T.– Alain T.2016年02月09日 00:42:01 +00:00Commented Feb 9, 2016 at 0:42
I would go with @AntonBronnikov suggestion, and put all your properties into an struct, making an Array
of that particular struct
and then sorting it.
This data is clearly related and it's a cleaner approach.
Edit this is valid for 2 arrays:
Adding to @AlainT answer, but using zip
:
var names = [ "Paul", "John", "David" ]
var ages = [ 35, 42, 27 ]
let sortedTuple = zip(names, ages).sort { 0ドル.0.0 < 0ドル.1.0 }
Something more generic:
names.enumerate().sort({0ドル.1<1ドル.1}).map({ (name: 0ドル.1, age: ages[0ドル.0]) })
-
While AlainT:s answer generalizes to the OP:s example of more than 2 arrays (indices in sorted order can be applied to each of them), zip is limited to pairs (2-tuples), and not really an option in this context.dfrib– dfrib2016年02月08日 16:36:59 +00:00Commented Feb 8, 2016 at 16:36
-
zip
were my first approach as well, some 30 minutes ago, but it ended up in a manual 5-zip instead, not very pretty :)dfrib– dfrib2016年02月08日 16:55:45 +00:00Commented Feb 8, 2016 at 16:55 -
@dfri credits go to @AlainT anyway, the trick is
enumerate
. I think we just prefer to return a tuple instead :)FranMowinckel– FranMowinckel2016年02月08日 16:58:28 +00:00Commented Feb 8, 2016 at 16:58 -
Yeah I think his solution is the most general in this case. I added my manual zip5-solution as well, for the sake of completion.dfrib– dfrib2016年02月08日 17:01:34 +00:00Commented Feb 8, 2016 at 17:01
I believe AlainT:s solution is to prefer, but to extend the variety of options, below follows a solution mimicking what a zip5
method could let us achive (in case we could use zip
for zipping together 5 sequences instead of its limit of 2):
/* example arrays */
var firstName: [String] = ["David", "Paul", "Lisa"]
var age: [Int] = [17, 27, 22]
var city: [String] = ["London", "Rome", "New York"]
var country: [String] = ["England", "Italy", "USA"]
var active: [Int] = [906, 299, 5060]
/* create an array of 5-tuples to hold the members of the arrays above.
This is an approach somewhat mimicking a 5-tuple zip version. */
var quinTupleArr : [(String, Int, String, String, Int)] = []
for i in 0..<firstName.count {
quinTupleArr.append((firstName[i], age[i], city[i], country[i], active[i]))
}
/* sort w.r.t. 'active' tuple member */
quinTupleArr.sort { 0ドル.4 < 1ドル.4 }
/* map back to original arrays */
firstName = quinTupleArr.map {0ドル.0}
age = quinTupleArr.map {0ドル.1}
city = quinTupleArr.map {0ドル.2}
country = quinTupleArr.map {0ドル.3}
active = quinTupleArr.map {0ドル.4}
-
just a typo:
quinTupleArr.count == 0
. And if you use named tuples... but that's just a preferenceFranMowinckel– FranMowinckel2016年02月08日 17:05:16 +00:00Commented Feb 8, 2016 at 17:05 -
@FranMowinckel Ah thanks. I'll leave the tuples unnamed for this technical example.dfrib– dfrib2016年02月08日 17:08:07 +00:00Commented Feb 8, 2016 at 17:08