1

I have pairs of list and i want to sort by Pair.second if Pair.first is not null

my list

val list = listOf<Pair<Int?, Int>>(
    Pair(1, 199),
    Pair(null, 180),
    Pair(10, 210),
    Pair(null, 178)
)

Desired result

 Pair(1, 199),
 Pair(10, 210),
 Pair(null, 178),
 Pair(null, 180)
Jonas
  • 207
  • 2
  • 7
  • You've got a couple of solutions here that both work, but it's not clear which one you want - they sort differently, but your example matches both of them. If you had ``Pair(null, 3)`` in your data, where would that end up? – cactustictacs Mar 26 '22 at 18:35
  • 1
    @cactustictacs Thanks for your time! both solution works for this specific question and if i had `Pair(null, 3)` that would be end up just below the `Pair(10,210)`. But for my use case Pawel answer works fine, if i change my value `Pair(1, 220)` then pawel solution works fine and your failed to do the task. – Jonas Mar 27 '22 at 13:34
  • Oh ok, so Pawel had you right - you wanted all the "use ``first``" items sorted together, followed by the "use ``second``" ones sorted together. I thought you wanted to use ``first`` as the sorting key, falling back to ``second`` if it's null. No worries! – cactustictacs Mar 27 '22 at 16:52

3 Answers3

3

I suggest checking out this question: How to sort based on/compare multiple values in Kotlin?

You can combine multiple sort criteria, first push nulls to the end then compare the fields:

val result = list.sortedWith(
    compareBy<Pair<Int?, Int>> { it.first == null } // sort nulls explicitly or they'll end up before non-null values
        .thenBy{ it.first }
        .thenBy{ it.second }
)
Pawel
  • 15,548
  • 3
  • 36
  • 36
  • I think we've both assumed the OP means something different - you're sorting all the pairs with a non-null ``first``, and then all the remaining ones by ``second``. Mine sorts them all together using ``second`` if there isn't a ``first``. The OP's example output matches both of those unfortunately! – cactustictacs Mar 26 '22 at 18:31
  • @Pawel Thanks for taking time for this. Your solution works fine for my use case – Jonas Mar 27 '22 at 13:29
1

You can just use an elvis operator to default to using the second item if the first is null

list.sortedBy { it.first ?: it.second }
cactustictacs
  • 17,935
  • 2
  • 14
  • 25
  • This is wrong - if you compare two pairs (A & B) and B has null first value while A doesn't it'll end up comparing `A.first` to `B.second` value which should never happen. If you introduce additional pair to the list like `Pair(null, 3)` you can see the result is incorrect. – Pawel Mar 26 '22 at 18:20
  • @Pawel works for me? https://pl.kotl.in/_imYLzrLH It ends up between ``1`` and ``10``. All that code does is pick a value to represent the ``Pair``, which is either the first value, or the second if the first is null. Then the list is sorted based on those representative values – cactustictacs Mar 26 '22 at 18:26
  • Ok so that was intentional on your part, but I'm guessing OP wanted to sort by second value entirely (against other second values) - at this point they have to clarify because their demo dataset is ambiguous. – Pawel Mar 26 '22 at 18:29
  • @Pawel yeah I just noticed that! Tricky – cactustictacs Mar 26 '22 at 18:33
  • Hey whoever voted this down, it *is* a correct solution to the question asked and example output given - whether it's what the OP wanted or not. It's not helpful to mark things down that might help others – cactustictacs Mar 26 '22 at 18:45
  • @cactustictacs I voted it down, because even if the question is not explained very well and example is ambiguous, it is still rather clear to me that your solution does a different thing than asked. "sort by Pair.second if Pair.first is not null" doesn't sound to me like: "mix first and second and sort them together". In general, we do this very rarely, because usually it doesn't make sense to mix two distinct fields as they are one. Even in the provided example we clearly see that value ranges for `first` and `second` are much different, suggesting that they are different kinds of data, – broot Mar 29 '22 at 08:46
  • @broot but it isn't that unusual to derive your sorting key from multiple potential values, and to fallback to a secondary field if a primary one is missing - it's definitely something someone might need to do! And that's how I read the question - "sort by ``first`` unless that's null, in which case use ``second``". It's ambiguous enough that it can be read either way (and was!) - I'm just thinking about people searching with this requirement, assuming the question matches their needs, and thinking they need a ``Comparator``. I think the comments make it clear which does what at this point – cactustictacs Mar 29 '22 at 18:38
1

You could split the list into two lists, one containing the pairs with the null value, the other containing the others, and then sort each group by the second value:

val list = listOf(
  Pair(1, 199),
  Pair(null, 180),
  Pair(10, 210),
  Pair(null, 178)
)

val result = list
  .groupBy { it.first == null }
  .flatMap { (_, subList) -> subList.sortedBy { it.second } }

result.forEach(::println)

Output:

(1, 199)
(10, 210)
(null, 178)
(null, 180)
lukas.j
  • 6,453
  • 2
  • 5
  • 24