12

I already have code to sort by 1 value as shown below, but I'm wondering how to sort using multiple values? I would like to sort by set and then by someString.

One is an integer, and one is a string in this case. I had considered converting the integer to a string and then concatenating them, but thought there must be a better way because I may have 2 integers to sort by in the future.

struct Condition {
    var set = 0
    var someString = ""
}

var conditions = [Condition]()

conditions.append(Condition(set: 1, someString: "string3"))
conditions.append(Condition(set: 2, someString: "string2"))
conditions.append(Condition(set: 3, someString: "string7"))
conditions.append(Condition(set: 1, someString: "string9"))
conditions.append(Condition(set: 2, someString: "string4"))
conditions.append(Condition(set: 3, someString: "string0"))
conditions.append(Condition(set: 1, someString: "string1"))
conditions.append(Condition(set: 2, someString: "string6"))

// sort
let sorted = conditions.sorted { (lhs: Condition, rhs: Condition) -> Bool in
    return (lhs.set) < (rhs.set)
}

// printed sorted conditions
for index in 0...conditions.count-1 {
    println("\(sorted[index].set) - \(sorted[index].someString)")
}

3 Answers 3

14

I'm not yet proficient in Swift, but the basic idea for a multiple-criteria sort is:

let sorted = conditions.sorted { (lhs: Condition, rhs: Condition) -> Bool in
    if lhs.set == rhs.set {
        return lhs.someString < rhs.someString
    }
    return (lhs.set) < (rhs.set)
}
Sign up to request clarification or add additional context in comments.

Comments

7

Even though the previous answers are perfectly fine in the requested case, I'd like to put a more general approach for that:


infix operator <=> {
associativity none
precedence 130
}
func <=> <T: Comparable>(lhs: T, rhs: T) -> NSComparisonResult {
    return lhs < rhs ? .OrderedAscending : lhs == rhs ? .OrderedSame : .OrderedDescending
}
private func _sortedLexicographically<S: SequenceType>(source: S, comparators: [(S.Generator.Element, S.Generator.Element) -> NSComparisonResult]) -> [S.Generator.Element] {
    return sorted(source, { lhs, rhs in
        for compare in comparators {
            switch compare(lhs, rhs) {
            case .OrderedAscending: return true
            case .OrderedDescending: return false
            case .OrderedSame: break
            }
        }
        return false
    })
}
public func sortedLexicographically<S: SequenceType>(source: S, comparators: [(S.Generator.Element, S.Generator.Element) -> NSComparisonResult]) -> [S.Generator.Element] {
    return _sortedLexicographically(source, comparators)
}
extension Array {
    func sortedLexicographically(comparators: [(Element, Element) -> NSComparisonResult]) -> [Element] {
        return _sortedLexicographically(self, comparators)
    }
}

from here it's quite easy to do an ordering like requested:


struct Foo {
    var foo: Int
    var bar: Int
    var baz: Int
}
let foos = [Foo(foo: 1, bar: 2, baz: 3), Foo(foo: 1, bar: 3, baz: 1), Foo(foo: 0, bar: 4, baz: 2), Foo(foo: 2, bar: 0, baz: 0), Foo(foo: 1, bar: 2, baz: 2)]
let orderedFoos = foos.sortedLexicographically([{ $0.foo <=> $1.foo }, { $0.bar <=> $1.bar }, { $0.baz <=> $1.baz }])

If this kind of comparison for that type is intrinsic to the type itself instead of being an one place-only sorting you need, you can follow the more stdlib-like approach and extending Comparable instead:


extension Foo: Comparable {}
func == (lhs: Foo, rhs: Foo) -> Bool {
    return lhs.foo == rhs.foo && lhs.bar == rhs.bar && lhs.baz == rhs.baz
}
func < (lhs: Foo, rhs: Foo) -> Bool {
    let comparators: [(Foo, Foo) -> NSComparisonResult] = [{ $0.foo <=> $1.foo }, { $0.bar <=> $1.bar }, { $0.baz <=> $1.baz }]
    for compare in comparators {
        switch compare(lhs, rhs) {
        case .OrderedAscending: return true
        case .OrderedDescending: return false
        case .OrderedSame: break
        }
    }
    return false
}
let comparableOrderedFoos = sorted(foos)

There would be another possible approach which is making a LexicographicallyComparable protocol which says which Comparable fields and in which priority they do have, but unfortunately I can't think of a way to make it without using variadic generics, which are not supported in Swift as 2.0, while maintaining the type safety typical to Swift code.

4 Comments

This is the correct approach, IMHO. If you are going to define a struct, and you want to be able to compare it, it's the struct that defines it's ordering
Indeed, I agree. But there still could be cases where you want to show an alternative sorting order and you would want that possible. As they say: YMMV
Extra points should be awarded for having demonstrated the use of Swift custom operators as well as type generics to solve this problem! Bravo!
Amazing answer! In Swift 2, sorted has changed to sort, and it is now called on the collection, i.e.: source.sort(...
6

You would compare someString if the set values were the same, otherwise, use your existing comparison:

let sorted = conditions.sorted { (lhs: Condition, rhs: Condition) -> Bool in
    if lhs.set == rhs.set {
        return lhs.someString < rhs.someString
    } else {
        return lhs.set < rhs.set
    }
}

1 Comment

That does it. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.