I have a map consisiting of Strings and Lists of tuples:
Map[String, List[(String, Int]]
Some examples of lines in the map look like:
"test1", List(("apple", 0), ("pear", 1), ("banana", 0))
"test2", List(("green", 1), ("yellow", 1), ("red", 0))
"test3", List(("monday", 0), ("tuesday", 1), ("wednesday", 0))
what is the best way to extract a list of the first Strings in each line only where the list of tuples contains more than 1 value that equals 0?
In other words I'd like a list that contains "test1" and "test2" since those 2 lines are the only lines that contain more than 1 '0' in their list of tuples.
CodePudding user response:
Your starting point is going to be either collect
or filter
; both methods are helpful for when you have a collection and you want a new collection with some conditional applied.
Your conditional, as described, sounds like a use-case for the count
method which is defined on pretty much every collection type in the standard library.
This should work...
theMapFromYourExample
.collect { case (key, list) if list.count(_._2 == 0) > 1 => key }
.toList
...but there are some considerations to make:
.collect
andtoList
will both create new entire collections. Putting them together means you are wasting some memory and CPU time by building an intermediate collection that's just getting send straight to garbage collection as soon as your expression is done running. To avoid allocating memory for a whole intermediate collection, you can use the View pattern, accessible via the.view
method on most collections..count
is O(N) on the size of the list, as it needs to check every item in the list to see if it matches your conditional. Since you only care that the count is "at least" a certain value, you could stop counting early if you get past 1. ThelengthCompare
method is useful for that kind of thing.
So here's roughly the same code, taking advantage of "views" to accomplish some better efficiency:
def hasAtLeastTwoZeroes(list: List[(String, Int)]) = {
list
.view // lets us call `.filter` without allocating a new List
.filter(_._2 == 0) // apply conditional: value is 0
.lengthCompare(1) > 0 // like saying `.length > 1`, but is O(1), not O(N)
}
theMapFromYourExample
.view // lets us call `collect` without allocating an intermediate collection
.collect { case (key, list) if hasAtLeastTwoZeroes(list) => key }
.toList // builds a List from the collected view
Update as pointed out by Luis in the comments, Scala 2.13 added the sizeIs
method, which has a more fluent interface than lengthCompare