Saar
Saar

Reputation: 1803

Scala - state while looping through a list

Newbie question. I am looping through a list and need keep state in between the items. For instance

  val l = List("a", "1", "2", "3", "b", "4")
  var state: String = ""
  l.foreach(o => {
    if (toInt(o).isEmpty) state = o else println(state + o.toString)
  })

what's the alternative for the usage of var here?

Upvotes: 1

Views: 973

Answers (5)

Victor Moroz
Victor Moroz

Reputation: 9225

There are two major options to pass a state in functional programming when processing collections (I assume you want to get your result as a variable):

Recursion (classic)

val xs = List("a", "11", "212", "b", "89")

@annotation.tailrec
def fold(seq: ListBuffer[(String, ListBuffer[String])], 
         xs: Seq[String]): ListBuffer[(String, ListBuffer[String])] = {
  (seq, xs) match {
    case (_, Nil) =>
      seq
    case (_, c :: tail) if toInt(c).isEmpty  =>
      fold(seq :+ ((c, ListBuffer[String]())), tail)
    case (init :+ ((c, seq)), i :: tail) =>
      fold(init :+ ((c, seq :+ i)), tail)

  }
}

val result =
  fold(ListBuffer[(String, ListBuffer[String])](), xs)
  // Get rid of mutable ListBuffer
  .toSeq
  .map { 
    case (c, seq) => 
      (c, seq.toSeq)
  }
  //> List((a,List(11, 212)), (b,List(89)))

foldLeft et al.

val xs = List("a", "11", "212", "b", "89")

val result = 
  xs.foldLeft(
    ListBuffer[(String, ListBuffer[String])]()
  ) {
    case (seq, c) if toInt(c).isEmpty  =>
      seq :+ ((c, ListBuffer[String]()))
    case (init :+ ((c, seq)), i) =>
      init :+ ((c, seq :+ i))
  }
  // Get rid of mutable ListBuffer
  .toSeq
  .map { 
    case (c, seq) => 
      (c, seq.toSeq)
  }
  //> List((a,List(11, 212)), (b,List(89)))

Which one is better? Unless you want to abort your processing in the middle of your collection (like e.g. in find) foldLeft is considered a better way and it has slightly less boilerplate, but otherwise they are very similar.

I'm using ListBuffer here to avoid reversing lists.

Upvotes: 0

Emiliano Martinez
Emiliano Martinez

Reputation: 4133

As simple as:

val list: List[Int] = List.range(1, 10) // Create list

def updateState(i : Int) : Int = i + 1 // Generate new state, just add one to each position. That will be the state

list.foldRight[List[(Int,Int)]](List())((a, b) => (a, updateState(a)) :: b)

Note that the result is a list of Tuple2: (Element, State), and each state depends on the element of the list.

Hope this helps

Upvotes: 0

som-snytt
som-snytt

Reputation: 39577

Pass an arg:

scala> def collapse(header: String, vs: List[String]): Unit = vs match {
     | case Nil =>
     | case h :: t if h.forall(Character.isDigit) => println(s"$header$h") ; collapse(header, t)
     | case h :: t => collapse(h, t)
     | }
collapse: (header: String, vs: List[String])Unit

scala> collapse("", vs)
a1
a2
a3
b4

Upvotes: 0

Jean Logeart
Jean Logeart

Reputation: 53819

Use foldLeft:

l.foldLeft(""){ (state, o) => 
  if(toInt(o).isEmpty) o 
  else {
    println(state + o.toString)
    state
  }
}

Upvotes: 2

Ross
Ross

Reputation: 21

You should keep in mind that it's sometimes (read: when it makes the code more readable and maintainable by others) okay to use mutability when performing some operation that's easily expressed with mutable state as long as that mutable state is confined to as little of your program as possible. Using (e.g.) foldLeft to maintain an accumulator here without using a var doesn't gain you much.

That said, here's one way to go about doing this:

val listOfThings: Seq[Either[Char, Int]] = Seq(Left('a'), Right(11), Right(212), Left('b'), Right(89))

val result = listOfThings.foldLeft(Seq[(Char, Seq[Int])]()) {
  case (accumulator, Left(nextChar)) => accumulator :+ (nextChar, Seq.empty)
  case (accumulator, Right(nextInt)) =>
    val (currentChar, currentSequence) = accumulator.last
    accumulator.dropRight(1) :+ (currentChar, currentSequence :+ nextInt)
}

result foreach {
  case (char, numbers) => println(numbers.map(num => s"$char-$num").mkString(" "))
}

Upvotes: 2

Related Questions