Reputation: 95
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
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
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:
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
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:
ListBuffer
s for efficiency, because appending to a mutable
ListBuffer
works in constant time, whereas doing the same with the immutable
List
is somewhat more cumbersomeseparate
method for all possible A
s that can be
in the list. Instead of hardcoding % 2
-check into the method, we pass
a generic predicate
from the outsidepredicate
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.case (name, age) => ...
when implementing the
predicate.Upvotes: 2