Jill Clover
Jill Clover

Reputation: 2328

if condition for partial argument in map

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.

Upvotes: 1

Views: 577

Answers (2)

Mike Allen
Mike Allen

Reputation: 8299

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") }

Upvotes: 3

Ramesh Maharjan
Ramesh Maharjan

Reputation: 41987

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)
})

Upvotes: 2

Related Questions