0

So I have an array of strings in Scala called pronunciationArray like this:

["EH1","N", "D", "P", "ER0", "EH1", "N", "TH", "AH0", "S", "IY2", "Z"]

and i want to write an if else statement that reads the array in reverse, and as soon as it finds a string with a number in it, it either removes all the strings after or puts everything before into a separate array.

so for the above example, id want it to stop at "IY2", then either create a new array with only ["IY2", "Z"] or remove every string after and leave the original array with, like I said, ["IY2", "Z"]. The number itself isnt an integer btw, its part of the string, and the numbers range from 0-2.

I've tried for loop in reverse with an if else that looks for the numbers 0, 1, 2 but it returns every string with a number, so it returns [IY2, AH0, EH1, ER0, EH1] it doesnt stop as soon as it finds the first string with a number. And i'm not sure how to put everything before that string into a new array if I even found a way to stop it.

for (sounds <- pronunciationArray.reverse) {

  val number0 = sounds.contains("0")

  if (number0 == true) {

    println(sounds)

  } else if (number0 == true){

    println(sounds)

  } else if (number1 == true){

    println(sounds)

  } else {

    -1

  }

}

I want it to return just ["IY2", "Z"] but it returned [IY2, AH0, EH1, ER0, EH1]

prayagupa
  • 30,204
  • 14
  • 155
  • 192
  • 1
    A few observations about your code: The `if` and `else` clauses don't return the same type (`println()` returns `Unit`, `-1` is an `Int`). `number1` doesn't exist before it is tested. `number0` is tested for the same condition twice. `if (number0 == true)` is the same thing as `if (number0)`, which is the same thing as `if (sounds.contains("0"))`. – jwvh Feb 09 '19 at 23:40
  • thanks this saves me a lot of headache – user11039395 Feb 10 '19 at 00:56

3 Answers3

1

I'm not completely clear on your objective, but Array has a span function that splits it into two pieces based on a condition--the first piece is a prefix of the array where everything satisfies the condition and the second piece is the remainder.

Let's say your condition was that the string contains 2 rather than contains a number (just because I'm lazy--it could be any function f: String => Boolean). If we reverse the array and then split on the negation of the function, we get:

scala> a.reverse.span(!_.contains("2"))
res5: (Array[String], Array[String]) = (Array(Z),Array(IY2, S, AH0, TH, N, EH1, ER0, P, D, N, EH1))

We know that the first element of the second array satisfies our condition, so we can just assemble the result:

scala> val (start, end) = a.reverse.span(!_.contains("2"))
start: Array[String] = Array(Z)
end: Array[String] = Array(IY2, S, AH0, TH, N, EH1, ER0, P, D, N, EH1)

scala> val result = end.take(1) ++ start.reverse
result: Array[String] = Array(IY2, Z)

All the reversing is perhaps not the most efficient, but gets the job done.

hoyland
  • 1,776
  • 14
  • 14
1

One of the algorithm you can solve with is

  • find the index of element from the last where element has 0, 1 or 2
  • split the array at above index
  • take the last element of first array and whole second array

Example,

val pronunciationArray = Array("EH1","N", "D", "P", "ER0", "EH1", "N", "TH", "AH0", "S", "IY2", "Z")

def takeUntil(inputArray: Array[String], condition: String => Boolean): Array[String] = {
  val findIndexFromLast = inputArray.reverse.zipWithIndex.collectFirst {
    case (elem, index) if condition.apply(elem) => index
  }

  findIndexFromLast match { // pattern match to check if there exists element with 0, 1, or 2
    case Some(indexFromLast) =>
      inputArray.splitAt(inputArray.length - indexFromLast) match {
        case (head, tail) => head.last +: tail
      }
    case None => // no element with 0, 1, 2 exists
      inputArray
  }
}

takeUntil(
  inputArray = pronunciationArray,
  condition = elem => elem.contains("0") || elem.contains("1") || elem.contains("2")) //gives [IY2, Z]

The other way to solve the same using .span(predicate) which is better version of .splitAt(index)

def takeUntil2(inputArray: Array[String]): Array[String] = {
  inputArray.reverse.span {
    element =>
      !element.contains("0") && !element.contains("1") && !element.contains("2")
  } match {
    case (head, tail) => tail.take(1) ++ head.reverse
  }
}

val result = takeUntil2(inputArray = pronunciationArray)

Now, using scala way you can extend Array to have custom function,

  implicit class ArrayOps(array: Array[String]) {
    def takeUntil(predicate: String => Boolean): Array[String] = {
      val splitAt = array.reverse.span(predicate.apply)
      splitAt._2.take(1) ++ splitAt._1.reverse
    }
  }

  val res = pronunciationArray
    .takeUntil(predicate = elem => !elem.contains("0") && !elem.contains("1") && !elem.contains("2"))

Similar question: How to implement 'takeUntil' of a list?

How do I pattern match arrays in Scala?

prayagupa
  • 30,204
  • 14
  • 155
  • 192
0

Scala arrays offer a lastIndexWhere method which takes a predicate and returns the index of the last element which fulfills the predicate:

val arr = Array("EH1","N", "D", "P", "ER0", "EH1", "N", "TH", "AH0", "S", "IY2", "Z")

val idx = arr.lastIndexWhere(e => e.intersect(Array('0', '1', '2')).size > 0)

and then you call the takeRight method to get the desired result

val res = arr.takeRight(arr.size - idx)
senjin.hajrulahovic
  • 2,961
  • 2
  • 17
  • 32