Reputation: 459
Suppose I have an array of a, b, c
.
val arr = arrayOf("a", "b", "c")
My task is to eliminate a
and b
pairs.
My approach here is as follows. I iterate through array and check what the next element is. If it's b
then I remove it.
for (a in arr.indices) {
when (arr[a]) {
"a" -> {
if (arr[a + 1] == "b"){/replacement part}
This gives me an IndexOutOfBoundsException when I pass
val arr = arrayOf("a")
It is obviously caused by the if condition where I check arr[a + 1]
, which doesn't exist in my array. What should be my approach to overcome this difficulty?
Upvotes: 1
Views: 144
Reputation: 19544
Not that I'd necessarily recommend it, but hey:
@gidds mentions zipWithNext
for lists, you can do something similar with windowed
though - but it's not defined as producing nice pairs for a window size of 2, so you have to deal with lists instead:
val arr = arrayOf("a", "b", "b", "a", "b", "c")
arr.reversed()
.windowed(size = 2, partialWindows = true)
.mapNotNull { if (it == listOf("b", "a")) null else it.first() }
.reversed()
.toTypedArray() // if you really need an array
.run(::println)
>> [a, b, a, c]
The basic approach is using windowed
to go through each item in the array, grabbing the one after it - partialWindows
means it will grab the last item on its own, since there's nothing following it. That way you can decide whether to keep the item, or drop it (by returning null
in this case, which is filtered out of the final list by mapNotNull
).
The reversed
stuff is there because you're looking at an item, and what follows it, and deciding whether to drop the following item - which effectively means remembering to drop it on the next step, when it becomes the main item you're looking at. By reversing the collection, you're looking at each item and what comes before it, so you can decide whether it needs to be dropped. You just need to reverse the list again at the end.
As you can see from the output, it's stripping any b
that has an a
immediately before it, but you can still end up with an a, b
sequence after a b
is removed. If you want to build up a list and make sure you never have a b
after an a
, you can use a fold:
arr.fold(listOf<String>()) { acc, item ->
if (item == "b" && acc.lastOrNull() == "a") acc else acc + item
}
>> [a, a, c]
Each step is returning either the same accumulator list, or creating a new list with the item added - you could make it a mutable list if you like, and add
the item (or don't) before passing that list on
Again, not saying I'd necessarily recommend doing these, it's more an example of the kind of things available to you in the toolbox, y'know? If you're messing with arrays it might be because you want xtreme speedz, in which case you're probably better iterating over the indices and looking back like in the other examples. Or if you're removing items in-place and modifying the array itself. Depends what you're up to!
Upvotes: 0
Reputation: 18577
As is often the case, there are multiple approaches… You'll have to use your skill and judgement to determine which is best in your particular case!
Perhaps the safest and most general is to amend the test to first check whether it's safe to access that array element:
for (a in arr.indices)
when (arr[a]) {
"a" ->
if (a < arr.length - 1 && arr[a + 1] == "b")
// …
A neater way of doing that is to use the getOrNull()
function, which returns null instead of throwing an exception:
for (a in arr.indices)
when (arr[a]) {
"a" ->
if (arr.getOrNull(a + 1) == "b")
// …
(Of course, that wouldn't work if the array type were nullable, and you were looking for a null value.)
A different approach is to stop the loop before the last element:
for (a in 0 .. arr.size - 2)
when (arr[a]) {
"a" ->
if (arr[a + 1] == "b")
// …
…though that presumes that there's nothing else in the loop that needs to access the last element. And it might be more likely to cause errors if someone looks at the code later and doesn't spot that it stops early.
(In all these cases, you could instead search for "b", and then check whether the previous character were "a" — but you still get the equivalent problem, and equivalent solutions.)
Depending what you're going to do with each match, another approach might be to convert the character array into a String, and use a regex, e.g.:
val a = arr.joinToString("")
val b = a.replace(Regex("ab"), "cd")
PS. In general it's better to use lists rather than arrays: they're more general, more flexible, have many more extension methods and other support in the standard library, play better with generics, can be read-only, and there are many different implementations to choose from. Arrays are needed for Java interoperability, for varargs, and for implementing low-level data structures; but for most other uses lists are preferable.
For example, if you had a list, you could find the index of the first pair (if any) with:
val index = list.zipWithNext().indexOfFirst{ (a, b) -> a == "a" && b == "b" }
(zipWithNext()
isn't defined on arrays, so you couldn't use it in your case.)
Upvotes: 2
Reputation: 5980
You should loop until the array length minus two. There is no point considering the last element if you're looking for pairs. So:
for (a in 0 until a.size - 1) {
...
}
Upvotes: 0