Scala large listing of cases in pattern matching - scala

For a long listing of cases which return a value within a limited domain, how to reduce the otherwise growing number of case declarations ? For instance consider
"abc" match {
case "a" => 1
case "ab" => 1
case "aw" => 2
case "hs" => 2
case "abc" => 1
case _ => 0
}
Tried a Map[Set[String],Int] where
val matches = Map( Set("a","ab","abc") -> 1, Set("aw","hs") -> 2 )
and defined
def getMatch(key: String, m: Map[Set[String],Int]) = {
val res = m.keys.collectFirst{ case s if s(key) => m(s) }
res.getOrElse(0)
}
Are there simpler and/or more efficient approaches to this ?

You can group your cases:
"abc" match {
case "a" | "ab" | "abc" => 1
case "aw" | "hs" => 2
case _ => 0
}

You can create your own matchers like this:
class InSet[T](set: Set[T]) {
def unapply(t: T) = set.find(t)
}
val set1 = new InSet(Set("a","ab","abc"))
val set2 = new InSet(Set("aw","hs"))
"aw" match {
case set1(a) => "1, " + a
case set2(a) => "2, " + a
case _ => "3"
}
The good thing about this is that it makes it easy to create and apply very different matchers.

You can shift the complexity a bit by doing this:
val m = matches.flatMap { case (xs,i) => xs.map(_ -> i) }.withDefaultValue(0)
m("abc") // 1
m("z") // 0
Thus avoiding the need to call things through your getMatch function. Possibly faster too since you upfront the work instead of iterating through the keys every time you need call getMatch.

Related

scala using calculations from pattern matching's guard (if) in body

I'm using pattern matching in scala a lot. Many times I need to do some calculations in guard part and sometimes they are pretty expensive. Is there any way to bind calculated values to separate value?
//i wan't to use result of prettyExpensiveFunc in body safely
people.collect {
case ...
case Some(Right((x, y))) if prettyExpensiveFunc(x, y) > 0 => prettyExpensiveFunc(x)
}
//ideally something like that could be helpful, but it doesn't compile:
people.collect {
case ...
case Some(Right((x, y))) if {val z = prettyExpensiveFunc(x, y); y > 0} => z
}
//this sollution works but it isn't safe for some `Seq` types and is risky when more cases are used.
var cache:Int = 0
people.collect {
case ...
case Some(Right((x, y))) if {cache = prettyExpensiveFunc(x, y); cache > 0} => cache
}
Is there any better solution?
ps: Example is simplified and I don't expect anwers that shows that I don't need pattern matching here.
You can use cats.Eval to make expensive calculations lazy and memoizable, create Evals using .map and extract .value (calculated at most once - if needed) in .collect
values.map { value =>
val expensiveCheck1 = Eval.later { prettyExpensiveFunc(value) }
val expensiveCheck2 = Eval.later { anotherExpensiveFunc(value) }
(value, expensiveCheck1, expensiveCheck2)
}.collect {
case (value, lazyResult1, _) if lazyResult1.value > 0 => ...
case (value, _, lazyResult2) if lazyResult2.value > 0 => ...
case (value, lazyResult1, lazyResult2) if lazyResult1.value > lazyResult2.value => ...
...
}
I don't see a way of doing what you want without creating some implementation of lazy evaluation, and if you have to use one, you might as well use existing one instead of rolling one yourself.
EDIT. Just in case you haven't noticed - you aren't losing the ability to pattern match by using tuple here:
values.map {
// originial value -> lazily evaluated memoized expensive calculation
case a # Some(Right((x, y)) => a -> Some(Eval.later(prettyExpensiveFunc(x, y)))
case a => a -> None
}.collect {
// match type and calculation
...
case (Some(Right((x, y))), Some(lazyResult)) if lazyResult.value > 0 => ...
...
}
Why not run the function first for every element and then work with a tuple?
Seq(1,2,3,4,5).map(e => (e, prettyExpensiveFunc(e))).collect {
case ...
case (x, y) if y => y
}
I tried own matchers and effect is somehow OK, but not perfect. My matcher is untyped, and it is bit ugly to make it fully typed.
class Matcher[T,E](f:PartialFunction[T, E]) {
def unapply(z: T): Option[E] = if (f.isDefinedAt(z)) Some(f(z)) else None
}
def newMatcherAny[E](f:PartialFunction[Any, E]) = new Matcher(f)
def newMatcher[T,E](f:PartialFunction[T, E]) = new Matcher(f)
def prettyExpensiveFunc(x:Int) = {println(s"-- prettyExpensiveFunc($x)"); x%2+x*x}
val x = Seq(
Some(Right(22)),
Some(Right(10)),
Some(Left("Oh now")),
None
)
val PersonAgeRank = newMatcherAny { case Some(Right(x:Int)) => (x, prettyExpensiveFunc(x)) }
x.collect {
case PersonAgeRank(age, rank) if rank > 100 => println("age:"+age + " rank:" + rank)
}
https://scalafiddle.io/sf/hFbcAqH/3

