I want to sort an array of string alphabetically, but with an exception that some elements should always be sorted first and second in the array. Here are the elements of the array:
["cat", "dog", "bird", "zebra", "elephant"]
I want it sorted alphabetically, but with zebra
always first and cat
always second, so after sorted it should look like:
["zebra", "cat", "bird", "dog", "elephant"]
This is how I have approached it:
let animals = ["cat", "dog", "bird", "zebra", "elephant"]
animals = animals.sorted(by: { first, second in
if first == "zebra" {return true}
if first == "cat" {return true}
return first < second
})
It returns zebra first, but not cat second
CodePudding user response:
The closure you pass to sorted(by:)
should return true
whenever the elements are in ascending order, and false
otherwise. You can’t tell if the elements are in ascending order unless you check both the first value and the second. That’s why returning true
whenever the first value is “zebra” or “cat” won’t work—not all your bases are covered.
One solution would be to use a switch
statement to specify the comparison logic depending on the values you’re looking at:
let animals = ["cat", "dog", "bird", "zebra", "elephant"].sorted {
switch ($0, $1) {
case ("zebra", "cat"): // zebra is before cat
return true
case ("cat", "zebra"): // cat is not before zebra
return false
case ("cat", _), ("zebra", _): // zebra/cat are before everything
return true
case (_, "cat"), (_, "zebra"): // zebra/cat are not after anything
return false
case let (lhs, rhs): // alphabetical order
return lhs < rhs
}
}
// ["zebra", "cat", "bird", "dog", "elephant"]
If this looks a little overengineered, that’s because it is. Covering all your bases like this is hard, so I would definitely recommend taking a look at your use case and considering whether you really need to do it this way. If you can get away with something simpler, that’s probably your best bet. For example:
let animals = ["zebra", "cat"] ["dog", "bird", "elephant"].sorted()
// ["zebra", "cat", "bird", "dog", "elephant"]
Or, if the animals
array can’t be modified, another option would be to hard-code the exceptions:
let exceptions = ["zebra", "cat"]
let otherAnimals = animals.filter { !exceptions.contains($0) }.sorted()
let sortedResult = exceptions otherAnimals
// ["zebra", "cat", "bird", "dog", "elephant"]
Edit: A now-removed comment questioned the reliability of the switch
statement method. I tested it with every possible order of the animals
array, and it returned the correct result every time.
CodePudding user response:
The answer you're going for is:
var animals = ["cat", "dog", "bird", "zebra", "elephant"]
animals = animals.sorted(by: { first, second in
if first == "cat" && second == "zebra" {return false;}
if second == "cat" && first == "zebra" {return true;}
if first == "zebra" || first == "cat" {return true;}
if second == "zebra" || second == "cat" {return false;}
return first < second
})
You could even try calling animals.shuffle()
before you sort to check that the sorting works regardless of input ordering.
This is the more readable way to do it, although potentially less efficient:
var animals = ["cat", "dog", "bird", "zebra", "elephant"]
animals = animals.sorted();
var wordIndex:Int = animals.firstIndex(where: {$0 == "zebra"})!;
animals.insert(animals.remove(at: wordIndex), at: 0);
wordIndex = animals.firstIndex(where: {$0 == "cat"})!;
animals.insert(animals.remove(at: wordIndex), at: 1);
Or if you want a more generalized version:
func sortAndPrepend(inputArr: [String], prependWords: [String]) -> [String]{
var array = inputArr.sorted();
var wordIndex:Int;
for i in 0...prependWords.count-1 {
wordIndex = array.firstIndex(where: {$0 == prependWords[i]}) ?? -1;
if (wordIndex != -1) {
array.insert(array.remove(at: wordIndex), at: i);
}
}
return array;
}