6

I wanna sort some strings that contain numbers but after a sort, it becomes like this ["s1", "s10", "s11", ... ,"s2", "s21", "s22"]. after i search i fount this question with same problem. but in my example, I have mutableList<myModel>, and I must put all string in myModel.title for example into a mutable list and place into under code:

   val sortData = reversedData.sortedBy {
          //pattern.matcher(it.title).matches()
             Collections.sort(it.title, object : Comparator<String> {
                override fun compare(o1: String, o2: String): Int {
                    return extractInt(o1) - extractInt(o2)
                }

                 fun extractInt(s: String): Int {
                     val num = s.replace("\\D".toRegex(), "")
                     // return 0 if no digits found
                     return if (num.isEmpty()) 0 else Integer.parseInt(num)
                 }
            })
        }

I have an error in .sortedBy and Collections.sort(it.title), may please help me to fix this.

4
  • "I have an error": would you reveal which one? Commented Dec 10, 2018 at 11:49
  • at first, I must find a way to convert it.title to mutable list. because Collections.sort need that. I don't know how to push all it.title from a model into a mutable list and then I think I must use return before Collections.sort for fixing .sortedBy Commented Dec 10, 2018 at 11:59
  • don't mix Collections.sort and sortedBy. You may want to have a look at sortWith instead.... also: sortedBy and sortedWith are similar (they return a new list), whereas sortWith operates on the current list... Commented Dec 10, 2018 at 12:32
  • how can the accepted answer be the answer to your problem? it is way too complicated than any other answer, it uses Observable/Subscriber, something you didn't... Commented Dec 11, 2018 at 15:19

6 Answers 6

9

you can use sortWith instead of sortBy for example:

class Test(val title:String) {
  override fun toString(): String {
    return "$title"
  }
}

val list = listOf<Test>(Test("s1"), Test("s101"),
Test("s131"), Test("s321"), Test("s23"), Test("s21"), Test("s22"))
val sortData = list.sortedWith( object : Comparator<Test> {
override fun compare(o1: Test, o2: Test): Int {
    return extractInt(o1) - extractInt(o2)
}

fun extractInt(s: Test): Int {
    val num = s.title.replace("\\D".toRegex(), "")
    // return 0 if no digits found
    return if (num.isEmpty()) 0 else Integer.parseInt(num)
}

})

will give output: [s1, s21, s22, s23, s101, s131, s321]

Sign up to request clarification or add additional context in comments.

2 Comments

how I must push all it.title into a list and use in sortData ?
@MehrdadDolatkhah I update my sample, hopefully it can give you the idea how to solve your problem, how to access title (attribute). change class Test with your MyModel class
5

A possible solution based on the data you posted:

sortedBy { "s(\\d+)".toRegex().matchEntire(it)?.groups?.get(1)?.value?.toInt() }

Of course I would move the regex out of the lambda, but it is a more concise answer this way.

Comments

2

A possible solution can be this:

  reversedData.toObservable()
                    .sorted { o1, o2 ->
                        val pattern = Pattern.compile("\\d+")
                        val matcher = pattern.matcher(o1.title)
                        val matcher2 = pattern.matcher(o2.title)

                        if (matcher.find()) {
                            matcher2.find()
                            val o1Num = matcher.group(0).toInt()
                            val o2Num = matcher2.group(0).toInt()

                            return@sorted o1Num - o2Num
                        } else {
                            return@sorted o1.title?.compareTo(o2.title ?: "") ?: 0
                        }
                    }
                    .toList()
                    .subscribeBy(
                        onError = {
                            it
                        },
                        onSuccess = {
                            reversedData = it
                        }
                    )

1 Comment

Use Kotlin's regex extensions, they're shorter and simpler. Check my answer for an example.
2

As you state that you need a MutableList, but don't have one yet, you should use sortedBy or sortedWith (in case you want to work with a comparator) instead and you get just a (new) list out of your current one, e.g.:

val yourMutableSortedList = reversedData.sortedBy {
  pattern.find(it)?.value?.toInt() ?: 0
}.toMutableList() // now calling toMutableList only because you said you require one... so why don't just sorting it into a new list and returning a mutable list afterwards?