How to store the intermediate results of a scala match expression into a list?

I have a list of integer as input and i would like to store the intermediate result of every comparison in a scala match expression into a ListBuffer.How can i achieve that?
Below is the code that i have written.Currently i am only able to store the result of last comparison not the intermediate ones.
import scala.collection.mutable.ListBuffer
object HelloWorld {
def main(args: Array[String]) {
var stor = ListBuffer[String]()
val inpLst = List(1, 2, 2, 2, 1)
for (i <- inpLst) {
stor = i match {
case 1 => "ok"
case 2 => "notok"
}
}
println(stor)
}
}
This is the output that i want.
opList = List("ok","notok","notok',"notok","ok")
#Mario Galic's answer is quite good way of approach to your problem, if you still insist on writing in your own way, below is the way to do it.
import scala.collection.mutable.ListBuffer
val inpLst = List(1,2,2,2,1)
val stor = ListBuffer.empty[String]
for (i <- inpLst) {
val str = i match {
case 1 => "ok"
case 2 => "notOk"
}
stor += str
}
println(stor)
This outputs below:
ListBuffer(ok, notOk, notOk, notOk, ok)
Instead of using ListBuffer consider mapping over a List like so
l.map {
case 1 => "ok"
case 2 => "notok"
case _ => "unknown"
}
which outputs
res0: List[String] = List(ok, notok, notok, notok, ok)
Applying Krzysztof's suggestion, we could omit case _ => "unknown" if we use collect like so
List(1,2,3).collect {
case 1 => "ok"
case 2 => "notok"
}
which outputs
res1: List[String] = List(ok, notok)
If you only need to convert list into "ok" or "notok" values you can use map function:
val inpLst = List(1, 2, 2, 2, 1)
inpLst.map{
case 1 => "ok"
case 2 => "notok"
}
else if you need to have intermediate container in each time of processing list, you can use foldLeft:
inpLst.foldLeft(List.empty[String]){
(buffer: List[String], i: Int) =>
buffer ++ (i match {
case 1 => List("ok")
case 2 => List("notok")
})
}
buffer will contain result of matching function on each previous iteration.

Find person and immediate neighbours in Seq[Person]

Given a Seq[Person], which contains 1-n Persons (and the minimum 1 Person beeing "Tom"), what is the easiest approach to find a Person with name "Tom" as well as the Person right before Tome and the Person right after Tom?
More detailed explanation:
case class Person(name:String)
The list of persons can be arbitrarily long, but will have at least one entry, which must be "Tom". So those lists can be a valid case:
val caseOne = Seq(Person("Tom"), Person("Mike"), Person("Dude"),Person("Frank"))
val caseTwo = Seq(Person("Mike"), Person("Tom"), Person("Dude"),Person("Frank"))
val caseThree = Seq(Person("Tom"))
val caseFour = Seq(Person("Mike"), Person("Tom"))
You get the idea. Since I already have "Tom", the task is to get his left neighbour (if it exists), and the right neighbour (if it exists).
What is the most efficient way to achieve to do this in scala?
My current approach:
var result:Tuple2[Option[Person], Option[Person]] = (None,None)
for (i <- persons.indices)
{
persons(i).name match
{
case "Tom" if i > 0 && i < persons.size-1 => result = (Some(persons(i-1)), Some(persons(i+1))) // (...), left, `Tom`, right, (...)
case "Tom" if i > 0 => result = (Some(persons(i-1)), None) // (...), left, `Tom`
case "Tom" if i < persons.size-1 => result = (Some(persons(i-1)), None) // `Tom`, right, (...)
case "Tom" => result = (None, None) // `Tom`
}
}
Just doesn't feel like I am doing it the scala way.
Solution by Mukesh prajapati:
val arrayPersons = persons.toArray
val index = arrayPersons.indexOf(Person("Tom"))
if (index >= 0)
result = (arrayPersons.lift(index-1), arrayPersons.lift(index+1))
Pretty short, seems to cover all cases.
Solution by anuj saxena
result = persons.sliding(3).foldLeft((Option.empty[Person], Option.empty[Person]))
{
case ((Some(prev), Some(next)), _) => (Some(prev), Some(next))
case (_, prev :: Person(`name`) :: next :: _) => (Some(prev), Some(next))
case (_, _ :: prev :: Person(`name`) :: _) => (Some(prev), None)
case (_, Person(`name`) :: next :: _) => (None, Some(next))
case (neighbours, _) => neighbours
}
First find out index where "Tom" is present, then use "lift". "lift" turns partial function into a plain function returning an Option result:
index = persons.indexOf("Tom")
doSomethingWith(persons.lift(index-1), persons.lift(index+1))
A rule of thumb: we should never access the content of a list / seq using indexes as it is prone to errors (like IndexNotFoundException).
If we want to use indexes, we better use Array as it provides us random access.
So to the current solution, here is my code to find prev and next element of a certain data in a Seq or List:
def findNeighbours(name: String, persons: Seq[Person]): Option[(Person, Person)] = {
persons.sliding(3).flatMap{
case prev :: person :: next :: Nil if person.name == name => Some(prev, next)
case _ => None
}.toList.headOption
}
Here the return type is in Option because there is a possibility that we may not find it here (in case of only one person is in the list or the required person is not in the list).
This code will pick the pair on the first occurrence of the person provided in the parameter.
If you have a probability that there might be several occurrences for the provided person, remove the headOption in the last line of the function findNeighbours. Then it will return a List of tuples.
Update
If Person is a case class then we can use deep match like this:
def findNeighbours(name: String, persons: Seq[Person]): Option[(Person, Person)] = {
persons.sliding(3).flatMap{
case prev :: Person(`name`) :: next :: Nil => Some(prev, next)
case _ => None
}.toList.headOption
}
For your solution need to add more cases to it (cchanged it to use foldleft in case of a single answer):
def findNeighboursV2(name: String, persons: Seq[Person]): (Option[Person], Option[Person]) = {
persons.sliding(3).foldLeft((Option.empty[Person], Option.empty[Person])){
case ((Some(prev), Some(next)), _) => (Some(prev), Some(next))
case (_, prev :: Person(`name`) :: next :: _) => (Some(prev), Some(next))
case (_, _ :: prev :: Person(`name`) :: _) => (Some(prev), None)
case (_, Person(`name`) :: next :: _) => (None, Some(next))
case (neighbours, _) => neighbours
}
}
You can use sliding function:
persons: Seq[Person] = initializePersons()
persons.sliding(size = 3).find { itr =>
if (itr(1).name = "Tom") {
val before = itr(0)
val middle = itr(1)
val after = itr(2)
}
}
If you know that there will be only one instance of "Tom" in your Seq use indexOf instead of looping by hand:
tomIndex = persons.indexOf("Tom")
doSomethingWith(persons(tomIndex-1), persons(tomIndex+1))
// Start writing your ScalaFiddle code here
case class Person(name: String)
val persons1 = Seq(Person("Martin"),Person("John"),Person("Tom"),Person("Jack"),Person("Mary"))
val persons2 = Seq(Person("Martin"),Person("John"),Person("Tom"))
val persons3 = Seq(Person("Tom"),Person("Jack"),Person("Mary"))
val persons4 = Seq(Person("Tom"))
def f(persons:Seq[Person]) =
persons
.sliding(3)
.filter(_.contains(Person("Tom")))
.maxBy {
case _ :: Person("Tom") :: _ => 1
case _ => 0
}
.toList
.take(persons.indexOf(Person("Tom")) + 2) // In the case where "Tom" is first, drop the last person
.drop(persons.indexOf(Person("Tom")) - 1) // In the case where "Tom" is last, drop the first person
println(f(persons1)) // List(Person(John), Person(Tom), Person(Jack))
println(f(persons2)) // List(Person(John), Person(Tom))
println(f(persons3)) // List(Person(Tom), Person(Jack))
println(f(persons4)) // List(Person(Tom))
Scalafiddle

Scala Stream/Iterator that Generates Excel Column Names?

I would like a Scala Stream/Iterator that generates Excel column names.
e.g. the first would be 'A' second would be 'B' and onwards to 'AA' and beyond.
I have a function (shown below) that does it from an index but it seems wasteful to generate from an index each time when all I'll ever be doing is generating them in order. In practice this isn't a problem so I am fine using this method but just thought I would ask to see if anyone has anything nicer.
val charArray = ('A' to 'Z').toArray
def indexToExcelColumnName(i:Int):String = {
if (i < 0) {
""
} else {
indexToExcelColumnName((i / 26) - 1) + charArray(i % 26)
}
}
Something like that?
class ExcelColumnIterator extends Iterator[String]{
private var currentColumnName = "A"
private def nextColumn(str: String):String = str.last match {
case 'Z' if str.length == 1 => "AA"
case 'Z' => nextColumn(str.init) + 'A'
case c => str.init + (c+1).toChar
}
override def hasNext = true
override def next() = {
val t = currentColumnName
currentColumnName = nextColumn(currentColumnName)
t
}
}
First I'd write something generating names of a fixed size.
val namesOfLength: Int => Iterator[String] = {
case 1 => ('A' to 'Z').iterator.map(_.toString)
case n => ('A' to 'Z').iterator.flatMap(a => namesOfLength(n-1).map(a + _))
}
or
def namesOfLength(n: Int) =
(1 until n).foldLeft[Iterable[String]](('A' to 'Z').view.map(_.toString)) {
case (it, _) => ('A' to 'Z').view.flatMap(a => it.map(a + _))
}
Then chain them together.
Iterator.iterate(1)(_ + 1).flatMap(namesOfLength).take(100).toStream.force
Here's a one-liner solution:
Stream.iterate(List(""))(_.flatMap(s => ('A' to 'Z').map(s + _)))
.flatten.tail
If you'd prefer to get an Iterator out, substitute Iterator.iterate for Stream.iterate and drop(1) for tail.
And here's an alternate solution you might find amusing:
Stream.from(0)
.map(n => Integer.toString(n, 36))
.map(_.toUpperCase)
.filterNot(_.exists(_.isDigit))
😜

Pattern Matching Pass Through in Scala

I want to do something like this:
val a = v match {
case 1 => 1
case 2 if (condition) => logging
case 2 if (other conditions) => 3
case 2 if (more conditions) => 4
case _ => 5
}
I want this to return just log for first case 2, but fall through otherwise to see what gets returned
edit: updated
Scala's case matching doesn't "fall through", but if I understand correctly, this will do what you want.
val a = v match {
case 1 => 1
case 2 => {
logging // I'm assuming `logging` is some Unit
if(some condition) 3
else if(some other condition) 4
else 5
}
case _ => 5
}
Something like this?
If v == 2, a will be assigned logging otherwise a will be assigned the value of v
val a = v match {
case 2 => logging
case _ => v
}