Scala: How to pass string variables to access Object values? - scala

I have an Object
object Constants {
val getA = "example.a.test"
val getB = "example.b.test"
val getC = "example.c.test"
.
.
.
}
I have another class where I'm accessing these values after importing the class in an if-else loop
if(str == "A") {
println(Constants.getA)
}
else if (str == "B") {
println(Constants.getB)
} else {
println(Constants.getC)
}
// and so on...
NOTE: This is just a sample code I have but the if-else loops get complicated. Is there a way to simplify this by passing the "str" variable directly to the object like "Constants.get$str" or something simpler? I get Cyclomatic complexity Scala check style warning

You can use pattern matching and create a new function in Constant.
def getString(str: String) = {
str match {
case "A" => "example.a.test"
case "B" => "example.b.test"
case "C" => "example.c.test"
case _ => "Wrong input"
}
}

you can use a key/value object for handle your code
for example use Map :
object Constants {
val get = Map("A" -> "example.a.test", "B" -> "example.b.test", "C" -> "example.c.test" , ...)
}
and you can use it by
println( get("A"))
instead of all if else loop which you had .
you can even iterate on the keys or values too :
get.keys.foreach{ i =>
print( "Key = " + i )
println(" Value = " + get(i) )}
i think this way could be simpler and i hope the answer is useful.

Related

How to make loop and exception inside flatmap?

I am having a requirement where I have to loop over a list and do create Map[String,String]. Here the header has values as list like below:
val headersMap = scala.collection.mutable.Map[String, String]()
try {
val payloadHeaders = collectorPayload.headers
for (values <- payloadHeaders.toList) {
for (value <- values) {
val header = value.split(":").map(_.trim)
headersMap += (header(0) -> header(1))
headersMap += ("Content-Type" -> "application/json; charset=UTF-8")
}
}
} catch {
case e: Exception => {
logger.error("Collector Payload extraction error with : " + e.getMessage)
}
}
Is there any better way to handle this any map or flatMap way?
Don't use mutable collections or variables (just pretend they don't exist util you run into a use case where you positively cannot do without them ... it won't be soon).
Also generally avoid using loops (because they kinda assume and promote mutability and side effects), you'll need them even less often than mutable collections.
collectorPayload
.headers
.iterator
.flatMap(_.split(":").map(_.trim))
.map { case Array(a,b) => a -> b }
.toMap + ("Content-Type" -> "application/json; charset=UTF-8")

How to break/escape from a for loop in Scala?

