Using the filter function in kotlin - filter

so the past couple of hours, i have been trying to understand how the filter function works in kotlin and if it has any correlation with that of Java.
basically, i have a code that's written in java and i would love to have it transcribed to kotlin
private List<Order> getFilteredOrders(Courier courier) {
String[] glovoBoxKeywords = glovoBoxWords.toLowerCase().split(",");
List<Vehicle> allowedVehicles = Arrays.asList(MOTORCYCLE, ELECTRIC_SCOOTER);
return orders.stream()
.filter(order -> {
String description = order.getDescription().toLowerCase();
if (!courier.getBox()) {
return Arrays.stream(glovoBoxKeywords).noneMatch(description::contains);
}
return true;
})
.filter(order -> {
Location pickupLocation = order.getPickup();
Location deliveryLocation = order.getDelivery();
Double distance = calculateDistance(pickupLocation, deliveryLocation);
if (distance > longDeliveryDistance) {
return allowedVehicles.contains(courier.getVehicle());
}
return true;
})
.collect(Collectors.toList());
}
i tried this but i got at this, and was literally stuck :(
private fun findFilteredOrder(courier: Courier) : List<Order> {
val glovoBoxKeyWords = glovoBoxWords.toLowerCase().split(",")
val allowedVehicles = listOf(Vehicle.ELECTRIC_SCOOTER, Vehicle.MOTORCYCLE)
orderList.filter { order ->
val description = order.getDescription().toLowerCase()
if(!courier.getBox()) {
}
true
}.filter {
val pickupLocation = it.getPickup()
val deliveryLocation = it.getDelivery()
val distance = calculateDistance(deliveryLocation, pickupLocation)
if(distance > longDeliveryDistance) {
courier.getVehicle() in allowedVehicles
}
true
}
}
Please this is my first attempt and doing something with kotlin, so please go easy guys. thanks, also i'd be appreciative if anyone could help me with informative stuff as to how to understand these kotlin functions better. let, apply, associateBy... etc.. THANKS

The filter function in Kotlin Collections has the same principle as other frameworks/libraries, including Java Streams. Given a predicate (a function from the type of the collection to Boolean) it will return a new collection with the elements matching the predicate. You can find more information and examples of other functions and operators in the official documentation and here.
Your code was almost there, I translate the Java Stream operation to Kotlin List and rewrite the return statements to remove the redundant if
private fun findFilteredOrder(courier: Courier) : List<Order> {
val glovoBoxKeyWords = glovoBoxWords.toLowerCase().split(",")
val allowedVehicles = listOf(Vehicle.ELECTRIC_SCOOTER, Vehicle.MOTORCYCLE)
orderList.filter { order ->
val description = order.getDescription().toLowerCase()
courier.getBox() || glovoBoxKeywords.none { it in description }
}.filter { order ->
val pickupLocation = order.getPickup()
val deliveryLocation = order.getDelivery()
val distance = calculateDistance(deliveryLocation, pickupLocation)
distance <= longDeliveryDistance || courier.getVehicle() in allowedVehicles
}
}

I don't know why no one mentioned the use of labels: https://kotlinlang.org/docs/returns.html#break-and-continue-labels.
Since this question has a nice google ranking, I'll add what I was originally searching for.
The OP probably was aware that filter needs a predicate that returns a Boolean and that the filter will return a list with the items that pass the predicate (the items which the predicate returned true).
What he was not aware is that we can "emulate" Java returns through Kotlin labels:
private fun findFilteredOrder(courier: Courier) : List<Order> {
val glovoBoxKeyWords = glovoBoxWords.toLowerCase().split(",")
val allowedVehicles = listOf(Vehicle.ELECTRIC_SCOOTER, Vehicle.MOTORCYCLE)
orderList.filter shouldSkip#{ order ->
val description = order.getDescription().toLowerCase()
if (courier.getBox()) {
return#shouldSkip true
}
if (glovoBoxKeywords.none { it in description }) {
return#shouldSkip true
}
return#shouldSkip false
}.filter shouldSkip# { order ->
val pickupLocation = order.getPickup()
val deliveryLocation = order.getDelivery()
val distance = calculateDistance(deliveryLocation, pickupLocation)
if (distance <= longDeliveryDistance) {
return#shouldSkip true
}
if (courier.getVehicle() in allowedVehicles) {
return#shouldSkip true
}
return#shouldSkip false
}
}
Since Kotlin allows us to return in the last block line and the return keyword returns to the outer scope, it is pretty easy to:
filter {
startPutting >= someMagic && andComplex ||
verificationsThat.is { hardToUnderstand }.because {
weNeedToReturnHere
}
}
The labels allow us to be more verbose but also more clear.

