Yuva
Yuva

Reputation: 95

Work on list of tuples in Scala - part 4

I'm new to Scala, and trying to understand how to work on lists of tuples, so I've created a fictive list of people:

val fichier : List[(String, Int)] = List(("Emma Jacobs",21), ("Mabelle Bradley",53), ("Mable Burton",47), ("Ronnie Walton",41), ("Bill Morton",36), ("Georgia Bates",30), ("Jesse Caldwell",46), ("Jeffery Wolfe",50), ("Roy Norris",18), ("Ella Gonzalez",48))

I would like to separate this list into two lists (regardless to dedicated methods such as partition and filter) according to a certain condition (for example even ages in a side, odd ones in the other side) and put those two lists in one other. something like List[evenList, oddList]

It is pretty easy to code in Python, but obviously not in Scala:

Python code:

def separate(list):
    evenList = [] ; oddList  = []
    for (i, j) in list:
        if j%2==0:
            evenList.append((i,j))
        else:
            oddList.append((i,j))
    bothLists = [evenList, oddList]
    return bothLists

Result (alterated for ease of reading):

[
  [('Bill Morton', 36), ('Georgia Bates', 30), ('Jesse Caldwell', 46), ('Jeffery Wolfe', 50), ('Roy Norris', 18), ('Ella Gonzalez', 48)],
  [('Emma Jacobs', 21), ('Mabelle Bradley', 53), ('Mable Burton', 47), ('Ronnie Walton', 41)]
]

I have also coded it this way to show how the two short lists evoluate from an iteration to another:

Python code:

def separate(list):
    evenList = [] ; oddList  = []
    for (i, j) in list:
        if j%2==0:
            evenList.append((i,j))
            yield evenList
        else:
            oddList.append((i,j))
            yield oddList

Result:

[('Emma Jacobs', 21)]
[('Emma Jacobs', 21), ('Mabelle Bradley', 53)]
[('Emma Jacobs', 21), ('Mabelle Bradley', 53), ('Mable Burton', 47)]
[('Emma Jacobs', 21), ('Mabelle Bradley', 53), ('Mable Burton', 47), ('Ronnie Walton', 41)]
[('Bill Morton', 36)]
[('Bill Morton', 36), ('Georgia Bates', 30)]
[('Bill Morton', 36), ('Georgia Bates', 30), ('Jesse Caldwell', 46)]
[('Bill Morton', 36), ('Georgia Bates', 30), ('Jesse Caldwell', 46), ('Jeffery Wolfe', 50)]
[('Bill Morton', 36), ('Georgia Bates', 30), ('Jesse Caldwell', 46), ('Jeffery Wolfe', 50), ('Roy Norris', 18)]
[('Bill Morton', 36), ('Georgia Bates', 30), ('Jesse Caldwell', 46), ('Jeffery Wolfe', 50), ('Roy Norris', 18), ('Ella Gonzalez', 48)]

Find below what I've coded in Scala:

Scala code:

def separate(list: List[(String, Int)]):  List[List[(String, Int)]]  = {
  val evenList = List[(String, Int)]()
  val oddList = List[(String, Int)]()
    for ( (i, j) <- list if j%2==0 ) (i,j)::evenList
    for ( (i, j) <- list if j%2!=0 ) (i,j)::oddList
  val evenAndodd = List(evenList,oddList)
  evenAndodd
  }

Result:

scala> separate(fichier) res16: List[List[(String, Int)]] = List(List(), List())

evenList and oddList are empty inside the evenAndodd list.

I think I got where it fails but I don't know the word in English. It is about "reachability" of variables in Scala.

Any help is welcome

Upvotes: 1

Views: 94

Answers (3)

user unknown
user unknown

Reputation: 36269

You can solve it with pattern matching, recursion and an inner function, too:

def separate (list: List[(String, Int)]):  List[List[(String, Int)]]  = {

    def separate (list: List[(String, Int)], first: List[(String, Int)], second: List[(String, Int)]): 
        List[List[(String, Int)]] = list match {

        case Nil => List (first, second)
        case ((s, i)) :: ps =>  if (i % 2 !=0)
            separate (ps, (s, i) :: first, second) else
            separate (ps, first, (s, i) :: second)
    }

    separate (list,  List[(String, Int)](),  List[(String, Int)]())
}

