Home > Enterprise >  Sort array on parameter index, but when item has no index, place them at the beginning
Sort array on parameter index, but when item has no index, place them at the beginning

Time:07-06

I have an array with elements which have an index and some don't have an index. I can sort them perfectly but the items without an index are placed after the indexed items. How can I sort the array with the items which have no index before the indexed sorted items?

let elements = content.sorted(by: { $0.Index ?? 0 <= $1.Index ?? 1 })

Example:

[
   {
       "Image" : "apple.png",
       "Index" : 2,
   },
   {
       "Image" : "pear.png",
       "Index" : 0,
   },
   {
       "Image" : "banana.png",
   },
   {
       "Image" : "orange.png",
       "Index" : 1,
   }
]

Now i get:

[ pear.png, orange.png, apple.png, banana.png ]

I want this order:

[ banana.png, pear.png, orange.png, apple.png ]

CodePudding user response:

You can do it neatly with a switch:

let ord = unordered.sorted { l, r in
    switch (l.idx, r.idx) {
    case let (.some(li), .some(ri)):
        // Both sides have an index, compare them:
        return li < ri
    case (.some, _):
        // the left hand one has an index, so that comes after
        // the right hand side one
        // if right hand side one had an index the previous case would have applied
        return false
    case (.none, _):
        // the left hand one has no index so comes first, regardless of if the right hand side one has an index or not
        return true
    }
}

This also extends for if/when you want to sort items by a second criteria, like sort all the unindexed ones using localizedStandardCompare:

CodePudding user response:

Compare when you have values.

Otherwise, push the nils to the front.

let elements = content.sorted {
  Optional($0.index, $1.index).map(<)
  ?? ($0.index == nil)
}
public extension Optional {
  /// Exchange two optionals for a single optional tuple.
  /// - Returns: `nil` if either tuple element is `nil`.
  init<Wrapped0, Wrapped1>(_ optional0: Wrapped0?, _ optional1: Wrapped1?)
  where Wrapped == (Wrapped0, Wrapped1) {
    self = .init((optional0, optional1))
  }

  /// Exchange two optionals for a single optional tuple.
  /// - Returns: `nil` if either tuple element is `nil`.
  init<Wrapped0, Wrapped1>(_ optionals: (Wrapped0?, Wrapped1?))
  where Wrapped == (Wrapped0, Wrapped1) {
    switch optionals {
    case let (wrapped0?, wrapped1?):
      self = (wrapped0, wrapped1)
    default:
      self = nil
    }
  }
}

CodePudding user response:

One solution, if you want to stick to sorted(by: is:

    struct Fruit {
        let image: String
        let index: Int?
    }
    
    let fruits: [Fruit] = [
        Fruit(image: "apple.png", index: 2),
        Fruit(image: "pear.png", index: 0),
        Fruit(image: "banana.png", index: nil),
        Fruit(image: "orange.png", index: 1)
    ]
        
    let fruitsSorted = fruits.sorted { lhs, rhs in
        guard let lhsIndex = lhs.index else {
            return true
        }
        guard let rhsIndex = rhs.index else {
            return false
        }
        return lhsIndex < rhsIndex
    }
        
    for fruit in fruitsSorted {
        print("\(fruit.image)")
    }

An alternative solution, preferred if you want to define the index-based ordering as a default one is to implement the Comparable protocol and use plain sorted:

struct Fruit {
    let image: String
    let index: Int?
}

extension Fruit: Comparable {
    static func < (lhs: Fruit, rhs: Fruit) -> Bool {
        guard let lhsIndex = lhs.index else {
            return true
        }
        guard let rhsIndex = rhs.index else {
            return false
        }
        return lhsIndex < rhsIndex
    }
}
                    
let fruits: [Fruit] = [
    Fruit(image: "apple.png", index: 2),
    Fruit(image: "pear.png", index: 0),
    Fruit(image: "banana.png", index: nil),
    Fruit(image: "orange.png", index: 1)
]

let fruitsSorted = fruits.sorted()

for fruit in fruitsSorted {
    print("\(fruit.image)")
}

Note that there's no particular ordering of the nil-indexed elements. They will all show up at the beginning but their order depends on the original (unsorted) ordering. You may want to use additional criteria (image name perhaps?) to make your sorted order fully deterministic.

  • Related