How to define variables by list droping extra parts? - scala

This is working fine,
val Array(a,b) = "Hello,Bye".split(',')
But it is an error because extra-information is not ignored:
val Array(a,b) = "Hello,Bye,etc".split(',')
// scala.MatchError: ...
how to ignore extra-information?
Same error in the case of less items:
val Array(a,b) = "Hello".split(',')
IMPORTANT: no elegant way like the Javascript Destructuring assignment?

Add a placeholder using underscore:
val Array(a,b, _) = "Hello,Bye,etc".split(',')
EDIT: Using match-case syntax is generally more preferred and more flexible (and you can catch all possible outcome):
val s = "Hello,Bye,etc"
s.split(',') match {
case Array(a) => //...
case Array(a, b) => //...
case Array(a, b, rest#_*) => //...
case _ => //Catch all case to avoid MatchError
}

#_ will cover both instances.
val Array(a,b,x#_*) = "Hello,Bye,etc".split(',')
//a: String = Hello
//b: String = Bye
//x: Seq[String] = ArraySeq(etc)
val Array(c,d,z#_*) = "Hello,Bye".split(',')
//c: String = Hello
//d: String = Bye
//z: Seq[String] = ArraySeq()
From your comments it looks like you want to default to "", an empty String. I found a way to do it with Stream, which has been deprecated in Scala 2.13, but so far it is the cleanest solution I've found.
val Stream(a,b,c,d,_*) = "one,two,etc".split(",").toStream ++ Stream.continually("")
//a: String = one
//b: String = two
//c: String = etc
//d: String = ""

I would consider making the result values of type Option[String] by lift-ing the split Array[String] (viewed as a partial function) into an Int => Option[String] function:
val opts = "Hello".split(",").lift
// opts: Int => Option[String] = <function1>
opts(0)
// res1: Option[String] = Some(Hello)
opts(1)
// res2: Option[String] = None
Or, if String values are preferred with None translated to "":
val strs = "Hello,world".split(",").lift.andThen(_.getOrElse(""))
// strs: Int => String = scala.Function1$$Lambda$...
strs(0)
// res3: String = Hello
strs(1)
// res4: String = "world"
strs(2)
// res5: String = ""
Note that with this approach, you can take as many opts(i) or strs(i), i = 0, 1, 2, ..., as wanted.

You can do this by converting to List first:
val a :: b :: _ = "Hello,Bye,etc".split(',').toList

Related

Replace empty space in array of string in spark scala

I have a scenario where Array[String] has got empty space. When i apply replace it doesn't return proper result. What would be mistake in my implementation.
scala> val chk2 =Array("8.0","60.0","")
chk2: Array[String] = Array(8.0, 60.0, "")
scala> val chk3 = chk2.map(x => (x.replace("", "0")))
chk3: Array[String] = Array(080.000, 06000.000, 0)
You can use map with pattern matching:
chk2.map{ case "" => "0"; case x => x }
// res2: Array[String] = Array(8.0, 60.0, 0)

Match String with List Items

I have a list and two strings :
val features = List("one","two","three")
val strOne = "one_five"
val strTwo = "seven_five"
I'd like to match each string to items of the list.
If beginning of string matches one of list items then print matched list item and string itself.
If not, nothing to print.
I have method that I think make what I need but I cannot compile it :
def getElement(any: String): String = any match {
case s :: rest if features.contains(s) => s + "= " + any
case _ => // Nothing
}
I wanted the following :
scala> getElement(strOne)
"one_five= one"
scala> getElement(strTwo)
You can't just return nothing. You promised that your method would return a String, so you must return one. You can either return an Option[String] (preferred) or return Unit and do the printing yourself. Further, the built in method TraversableLike#find will do part of the job.
def findFeature(str: String): Option[String] = features.find(_ startsWith str) map { value => s"$str=$value" }
In order to get the printing behavior:
findFeature(str) foreach println
// or redefine findFeature similarly
Further, you seem to misunderstand pattern matching: You don't want to match on the string; you want to match the list's elements against the string. Here's a version that uses pattern matching:
def getElement(feature: String): Option[String] = {
#tailrec def getElem0(feature: String, strs: List[String]): Option[String] = strs match {
case s :: _ if s startsWith feature => Some(s"$feature=$s") // Matching case
case _ :: rest => getElem0(feature, rest) // Not matched, but more to search
case Nil => None // Empty list; failure
}
getElem0(feature, features)
}
Your solution can't compile because :: is a List method, and s is a String. Moreover, getElement is declared to return a String therefore it should return a String for any input. So you can't just return "nothing" in the second case.
Here's an alternative implementation:
def printElement(any: String): Unit = features
.find(s => any.startsWith(s)) // find matching (returns Option[String])
.foreach(s => println(s + "= "+ any)) // print if found
printElement(strOne) // one= one_five
printElement(strTwo)
Simple one line Scala code
Find in list the item who's first part is present in the list
features.find(_ == str.split("_")(0)).map { elem => s"$str= $elem"}.getOrElse("")
Put the above line inside the function.
def getElement(str: String): String = features.find(_ == str.split("_")(0)).map { elem => s"$str= $elem"}.getOrElse("")
Scala REPL
scala> val strOne = "one_five"
strOne: String = one_five
scala> val str = "one_five"
str: String = one_five
scala> features.find(_ == str.split("_")(0)).getOrElse("")
res2: String = one
scala> features.find(_ == str.split("_")(0)).map(elem => s"$str= $elem").getOrElse("")
res3: String = one_five= one

Appending to Map with value as a list

I have initialized a mutable Map (not sure if I should use a mutable here, but to start with I use mutable):
val aKey = "aa"
val myMap = scala.collection.mutable.Map[String, List[String]]()
if (myMap.exists(_._1 == aKey))
myMap(aKey) = myMap.get(aKey) :: "test"
But on myMap.get(aKey) I get the following error:
Type Mismatch expected List[String] actual option[String]
I thought the way I did is correct to append to list.
You can append to a mutable map with +=.
scala> myMap += ("placeholders" -> List("foo", "bar", "baz"))
res0: scala.collection.mutable.Map[String,List[String]] = Map(placeholders -> List(foo, bar, baz))
To append a new item to the list for aKey as mentioned in the commments.
myMap.get("placeholders") match {
case Some(xs:List[String]) => myMap.update("placeholders", xs :+ "buzz")
case None => myMap
}
res22: Any = ()
scala> myMap
res23: scala.collection.mutable.Map[String,List[String]] = Map(placeholders -> List(foo, bar, baz, buzz))
If you have mutable Map and inside that map immutable List. This is a simple example on how to do it. The example is also defining using withDefaultValue - so that you always get something back from Map.
var posts: collection.mutable.Map[Int, List[String]] = collection.mutable.Map().
withDefaultValue List.empty[String]
def addTag(postID: Int, tag: String): Unit = posts.get(postID) match {
case Some(xs: List[String]) => posts(postID) = xs :+ tag
case None => posts(postID) = List(tag)
}
posts(42)
// List[String] = List()
addTag(42, "tag-a")
addTag(42, "tag-b")
addTag(42, "tag-c")
posts(42)
// List[String] = List(tag-a, tag-b, tag-c)
Everything is fine. Except for list append operator
To add an element to the list. The operator should be something like
myList = element :: myList
myList = myList :: element // wrong
so your program should be
val aKey = "aa"
var myMap = scala.collection.mutable.Map[String, List[String]]().withDefaultValues(List())
myMap(aKey) = "test" :: myMap(aKey)
Starting Scala 2.13, you could alternatively use Map#updateWith on mutable Maps (or Map#updatedWith on immutable Maps):
map.updateWith("a")({
case Some(list) => Some("test" :: list)
case None => Some(List("test"))
})
def updateWith(key: K)(remappingFunction: (Option[V]) => Option[V]): Option[V]
For instance,
val map = collection.mutable.Map[String, List[String]]()
// map: collection.mutable.Map[String, List[String]] = HashMap()
val key = "key"
// key: String = "key"
if the key doesn't exist:
map.updateWith(key)({ case Some(list) => Some("test" :: list) case None => Some(List("test")) })
// Option[List[String]] = Some(List("test"))
map
// collection.mutable.Map[String, List[String]] = HashMap("key" -> List("test"))
and if the key exists:
map.updateWith(key)({ case Some(list) => Some("test2" :: list) case None => Some(List("test2")) })
// Option[List[String]] = Some(List("test2", "test"))
map
// collection.mutable.Map[String, List[String]] = HashMap("key" -> List("test2", "test"))
I figured the how to do it:
val aKey = "aa"
var myMap = scala.collection.mutable.Map[String, List[String]]()
if (myMap.exists(_._1 == aKey))
myMap.get(aKey).get :+ "foo"
else {
myMap(aKey) = List("zoo")
}
This might not be the scala way of doing but this gives the correct results.
first you shouldn't Mutable Map :). for add one item on List within Map, you can use method get of Map.
val m = Map(1 -> List(2))
val m2 = m.get(1).fold{
// In this case don't exist any List for this key
m + (1 -> List(3))
}{ list =>
// In this case exist any List for this key
m + (1 -> (3 :: list))
}

get first 2 values in a comma separated string

I am trying to get the first 2 values of a comma separated string in scala. For example
a,b,this is a test
How do i store the values a,b in 2 separate variables?
To keep it easy and clean.
KISS solution:
1.Use split for separation. Then use take which is defined on all ordered sequences to get the elements as needed:
scala> val res = "a,b,this is a test" split ',' take 2
res: Array[String] = Array(a, b)
2.Use Pattern matching to set the variables:
scala> val Array(x,y) = res
x: String = a
y: String = b*
Another solution using Sequence Pattern match in Scalaenter link description here
Welcome to Scala version 2.11.2 (OpenJDK 64-Bit Server VM, Java 1.7.0_65).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val str = "a,b,this is a test"
str: String = a,b,this is a test
scala> val Array(x, y, _*) = str.split(",")
x: String = a
y: String = b
scala> println(s"x = $x, y = $y")
x = a, y = b
Are you looking for the method split ?
"a,b,this is a test".split(',')
res0: Array[String] = Array(a, b, this is a test)
If you want only the first two values you'll need to do something like:
val splitted = "a,b,this is a test".split(',')
val (first, second) = (splitted(0), splitted(1))
There should be some regex options here.
scala> val s = "a,b,this is a test"
s: String = a,b,this is a test
scala> val r = "[^,]+".r
r: scala.util.matching.Regex = [^,]+
scala> r findAllIn s
res0: scala.util.matching.Regex.MatchIterator = non-empty iterator
scala> .toList
res1: List[String] = List(a, b, this is a test)
scala> .take(2)
res2: List[String] = List(a, b)
scala> val a :: b :: _ = res2
a: String = a
b: String = b
but
scala> val a :: b :: _ = (r findAllIn "a" take 2).toList
scala.MatchError: List(a) (of class scala.collection.immutable.$colon$colon)
... 33 elided
or if you're not sure there is a second item, for instance:
scala> val r2 = "([^,]+)(?:,([^,]*))?".r.unanchored
r2: scala.util.matching.UnanchoredRegex = ([^,]+)(?:,([^,]*))?
scala> val (a,b) = "a" match { case r2(x,y) => (x, Option(y)) }
a: String = a
b: Option[String] = None
scala> val (a,b) = s match { case r2(x,y) => (x, Option(y)) }
a: String = a
b: Option[String] = Some(b)
This is a bit nicer if records are long strings.
Footnote: the Option cases look nicer with a regex interpolator.
If your string is short, you may as well just use String.split and take the first two elements.
val myString = "a,b,this is a test"
val splitString = myString.split(',') // Scala adds a split-by-character method in addition to Java's split-by-regex
val a = splitString(0)
val b = splitString(1)
Another solution would be to use a regex to extract the first two elements. I think it's quite elegant.
val myString = "a,b,this is a test"
val regex = """(.*),(.*),.*""".r // all groups (in parenthesis) will be extracted.
val regex(a, b) = myString // a="a", b="b"
Of course, you can tweak the regex to only allow non-empty tokens (or anything else you might need to validate) :
val regex = """(.+),(.+),.+""".r
Note that in my examples I assumed that the string always had at least two tokens. In the first example, you can test the length of the array if needed. The second one will throw a MatchError if the regex doesn't match the string.
I had originally proposed the following solution. I will leave it because it works and doesn't use any class formally marked as deprecated, but the Javadoc for StringTokenizer mentions that it is a legacy class and should no longer be used.
val myString = "a,b,this is a test"
val st = new StringTokenizer(",");
val a = st.nextToken()
val b = st.nextToken()
// You could keep calling st.nextToken(), as long as st.hasMoreTokens is true

how to use go to different branch if a string contains different words in scala using match/case?

I have the following code using if, but is there any nicer syntax using case match?
val a="somestring"
if (a.contains("companyA")){
"companyA"
}
if (a.contains("companyB")){
"companyB"
}
if (a.contains("companyC")){
"companyC"
}
else {
"no company matched"
}
You can put all yours companies in a List and use find to find your company or return "no company matched"
scala> val listOfCompanies = List("companyA", "companyB", "companyC")
listOfCompanies: List[String] = List(companyA, companyB, companyC)
scala> val a = "somestring"
a: String = somestring
scala> listOfCompanies find { comp => a contains comp } orElse Some("no company matched")
res20: Option[String] = Some(no company matched)
scala> val a = "companyB"
a: String = companyB
scala> listOfCompanies find { comp => a contains comp } orElse Some("no company matched")
res21: Option[String] = Some(companyB)
Notice that find returns the first matching element. See here
val a = "somestring"
a match {
case a if a.contains("companyA") => "companyA"
case a if a.contains("companyB") => "companyB"
case default => "no company matched"
}
Possibly nicer syntax, but not using match:
def checkCompanies(c:String, cs:List[String]) = cs.collectFirst{case m if c contains m => m}.getOrElse("no company matched")
//> checkCompanies: (c: String, cs: List[String])String
checkCompanies("companyA", List("companyA", "companyB"))
//> res0: String = companyA
checkCompanies("companyC", List("companyA", "companyB"))
//> res1: String = no company matched
If the list of names is fixed, you can obviously change the def to refer to a definition in a wider scope and not pass it in each time