You may want to take advantage of compareBy (or Javas Comparator.comparing) for sortedWith.

If you just want to sort an existing mutable list use sortWith (or Collections.sort):

reversedData.sortWith(compareBy {
  pattern.find(it)?.value?.toInt() ?: 0
})

// or using Java imports:
Collections.sort(reversedData, Compatarator.comparingInt {
  pattern.find(it)?.value?.toInt() ?: 0 // what would be the default for non-matching ones?
})

Of course you can also play around with other comparator helpers (e.g. mixing nulls last, or similar), e.g.:

reversedData.sortWith(nullsLast(compareBy {
  pattern.find(it)?.value
}))

For the samples above I used the following Regex:

val pattern = """\d+""".toRegex()

Comments

0

None of the other answers on this page worked for me, or were too confusing, so I'm sharing my simpler solution that I came up with:

Short answer:

Simply call:

yourArray.sortedWith(::numberAwareAlphabeticalSort)

And what is numberAwareAlphabeticalSort, you ask? It's a simple function that you copy-paste somewhere into your code. See full answer below:

Long answer:

This example code below is fully copy-pasteable. Here's a link to a Kotlin REPL where you can try it out yourself: https://pl.kotl.in/oPGE-NekG

val unsortedList = listOf(
    "Zebra",
    "Title 1",
    "Title 10",
    "A title 2",
    "Title 3",
    "Aardvark",
)

// Our helper function which looks for numbers at the end of a string:
fun extractIntegerFromEndOfString(input: String): Int? {
    val words = input.split(" ")
    val lastWord = words.lastOrNull()

    return if (lastWord != null && lastWord.all { it.isDigit() }) {
        lastWord.toInt()
    } else {
        null
    }
}

// Our actual sorting function:
fun numberAwareAlphabeticalSort(item1: String, item2: String): Int {
    val number1 = extractIntegerFromEndOfString(item1)
    val number2 = extractIntegerFromEndOfString(item2)

    return if (number1 != null && number2 != null) {
        // Slice off the number from the end of both strings:
        //   - if the remaining portions of each string are identical, then compare the two integer values that
        //     you extracted
        //   - otherwise, simply compare the two remaining portions alphabetically
        val remaining1 = item1.substring(0, item1.length - number1.toString().length).trim()
        val remaining2 = item2.substring(0, item2.length - number2.toString().length).trim()

        if (remaining1 == remaining2) {
            number1.compareTo(number2)
        } else {
            remaining1.compareTo(remaining2)
        }
    } else {
        // Otherwise, one or both items don't have numbers at the end, so just compare them alphabetically:
        item1.compareTo(item2)
    }
}

val sortedIntelligently = unsortedList.sortedWith(::numberAwareAlphabeticalSort)

sortedIntelligently.map { println(it) }
// Outputs:
//
// A title 2
// Aardvark
// Title 1
// Title 3
// Title 10
// Zebra

Comments

-1

I wrote a custom comparator for my JSON sorting. It can be adapted from bare String/Number/Null

fun getComparator(sortBy: String, desc: Boolean = false): Comparator<SearchResource.SearchResult> {
    return Comparator { o1, o2 ->
        val v1 = getCompValue(o1, sortBy)
        val v2 = getCompValue(o2, sortBy)

        (if (v1 is Float && v2 is Float) {
            v1 - v2
        } else if (v1 is String && v2 is String) {
            v1.compareTo(v2).toFloat()
        } else {
            getCompDefault(v1) - getCompDefault(v2)
        }).sign.toInt() * (if (desc) -1 else 1)
    }
}

private fun getCompValue(o: SearchResource.SearchResult, sortBy: String): Any? {
    val sorter = gson.fromJson<JsonObject>(gson.toJson(o))[sortBy]
    try {
        return sorter.asFloat
    } catch (e: ClassCastException) {
        try {
            return sorter.asString
        } catch (e: ClassCastException) {
            return null
        }
    }
}

private fun getCompDefault(v: Any?): Float {
    return if (v is Float) v else if (v is String) Float.POSITIVE_INFINITY else Float.NEGATIVE_INFINITY
}

1 Comment

So adapt your comparator and share adapted version please.

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.