Andrei Markhel
Andrei Markhel

Reputation: 175

Scala. How to create general method that accept tuple with different arities?

In my application I have many places, where I need get a list of tuples, groupBy it by first element of tuple and remove it from the rest. For example, I have tuples

(1, "Joe", "Account"), (1, "Tom", "Employer"), (2, "John", "Account"), and result should be Map(1 -> List(("Joe", "Account"), ("Joe", "Account")), 2 -> List(("John", "Account")))

It is easy implemented as

data.groupBy(_._1).map { case (k, v) => k -> v.map(f => (f._2, f._3)) }

But I am looking general solution, because I can have tuples with different arities, 2, 3, 4 or even 7. I think Shapeless or Scalaz can help me, but my experience is low in those libraries, please point to some example

Upvotes: 2

Views: 141

Answers (1)

Oleg Pyzhcov
Oleg Pyzhcov

Reputation: 7353

This is easily implemented using shapeless (for simplicity, I won't be generalizing it for all collection types). There's specific type class for tuples that can deconstruct them into head and tail called IsComposite

import shapeless.ops.tuple.IsComposite

def groupTail[P, H, T](tuples: List[P])(
    implicit ic: IsComposite.Aux[P, H, T]): Map[H, List[T]] = {
  tuples
        .groupBy(ic.head)
        .map { case (k, vs) => (k, vs.map(ic.tail)) }
}

This works for your case:

val data =
  List((1, "Joe", "Account"), (1, "Tom", "Employer"), (2, "John", "Account"))

assert {
  groupTail(data) == Map(
    1 -> List(("Joe", "Account"), ("Tom", "Employer")),
    2 -> List(("John", "Account"))
  )
}

As well as for Tuple4 of different types:

val data2 = List((1, 1, "a", 'a), (1, 2, "b", 'b), (2, 1, "a", 'b))

assert {
  groupTail(data2) == Map(
    1 -> List((1, "a", 'a), (2, "b", 'b)),
    2 -> List((1, "a", 'b))
  )
}

Runnable code is available at Scastie

Upvotes: 6

Related Questions