Related

How to create a sorted merged list from two diffrent ArrayList of Objects based on a common value field in Kotlin?

I have two ArrayLists of different Data classes as given below:
class Record{
var id: Long = 0
var RecordId: Int = 0
var Record: String? = null
var title: String? = null
var description: String? = null
var longDate: Long = 0
}
class Type{
var id: Long = 0
var typeId: Int = 0
var subTypeId: Int = 0
var typeString: String? = null
var longDate: Long = 0
}
var recordsList: ArrayList<Record>
var typesList: ArrayList<Type>
Now, I want a merged list of these two which will be sorted based on a common field in both the Objects i.e. longDate. I have tried .associate , sortedBy, sortedWith(compareBy<>) etc. but could not achieve the desired result.
Here, also there is one point to note is that while comparing the two lists it is possible that one on them may be empty.
This will produce a List<Any> with all items sorted by longDate:
(recordsList + typesList)
.sortedBy {
when (it) {
is Record -> it.longDate
is Type -> it.longDate
else -> error("")
}
}
Or you might consider creating an interface that has val longDate: Long that both of these classes implement. Then you wouldn't need the when expression, and your List would be of the type of the interface.
Something like this should work, but I personally think that it is quite the code smell. There is no guarantee that Record.longDate is truly the same type as Type.longDate (we know that it is, since we create the model, but the compiler would never know).
val result = (recordsList + typesList).sortedBy {
when(it){
is Record -> it.longDate
is Type -> it.longDate
else -> error("incompatible list element $it")
}
}
And it would work something like this: (I've removed some parameters from the models as they don't really count here)
fun main() {
val recordsList = listOf(Record().apply { longDate = 5 }, Record().apply { longDate = 3})
val typesList = listOf(Type().apply { longDate = 3 }, Type().apply { longDate = 2 })
val result = (recordsList + typesList).sortedBy {
when(it){
is Record -> it.longDate
is Type -> it.longDate
else -> error("incompatible list element $it")
}
}
result.forEach{
println(it.toString())
}
}
class Record{
var longDate: Long = 0
override fun toString(): String {
return "Record(longDate=$longDate)"
}
}
class Type{
var longDate: Long = 0
override fun toString(): String {
return "Type(longDate=$longDate)"
}
}
This will output:
Type(longDate=2)
Record(longDate=3)
Type(longDate=3)
Record(longDate=5)
Doing it in a more generic way, so that you can create a fun where you state which property to be used from each object type would most likely use reflection, which I'd avoid at all costs.
So I would definitely consider if one object can inherit the other, or create an interface, or anything else.
I'll end with 2 questions: why no constructors? why ArrayList and not list?

Functional/Stream programming for the graph problem "Reconstruct Itinerary"

I am trying to solve the reconstruct itinerary problem (https://leetcode.com/problems/reconstruct-itinerary/) in Scala using functional approach. Java solution works but Scala doesn't. One reason I found out was the hashmap is being updated and every iteration has the latest hashmap (even when popping from recursion) which is weird.
Here is the solution in Java:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
public class Solution1 {
private void dfg(Map<String, PriorityQueue<String>> adj, LinkedList<String> result, String vertex){
PriorityQueue<String> pq = adj.get(vertex);
while (pq!=null && !pq.isEmpty()){
System.out.println("Before :"+adj.get(vertex));
String v = pq.poll();
System.out.println("After :"+ adj.get(vertex));
dfg(adj,result,v);
}
result.addFirst(vertex);
}
public List<String> findItinerary(List<List<String>> tickets){
Map<String,PriorityQueue<String>> adj = new HashMap<>();
for(List<String> ticket: tickets){
adj.putIfAbsent(ticket.get(0),new PriorityQueue<>());
adj.get(ticket.get(0)).add(ticket.get(1));
}
LinkedList<String> result = new LinkedList<>();
dfg(adj,result,"JFK");
//not reverse yet
return result;
}
public static void main(String[] args){
List<List<String>> tickets = new ArrayList<>();
List t1= new ArrayList();
t1.add("JFK");
t1.add("SFO");
tickets.add(t1);
List t2= new ArrayList();
t2.add("JFK");
t2.add("ATL");
tickets.add(t2);
List t3= new ArrayList();
t3.add("SFO");
t3.add("ATL");
tickets.add(t3);
List t4= new ArrayList();
t4.add("ATL");
t4.add("JFK");
tickets.add(t4);
List t5= new ArrayList();
t5.add("ATL");
t5.add("SFO");
tickets.add(t5);
System.out.println();
Solution1 s1 = new Solution1();
List<String> finalRes = s1.findItinerary(tickets);
for(String model : finalRes) {
System.out.print(model + " ");
}
}
}
Here is my solution in Scala which is not working:
package graph
class Itinerary {
}
case class Step(g: Map[String,List[String]],sort: List[String]=List())
object Solution {
def main(arr: Array[String]) = {
val tickets = List(List("JFK","SFO"),List("JFK","ATL"),List("SFO","ATL"),List("ATL","JFK"),List("ATL","SFO"))
println(findItinerary(tickets))
}
def findItinerary(tickets: List[List[String]]): List[String] = {
val g = tickets.foldLeft(Map[String,List[String]]())((m,t)=>{
val key=t(0)
val value= t(1)
m + (key->(m.getOrElse(key,Nil) :+ value).sorted)
})
println(g)
// g.keys.foldLeft(Step())((s,n)=> dfs(n,g,s)).sort.toList
dfs("JFK",Step(g)).sort.toList
}
def dfs(vertex: String,step: Step): Step = {
println("Input vertex " + vertex)
println("Input map "+ step.g)
val updatedStep= step.g.getOrElse(vertex,Nil).foldLeft(step) ((s,n)=>{
//println("Processing "+n+" of vertex "+vertex)
//delete link
val newG = step.g + (vertex->step.g.getOrElse(vertex,Nil).filter(v=>v!=n))
// println(newG)
dfs(n,step.copy(g=newG))
})
println("adding vertex to result "+vertex)
updatedStep.copy(sort = updatedStep.sort:+vertex)
}
}
Scala is sometimes approached as a "better" Java, but that's really very limiting. If you can get into the FP mindset, and study the Standard Library, you'll find that it's a whole new world.
def findItinerary(tickets: List[List[String]]): List[String] = {
def loop(from : String
,jump : Map[String,List[String]]
,acc : List[String]) : List[String] = jump.get(from) match {
case None => if (jump.isEmpty) from::acc else Nil
case Some(next::Nil) => loop(next, jump - from, from::acc)
case Some(nLst) =>
nLst.view.map{ next =>
loop(next, jump+(from->(nLst diff next::Nil)), from::acc)
}.find(_.lengthIs > 0).getOrElse(Nil)
}
loop("JFK"
,tickets.groupMap(_(0))(_(1)).map(kv => kv._1 -> kv._2.sorted)
,Nil).reverse
}
I am going to be honest that I didn't look through your code to see where the problem was. But, I got caught by the problem and decided to give it a go; here is the code:
(hope my code helps you)
type Airport = String // Refined 3 upper case letters.
final case class AirlineTiket(from: Airport, to: Airport)
object ReconstructItinerary {
// I am using cats NonEmptyList to improve type safety, but you can easily remove it from the code.
private final case class State(
currentAirport: Airport,
availableDestinations: Map[Airport, NonEmptyList[Airport]],
solution: List[Airport]
)
def apply(tickets: List[AirlineTiket])(start: Airport): Option[List[Airport]] = {
#annotation.tailrec
def loop(currentState: State, checkpoints: List[State]): Option[List[Airport]] = {
if (currentState.availableDestinations.isEmpty) {
// We used all the tickets, so we can return this solution.
Some((currentState.currentAirport :: currentState.solution).reverse)
} else {
val State(currentAirport, availableDestinations, solution) = currentState
availableDestinations.get(currentAirport) match {
case None =>
// We got into nowhere, lets see if we can return to a previous state...
checkpoints match {
case checkpoint :: remaining =>
// If we can return from there
loop(currentState = checkpoint, checkpoints = remaining)
case Nil =>
// If we can't, then we can say that there is no solution.
None
}
case Some(NonEmptyList(destination, Nil)) =>
// If from the current airport we can only travel to one destination, we will just follow that.
loop(
currentState = State(
currentAirport = destination,
availableDestinations - currentAirport,
currentAirport :: solution
),
checkpoints
)
case Some(NonEmptyList(destination, destinations # head :: tail)) =>
// If we can travel to more than one destination, we are going to try all in order.
val newCheckpoints = destinations.map { altDestination =>
val newDestinations = NonEmptyList(head = destination, tail = destinations.filterNot(_ == altDestination))
State(
currentAirport = altDestination,
availableDestinations.updated(key = currentAirport, value = newDestinations),
currentAirport :: solution
)
}
loop(
currentState = State(
currentAirport = destination,
availableDestinations.updated(key = currentAirport, value = NonEmptyList(head, tail)),
currentAirport :: solution
),
newCheckpoints ::: checkpoints
)
}
}
}
val availableDestinations = tickets.groupByNel(_.from).view.mapValues(_.map(_.to).sorted).toMap
loop(
currentState = State(
currentAirport = start,
availableDestinations,
solution = List.empty
),
checkpoints = List.empty
)
}
}
You can see the code running here.

Replacing for loops for searching list in kotlin

I am trying to convert my code as clean as possible using the Kotlin's built-in functions. I have done some part of the code using for loops. But I want to know the efficient built-in functions to be used for this application
I have two array lists accounts and cards.
My goal is to search a specific card with the help of its card-number, in the array list named cards.
Then I have to validate the pin. If the pin is correct, by getting that gift card's customerId I have to search the account in the array list named accounts. Then I have to update the balance of the account.
These are the class which I have used
class Account{
constructor( )
var id : String = generateAccountNumber()
var name: String? = null
set(name) = if (name != null) field = name.toUpperCase() else { field = "Unknown User"; println("invalid details\nAccount is not Created");}
var balance : Double = 0.0
set(balance) = if (balance >= 0) field = balance else { field = 0.0 }
constructor(id: String = generateAccountNumber(), name: String?,balance: Double) {
this.id = id
this.balance = balance
this.name = name
}
}
class GiftCard {
constructor( )
var cardNumber : String = generateCardNumber()
var pin: String? = null
set(pin) = if (pin != null) field = pin else { field = "Unknown User"; println("Please set the pin\nCard is not Created");}
var customerId : String = ""
set(customerId) = if (customerId != "") field = customerId else { field = "" }
var cardBalance : Double = 0.0
set(cardBalance) = if (cardBalance > 0) field = cardBalance else { field = 0.0; println("Card is created with zero balance\nPlease deposit") }
var status = Status.ACTIVE
constructor(cardNumber: String = generateCardNumber(),
pin: String,
customerId: String,
cardBalance: Double = 0.0,
status: Status = Status.ACTIVE){
this.cardNumber = cardNumber
this.pin = pin
this.customerId = customerId
this.cardBalance = cardBalance
this.status = status
}
}
This is the part of code, I have to be changed :
override fun closeCard(cardNumber: String, pin: String): Pair<Boolean, Boolean> {
for (giftcard in giftcards) {
if (giftcard.cardNumber == cardNumber) {
if (giftcard.pin == pin) {
giftcard.status = Status.CLOSED
for (account in accounts)
account.balance = account.balance + giftcard.cardBalance
giftcard.cardBalance = 0.0
return Pair(true,true)
}
\\invalid pin
return Pair(true,false)
}
}
\\card is not present
return Pair(false,false)
}
Both classes are not very idiomatic. The primary constructor of a Kotlin class is implicit and does not need to be defined, however, you explicitly define a constructor and thus you add another one that is empty.
// good
class C
// bad
class C {
constructor()
}
Going further, Kotlin has named arguments and default values, so make use of them.
class Account(
val id: String = generateAccountNumber(),
val name: String = "Unknown User",
val balance: Double = 0.0
)
Double is a very bad choice for basically anything due to its shortcomings, see for instance https://www.floating-point-gui.de/ Choosing Int, Long, heck even BigDecimal would be better. It also seems that you don’t want the balance to ever go beneath zero, in that case consider UInt and ULong.
Last but not least is the mutability of your class. This can make sense but it also might be dangerous. It is up to you to decide upon your needs and requirements.
enum class Status {
CLOSED
}
#ExperimentalUnsignedTypes
class Account(private var _balance: UInt) {
val balance get() = _balance
operator fun plusAssign(other: UInt) {
_balance += other
}
}
#ExperimentalUnsignedTypes
class GiftCard(
val number: String,
val pin: String,
private var _status: Status,
private var _balance: UInt
) {
val status get() = _status
val balance get() = _balance
fun close() {
_status = Status.CLOSED
_balance = 0u
}
}
#ExperimentalUnsignedTypes
class Main(val accounts: List<Account>, val giftCards: List<GiftCard>) {
fun closeCard(cardNumber: String, pin: String) =
giftCards.find { it.number == cardNumber }?.let {
(it.pin == pin).andAlso {
accounts.forEach { a -> a += it.balance }
it.close()
}
}
}
inline fun Boolean.andAlso(action: () -> Unit): Boolean {
if (this) action()
return this
}
We change the return type from Pair<Boolean, Boolean> to a more idiomatic Boolean? where Null means that we did not find anything (literally the true meaning of Null), false that the PIN did not match, and true that the gift card was closed. We are not creating a pair anymore and thus avoid the additional object allocation.
The Boolean.andAlso() is a handy extension function that I generally keep handy, it is like Any.also() from Kotlin’s STD but only executes the action if the Boolean is actually true.
There's probably a million different ways to do this, but here's one that at least has some language features I feel are worthy to share:
fun closeCard(cardNumber: String, pin: String): Pair<Boolean, Boolean> {
val giftCard = giftcards.find { it.cardNumber == cardNumber }
?: return Pair(false, false)
return if (giftCard.pin == pin) {
giftCard.status = Status.CLOSED
accounts.forEach {
it.balance += giftCard.cardBalance
}
Pair(true, true)
} else
Pair(true, false)
}
The first thing to notice if the Elvis operator - ?: - which evaluates the right side of the expression if the left side is null. In this case, if find returns null, which is equivalent to not finding a card number that matches the desired one, we'll immediately return Pair(false, false). This is the last step in your code.
From there one it's pretty straight forward. If the pins match, you loop through the accounts list with a forEach and close the card. If the pins don't match, then we'll go straight to the else branch. In kotlin, if can be used as an expression, therefore we can simply put the return statement before the if and let it return the result of the last expression on each branch.
PS: I won't say this is more efficient than your way. It's just one way that uses built-in functions - find and forEach - like you asked, as well as other language features.
PPS: I would highly recommend to try and find another way to update the lists without mutating the objects. I don't know your use cases, but this doesn't feel too thread-safe. I didn't post any solution for this, because it's outside the scope of this question.

Kotlin Cannibals and Missionaries Algorithm - How to convert to full FP

Any suggestions on how to improve the following code to make it more Functional Programming oriented. Specifically how to remove the MutableList which signifies historical states. There are two data classes: Bank, which represents a riverbank (number of missionaries and number of cannibals currently on the bank) and BankState which represents a historical state of the two banks (the source bank, target bank and boatAtSource - a boolean which indicates whether the boat is currently at the source or target bank). overloaded operator function plus adds missionaries and cannibals to a riverbank and function minus removes them from a riverbank. The boat function is the one which carries the most heft. You can call the following algorithm from fun main (app.kt) as such:
app.kt
fun main(args:Array<String>) {
val source:Bank = Bank(3,3)
val target:Bank = Bank()
source boat target
}
Bank.kt
data class Bank(val missionaries:Int=0,val cannibals:Int=0)
data class BankState(val sourceTarget:Pair<Bank,Bank>,val boatAtSource:Boolean)
operator fun Bank.plus(b:Pair<Int,Int>):Bank = Bank(this.missionaries+b.first,this.cannibals+b.second)
operator fun Bank.minus(b:Pair<Int,Int>):Bank = Bank(this.missionaries-b.first,this.cannibals-b.second)
infix fun Bank.boat(target:Bank):List<BankState> {
val begin = Pair(this,target)
val history = mutableListOf<BankState>(BankState(begin,true))
boat(begin,true,this.missionaries,this.cannibals,history)
return history
}
fun boat(sourceTarget:Pair<Bank,Bank>,
boatAtSource:Boolean,
totalMissionaries:Int,
totalCannibals:Int,
history:MutableList<BankState>):Boolean {
if(sourceTarget.first.cannibals+sourceTarget.second.cannibals==totalCannibals &&
sourceTarget.first.missionaries + sourceTarget.second.missionaries==totalMissionaries &&
sourceTarget.first.cannibals>=0 &&
sourceTarget.first.missionaries>=0 &&
sourceTarget.second.cannibals>=0 &&
sourceTarget.second.missionaries>=0 &&
(sourceTarget.first.missionaries==0 || sourceTarget.first.missionaries>=sourceTarget.first.cannibals) &&
(sourceTarget.second.missionaries==0 || sourceTarget.second.missionaries >= sourceTarget.second.cannibals)) {
if(sourceTarget.second.missionaries==totalMissionaries &&
sourceTarget.second.cannibals==totalCannibals) {
history.forEach(::println)
return true
} else {
val deltas = listOf(Pair(0,1),Pair(1,1),Pair(1,0),Pair(2,0),Pair(0,2))
val comparator = object : Comparator<Pair<Pair<Boolean,Int>,Pair<Bank,Bank>>> {
override fun compare(arg1:Pair<Pair<Boolean,Int>,Pair<Bank,Bank>>,arg2:Pair<Pair<Boolean,Int>,Pair<Bank,Bank>>):Int {
if(arg1.first.first && arg2.first.first) {
return if(arg1.first.second<arg2.first.second) -1 else if(arg1.first.second>arg2.first.second) 1 else 0
} else if(arg1.first.first){
return 1
} else if(arg2.first.first) {
return -1
}
return 0
}
}
val result = deltas.map{
checkNext(it.first,it.second,totalMissionaries,totalCannibals,history,sourceTarget,boatAtSource)
}.maxWith(comparator)
if(result?.first?.first!=null && result.first.first) {
history.add(BankState(result.second,!boatAtSource))
return true;
}
}
}
return false
}
fun checkNext(missionariesDelta:Int,
cannibalsDelta:Int,
totalMissionaries:Int,
totalCannibals:Int,
history:MutableList<BankState>,
sourceTarget:Pair<Bank,Bank>,
boatAtSource:Boolean):Pair<Pair<Boolean,Int>,Pair<Bank,Bank>> {
val nextSrcTgt = if(boatAtSource) Pair(sourceTarget.first-Pair(missionariesDelta,cannibalsDelta),sourceTarget.second+Pair(missionariesDelta,cannibalsDelta))
else Pair(sourceTarget.first+Pair(missionariesDelta,cannibalsDelta),sourceTarget.second-Pair(missionariesDelta,cannibalsDelta))
val bankState:BankState = BankState(nextSrcTgt,!boatAtSource)
if(!history.contains(bankState)) {
history.add(bankState)
val combo2:Boolean = boat(nextSrcTgt,!boatAtSource,totalMissionaries,totalCannibals,history)
val combo2Depth = history.size
history.remove(bankState)
return Pair(Pair(combo2,combo2Depth),nextSrcTgt)
} else {
return Pair(Pair(false,0),nextSrcTgt)
}
}

Sorting Strings by Character and Length

In my Android app, I am trying to sort Bus route tags in order 1, 2, 3..etc.
For that I am using this
Collections.sort(directions, Comparator { lhs, rhs ->
var obj1 = lhs.short_names.firstOrNull() ?: ""
var obj2 = rhs.short_names.firstOrNull() ?: ""
if (obj1 === obj2) {
obj1 = lhs.headsigns.firstOrNull() ?: ""
obj2 = rhs.headsigns.firstOrNull() ?: ""
if (obj1 === obj2) {
return#Comparator 0
}
obj1.compareTo(obj2)
} else {
obj1.compareTo(obj2)
}
The issue I am having is this sorts them, but will run into the issue of
1, 2, 3, 30, 31, 4, 5
How should I change this to get the correct ordering.
If you need just a simple number comparison you can do it like that.
directions.sortWith(Comparator { lhs, rhs ->
val i1 = lhs.toInt()
val i2 = rhs.toInt()
when {
i1 < i2 -> -1
i1 > i2 -> 1
else -> 0
}
})
As hotkey pointed out the code above can be replaced with almost identical implementation that looks much simplier.
directions.sortBy { it.toInt() }
The general version of this algorithm is called alphanum sorting and described in details here. I made a Kotlin port of this algorithm, which you can use. It's more complicated than what you need, but it will solve your problem.
class AlphanumComparator : Comparator<String> {
override fun compare(s1: String, s2: String): Int {
var thisMarker = 0
var thatMarker = 0
val s1Length = s1.length
val s2Length = s2.length
while (thisMarker < s1Length && thatMarker < s2Length) {
val thisChunk = getChunk(s1, s1Length, thisMarker)
thisMarker += thisChunk.length
val thatChunk = getChunk(s2, s2Length, thatMarker)
thatMarker += thatChunk.length
// If both chunks contain numeric characters, sort them numerically.
var result: Int
if (isDigit(thisChunk[0]) && isDigit(thatChunk[0])) {
// Simple chunk comparison by length.
val thisChunkLength = thisChunk.length
result = thisChunkLength - thatChunk.length
// If equal, the first different number counts.
if (result == 0) {
for (i in 0..thisChunkLength - 1) {
result = thisChunk[i] - thatChunk[i]
if (result != 0) {
return result
}
}
}
} else {
result = thisChunk.compareTo(thatChunk)
}
if (result != 0) {
return result
}
}
return s1Length - s2Length
}
private fun getChunk(string: String, length: Int, marker: Int): String {
var current = marker
val chunk = StringBuilder()
var c = string[current]
chunk.append(c)
current++
if (isDigit(c)) {
while (current < length) {
c = string[current]
if (!isDigit(c)) {
break
}
chunk.append(c)
current++
}
} else {
while (current < length) {
c = string[current]
if (isDigit(c)) {
break
}
chunk.append(c)
current++
}
}
return chunk.toString()
}
private fun isDigit(ch: Char): Boolean {
return '0' <= ch && ch <= '9'
}
}
To use this Comparator just call
directions.sortWith(AlphanumComparator())
If you don't need it to be coded in Kotlin you can just take an original Java version on Dave Koelle's page. And the Kotlin version of the algorithm can be also found on GitHub.

Resources