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.
Related
I have the following implementation:
val dateFormats = Seq("dd/MM/yyyy", "dd.MM.yyyy")
implicit def dateTimeCSVConverter: CsvFieldReader[DateTime] = (s: String) => Try {
val elem = dateFormats.map {
format =>
try {
Some(DateTimeFormat.forPattern(format).parseDateTime(s))
} catch {
case _: IllegalArgumentException =>
None
}
}.collectFirst {
case e if e.isDefined => e.get
}
if (elem.isDefined)
elem.get
else
throw new IllegalArgumentException(s"Unable to parse DateTime $s")
}
So basically what I'm doing is that, I'm running over my Seq and trying to parse the DateTime with different formats. I then collect the first one that succeeds and if not I throw the Exception back.
I'm not completely satisfied with the code. Is there a better way to make it simpler? I need the exception message passed on to the caller.
The one problem with your code is it tries all patterns no matter if date was already parsed. You could use lazy collection, like Stream to solve this problem:
def dateTimeCSVConverter(s: String) = Stream("dd/MM/yyyy", "dd.MM.yyyy")
.map(f => Try(DateTimeFormat.forPattern(format).parseDateTime(s))
.dropWhile(_.isFailure)
.headOption
Even better is the solution proposed by jwvh with find (you don't have to call headOption):
def dateTimeCSVConverter(s: String) = Stream("dd/MM/yyyy", "dd.MM.yyyy")
.map(f => Try(DateTimeFormat.forPattern(format).parseDateTime(s))
.find(_.isSuccess)
It returns None if none of patterns matched. If you want to throw exception on that case, you can uwrap option with getOrElse:
...
.dropWhile(_.isFailure)
.headOption
.getOrElse(throw new IllegalArgumentException(s"Unable to parse DateTime $s"))
The important thing is, that when any validation succeedes, it won't go further but will return parsed date right away.
This is a possible solution that iterates through all the options
val dateFormats = Seq("dd/MM/yyyy", "dd.MM.yyyy")
val dates = Vector("01/01/2019", "01.01.2019", "01-01-2019")
dates.foreach(s => {
val d: Option[Try[DateTime]] = dateFormats
.map(format => Try(DateTimeFormat.forPattern(format).parseDateTime(s)))
.filter(_.isSuccess)
.headOption
d match {
case Some(d) => println(d.toString)
case _ => throw new IllegalArgumentException("foo")
}
})
This is an alternative solution that returns the first successful conversion, if any
val dateFormats = Seq("dd/MM/yyyy", "dd.MM.yyyy")
val dates = Vector("01/01/2019", "01.01.2019", "01-01-2019")
dates.foreach(s => {
dateFormats.find(format => Try(DateTimeFormat.forPattern(format).parseDateTime(s)).isSuccess) match {
case Some(format) => println(DateTimeFormat.forPattern(format).parseDateTime(s))
case _ => throw new IllegalArgumentException("foo")
}
})
I made it sweet like this now! I like this a lot better! Use this if you want to collect all the successes and all the failures. Note that, this might be a bit in-efficient when you need to break out of the loop as soon as you find one success!
implicit def dateTimeCSVConverter: CsvFieldReader[DateTime] = (s: String) => Try {
val (successes, failures) = dateFormats.map {
case format => Try(DateTimeFormat.forPattern(format).parseDateTime(s))
}.partition(_.isSuccess)
if (successes.nonEmpty)
successes.head.get
else
failures.head.get
}
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 _ =>
}
}
Given a list of inputs that could be valid or invalid, is there a nice way to transform the list but to fail given one or more invalid inputs and, if necessary, to return information about those invalid inputs? I have something like this, but it feels very inelegant.
def processInput(inputList: List[Input]): Try[List[Output]] = {
inputList map { input =>
if (isValid(input)) Left(Output(input))
else Right(input)
} partition { result =>
result.isLeft
} match {
case (valids, Nil) =>
val outputList = valids map { case Left(output) => output }
Success(outputList)
case (_, invalids) =>
val errList = invalids map { case Right(invalid) => invalid }
Failure(new Throwable(s"The following inputs were invalid: ${errList.mkString(",")}"))
}
}
Is there a better way to do this?
I think you can simplify your current solution quite a bit with standard scala:
def processInput(inputList: List[Input]): Try[List[Output]] =
inputList.partition(isValid) match {
case (valids, Nil) => Success(valids.map(Output))
case (_, invalids) => Failure(new Throwable(s"The following inputs were invalid: ${invalids.mkString(",")}"))
}
Or, you can have a quite elegant solution with scalactic's Or.
import org.scalactic._
def processInputs(inputList: List[Input]): List[Output] Or List[Input] =
inputList.partition(isValid) match {
case (valid, Nil) => Good(valid.map(Output))
case (_, invalid) => Bad(invalid)
}
The result is of type org.scalactic.Or, which you then have to match to Good or Bad. This approach is more useful if you want the list of invalid inputs, you can match it out of Bad.
scalaz's validation is designed exactly for this. Try reading the tale of three nightclubs for how this would work, but the body of your function would probably end up just consisting of something like:
def processInput(inputList: List[Input]): Validation[List[Output]] = {
inputList foldMap { input =>
if (isValid(input)) Failure(Output(input))
else Success(List(input))
}
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";
}
}
}
I'm still a Scala noob, and this confuses me:
import java.util.regex._
object NumberMatcher {
def apply(x:String):Boolean = {
val pat = Pattern.compile("\\d+")
val matcher = pat.matcher(x)
return matcher.find
}
def unapply(x:String):Option[String] = {
val pat = Pattern.compile("\\d+")
val matcher = pat.matcher(x)
if(matcher.find) {
return Some(matcher.group())
}
None
}
}
object x {
def main(args : Array[String]) : Unit = {
val strings = List("geo12","neo493","leo")
for(val string <- strings) {
string match {
case NumberMatcher(group) => println(group)
case _ => println ("no")
}
}
}
}
I wanted to add pattern matching for strings containing digits ( so I can learn more about pattern matching ), and in unapply I decided to return a Option[String]. However, in the println in the NumberMatcher case, group is seen as a String and not as an Option. Can you shed some light? The output produced when this is ran is:
12,493,no
Take a look at this example.
The unapply method returns Some value if it succeeded in extracting one, otherwise None. So internally the
case NumberMatcher(group) => println(group)
invokes unapply and looks whether it returns some value. If it does, we already have to true result and therefore no Option type remains. The pattern matching extracts the returned value from the option.