virtualeyes
virtualeyes

Reputation: 11237

Multi-Assignment based on Collection

Edit

originally the question was "Collection to Tuple" as I assumed I needed a tuple in order to do variable multi-assignment. It turns out that one can do variable multi-assignment directly on collections. Retitled the question accordingly.

Original Have a simple Seq[String] derived from a regex that I would like to convert to a Tuple.

What's the most direct way to do so?

I currently have:

val(clazz, date) = captures match {
  case x: Seq[String] => (x(0), x(1))
}

Which is ok, but my routing layer has a bunch of regex matched routes that I'll be doing val(a,b,c) multi-assignment on (the capture group is always known since the route is not processed if regex does not match). Would be nice to have a leaner solution than match { case.. => ..}

What's the shortest 1-liner to convert collections to tuples in Scala?

Upvotes: 4

Views: 214

Answers (5)

Blaisorblade
Blaisorblade

Reputation: 6488

Seqs to tuple

To perform multi-assignment from a Seq, what about the following?

val Seq(clazz, date) = captures

As you see, no need to restrict to Lists; this code will throw a MatchError if the length does not match (in your case, that's good because it means that you made a mistake). You can then add

(clazz, date)

to recreate the tuple.

Tuples from matches

However, Jed Wesley-Smith posted a solution which avoids this problem and solves the original question better. In particular, in your solution you have a Seq whose length is not specified, so if you make a mistake the compiler won't tell you; with tuples instead the compiler can help you (even if it can't check against the regexp).

Upvotes: 0

Jed Wesley-Smith
Jed Wesley-Smith

Reputation: 4706

Your question is originally specifically about assigning the individual capturing groups in a regex, which already allow you to assign from them directly:

scala> val regex = """(\d*)-(\d*)-(\d*)""".r
regex: scala.util.matching.Regex = (\d*)-(\d*)-(\d*)

scala> val regex(a, b, c) = "29-1-2012"
d: String = 29
m: String = 1
y: String = 2012

obviously you can use these in a case as well:

scala> "29-1-2012" match { case regex(d, m, y) => (y, m, d) }
res16: (String, String, String) = (2012,1,29)

and then group these as required.

Upvotes: 2

Christopher Chiche
Christopher Chiche

Reputation: 15345

As proposed by @ziggystar in comments you can do something like:

val (clazz, date) = { val a::b::_ = capture; (a, b)} 

or

val (clazz, date) = (capture(0), capture(1)) 

If you verified the type of the list before it is OK, but take care of the length of the Seq because the code will run even if the list is of size 0 or 1.

Upvotes: 2

ziggystar
ziggystar

Reputation: 28688

This is not an answer to the question but might solve the problem in a different way.

You know you can match a xs: List[String] like so:

val a :: b :: c :: _ = xs 

This assigns the first three elements of the list to a,b,c? You can match other things like Seq in the declaration of a val just like inside a case statement. Be sure you take care of matching errors:

Catching MatchError at val initialisation with pattern matching in Scala?

Upvotes: 5

missingfaktor
missingfaktor

Reputation: 92066

You can make it slightly nicer using |> operator from Scalaz.

scala> val captures = Vector("Hello", "World")
captures: scala.collection.immutable.Vector[java.lang.String] = Vector(Hello, World)

scala> val (a, b) = captures |> { x => (x(0), x(1)) }
a: java.lang.String = Hello
b: java.lang.String = World

If you don't want to use Scalaz, you can define |> yourself as shown below:

scala> class AW[A](a: A) {
     |   def |>[B](f: A => B): B = f(a)
     | }
defined class AW

scala> implicit def aW[A](a: A): AW[A] = new AW(a)
aW: [A](a: A)AW[A]

EDIT:

Or, something like @ziggystar's suggestion:

scala> val Vector(a, b) = captures
a: java.lang.String = Hello
b: java.lang.String = World

You can make it more concise as shown below:

scala> val S = Seq
S: scala.collection.Seq.type = scala.collection.Seq$@157e63a

scala> val S(a, b) = captures
a: java.lang.String = Hello
b: java.lang.String = World

Upvotes: 3

Related Questions