Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern Matching tuples in Scala

Trying to get a handle on pattern matching here-- coming from a C++/Java background it's very foreign to me.

The point of this branch is to check each member of a List d of tuples [format of (string,object). I want to define three cases.

1) If the counter in this function is larger than the size of the list (defined in another called acc), I want to return nothing (because there is no match) 2) If the key given in the input matches a tuple in the list, I want to return its value (or, whatever is stored in the tuple._2). 3) If there is no match, and there is still more list to iterate, increment and continue.

My code is below:

def get(key:String):Option[Any] = {

        var counter: Int = 0
        val flag: Boolean = false 

        x match {

                case (counter > acc) => None

                case ((d(counter)._1) == key) => d(counter)._2

                case _ =>   counter += 1
                }

My issue here is while the first case seems to compile correctly, the second throws an error:

:36: error: ')' expected but '.' found.
                case ((d(counter)._1) == key) => d(counter)._2

The third as well:

scala>         case _ =>  counter += 1
:1: error: illegal start of definition

But I assume it's because the second isn't correct. My first thought is that I'm not comparing tuples correctly, but I seem to be following the syntax for indexing into a tuple, so I'm stumped. Can anyone steer me in the right direction?

like image 756
aerotwelve Avatar asked Feb 20 '23 02:02

aerotwelve


2 Answers

Hopefully a few things to clear up your confusion:

Matching in scala follows this general template:

x match {
  case SomethingThatXIs if(SomeCondition) => SomeExpression
  // rinse and repeat
  // note that `if(SomeCondition)` is optional
}

It looks like you may have attempted to use the match/case expression as more of an if/else if/else kind of block, and as far as I can tell, the x doesn't really matter within said block. If that's the case, you might be fine with something like

case _ if (d(counter)._1 == key) => d(counter)._2

BUT

Some info on Lists in scala. You should always think of it like a LinkedList, where indexed lookup is an O(n) operation. Lists can be matched with a head :: tail format, and Nil is an empty list. For example:

val myList = List(1,2,3,4)
myList match {
  case first :: theRest => 
    // first is 1, theRest is List(2,3,4), which you can also express as
    // 2 :: 3 :: 4 :: Nil
  case Nil =>
    // an empty list case
}

It looks like you're constructing a kind of ListMap, so I'll write up a more "functional"/"recursive" way of implementing your get method.

I'll assume that d is the backing list, of type List[(String, Any)]

def get(key: String): Option[Any] = {
  def recurse(key: String, list: List[(String, Any)]): Option[Any] = list match {
    case (k, value) :: _ if (key == k) => Some(value)
    case _ :: theRest => recurse(key, theRest)
    case Nil => None
  }
  recurse(key, d)
}

The three case statements can be explained as follows:

1) The first element in list is a tuple of (k, value). The rest of the list is matched to the _ because we don't care about it in this case. The condition asks if k is equal to the key we are looking for. In this case, we want to return the value from the tuple.

2) Since the first element didn't have the right key, we want to recurse. We don't care about the first element, but we want the rest of the list so that we can recurse with it.

3) case Nil means there's nothing in the list, which should mark "failure" and the end of the recursion. In this case we return None. Consider this the same as your counter > acc condition from your question.

Please don't hesitate to ask for further explanation; and if I've accidentally made a mistake (won't compile, etc), point it out and I will fix it.

like image 79
Dylan Avatar answered Feb 24 '23 05:02

Dylan


I'm assuming that conditionally extracting part of a tuple from a list of tuples is the important part of your question, excuse me if I'm wrong.

First an initial point, in Scala we normally would use AnyRef instead of Object or, if worthwhile, we would use a type parameter which can increase reuse of the function or method and increase type safety.

The three cases you describe can be collapsed into two cases, the first case uses a guard (the if statement after the pattern match), the second case matches the entire non-empty list and searches for a match between each first tuple argument and the key, returning a Some[T] containing the second tuple argument of the matching tuple or None if no match occurred. The third case is not required as the find operation traverses (iterates over) the list.

The map operation after the find is used to extract the second tuple argument (map on an Option returns an Option), remove this operation and change the method's return type to Option[(String, T)] if you want the whole tuple returned.

def f[T](key: String, xs: List[(String, T)], initialCount: Int = 2): Option[T] = {
  var counter = initialCount

  xs match {
    case l: List[(String, T)] if l.size < counter => None
    case l: List[(String, T)] => l find {_._1 == key} map {_._2}
  }
}

f("A", List(("A", 1), ("B", 2))) // Returns Some(1)
f("B", List(("A", 1), ("B", 2))) // Returns Some(2)
f("A", List(("A", 1)))           // Returns None
f("C", List(("A", 1), ("B", 2))) // Returns None
f("C", Nil)                      // Returns None
like image 30
Don Mackenzie Avatar answered Feb 24 '23 05:02

Don Mackenzie