Im new to scala and searched a lot for the solution.
I'm querying the database and storing the value of the http request parsed as a json4s object in response. I wait for the response and parse the json.
val refService = url("http://url//)
val response = Http(refService OK dispatch.as.json4s.Json)
var checkVal :Boolean = true
val json = Await.result(response, 30 seconds)
val data = json \ "data"
I want to run a loop and check if the value of "name" is present in the data returned. If present I want to break and assign checkVal to false. So far I have this:
for {
JObject(obj) <- data
JField("nameValue", JString(t)) <- obj //nameValue is the column name in the returned data
} yield {checkVal= if (t == name){ break }
else
true
}
Eclipse is giving me the following error: type mismatch; found : List[Unit] required:
List[String]
Please advice. Thank you.
One of your problems is that you have different return types in yield: if t==name, return type is the type of break, and if t!=name return type is Boolean.
In scala you don't have break operator, this behaviour is achieved using breakable construct and calling break() method which actually throws an exception to exit from breakable block. Also you can use if statements in for body to filter you results:
import scala.util.control.Breaks._
breakable {
for {
JObject(obj) <- data
JField("nameValue", JString(t)) <- obj
if t == name
} yield {
checkVal = false
break()
}
}
UPDATE:
I used this imperative approach because you are new to scala, but it's not scala way. IMHO you should stick to #Imm code in comments to your question.
I actually don't like using pattern matching in for loops as if for some reason data is not a JObject it won't be handled well. I prefer an approach like below.
data match {
case JObject(fields) => fields.exists{
case (name:String,value:JString) => name == "nameValue" && value.s == "name"
case _ => false
}
case _ => false // handle error as not a JObject
}
Edit: revised to include your matches.
I would suggest to use exists as it is lazy on all collection members.
code:-
val list= Map(
"nameValue1" -> 1,
"nameValue2" -> 2,
"nameValue3" -> 3,
"nameValue4" -> 4,
"nameValue5" -> 5
)
val requiredHeader = "nameValue2"
var keyvalue:Int=0
list.exists(p=>{ if(p._1.equalsIgnoreCase(requiredHeader))keyvalue=p._2;p._1.equalsIgnoreCase(requiredHeader) })
if(keyvalue!=0){
//header present
}else{
//header doesn't exit
}

Filtering inside `for` with pattern matching

I am reading a TSV file and using using something like this:
case class Entry(entryType: Int, value: Int)
def filterEntries(): Iterator[Entry] = {
for {
line <- scala.io.Source.fromFile("filename").getLines()
} yield new Entry(line.split("\t").map(x => x.toInt))
}
Now I am both interested in filtering out entries whose entryType are set to 0 and ignoring lines with column count greater or lesser than 2 (that does not match the constructor). I was wondering if there's an idiomatic way to achieve this may be using pattern matching and unapply method in a companion object. The only thing I can think of is using .filter on the resulting iterator.
I will also accept solution not involving for loop but that returns Iterator[Entry]. They solutions must be tolerant to malformed inputs.
This is more state-of-arty:
package object liner {
implicit class R(val sc: StringContext) {
object r {
def unapplySeq(s: String): Option[Seq[String]] = sc.parts.mkString.r unapplySeq s
}
}
}
package liner {
case class Entry(entryType: Int, value: Int)
object I {
def unapply(s: String): Option[Int] = util.Try(s.toInt).toOption
}
object Test extends App {
def lines = List("1 2", "3", "", " 4 5 ", "junk", "0, 100000", "6 7 8")
def entries = lines flatMap {
case r"""\s*${I(i)}(\d+)\s+${I(j)}(\d+)\s*""" if i != 0 => Some(Entry(i, j))
case __________________________________________________ => None
}
Console println entries
}
}
Hopefully, the regex interpolator will make it into the standard distro soon, but this shows how easy it is to rig up. Also hopefully, a scanf-style interpolator will allow easy extraction with case f"$i%d".
I just started using the "elongated wildcard" in patterns to align the arrows.
There is a pupal or maybe larval regex macro:
https://github.com/som-snytt/regextractor
You can create variables in the head of the for-comprehension and then use a guard:
edit: ensure length of array
for {
line <- scala.io.Source.fromFile("filename").getLines()
arr = line.split("\t").map(x => x.toInt)
if arr.size == 2 && arr(0) != 0
} yield new Entry(arr(0), arr(1))
I have solved it using the following code:
import scala.util.{Try, Success}
val lines = List(
"1\t2",
"1\t",
"2",
"hello",
"1\t3"
)
case class Entry(val entryType: Int, val value: Int)
object Entry {
def unapply(line: String) = {
line.split("\t").map(x => Try(x.toInt)) match {
case Array(Success(entryType: Int), Success(value: Int)) => Some(Entry(entryType, value))
case _ =>
println("Malformed line: " + line)
None
}
}
}
for {
line <- lines
entryOption = Entry.unapply(line)
if entryOption.isDefined
} yield entryOption.get
The left hand side of a <- or = in a for-loop may be a fully-fledged pattern. So you may write this:
def filterEntries(): Iterator[Int] = for {
line <- scala.io.Source.fromFile("filename").getLines()
arr = line.split("\t").map(x => x.toInt)
if arr.size == 2
// now you may use pattern matching to extract the array
Array(entryType, value) = arr
if entryType == 0
} yield Entry(entryType, value)
Note that this solution will throw a NumberFormatException if a field is not convertible to an Int. If you do not want that, you'll have to encapsulate x.toInt with a Try and pattern match again.

declare variable in custom control structure in scala

I am wondering if there is a way to create a temp variable in the parameter list of a custom control structure.
Essentially, I would like create a control structure that looks something like the
for loop where I can create a variable, i, and have access to i in the loop body only:
for(i<- 1 to 100) {
//loop body can access i here
}
//i is not visible outside
I would like to do something similar in my code. For example,
customControl ( myVar <- "Task1") {
computation(myVar)
}
customControl ( myVar <- "Task2") {
computation(myVar)
}
def customControl (taskId:String) ( body: => Any) = {
Futures.future {
val result = body
result match {
case Some(x) =>
logger.info("Executed successfully")
x
case _ =>
logger.error(taskId + " failed")
None
}
}
}
Right now, I get around the problem by declaring a variable outside of the custom control structure, which doesn't look very elegant.
val myVar = "Task1"
customControl {
computation(myVar)
}
val myVar2 = "Task2"
customControl {
computation(myVar2 )
}
You could do something like this:
import scala.actors.Futures
def custom(t: String)(f: String => Any) = {
Futures.future {
val result = f(t)
result match {
case Some(x) =>
println("Executed successfully")
x
case _ =>
println(t + " failed")
None
}
}
}
And then you can get syntax like this, which isn't exactly what you asked for, but spares you declaring the variable on a separate line:
scala> custom("ss") { myvar => println("in custom " + myvar); myvar + "x" }
res7: scala.actors.Future[Any] = <function0>
in custom ss
ss failed
scala> custom("ss") { myvar => println("in custom " + myvar); Some(myvar + "x") }
in custom ss
Executed successfully
res8: scala.actors.Future[Any] = <function0>
scala>
Note that the built-in for (x <- expr) body is just syntactic sugar for
expr foreach (x => body)
Thus it might be possible to achieve what you want (using the existing for syntax) by defining a custom foreach method.
Also note that there is already a foreach method that applies to strings. You could do something like this:
case class T(t: String) {
def foreach(f: String => Unit): Unit = f(t)
}
Note: You can also change the result type of f above from Unit to Any and it will still work.
Which would enable you to do something like
for (x <- T("test"))
print(x)
This is just a trivial (and useless) example, since now for (x <- T(y)) f(x) just abbreviates (or rather "enlongishes") f(y). But of course by changing the argument of f in the above definition of foreach from String to something else and doing a corresponding translation from the string t to this type, you could achieve more useful effects.

Tune Nested Loop in Scala

I was wondering if I can tune the following Scala code :
def removeDuplicates(listOfTuple: List[(Class1,Class2)]): List[(Class1,Class2)] = {
var listNoDuplicates: List[(Class1, Class2)] = Nil
for (outerIndex <- 0 until listOfTuple.size) {
if (outerIndex != listOfTuple.size - 1)
for (innerIndex <- outerIndex + 1 until listOfTuple.size) {
if (listOfTuple(i)._1.flag.equals(listOfTuple(j)._1.flag))
listNoDuplicates = listOfTuple(i) :: listNoDuplicates
}
}
listNoDuplicates
}
Usually if you have someting looking like:
var accumulator: A = new A
for( b <- collection ) {
accumulator = update(accumulator, b)
}
val result = accumulator
can be converted in something like:
val result = collection.foldLeft( new A ){ (acc,b) => update( acc, b ) }
So here we can first use a map to force the unicity of flags. Supposing the flag has a type F:
val result = listOfTuples.foldLeft( Map[F,(ClassA,ClassB)] ){
( map, tuple ) => map + ( tuple._1.flag -> tuple )
}
Then the remaining tuples can be extracted from the map and converted to a list:
val uniqList = map.values.toList
It will keep the last tuple encoutered, if you want to keep the first one, replace foldLeft by foldRight, and invert the argument of the lambda.
Example:
case class ClassA( flag: Int )
case class ClassB( value: Int )
val listOfTuples =
List( (ClassA(1),ClassB(2)), (ClassA(3),ClassB(4)), (ClassA(1),ClassB(-1)) )
val result = listOfTuples.foldRight( Map[Int,(ClassA,ClassB)]() ) {
( tuple, map ) => map + ( tuple._1.flag -> tuple )
}
val uniqList = result.values.toList
//uniqList: List((ClassA(1),ClassB(2)), (ClassA(3),ClassB(4)))
Edit: If you need to retain the order of the initial list, use instead:
val uniqList = listOfTuples.filter( result.values.toSet )
This compiles, but as I can't test it it's hard to say if it does "The Right Thing" (tm):
def removeDuplicates(listOfTuple: List[(Class1,Class2)]): List[(Class1,Class2)] =
(for {outerIndex <- 0 until listOfTuple.size
if outerIndex != listOfTuple.size - 1
innerIndex <- outerIndex + 1 until listOfTuple.size
if listOfTuple(i)._1.flag == listOfTuple(j)._1.flag
} yield listOfTuple(i)).reverse.toList
Note that you can use == instead of equals (use eq if you need reference equality).
BTW: https://codereview.stackexchange.com/ is better suited for this type of question.
Do not use index with lists (like listOfTuple(i)). Index on lists have very lousy performance. So, some ways...
The easiest:
def removeDuplicates(listOfTuple: List[(Class1,Class2)]): List[(Class1,Class2)] =
SortedSet(listOfTuple: _*)(Ordering by (_._1.flag)).toList
This will preserve the last element of the list. If you want it to preserve the first element, pass listOfTuple.reverse instead. Because of the sorting, performance is, at best, O(nlogn). So, here's a faster way, using a mutable HashSet:
def removeDuplicates(listOfTuple: List[(Class1,Class2)]): List[(Class1,Class2)] = {
// Produce a hash map to find the duplicates
import scala.collection.mutable.HashSet
val seen = HashSet[Flag]()
// now fold
listOfTuple.foldLeft(Nil: List[(Class1,Class2)]) {
case (acc, el) =>
val result = if (seen(el._1.flag)) acc else el :: acc
seen += el._1.flag
result
}.reverse
}
One can avoid using a mutable HashSet in two ways:
Make seen a var, so that it can be updated.
Pass the set along with the list being created in the fold. The case then becomes:
case ((seen, acc), el) =>