I'm writing extractor object for functions expressions. Here is how it looks like:
object FunctionTemplate2 {
private final val pattern = Pattern.compile("^(.+?)\\((.+?)\\,(.+?)\\)")
//e.g. foo(1, "str_arg")
def unapply(functionCallExpression: String): Option[(String, String, String)] = {
//parse expression and extract
}
}
And I can extract as follows:
"foo(1, \"str_arg\")" match {
case FunctionTemplate2("foo", first, second) =>
println(s"$first,$second")
}
But this is not as cute as it could be. I would like to have something like that:
case FunctionTemplate2("foo")(first, second) =>
println(s"$first,$second")
Like curried extractor. So I tried this:
case class Function2Extractor(fooName: String){
private final val pattern = Pattern.compile("^(.+?)\\((.+?)\\,(.+?)\\)")
println("creating")
def unapply(functionCallExpression: String): Option[(String, String, String)] =
//parse and extract as before
}
But it did not work:
"foo(1, \"str_arg\")" match {
case Function2Extractor("foo")(first, second) =>
println(s"$first,$second")
}
Is there a way to do this in Scala?
You can simply it by using some utilities in Scala toolset
Notice how pattern is used in match case.
Scala REPL
scala> val pattern = "^(.+?)\\((.+?)\\,(.+?)\\)".r
pattern: scala.util.matching.Regex = ^(.+?)\((.+?)\,(.+?)\)
scala> "foo(1, \"str_arg\")" match { case pattern(x, y, z) => println(s"$x $y $z")}
foo 1 "str_arg"
Related
I've got various case classes with different fields inherit some trait. All are mixed in a List. What is the way to collect (or group by) specific field's values?
sealed trait Template
object Template {
case class TemplateA(field: String) extends Template
case class TemplateB extends Template
}
object Runner {
def main(args: String*) {
val list = List(TemplateA("abc"), TemplateB, Template("cde"))
// need to output something like "abc;1", "cde;1"
}
}
Totally agree with #LuisMiguel, just to show one way of doing this, here's what I can think of:
trait Template { val field: Option[String] }
case class TemplateA(field: Option[String]) extends Template
case class TemplateB() extends Template { override val field: Option[String] = None }
val list: List[Template] = List(
TemplateA(Some("abc")),
TemplateB(),
TemplateA(Some("cde"))
)
list.collect {
case template if template.field.nonEmpty =>
template.field.get
}.groupMapReduce(identity)(_ => 1)(_ + _)
// res8: Map[String, Int] = Map("abc" -> 1, "cde" -> 1)
Or if you want to get rid of the Optional argument when instantiating TemplateA instances, you can also do this:
case class TemplateA(private val value: String) extends Template {
override val field: Option[String] = Option(value)
}
val list: List[Template] = List(TemplateA("abc"), TemplateB(), TemplateA("cde"))
As #DmytroMitin mentioned, we can do a bit of refactoring to avoid using ifs in our collect function, I'd rather use some sort of unapply function, that can extract the field value of TemplateA instances:
object Template { // or any name as you wish
def unapply(t: Template): Option[String] = t match {
case TemplateA(Some(value)) => Option(value)
case _ => None
}
}
And then, we can use pattern matching:
list.collect {
case Template(field) => field
}.groupMapReduce(identity)(_ => 1)(_ + _)
I need to change the the definition of the function selectSeq which should now work with old call and new a call which slightly differ.
This code is a part of my project.
def selectSeq(option0: Option[String] = Some("- Choose -")): Future[Seq[(String, String)]] = db.run {
tableQuery().result.map { locations =>
(option0 match {
case Some(option0) => Seq(("0", option0))
case None => Seq()
})
}
}
Now, I call this function by
selectSeq(Some("any"))
I need to change this function to also accept call the
selectSeq(Some("","any"))
and obtain Seq(("", option0)). I have tried already the following definition
def selectSeq(option0: Option[(String,String)] = Some("","- Choose-")
which accepts call
selectSeq(Some("","any")
but it doesn't work with an old call. Please, how to fix it?
Consider overloading the method by providing a definition of a method with the same name selectSeq but different signature like so:
def selectSeq(option0: Option[(String, String)] = Some("","- Choose-")): Future[Seq[(String, String)]] = db.run {
tableQuery().result.map { locations =>
(option0 match {
...
})
}
}
def selectSeq(option0: Option[String] = Some("- Choose -")): Future[Seq[(String, String)]] = db.run {
...
Compiler will be able to figure out which definition to call, because despite the two methods having the same name, they have different types, namely
Option[(String, String)] => Future[Seq[(String, String)]]
vs
Option[String] => Future[Seq[(String, String)]]
Applying the comment, to avoid overloading, consider defining your own ADT and then pattern matching like so
sealed trait Foo
case class Bar(v: String) extends Foo
case class Qux(v: (String,String)) extends Foo
def selectSeq(foo: Option[Foo] = Some(Bar("- Choose -"))): Future[Seq[(String, String)]] = db.run {
tableQuery().result.map { locations =>
(option0 match {
case Some(Bar(v)) => ...
case Some(Qux((a,b)) => ...
...
})
}
}
}
selectSeq(Some(Bar("any")))
selectSeq(Some(Qux("","any")))
I want to make a string representation of case class
case class Person(s:Student)
case class Student(name:String,value:String){
def toMyString(flag:Boolean):String= if(flag)s"${name} =${value}" else s"Hello${name}+${value}"
}
Person(Student("abc","def")).productIterator
I want to call toMyString from productIterator.
My actual use case has many elements in case class .
Just ask each iterator element if it is the target type.
Person(Student("abc","def")).productIterator.collect{
case x:Student => x.toMyString(true)
}
// res0: Iterator[String] = non-empty iterator (String: "abc=def")
There are 2 solutions for generating the String for the element:
1.Create a trait for elements that need to implement toMyString and the elements that need to generate string extends from it, and implement it, like:
trait MyString {
def toMyString(flag: Boolean): String
}
case class Student(name: String, value: String) extends MyString{
def toMyString(flag: Boolean): String = if (flag) s"${name} =${value}" else s"Hello${name}+${value}"
}
val result: List[String] = Person(Student("abc","def")).productIterator.collect{
case x: MyString => x.toMyString(true)
}
2. Use the reflection for this by get toMyString method by method name, this is a tricky way and not type safe, should think more about this, like:
val student = Student("abc", "def")
val res: Iterator[String] = Person(student).productIterator.map(i => {
val method = i.getClass.getDeclaredMethods.filter(i => i.getName.equals("toMyString")).headOption
method match {
case Some(m) =>
m.invoke(i, true.asInstanceOf[AnyRef])
case None =>
null
}
})
We need Iterator[Student] instead of Iterator[Any]
Person(Student("abc","def")).productIterator.asInstanceOf[Iterator[Student]]
What's the best way to create the complement for an f-style string interpolation, i.e. a way to parse the arguments/numbers in a formatted string?
E.g.
val format = "frame%04d.png"
val i = 123
val out = f"frame$i%04d.png"
assert(out == "frame0123.png")
def parse(s: String, pat: String): Int = ???
val j = parse(out, format)
assert(j == 123)
I know I can manually construct a reg-exp, I just wonder if there is a general approach with f-style interpolated strings.
I was hoping for something simple like:
val f"frame$j%04d.png" = out
But
error: macro method f is not a case class, nor does it have an unapply/unapplySeq member
I found this nice post about using interpolators as pattern matchers so this is starting point for you:
object StringMatcher {
val ZeroFillDec = "%0(\\d+)d(.*)".r
val SimpleDec = "%(\\d+)d(.*)".r
val String = "%s(.*)".r
def patternize(part: String) = part match {
case ZeroFillDec(len, rest) => f"(\\d{${len.toInt}})$rest"
case SimpleDec(len, rest) => f"(\\d{1,${len.toInt}})$rest"
case String(rest) => f"(.*)$rest"
case rest => f"(.*)$rest"
}
}
implicit class StringMatcher(sc: StringContext) {
import StringMatcher.patternize
object pat {
def unapplySeq(s: String): Option[Seq[String]] = {
val re = (sc.parts.head ++ (sc.parts.tail map patternize)).mkString.r
re.unapplySeq(s)
}
}
}
Using that, code
val out = "frame0123_023.png"
val pat"frame$j%04d_$i%03d.png" = out
assigns
j: String = 0123
i: String = 023
With Scala's pattern matching I would like to confirm not only that two Strings are equal but for example, whether a String starts with, ends, or is contained in another etc.
I experimented with case classes and extractor objects, neither giving me a concise solution. So the solution I came up with looks like the following:
class StrMatches(private val str: Option[String]) {
def ^(prefix: String) = str.exists(_.startsWith(prefix))
def §(suffix: String) = str.exists(_.endsWith(suffix))
def %(infix: String) = str.exists(_.contains(infix))
def ~(approx: String) = str.exists(_.equalsIgnoreCase(approx))
def /(regex: scala.util.matching.Regex) = str.collect({ case regex() => true }).isDefined
def °(len: Int) = str.exists(_.length == len)
def °°(len: (Int, Int)) = str.exists(a => a.length >= len._1 && a.length <= len._2)
def `\\s*` = str.exists(_.trim.isEmpty)
override def toString = str.mkString
}
object StrMatches {
implicit def apply(x: Str) = new StrMatches(x)
def unapply(x: StrMatches) = x.str
implicit def unwrap(x: StrMatches) = x.toString
}
A client using the StrMatches class could look like the following:
object TestApp extends App {
val str = "foobar"
val strMatches = StrMatches(str)
if (strMatches ^ "foo") {
println(strMatches)
}
if (strMatches § "bar") {
println(strMatches)
}
if (strMatches % "ob") {
println(strMatches)
}
}
As opposed to writing:
object TestApp extends App {
val str: String = null // Just as an illustration for Scala interfacing Java.
if (str != null) {
if (str.startsWith("foo")) {
println(str)
}
if (strMatches.endsWith("bar")) {
println(str)
}
if (strMatches.contains("ob")) {
println(strMatches)
}
}
}
With what kind of solutions would you come up with?
You could use regular expressions. Then you could use pattern matching (which I think was the original intent of your question):
object TestApp extends App {
val str = "foobar"
val StartsWithFooRE = """^foo.*""".r
val EndsWithBarRE = """.*bar$""".r
val ContainsBoRE = """.*bo.*""".r
str match {
case StartsWithFooRE() => println(str)
case EndsWithBarRE() => println(str)
case ContainsBoRE() => println(str)
case _ =>
}
}
To make this more convenient, you could define an object with factory methods to construct the regular expressions. However, due to how pattern matching works, you'll still have to define the expressions outside of the match:
import scala.util.matching.Regex
object RegexFactory {
def startsWith(str: String) = new Regex("^%s.*" format str)
def endsWith(str: String) = new Regex(".*%s$" format str)
def contains(str: String) = new Regex(".*%s.*" format str)
}
object TestApp extends App {
val str = "foobar"
import RegexFactory._
val StartsWithFooRE = startsWith("foo")
val EndsWithBarRE = endsWith("bar")
val ContainsBoRE = contains("bo")
str match {
case StartsWithFooRE() => println(str)
case EndsWithBarRE() => println(str)
case ContainsBoRE() => println(str)
case _ =>
}
}