Pattern matching against URL with question mark in Scala - scala

I am trying to extract few values from the URL consisting of question mark.
However, the below code doesn't work. Would you please help me in figuring out what went wrong?
val LibraryPattern = ".*/library/([A-Za-z0-9\\-]+)?book=([A-Za-z0-9\\-]+)".r
val url = "https://bookscollection.com/library/mylib?book=abc"
Try(new URL(url)) match {
case Success(url) =>
println("my url:"+url)
url.getPath match {
case LibraryPattern(libId, bookId) =>
println(libId)
println(bookId)
case _ =>
}
}

As few answer already pointed how to fix code example, I want to suggest another solution. Parsing URL with regex may be inefficient in terms of future readability, type safety and flexability of your codebase.
I want to suggest using scala-uri library or something similar.
With this library one can do url parsing as simple as:
import io.lemonlabs.uri.Url
val url = Url.parse("https://bookscollection.com/library/mylib?book=abc")
val lastPathPart = url.path.parts.last
// println(lastPathPart)
// res: String = "mylib"
val bookParam: Option[String] = url.query.param("book")
// println(bookParam)
// res: Option[String] = Some("abc")

The URL object has already parsed the URL for you. getPath returns everything before the ?, use getQuery to obtain the part after the ?:
val LibraryPattern = ".*/library/([A-Za-z0-9\\-]+)".r
val BookPattern = "book=([A-Za-z0-9\\-]+)".r
val url = "https://bookscollection.com/library/mylib?book=abc"
Try(new URL(url)) match {
case Success(url) =>
url.getPath match {
case LibraryPattern(libId) =>
url.getQuery match {
case BookPattern(bookId) =>
println(libId)
println(bookId)
}
}
}

? is a special character in Regex (it essentially makes the previous character/group optional). You'll need to escape it.
EDIT: url.getPath only returns /library/mylib, so you shouldn't use this if you want your Regex to match.
val LibraryPattern = ".*/library/([A-Za-z0-9\\-]+)\\?book=([A-Za-z0-9\\-]+)".r
val url = "https://bookscollection.com/library/mylib?book=abc"
Try(new URL(url)) match {
case Success(url) =>
println("my url:"+url)
url.toString match {
case LibraryPattern(libId, bookId) =>
println(libId)
println(bookId)
case _ =>
}
}

Related

Pattern matching to convert string to Long

