Search code examples
scalaiterator

How to get out of(block) nested Iterators once value is found?


I have some code like this with nested Iterators. I want to stop, get out of foreach/map and return(value) if the find returns and Some(value), and continue to incase of None. What's the right approach here?

Edit: Here's the full function

Line Schema: line_id, name

Stop Schema: stop_id, x, y

Time Schema: line_id, stop_id, time

Given time and x,y I want to find line_id and name. So for the time being Im stuck with getting valueWhichImTryingToGet.

  def findVehicle(time: String, x: String, y: String) = {
    val stopIter = Source.fromFile("stops.csv").getLines().drop(1)
    val stopCols = stopIter.map(_.split(",").map(_.trim))
    val stopIds = stopCols.filter(arr => arr(1) == x && arr(2) == y)

     val valueWhichImTryingToGet = stopIds.map { arr =>
      val iter = Source.fromFile("times.csv").getLines().drop(1)
      val cols = iter.map(_.split(",").map(_.trim))
      cols.find(col => col(1) == arr(0) && col(2) == time) match {
        case Some(value) => value(0) //String type
        case None => NotFound(s"No Vehicle available at the given time ${time}")
      }
    }

    val lineIter = Source.fromFile("lines.csv").getLines().drop(1)
    val lineCols = lineIter.map(_.split(",").map(_.trim))
    lineCols.find(_(0) == valueWhichImTryingToGet ).getOrElse("No vechile can be found with the given information")
  }

Also any improvements in the code?

one more thing I noticed is that If I do the length/size check on iterator stopIds it exhausts the iterator and no further processing can happen. So how can I find if the first filter returned 0?


Solution

  • Your logic can be expressed without breaks more or less like this (I didn't dig into domain context, as it is apparently irrelevant and you can adjust it yourself):

    // instead of running whole code for all entries we can just check
    // if value exist within some set of values
    val stopIds = cols.filter(arr=> arr(1) == x && arr(2) == y).map(_(0)).toSet
    
    
    Source
      .fromFile("abc.csv")
      .getLines()
      .drop(1) // drop headers
      .map { line =>
        line.split(",").map(_.trim).toList
      }
      .collectFirst {
        // find first entry which has second value from stopIds set
        // and third value equal to expected time
        case _ :: id :: atTime :: _ if stopIds.contains(id) && atTime == time =>
          Vehicle(value(0), value(1))
      }
      .getOrElse {
        // if no matching value is found fall back on "not found" case
        NotFound(s"No Vehicle available at the given time ${time}")
      }
    

    As a side note - I would recommend some proper CSV library here as this solution is not bulletproof, and would break if e.g. someone created a file with an escaped coma.