How can one iterate over list of tuples/arrays, us

2019-09-17 22:43发布

问题:

Suppose you have a list of lists in Scala,

thing = List( List("a", "AAA", true)
    ,List("b", "BBB", true)
    ,List("c", "CCC", true)
    ,List("d", "DDD", false)
    ,List("e", "EEE", true) )

And you want to iterate through the list, and use the elements of the inner list for some additional work.

Python would be easy,

foreach x in thing:
    if x[2]: func1( x[0], x[1] )
    else: func2( x[0], x[1], something_else )

I think this can be roughly translated to scala as something like this,

thing.foreach { x =>
    if( x.lift(2) ) func1( x.lift(0), x.lift(1) )
    else func2( x.lift(0), x.lift(1), something_else )
}

Is the above idiomatic scala? What is the scala idiom for doing this?

回答1:

I'd suggest using a List (or better yet, a Seq) of Tuples instead of a List of Lists. It's more idiomatic for Scala

val thing = Seq(("a", "AAA", true)
  ,("b", "BBB", true)
  ,("c", "CCC", true)
  ,("d", "DDD", false)
  ,("e", "EEE", true)
)

Then make your decision with case statments

thing.map{
  _ match {
    case (x, y, true) => func1(x,y)
    case (x, y, false) => func2(x,y,something_else)
    case _ => throw new UnsupportedOperationException("Ack!")
  }
}

Using a case class instead of the Tuple would allow you to type the various values. This would obviate the last case statement, among other advantages. Scala is type safe. This is good.

case class Thing(x:String,y:String,z:Boolean)

val thing = Seq[Thing](Thing("a", "AAA", true)
  ,Thing("b", "BBB", true)
  ,Thing("c", "CCC", true)
  ,Thing("d", "DDD", false)
  ,Thing("e", "EEE", true)
)

The rest is the same (Goooooooo Scala Type Inference!)

one more thing...

If a case class seems overkill (you won't use it elsewhere, code is tiny and localized) you can also type Tuples ->

val thing = Seq[(String,String,Boolean)](("a", "AAA", true)
...


回答2:

  • foreach should be used only for side-effecting functions ie if func1 and func2 returns Unit or you are not bothered with the return value. use map if you want to generate a new list from the existing list thing.

  • Drop the x.lift(2) and use just x(2). x.lift(2) would return an Option. lift is required if you think the inner list might not have the third element at all. for instance x(2) has value true x.lift(2) would return Some(true) and if x(2) has no value (i.e. the list has only 2 elements) x.lift(2) would return None

  • use of Pattern matching is recommended here instead of if-else clause since your inner list doesnt have homogenous types (String and Boolean) and hence it will upcasted to type Any. so x(2) returns type Any and not Boolean as one would expect. The Pattern matching will eliminate the need for any ugly type casting.

    thing foreach {  //use map if func1 and func2 are not side-effecting functions
    x =>  x(2) match {
       case true => func1(x.lift(0), x.lift(1))
       case false => func2(x.lift(0), x.lift(1), something_else)
     }
    }
    
  • If the inner list will always have three elements of types String, String and Boolean respectively consider using a Tuple instead of List. so the thing would look like the below.

    val thing = List(("a", "AAA", True),("b", "BBB", True),("c", "CCC", True),("d", "DDD", False),("e", "EEE", True))
    thing foreach {
      case (x,y,true) => func1(x, y)
      case (x,y,false) => func2(x, y, something_else) 
    }
    


回答3:

Try This

   val thing = List( List("a", "AAA", true)
  ,List("b", "BBB", true)
  ,List("c", "CCC", true)
  ,List("d", "DDD", false)
  ,List("e", "EEE", true) )

  thing.map{ x => x match{case List(x,y,z) => if(z == true) func1( x,y)  else func2( x, y, something_else ) }}


标签: scala