0

I'm trying to sort list of strings in this order:

("7foo", "FOO1", "FOO2", "foo4", "foo4_1", "foo5", "foo5_1", "FOO8", "Foo27_QA", "Foo29_QA")

I tried to use list.sortedWith{}:

val list = mutableListOf("FOO1", "FOO2", "7foo", "FOO8", "foo5","foo27_QA", "foo4_1", "foo29_QA", "foo5_1", "foo4")

val sorted2 = list.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it }))

but the result is: [7foo, FOO1, FOO2, Foo27_QA, Foo29_QA, foo4, foo4_1, foo5, foo5_1, FOO8]

mad_lad
  • 654
  • 3
  • 8
  • 20
Randy
  • 9
  • 2
  • You can look here: https://stackoverflow.com/questions/104599/sort-on-a-string-that-may-contain-a-number It is for Java, but it is pretty much the same. – broot Mar 01 '23 at 07:46
  • @broot thanks for your help, I already tried some of the comments in the link you provided. – Randy Mar 01 '23 at 08:29
  • 2
    You need to start with describing the kind of order you want. I can't see any order in your goal, to me it looks completely arbitrary. – Agent_L Mar 01 '23 at 10:01

2 Answers2

2

The problem is the order you would like to get.

Default comparator, as well as CASE_INSENSITIVE_ORDER comparator, follows lexicographic order. But, your expected result is not in such an order.

In lexicographic-case-insensitive order: Foo29_QA is before foo4. So the result is fine while using the default comparator.

So, you need to write your own comparator to apply your own logic.

val sorted3 = list.sortedWith { o1, o2 -> 
    // put your comparing logic here
}

Okey, so how to sort your list (this won't be an easy-peasy task). I assume that your orders follows the pattern:

{$number_prefix}{"foo"}{$number}{"_"}{$alphanumeric_suffix}

And we would like to sort it by:

  • $number_prefix DESC
  • $number ASC
  • $alphanumeric_suffix DESC

So, finally, we can use something like:

// create helper class for comparing
data class Element(
    val originalValue: String,
    val numberPrefix: Int?,
    val number: Int?,
    val suffix: String
)

// parse string into Element (for strings that not follow pattern
// `{$number_prefix}{"foo"}{$number}{"_"}{$alphanumeric_suffix}`
// this may throw an exception
fun parse(s: String): Element {
    val normalized = s.lowercase()
    val fooSplit = normalized.split("foo")

    // determine numberPrefix
    val numberPrefix = fooSplit.first().toIntOrNull()

    // parse the rest
    val suffixRest = fooSplit.last()
    val suffixRestSplit = suffixRest.split("_")

    // and get rest of the data
    val number = suffixRestSplit.first().toIntOrNull()
    val suffix = suffixRestSplit.last()

    return Element(s, numberPrefix, number, suffix)
}

fun test() {
    // define list
    val list = mutableListOf("FOO1", "FOO2", "7foo", "FOO8", "foo5", "foo27_QA", "foo4_1", "foo29_QA", "foo5_1", "foo4")

    // create a comparator with defined comparing rules
    val elementComparator = compareByDescending <Element> { it.numberPrefix }
        .thenBy { it.number }
        .thenByDescending { it.suffix }

    // and sort it with the defined comparator (map it in the fly)
    val sorted = list
        .map { parse(it) } // parse to helper-Element class
        .sortedWith(elementComparator) // compare with our defined rules
        .map { it.originalValue } // go back to original values

    // prints: [7foo, FOO1, FOO2, foo4, foo4_1, foo5, foo5_1, FOO8, foo27_QA, foo29_QA]
    println(sorted)
}
Cililing
  • 4,303
  • 1
  • 17
  • 35
  • thanks for the explanation. Do you have any suggestion on how to achieve the order? – Randy Mar 01 '23 at 11:08
  • Could you explain what order do you expect here? As I see, the patter here is something like: `{$number_prefix}{"foo"}{$number}{"_"}{$alphanumeric_suffix}`, right? And you need to sort the collection by each component separately, right? – Cililing Mar 01 '23 at 11:47
0

You could try to use this function to extract the number

fun getNumber(s: String) = ("foo(\\d+)".toRegex().find(s.lowercase())?.groups?.get(1)?.value ?: "0").toInt()

then you can do

val sorted2  = list.sortedWith(compareBy({
    getNumber(it)
}, { it }))
Ivo
  • 18,659
  • 2
  • 23
  • 35