The outer function just sets up 2 empty lists, for odd and even, to pass them along, casually adding the next element to one of them.

It get's much nicer, when generalized over the type. We get rid of all those (String, Int) Tuples and have solution for every Type, we just need to pass a function from A to Boolean:

def separate [A] (list: List[A]) (f: A => Boolean): List[List[A]]  = {

    def separate (list: List[A], first: List[A], second: List[A]): List[List[A]] = list match {
        case Nil => List (first, second)
        case p :: ps =>  if (f (p))
            separate (ps, p :: first, second) else
            separate (ps, first, p :: second)
    }

    separate (list, List[A](), List[A]())
}

def evan (si : (String, Int)) : Boolean = (si._2 % 2) == 0
separate (fichier) (evan)

or

separate (fichier) (_._2 % 2 == 0)

Upvotes: 0

user9223219
user9223219

Reputation:

You forgot to yield values from a for expression:

def separate(list: List[(String, Int)]): List[List[(String, Int)]] = {
  val evenList = for ((i, j) <- list if j % 2 == 0) yield (i, j)
  val oddList = for ((i, j) <- list if j % 2 != 0) yield (i, j)
  val evenAndOdd = List(evenList, oddList)
  evenAndOdd
}

There are 2 types of for expressions in Scala:

  1. For loop
  2. For comprehension

For expression without yield is a for loop which returns Unit:

val x = for ((i, j) <- fichier if j % 2 == 0) (i, j)
x.getClass // Class[Unit] = void

Think of this as a regular Python loop with side-effects:

items = []
for item in [1, 2, 3]:
    items.append(item + 1)

When you are yielding from for, you are using a for comprehension which produces a new collection:

val y = for ((i, j) <- fichier if j % 2 == 0) yield (i, j)
y.take(2) // List(("Bill Morton", 36), ("Georgia Bates", 30))

This is more like Python for comprehensions the result of which you can assign to a variable:

items = [item + 1 for item in [1, 2, 3]]

Upvotes: 2

Andrey Tyukin
Andrey Tyukin

Reputation: 44992

First, notice that there already is a method partition on List:

val fichier : List[(String, Int)] = List(
  ("Emma Jacobs",21), ("Mabelle Bradley",53), 
  ("Mable Burton",47), ("Ronnie Walton",41), 
  ("Bill Morton",36), ("Georgia Bates",30), 
  ("Jesse Caldwell",46), ("Jeffery Wolfe",50), 
  ("Roy Norris",18), ("Ella Gonzalez",48)
)

val (odd, even) = fichier.partition(_._2 % 2 == 1)

println(odd)
println(even)

this prints:

List((Emma Jacobs,21), (Mabelle Bradley,53), (Mable Burton,47), (Ronnie Walton,41))
List((Bill Morton,36), (Georgia Bates,30), (Jesse Caldwell,46), (Jeffery Wolfe,50), (Roy Norris,18), (Ella Gonzalez,48))

You could of course easily implement it yourself as follows:

def separate[A](list: List[A])(predicate: A => Boolean):  (List[A], List[A]) = {
  import scala.collection.mutable.ListBuffer
  val trueListBuf = new ListBuffer[A]
  val falseListBuf = new ListBuffer[A]
  for (x <- list) {
    if (predicate(x)) trueListBuf += x
    else falseListBuf += x
  }
  (trueListBuf.toList, falseListBuf.toList)
}

val (odd2, even2) = separate(fichier){ case (n, a) => a % 2 == 1 }

println(odd2)
println(even2)

This outputs the same result as previously.

Several important points to notice here:

  • We use mutable ListBuffers for efficiency, because appending to a mutable ListBuffer works in constant time, whereas doing the same with the immutable List is somewhat more cumbersome
  • We implement the separate method for all possible As that can be in the list. Instead of hardcoding % 2-check into the method, we pass a generic predicate from the outside
  • Notice that the predicate is in the second (separate) argument list. This enables us to use a somewhat nicer (at least in my opinion) syntax separate(list){ predicateImpl }. This also simplifies type inference for the predicate argument.
  • We can use pattern matching case (name, age) => ... when implementing the predicate.

Upvotes: 2

Related Questions