Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple assignment in Scala without using Array?

I have an input something like this: "1 2 3 4 5".

What I would like to do, is to create a set of new variables, let a be the first one of the sequence, b the second, and xs the rest as a sequence (obviously I can do it in 3 different lines, but I would like to use multiple assignment).

A bit of search helped me by finding the right-ignoring sequence patterns, which I was able to use:

val Array(a, b, xs @ _*) = "1 2 3 4 5".split(" ")

What I do not understand is that why doesn't it work if I try it with a tuple? I get an error for this:

val (a, b, xs @ _*) = "1 2 3 4 5".split(" ")

The error message is:

<console>:1: error: illegal start of simple pattern

Are there any alternatives for multiple-assignment without using Array?

I have just started playing with Scala a few days ago, so please bear with me :-) Thanks in advance!

like image 217
rlegendi Avatar asked Apr 24 '12 17:04

rlegendi


5 Answers

Other answers tell you why you can't use tuples, but arrays are awkward for this purpose. I prefer lists:

val a :: b :: xs = "1 2 3 4 5".split(" ").toList
like image 191
Rex Kerr Avatar answered Oct 05 '22 19:10

Rex Kerr


Simple answer

val Array(a, b, xs @ _*) = "1 2 3 4 5".split(" ")

The syntax you are seeing here is a simple pattern-match. It works because "1 2 3 4 5".split(" ") evaluates to an Array:

scala> "1 2 3 4 5".split(" ")
res0: Array[java.lang.String] = Array(1, 2, 3, 4, 5)

Since the right-hand-side is an Array, the pattern on the left-hand-size must, also, be an Array

The left-hand-side can be a tuple only if the right-hand-size evaluates to a tuple as well:

val (a, b, xs) = (1, 2, Seq(3,4,5))

More complex answer

Technically what's happening here is that the pattern match syntax is invoking the unapply method on the Array object, which looks like this:

def unapplySeq[T](x: Array[T]): Option[IndexedSeq[T]] =
  if (x == null) None else Some(x.toIndexedSeq)

Note that the method accepts an Array. This is what Scala must see on the right-hand-size of the assignment. And it returns a Seq, which allows for the @_* syntax you used.

Your version with the tuple doesn't work because Tuple3's unapplySeq is defined with a Product3 as its parameter, not an Array:

def unapply[T1, T2, T3](x: Product3[T1, T2, T3]): Option[Product3[T1, T2, T3]] =
  Some(x)

You can actually "extractors" like this that do whatever you want by simply creating an object and writing an unapply or unapplySeq method.

like image 33
dhg Avatar answered Oct 05 '22 18:10

dhg


The answer is:

val a :: b :: c = "1 2 3 4 5".split(" ").toList

Should clarify that in some cases one may want to bind just the first n elements in a list, ignoring the non-matched elements. To do that, just add a trailing underscore:

val a :: b :: c :: _ = "1 2 3 4 5".split(" ").toList

That way:

c = "3" vs. c = List("3","4","5")
like image 32
virtualeyes Avatar answered Oct 05 '22 18:10

virtualeyes


I'm not an expert in Scala by any means, but I think this might have to do with the fact that Tuples in Scala are just syntatic sugar for classes ranging from Tuple2 to Tuple22.

Meaning, Tuples in Scala aren't flexible structures like in Python or other languages of the sort, so it can't really create a Tuple with an unknown a priori size.

like image 30
pcalcao Avatar answered Oct 05 '22 20:10

pcalcao


We can use pattern matching to extract the values from string and assign it to multiple variables. This requires two lines though.

Pattern says that there are 3 numbers([0-9]) with space in between. After the 3rd number, there can be text or not, which we don't care about (.*).

val pat = "([0-9]) ([0-9]) ([0-9]).*".r
val (a,b,c) = "1 2 3 4 5" match { case pat(a,b,c) => (a,b,c) }

Output

a: String = 1
b: String = 2
c: String = 3
like image 44
Ravi Avatar answered Oct 05 '22 19:10

Ravi