Reputation: 11237
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
Reputation: 6488
To perform multi-assignment from a Seq
, what about the following?
val Seq(clazz, date) = captures
As you see, no need to restrict to List
s; 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.
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
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
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
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
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