简体   繁体   中英

Scala return types with foreach and map

I'm new to Scala, and I'm getting confused on how functions stitch together and their return types. Consider the following:

val nodes = List(0,1,2)               
val links = List(List(1, 2), List(1, 0), List(1,3))

Each node has a bi-directional link, as described in the tuple-pair 'links'. I am attempting to building a map where each node points to it's neighbours, for example

Map(0 -> List(1), 1 -> List(2, 0), 2 -> List(1) )

However, my initial thinking on how to code this has me stumped on why it's returning an empty 'List[Any]' value.

nodes.foreach(z => (links.map { case List(a,b) => if(a == z) a else if (b == z) b }))

What is the right way to do this?

Or,

scala> nodes.map(n => (n, links.flatMap {
     | case List(`n`, x) => Some(x)
     | case List(x, `n`) => Some(x)
     | case _ => None }))
res3: List[(Int, List[Int])] = List((0,List(1)), (1,List(2, 0, 3)), (2,List(1)))

As commented,

scala> .toMap
res4: scala.collection.immutable.Map[Int,List[Int]] = Map(0 -> List(1), 1 -> List(2, 0, 3), 2 -> List(1))

There are some problems in your code:

First of all, foreach returns Unit as you can see in the Scala Doc

Also, your condition is not entirely correct - eg for node 0 and connection 1<->0 you are going to print 0 but you actually want to return 1 .


The correct code would go like this:

val nodes = List(0,1,2)               
val links = List(List(1, 2), List(1, 0), List(1,3))
val result = nodes.map(z => 
    (z, links.flatMap { 
                case List(a,b) => if(a == z && nodes.contains(b)) Some(b) else if (b == z && nodes.contains(a)) Some(a) else None 
              }
    )
).toMap
println(result)

Notice how I am calling map on the nodes list (instead of foreach ) - this gives me then the possibility to call toMap at the end - which is returning a Map (what you want to achieve eventually). Also I am map ping each node to a Pair of the node and a List of its neighbours

Note 2: I added a condition which checks if the discovered neighbour is part of the nodes collections (with contains ) - as this is how your example is. If you have a different requirement, just remove it

Will give:

Map(0 -> List(1), 1 -> List(2, 0), 2 -> List(1))

I am not exactly sure where you get a List[Any] in your code example.

However, foreach takes a function with return type Unit as argument and therefore returns Unit . It is basically a classic for each loop for side effects.

The 'map' function of the List collection can turn a List[A] into a List[B] . So if your were for example to return either a List[Int] or an Int in your map function, the smallest common ancestor type of both would Any so the result would be a List[Any] .

However, as scala has a very powerful collections API, there is an easy solution.

Explicitly, you can provide a CanBuildFrom[List[Int], List[Int], Map[Int, Int]] to build a Map[Int, List[Int]] from a List[Int] while mapping using a function Int => (Int, Int)

But scala provides an even easier way to accomplish that (Have a look at breakOut ).

So, if I interpreted your question correctly, you would need something like this:

val neighbors: Map[Int, List[Int]] = nodes.map(z =>
  z -> links.withFilter(_.contains(z)).flatMap(_.filter(candidate => candidate != z && nodes.contains(candidate)))
)(collection.breakOut)

--> neighbors: Map[Int,List[Int]] = Map(0 -> List(1), 1 -> List(2, 0), 2 -> List(1))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM