2

I have one array that defines an order,

let orderSettingArray = ["Admiral", "Captain", "Lieutenant"]

and another array with a few of these values

var myArray = ["Lieutenant", "Captain"]

I want to sort myArray to reflect the order of orderSettingArray:

var myArraySorted = myArray.getSorted(by: orderSettingArray)

Now print(myArraySorted) should print ["Captain", "Lieutenant"]

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
JOG
  • 5,590
  • 7
  • 34
  • 54
  • Military ranks are one of the ideal use cases for enums. You should take advantage of that. – Alexander May 17 '17 at 07:27
  • 1
    @NiravD: You are right (and I should have known about that one :) – This question is a tiny bit more general because it asks for an array extension method, but the underlying ideas are the same. Let's wait for more opinions whether to close this as a duplicate or not. – Martin R May 17 '17 at 07:53
  • @MartinR I know​ that's why I added duplicates question as just suggestion – Nirav D May 17 '17 at 07:59
  • The military ranks are just an example. The real scenario has customizable strings. @Alexander – JOG May 17 '17 at 20:23

4 Answers4

6

So Easy let new = orderSettingArray.filter{ return myArray.contains($0) }

Magic
  • 115
  • 6
2
  • Map each array element to a (element, index) tuple, where the index is the index of the array element in the order array. In your example that would be

    [("Lieutenant", 2), ("Captain", 1)]
    
  • Sort the array of tuples by the second tuple element (the index). In your case

    [("Captain", 1), ("Lieutenant", 2)]
    
  • Extract the array elements from the sorted tuples array. In your case

    ["Captain", "Lieutenant"]
    

Code (for any array of equatable elements, not restricted to arrays of strings):

extension Array where Element: Equatable {
    func getSorted(by orderArray: [Element]) -> [Element] {

        return self.map { ($0, orderArray.index(of: $0) ?? Int.max) }
            .sorted(by: { $0.1 < $1.1 })
            .map { $0.0 }

    }
}

let orderSettingArray = ["Admiral", "Captain", "Lieutenant"]
let myArray = ["Lieutenant", "Captain"]
let myArraySorted = myArray.getSorted(by: orderSettingArray)
print(myArraySorted) // ["Captain", "Lieutenant"]

Elements in myArray which are not present in orderSettingArray are assigned the index Int.max and therefore sorted to the end of the result.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Hello, Don't you think that [this answer](http://stackoverflow.com/a/44017405/5501940) is even simpler? and it should be applicable for any array of equatable elements, not restricted to arrays of strings... – Ahmad F May 17 '17 at 06:59
  • @AhmadF: The difference is that it won't handle repeated elements in `myArray`, or elements in `myArray` which are not in `orderSettingArray`. If that is not an issue, the solution is fine. – Martin R May 17 '17 at 07:02
1

Swift 3

extension Array where Element: Hashable {
    func getSorted(by: Array<String>) -> Array<String> {
        var d = Dictionary<Int, String>()

        for value in self {
            for i in 0 ..< by.count {
                if value as! String == by[i] {
                    d[i] = value as? String
                }
            }
        }

        var sortedValues = Array<String>()
        for key in d.keys.sorted(by: <) {
            sortedValues.append(d[key]!)
        }

        return sortedValues
    }
}
Ahmad F
  • 30,560
  • 17
  • 97
  • 143
JOG
  • 5,590
  • 7
  • 34
  • 54
1

Update

I've found a better design for this. It has quite a few improvements:

  • I use a Dictionary to speed up ordering look-ups
  • By extracting this behaviour into a separate type, the dictionary can be cached and reused between multiple sorts
  • The ordering-derivation is decoupled from sorting, so it could be used in more ways
  • The omitEntirely responsibility was removed entirely. It should instead by done by a simple call to filter, with hardcodedOrdering.contains as the predicate.

See my new answer, here

Alexander
  • 59,041
  • 12
  • 98
  • 151