1

My task is to remove letters from two strings one by one if there are equals and stay at the same positions. i do this and always get index out of bound exception. can't understand how to fix indexes and make my high board dynamicly.

   var newSecret = StringBuilder(secret)
    var newGuess = StringBuilder(guess)    

 for (i in newSecret.indices){
            if (newSecret[i]==newGuess[i]){
                newSecret.deleteCharAt(i)
                newGuess.deleteCharAt(i)
                rightPosition++
    
            }

3 Answers 3

1

Honestly I think you're making this harder by starting with a full copy of each and then trying to remove stuff, and keep track of that new state. It's way simpler if you can build up the strings from scratch, discarding what you don't need:

val newSecret = StringBuilder()
val newGuess = StringBuilder()
secret.zip(guess).forEach { (a, b) ->
    if (a != b) {
        newSecret.append(a)
        newGuess.append(b)
    }
}

zip takes two iterables and gives you their elements in pairs, one from each (like a zipper bringing the two sets of teeth together). So it's perfect for situations like this where you're comparing items in the same positions.

I think that's the most readable approach up there when you want a String result, the general case is

secret.zip(guess).filterNot { (a, b) -> a == b }.unzip()

where zipping gives you a list of pairs, and unzip turns that into a pair of lists. You could use that here too, but it means you have to call joinToString("") on each list, and then assign each component of the Pair to a variable... I think the StringBuilder version above is nicer in that case

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

Comments

1

If you debug the code using IDE or just print statements, you will quickly realise that the index i is getting incremented but the length of newSecret and newGuess decreases with every deleteCharAt call. Hence newSecret[i] will give you Index OutofBoundException.

Good explanation from @gidds in the comment : newSecret.indices is evaluated only once, before the loop — the range is not re-evaluated when characters are deleted, which is the cause of the exception

for loop might not be the right one for this use case, you can try while loop. You have to check the index with the length of both the strings, and increment i only if required

  val newSecret = StringBuilder("abcdef")
  val newGuess = StringBuilder("abddeg")

  var i = 0
  while(i<newSecret.length && i < newGuess.length) {
    if (newSecret[i] == newGuess[i]) {
      newSecret.deleteCharAt(i)
      newGuess.deleteCharAt(i)
    }
    else i++
  }

1 Comment

Might be worth making explicit that in the question, newSecret.indices is evaluated only once, before the loop — the range is not re-evaluated when characters are deleted, which is the cause of the exception.
0

There is a reason for not changing any sort of collection while iterating through it, even if is it not part of Collection, StringBuilder clearly is a sort of collection, since it has append and deleteAt functions.

If you have a string that is 0123456789, 0 is in index 0, 5 is in index 5. But if we remove let's say 3, then everything else after it, now has a different index. We have 012456789, and if you count, you can see that 5 will not have index 4, and so on. That means that your procedure will not only remove things it shouldn't, but if it wants to remove index 9, well, that no longer exists, hence, index out of bounds.

There are probably other ways of doing it than this, that are much more efficient and easy to understand, but I've tried to keep the same idea as you had in your example code.

val newSecret = StringBuilder(secret)
val newGuess = StringBuilder(guess)

secret.withIndex()
    .filter { guess.length >= it.index + 1 //in case that guess is shorter than secret (if you know that they are the same length, this part of the predicate can be removed
            && it.value == guess[it.index] }
    .map { it.index }
    .reversed() //we want to delete from end to start, to avoid what we discussed above
    .forEach {
        newSecret.deleteCharAt(it)
        newGuess.deleteCharAt(it)
    }

In the above procedure, if secret and guess are:

 val secret = "bar1bar2x5"
 val guess = "foo1foo3y5"

Then newSecret and newGuess will be:

newSecret -> barbar2x
newGuess -> foofoo3y

Comments

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.