简体   繁体   中英

Flatten a list of tuples in Scala?

I would have thought that a list of tuples could easily be flattened:

scala> val p = "abcde".toList
p: List[Char] = List(a, b, c, d, e)

scala> val q = "pqrst".toList
q: List[Char] = List(p, q, r, s, t)

scala> val pq = p zip q
pq: List[(Char, Char)] = List((a,p), (b,q), (c,r), (d,s), (e,t))

scala> pq.flatten

But instead, this happens:

<console>:15: error: No implicit view available from (Char, Char) => scala.collection.GenTraversableOnce[B].
       pq.flatten
          ^

I can get the job done with:

scala> (for (x <- pq) yield List(x._1, x._2)).flatten
res1: List[Char] = List(a, p, b, q, c, r, d, s, e, t)

But I'm not understanding the error message. And my alternative solution seems convoluted and inefficient.

What does that error message mean and why can't I simply flatten a List of tuples?

If the implicit conversion can't be found you can supply it explicitly.

pq.flatten {case (a,b) => List(a,b)}

If this is done multiple times throughout the code then you can save some boilerplate by making it implicit.

scala> import scala.language.implicitConversions
import scala.language.implicitConversions

scala> implicit def flatTup[T](t:(T,T)): List[T]= t match {case (a,b)=>List(a,b)}
flatTup: [T](t: (T, T))List[T]

scala> pq.flatten
res179: List[Char] = List(a, p, b, q, c, r, d, s, e, t)

jwvh's answer covers the "coding" solution to your problem perfectly well, so I am not going to go into any more detail about that. The only thing I wanted to add was clarifying why the solution that both you and jwvh found is needed.

As stated in the Scala library, Tuple2 (which (,) translates to) is:

A tuple of 2 elements; the canonical representation of a Product2 .

And following up on that:

Product2 is a cartesian product of 2 components.

...which means that Tuple2[T1,T2] represents:

The set of all possible pairs of elements whose components are members of two sets (all elements in T1 and T2 respectively) .

A List[T] , on the other hand, represents an ordered collections of T elements.

What all this means practically is that there is no absolute way to translate any possible Tuple2[T1,T2] to a List[T] , simply because T1 and T2 could be different . For example, take the following tuple:

val tuple = ("hi", 5)

How could such tuple be flattened? Should the 5 be made a String ? Or maybe just flatten to a List[Any] ? While both of these solutions could be used, they are working around the type system , so they are not encoded in the Tuple API by design.

All this comes down to the fact that there is no default implicit view for this case and you have to supply one yourself, as both jwvh and you already figured out.

We needed to do this recently. Allow me to explain the use case briefly before noting our solution.

Use case

Given a pool of items (which I'll call type T ), we want to do an evaluation of each one against all others in the pool. The result of these comparisons is a Set of failed evaluations , which we represent as a tuple of the left item and the right item in said evaluation: (T, T) .

Once these evaluations are complete, it becomes useful for us to flatten the Set[(T, T)] into another Set[T] that highlights all the items that have failed any comparisons.

Solution

Our solution for this was a fold:

val flattenedSet =
    set.foldLeft(Set[T]())
                { case (acc, (x, y)) => acc + x + y }

This starts with an empty set (the initial parameter to foldLeft ) as the accumulator .

Then, for each element in the consumed Set[(T, T)] (named set ) here, the fold function is passed:

  1. the last value of the accumulator ( acc ), and
  2. the (T, T) tuple for that element, which the case deconstructs into x and y .

Our fold function then returns acc + x + y , which returns a set containing all the elements in the accumulator in addition to x and y . That result is passed to the next iteration as the accumulator—thus, it accumulates all the values inside each of the tuples.

Why not List s?

I appreciated this solution in particular since it avoided creating intermediate List s while doing the flattening—instead, it directly deconstructs each tuple while building the new Set[T] .

We could also have changed our evaluation code to return List[T] s containing the left and right items in each failed evaluation—then flatten would Just Work™. But we thought the tuple more accurately represented what we were going for with the evaluation—specifically one item against another, rather than an open-ended type which could conceivably represent any number of items.

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