简体   繁体   中英

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)
    }

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.

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM