1

I understand how to use if in map. For example, val result = list.map(x => if (x % 2 == 0) x * 2 else x / 2).

However, I want to use if for only part of the arguments.

val inputColumns = List(
  List(1, 2, 3, 4, 5, 6),   // first "column"
  List(4, 6, 5, 7, 12, 15)  // second "column"
)
inputColumns.zipWithIndex.map{ case (col, idx) => if (idx == 0) col * 2 else col / 10}
<console>:1: error: ';' expected but integer literal found.

inputColumns.zipWithIndex 
res4: List[(List[Int], Int)] = List((List(1, 2, 3, 4, 5, 6),0), (List(4, 6, 5, 7, 12, 15),1))

I have searched the error info but have not found a solution.

Why my code is not 'legal' in Scala? Is there a better way to write it? Basically, I want to do a pattern matching and then do something on other arguments.

Jill Clover
  • 2,168
  • 7
  • 31
  • 51
  • are you trying to multiply the list by 2 and devide by 10? and there is no then keyword in if else scala – Ramesh Maharjan Feb 27 '18 at 17:51
  • you should be getting `error: value * is not a member of List[Int]` error . are you sure that you are still getting `error: ';' expected but integer literal found.` error after you corrected the code? By the way I have explained in the answer below :) – Ramesh Maharjan Feb 27 '18 at 18:15
  • `zipWithIndex.map{case (c,0)=>c.map(_*2);case (c,_)=>c.map(_/10)}` – jwvh Feb 27 '18 at 18:46

2 Answers2

3

To explain your problem another way, inputColumns has type List[List[Int]]. You can verify this in the Scala REPL:

$ scala
Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_161).
Type in expressions for evaluation. Or try :help.

scala> val inputColumns = List(
 |   List(1, 2, 3, 4, 5, 6),   // first "column"
 |   List(4, 6, 5, 7, 12, 15)  // second "column"
 | )
inputColumns: List[List[Int]] = List(List(1, 2, 3, 4, 5, 6), List(4, 6, 5, 7, 12, 15))

Now, when you call .zipWithIndex on that list, you end up with a List[(List[Int], Int)] - that is, a list of a tuple, in which the first tuple type is a List[Int] (the column) and the second is an Int (the index):

scala> inputColumns.zipWithIndex
res0: List[(List[Int], Int)] = List((List(1, 2, 3, 4, 5, 6),0), (List(4, 6, 5, 7, 12, 15),1))

Consequently, when you try to apply a map function to this list, col is a List[Int] and not an Int, and so col * 2 makes no sense - you're multiplying a List[Int] by 2. You then also try to divide the list by 10, obviously.

scala> inputColumns.zipWithIndex.map{ case(col, idx) => if(idx == 0) col * 2 else col / 10 }
<console>:13: error: value * is not a member of List[Int]
       inputColumns.zipWithIndex.map{ case(col, idx) => if(idx == 0) col * 2 else col / 10 }
                                                                         ^
<console>:13: error: value / is not a member of List[Int]
       inputColumns.zipWithIndex.map{ case(col, idx) => if(idx == 0) col * 2 else col / 10 }
                                                                                      ^

In order to resolve this, it depends what you're trying to achieve. If you want a single list of integers, and then zip those so that each value has an associated index, you should call flatten on inputColumns before calling zipWithIndex. This will result in List[(Int, Int)], where the first value in the tuple is the column value, and the second is the index. Your map function will then work correctly without modification:

scala> inputColumns.flatten.zipWithIndex.map{ case(col, idx) => if(idx == 0) col * 2 else col / 10 }
res3: List[Int] = List(2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1)

Of course, you no longer have separate columns.

If you wish each value in each list to have an associated index, you need to firstly map inputColumns into two zipped lists, using inputColumns.map(_.zipWithIndex) to create a List[List[(Int, Int)]] - a list of a list of (Int, Int) tuples:

scala> inputColumns.map(_.zipWithIndex)
res4: List[List[(Int, Int)]] = List(List((1,0), (2,1), (3,2), (4,3), (5,4), (6,5)), List((4,0), (6,1), (5,2), (7,3), (12,4), (15,5)))

We can now apply your original map function to the result of the zipWithIndex operation:

scala> inputColumns.map(_.zipWithIndex.map { case (col, idx) => if(idx == 0) col * 2 else col / 10 })
res5: List[List[Int]] = List(List(2, 0, 0, 0, 0, 0), List(8, 0, 0, 0, 1, 1))

The result is another List[List[Int]] with each internal list being the results of your map operation on the original two input columns.

On the other hand, if idx is meant to be the index of the column, and not of each value, and you want to multiply all of the values in the first column by 2 and divide all of the values in the other columns by 10, then you need to change your original map function to map across each column, as follows:

scala> inputColumns.zipWithIndex.map {
     |   case (col, idx) => {
     |     if(idx == 0) col.map(_ * 2) // Multiply values in first column by 1
     |     else col.map(_ / 10) // Divide values in all other columns by 10
     |   }
     | }
res5: List[List[Int]] = List(List(2, 4, 6, 8, 10, 12), List(0, 0, 0, 0, 1, 1))

Let me know if you require any further clarification...

UPDATE:

The use of case in map is a common Scala shorthand. If a higher-order function takes a single argument, something such as this:

def someHOF[A, B](x: A => B) = //...

and you call that function like this (with what Scala terms a partial function - a function consisting solely of a list of case statements):

someHOF {
  case expr1 => //...
  case expr2 => //...
  ...
}

then Scala treats it as a kind-of shorthand for:

someHOF {a =>
  a match {
    case expr1 => //...
    case expr2 => //...
    ...
  }
}

or, being slightly more terse,

someHOF {
  _ match {
    case expr1 => //...
    case expr2 => //...
    ...
  }
}

For a List, for example, you can use it with functions such as map, flatMap, filter, etc.

In the case of your map function, the sole argument is a tuple, and the sole case statement acts to break open the tuple and expose its contents. That is:

val l = List((1, 2), (3, 4), (5, 6))
l.map { case(a, b) => println(s"First is $a, second is $b") }

is equivalent to:

l.map {x =>
  x match {
    case (a, b) => println(s"First is $a, second is $b")
  }
}

and both will output:

First is 1, second is 2
First is 3, second is 4
First is 5, second is 6

Note: This latter is a bit of a dumb example, since map is supposed to map (i.e. change) the values in the list into new values in a new list. If all you were doing was printing the values, this would be better:

val l = List((1, 2), (3, 4), (5, 6))
l.foreach { case(a, b) => println(s"First is $a, second is $b") }
Mike Allen
  • 8,139
  • 2
  • 24
  • 46
  • Awesome! Could you explain how does `case` work here? Is there another way to write it? Does it do pattern matching? – Jill Clover Feb 27 '18 at 22:16
  • @JohnHass I'll update my answer shortly with an explanation. – Mike Allen Feb 27 '18 at 22:32
  • @JohnHass I just expanded my updated answer with some further information. Let me know if that explains things better... – Mike Allen Feb 27 '18 at 23:19
  • Excellent!! I asked a related question in this link https://stackoverflow.com/questions/49020570/why-zip-does-not-work-with-match-case-in-scala – Jill Clover Feb 28 '18 at 01:23
2

You are trying to multiply a list by 2 when you do col * 2 as col is List(1, 2, 3, 4, 5, 6) when idx is 0, which is not possible and similar is the case with else part col / 10

If you are trying to multiply the elements of first list by 2 and devide the elements of rest of the list by 10 then you should be doing the following

inputColumns.zipWithIndex.map{ case (col, idx) => if (idx == 0) col.map(_*2) else col.map(_/10)}

Even better approach would be to use match case

inputColumns.zipWithIndex.map(x => x._2 match {
  case 0 => x._1.map(_*2)
  case _ => x._1.map(_/10)
})
Ramesh Maharjan
  • 41,071
  • 6
  • 69
  • 97