4

I have one array of models which has 2 properties:

1. title (String)

2. isSelected (Bool)

I want to sort them based on the isSelected property such that it does relatively to the sequence of source array. For example, if all are FALSE or TRUE then there should be same output after ordering.

But if I use following method, it alters the original sorting after ordering them based on that BOOL property.

someArray = array.sort { $0.selected && !$1.selected }

What should I do to retain original sequence?

EDIT 1

Sorting order of title does not come in picture. May be server send me 4 objects having title Z,Y,P,A. So if all are false and once I sort, it should be like Z false,Y false, P false, A false.

NSPratik
  • 4,714
  • 7
  • 51
  • 81
  • Is that your actual code? You should see a compiler warning *“Constant 'someArray' inferred to have type '()', which may be unexpected”* – Martin R Apr 03 '19 at 12:12
  • I have just placed a prototype – NSPratik Apr 03 '19 at 12:13
  • What you are (apparently) looking for is a “stable” sort. In Swift 5 the `sort()` method happens to be stable, but that is not guaranteed. – Martin R Apr 03 '19 at 12:28
  • 2
    See also [How to stable sort an array in swift?](https://stackoverflow.com/questions/40673374/how-to-stable-sort-an-array-in-swift). – Martin R Apr 03 '19 at 12:32

4 Answers4

6

You need

someArray = array.sorted (by:{ $0.selected && !$1.selected && <#Add more conditions #>})

sorted (by doesn't mutate original array unlike sort

https://developer.apple.com/documentation/swift/array/1688499-sort

https://developer.apple.com/documentation/swift/array/2296815-sorted

Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
6

Instead of sorting, you can do that more effectively using a filter operation:

let newArray = array.filter { $0.selected } + array.filter { !$0.selected }

Unfortunately, Array.sorted does not guarantee a stable sort, therefore you cannot just sort by one property and expect the original sort to be kept.

You can implement stable sort easily though (e.g. https://stackoverflow.com/a/50545761/669586)

Then you can simply use:

let newArray = array.stableSorted {    
   let numeric1 = $0.selected ? 0 : 1
   let numeric2 = $1.selected ? 0 : 1

   return numeric1 <= numeric2
}

or

let newArray = array.stableSorted {
   return $0.selected == $1.selected || $0.selected
}
Sulthan
  • 128,090
  • 22
  • 218
  • 270
2

You should use the sorted method instead of sort;

let newArray = array.sorted { $0.selected && !$1.selected }

Basically sort sorts in the same array, sorted return a new array, without changing the original

Rico Crescenzio
  • 3,952
  • 1
  • 14
  • 28
2

If you want to preserve the order that you get from a server I will suggest you not using sort or sorted method, because they are not guaranteed to preserve original order for keys with the same order. I will recommend you to write own sorting-like function:

struct Model {
    let title:String
    let selected:Bool
}

let array = [ Model(title: "Z", selected: true), Model(title: "Z", selected: false), Model(title: "Y", selected: false), Model(title: "P", selected: false), Model(title: "A", selected: true), Model(title: "A", selected: false)]

extension Array where Element == Model {
    func preserveSorted() -> [Model] {
        var selected:[Model] = []
        var notSelected:[Model] = []

        for model in self {
            if model.selected {
                selected.append(model)
            } else {
                notSelected.append(model)
            }
        }
        var output:[Model] = []
        output.append(contentsOf: selected)
        output.append(contentsOf: notSelected)
        return output
    }
}


var newArray:[Model] = array.preserveSorted()
print(newArray)
/* output:
[
    Model(title: "Z", selected: true),
    Model(title: "A", selected: true),
    Model(title: "Z", selected: false),
    Model(title: "Y", selected: false),
    Model(title: "P", selected: false),
    Model(title: "A", selected: false)
]
*/

If you don't want to create Comparable extension to your model (because for example, you are using a different order of these models in a few places), you can use method sorted, but provide a different sorting block:

struct Model {
    let title:String
    let selected:Bool
}

let array = [ Model(title: "a", selected: true), Model(title: "a", selected: false), Model(title: "b", selected: false), Model(title: "c", selected: true)]

let newArray = array.sorted { (lhs, rhs) -> Bool in
    if lhs.selected == rhs.selected {
        return lhs.title < rhs.title
    }
    return lhs.selected
}
print(newArray)

and the sorting order will be:

[
  Model(title: "a", selected: true), 
  Model(title: "c", selected: true), 
  Model(title: "a", selected: false), 
  Model(title: "b", selected: false)
 ]
Maciej Gad
  • 1,701
  • 16
  • 21
  • Thanks, title does not come in picture. May be server send me 4 objects having title Z,Y,P,A. So if all are false and once I sort, it should be like Z false,Y false, P false, A false. – NSPratik Apr 03 '19 at 12:21
  • @NSPratik I've updated my answer, could you check it? – Maciej Gad Apr 03 '19 at 12:41