I have a string that I need to convert into a long if it is not empty or blank. If it either can't be converted(it fails during conversion) or the string is empty or blank I need to run a secondary function. Right now I have a if statement but I would like to convert it to pattern matching if possible to make my code a little cleaner.
Heres what I have right now
val stringToConvert = "1234"
if (!stringToConvert.isBlank && !stringToConvert.isEmpty) {
try {
val converted = stringToConvert.toLong
doThingWithConvertedValue(converted)
}
catch {
case _: Throwable =>
Log.error("Encountered error while trying to convert string to long")
}
}
else {
doOtherThing()
}
Whats the best way to do this?
Try this:
import scala.util.{Try, Success, Failure}
val stringToConvert = "1234"
Try(stringToConvert.toLong) match {
case Success(value) => doThingWithConvertedValue(value)
case Failure(_) if stringToConvert.isBlank => doOtherThing()
case Failure(exception) =>
Log.error(s"Encounted error while trying to convert string to long: $exception")
}
Please note that isBlank already covers isEmpty case.
Considering the following functions:
def doThingWithConvertedValue(l: Long) = println (s"doThingWithConvertedValue($l)")
def doOtherThing() = println ("doOtherThing()")
Your code can be transferred to the following:
Try(stringToConvert).filter(stringToConvert => !stringToConvert.isBlank && stringToConvert.nonEmpty).fold(
sBlankOrEmpty => doOtherThing(),
nEmpty => Try(doThingWithConvertedValue(nEmpty.toLong)).getOrElse {
Log.error("Encountered error while trying to convert string to long")
}
)
Outputs:
"1234" => doThingWithConvertedValue(1234)
"notConvertable" => Encountered error while trying to convert string to long
"" => doOtherThing()
If you prefer using Pattern matching, you can do it as follows:
val stringToConvert = "123s"
stringToConvert match {
case sBlankOrEmpty if sBlankOrEmpty.isBlank || sBlankOrEmpty.isEmpty => doOtherThing()
case canBeLong if Try(canBeLong.toLong).isSuccess => doThingWithConvertedValue(canBeLong.toLong)
case _ => Log.error("Encountered error while trying to convert string to long")
}
Note:
doThingWithConvertedValue can throw an exception (which you didnt recover properly for that case in your main file)
.toLong is used twice O(n) Each, but if its fine for you i guess its pretty
readable
I would do something like this:
val stringToConvert = "1234"
Option(stringToConvert)
.map(_.trim)
.filter(_.isEmpty)
.fold(ifEmpty = doOtherThing() { str =>
trs
.toLongOption
.fold(ifEmpty = Log.error("Encounted error while trying to convert string to long"))
(doThingWithConvertedValue)
}
I'm going to suggest that you don't need a pattern match. Just fold() over a Try.
util.Try(stringToConvert.toLong)
.fold(exptn => {
//Log.error() and doOtherThing() go here depending on requirements
if (stringToConvert.isEmpty) ... //only when empty
else ... //only when non-empty
... //do this regardless of empty status
},doThingWithConvertedValue)
You can do:
val stringToConvert = "1234"
Try(doThingWithConvertedValue(stringToConvert.toLong)).getOrElse {
println("Encounted error while trying to convert string to long")
doOtherThing()
}
Code run at Scastie.

Scala - How to safely operate on a map element

I want to get an element from a mutable map and do an operation on it.
For example I want to change his name value (the element on the map will be with the new value)
and I want to return it in the end
to start I wrote a working code but it is very Java
var newAppKey: AppKey = null
val appKey = myMap(request.appKeyId)
if (appKey != null) {
newAppKey = appKey.copy(name = request.appKeyName)
myMap.put(appKey.name, newAppKey)
newAppKey
} else {
newAppKey = null
}
This code works but it very java.
I though about something like
val newAppKey = appIdToApp(request.appKeyId) match {
case: Some(appKey) => appKey.copy(name = request.appKeyName)
case: None => None{AppKey}
}
Which doesn't compile or updates the myMap object with the new value.
How do I improve it to scala concepts.
Simply:
val key = request.appKeyId
val newValueOpt = myMap.get(key).map(_.copy(name = request.appKeyName))
newValueOpt.foreach(myMap.update(key, _))
There are a couple of mistakes in your code.
case: Some(appKey) => appKey.copy(name = request.appKeyName)
This syntax for case is incorrect. It should be
case Some(appKey) => appKey.copy(name = request.appKeyName)
Also, the return type of your expression is currently Any (Scala equivalent of Object), because your success case returns an object of type (appKey's type) whereas the failure case returns a None, which is of type Option. To make things consistent, your success case should return
Some(appKey.copy(name = request.appKeyName))
While there are better ways to deal with Options than pattern matching, the corrected code would be
val newAppKey = appIdToApp(request.appKeyId) map (appKey =>
appKey.copy(name = request.appKeyName))

Cleanest way in Scala to avoid nested ifs when transforming collections and checking for error conditions in each step

I have some code for validating ip addresses that looks like the following:
sealed abstract class Result
case object Valid extends Result
case class Malformatted(val invalid: Iterable[IpConfig]) extends Result
case class Duplicates(val dups: Iterable[Inet4Address]) extends Result
case class Unavailable(val taken: Iterable[Inet4Address]) extends Result
def result(ipConfigs: Iterable[IpConfig]): Result = {
val invalidIpConfigs: Iterable[IpConfig] =
ipConfigs.filterNot(ipConfig => {
(isValidIpv4(ipConfig.address)
&& isValidIpv4(ipConfig.gateway))
})
if (!invalidIpConfigs.isEmpty) {
Malformatted(invalidIpConfigs)
} else {
val ipv4it: Iterable[Inet4Address] = ipConfigs.map { ipConfig =>
InetAddress.getByName(ipConfig.address).asInstanceOf[Inet4Address]
}
val dups = ipv4it.groupBy(identity).filter(_._2.size != 1).keys
if (!dups.isEmpty) {
Duplicates(dups)
} else {
val ipAvailability: Map[Inet4Address, Boolean] =
ipv4it.map(ip => (ip, isIpAvailable(ip)))
val taken: Iterable[Inet4Address] = ipAvailability.filter(!_._2).keys
if (!taken.isEmpty) {
Unavailable(taken)
} else {
Valid
}
}
}
}
I don't like the nested ifs because it makes the code less readable. Is there a nice way to linearize this code? In java, I might use return statements, but this is discouraged in scala.
I personally advocate using a match everywhere you can, as it in my opinion usually makes code very readable
def result(ipConfigs: Iterable[IpConfig]): Result =
ipConfigs.filterNot(ipc => isValidIpv4(ipc.address) && isValidIpv4(ipc.gateway)) match {
case Nil =>
val ipv4it = ipConfigs.map { ipc =>
InetAddress.getByName(ipc.address).asInstanceOf[Inet4Address]
}
ipv4it.groupBy(identity).filter(_._2.size != 1).keys match {
case Nil =>
val taken = ipv4it.map(ip => (ip, isIpAvailable(ip))).filter(!_._2).keys
if (taken.nonEmpty) Unavailable(taken) else Valid
case dups => Duplicates(dups)
}
case invalid => Malformatted(invalid)
}
Note that I've chosen to match on the else part first, since you generally go from specific to generic in matches, since Nil is a subclass of Iterable I put that as the first case, eliminating the need for an i if i.nonEmpty in the other case, since it would be a given if it didn't match Nil
Also a thing to note here, all your vals don't need the type explicitly defined, it significantly declutters the code if you write something like
val ipAvailability: Map[Inet4Address, Boolean] =
ipv4it.map(ip => (ip, isIpAvailable(ip)))
as simply
val ipAvailability = ipv4it.map(ip => (ip, isIpAvailable(ip)))
I've also taken the liberty of removing many one-off variables I didn't find remotely necessary, as all they did was add more lines to the code
A thing to note here about using match over nested ifs, is that is that it's easier to add a new case than it is to add a new else if 99% of the time, thereby making it more modular, and modularity is always a good thing.
Alternatively, as suggested by Nathaniel Ford, you can break it up into several smaller methods, in which case the above code would look like so:
def result(ipConfigs: Iterable[IpConfig]): Result =
ipConfigs.filterNot(ipc => isValidIpv4(ipc.address) && isValidIpv4(ipc.gateway)) match {
case Nil => wellFormatted(ipConfigs)
case i => Malformatted(i)
}
def wellFormatted(ipConfigs: Iterable[IpConfig]): Result = {
val ipv4it = ipConfigs.map(ipc => InetAddress.getByName(ipc.address).asInstanceOf[Inet4Address])
ipv4it.groupBy(identity).filter(_._2.size != 1).keys match {
case Nil => noDuplicates(ipv4it)
case dups => Duplicates(dups)
}
}
def noDuplicates(ipv4it: Iterable[IpConfig]): Result =
ipv4it.map(ip => (ip, isIpAvailable(ip))).filter(!_._2).keys match {
case Nil => Valid
case taken => Unavailable(taken)
}
This has the benefit of splitting it up into smaller more manageable chunks, while keeping to the FP ideal of having functions that only do one thing, but do that one thing well, rather than having god-methods that do everything.
Which style you prefer, of course is up to you.
This has some time now but I will add my 2 cents. The proper way to handle this is with Either. You can create a method like:
def checkErrors[T](errorList: Iterable[T], onError: Result) : Either[Result, Unit] = if(errorList.isEmpty) Right() else Left(onError)
so you can use for comprehension syntax
val invalidIpConfigs = getFormatErrors(ipConfigs)
val result = for {
_ <- checkErrors(invalidIpConfigs, Malformatted(invalidIpConfigs))
dups = getDuplicates(ipConfigs)
_ <- checkErrors(dups, Duplicates(dups))
taken = getAvailability(ipConfigs)
_ <- checkErrors(taken, Unavailable(taken))
} yield Valid
If you don't want to return an Either use
result.fold(l => l, r => r)
In case of the check methods uses Futures (could be the case for getAvailability, for example), you can use cats library to be able of use it in a clean way: https://typelevel.org/cats/datatypes/eithert.html
I think it's pretty readable and I wouldn't try to improve it from there, except that !isEmpty equals to nonEmpty.

Scala: parsing an API parameter

My API currently take an optional parameter named gamedate. It is passed in as a string at which time I later parse it to a Date object using some utility code. The code looks like this:
val gdate:Option[String] = params.get("gamedate")
val res = gdate match {
case Some(s) => {
val date:Option[DateTime] = gdate map { MyDateTime.parseDate _ }
val dateOrDefault:DateTime = date.getOrElse((new DateTime).withTime(0, 0, 0, 0))
NBAScoreboard.findByDate(dateOrDefault)
}
case None => NBAScoreboard.getToday
}
This works just fine. Now what I'm trying to solve is I'm allowing multiple gamedates get passed in via a comma delimited list. Originally you can pass a parameter like this:
gamedate=20131211
now I want to allow that OR:
gamedate=20131211,20131212
That requires modifying the code above to try to split the comma delimited string and parse each value into a Date and change the interface to findByDate to accept a Seq[DateTime] vs just DateTime. I tried running something like this, but apparently it's not the way to go about it:
val res = gdates match {
case Some(s) => {
val dates:Option[Seq[DateTime]] = gdates map { _.split(",").distinct.map(MyDateTime.parseDate _ )}
val datesOrDefault:Seq[DateTime] = dates map { _.getOrElse((new DateTime).withTime(0, 0, 0, 0))}
NBAScoreboard.findByDates(datesOrDefault)
}
case None => NBAScoreboard.getToday
}
What's the best way to convert my first set of code to handle this use case? I'm probably fairly close in the second code example I provided, but I'm just not hitting it right.
You mixed up the containers. The map you call on dates unpackes the Option so the getOrElse is applied to a list.
val res = gdates match {
case Some(s) =>
val dates = gdates.map(_.split(",").distinct.map(MyDateTime.parseDate _ ))
val datesOrDefault = dates.getOrElse(Array((new DateTime).withTime(0, 0, 0, 0)))
NBAScoreboard.findByDates(datesOrDefault)
case _ =>
NBAScoreboard.getToday
}
This should work.

How to match a string on a prefix and get the rest?

I can write the code like this:
str match {
case s if s.startsWith("!!!") => s.stripPrefix("!!!")
case _ =>
}
But I want to know is there any better solutions. For example:
str match {
case "!!!" + rest => rest
case _ =>
}
val r = """^!!!(.*)""".r
val r(suffix) = "!!!rest of string"
So suffix will be populated with rest of string, or a scala.MatchError gets thrown.
A different variant would be:
val r = """^(!!!){0,1}(.*)""".r
val r(prefix,suffix) = ...
And prefix will either match the !!! or be null. e.g.
(prefix, suffix) match {
case(null, s) => "No prefix"
case _ => "Prefix"
}
The above is a little more complex than you might need, but it's worth looking at the power of Scala's regexp integration.
Starting Scala 2.13, it's now possible to pattern match a String by unapplying a string interpolator:
"!!!hello" match {
case s"!!!$rest" => rest
case _ => "oups"
}
// "hello"
If it's the sort of thing you do often, it's probably worth creating an extractor
object BangBangBangString{
def unapply(str:String):Option[String]= {
str match {
case s if s.startsWith("!!!") => Some(s.stripPrefix("!!!"))
case _ => None
}
}
}
Then you can use the extractor as follows
str match{
case BangBangBangString(rest) => println(rest)
case _ => println("Doesn't start with !!!")
}
or even
for(BangBangBangString(rest)<-myStringList){
println("rest")
}
Good question !
Even i was trying a lot to find out the answer.
Here is a good link where I found the answer
object _04MatchExpression_PatternGuards {
def main(args: Array[String]): Unit = {
val url: String = "Jan";
val monthType = url match {
case url if url.endsWith(".org") => "Educational Websites";
case url if url.endsWith(".com") => "Commercial Websites";
case url if url.endsWith(".co.in") => "Indian Websites"
case _ => "Unknow Input";
}
}
}