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
Related
I implemented simple unapplySeq that extracts elements of filepath to sequence:
object PathSequence {
def unapplySeq(path: Path): Seq[String] =
if (path == null || path.getFileName == null) Seq.empty
else path.getFileName.toString +: unapplySeq(path.getParent)
}
After runing it like this:
val path = Path.of("C:/Users/WIN10/IdeaProjects/LearningSandbox/src/worksheet/resources/AddTwoNumbers.java")
val PathSequence(first, second, third, _*) = path
I get an error: Star pattern must correspond with varargs or unapplySeq.
unapplySeq is implemented correctly here, the result is List(AddTwoNumbers.java, resources, worksheet, src, LearningSandbox, IdeaProjects, WIN10, Users)
How to set first, second and third variables to first three elements of result sequence?
Apparently the problem is that unapplySeq also has to return an Option[Seq[A]].
However, if you are sure that you will always match you can return a Some[Seq[A]].
Check this:
object PathSequence {
#annotation.tailrec
private def loop(remaining: Option[Path], acc: List[String]): List[String] =
remaining match {
case Some(path) =>
Option(path.getFileName) match {
case Some(file) =>
loop(remaining = Option(path.getParent), file.toString :: acc)
case None =>
List.empty
}
case None =>
acc.reverse
}
def unapplySeq(path: Path): Some[List[String]] =
Some(loop(remaining = Option(path), acc = List.empty))
}
Which you can see how it working here.
I need to concatenate strings present inside a WrappedArray in Scala. I am able to do it with the help of List but that is not what I have to do. I want a solution specifically designed for WrappedArray and with scope of adding/deleting elements while concatenating. I have to use this function as a udf for transforming data via Spark SQL collect_list. That is why I am forced to use WrappedArray.
For e.g ->
WrappedArray("I","love","coding")
Output : String = I : love : coding
This is just an example like adding a colon. I am facing various type issues while matching in case of Wrapped Array.
import scala.annotation.tailrec
object tailRecursionString {
def getString(ints :scala.collection.mutable.WrappedArray[String]): String = {
#tailrec
def sumAccumulator(ints: scala.collection.mutable.WrappedArray[String], accum: String): String = {
ints match {
case Nil : => accum
case x :: tail => sumAccumulator(tail, accum + x)
}
}
sumAccumulator(ints, "[") + "]"
}
def main(args: Array[String]): Unit = {
val list : scala.collection.mutable.WrappedArray[String] = Array("kumar","sambhav","is","a","good","boy")
val sum = getString(list)
println(sum)
}
}
The reason of your problems is using WrappedArray which hasn't unapply method. Pattern matching works using unapply method, you can read more about this in scala documentation. Just replace WrappedArray to Array and matching expressions then it should works:
#tailrec
def sumAccumulator(ints: Array[String], accum: String): String = {
ints match {
case Array() => accum
case _ => sumAccumulator(ints.tail, accum + ints.head)
}
}
List has subtypes :: and Nil. They are case classes and case classes have unapply method generated by compiler.
Shortly, I try to describe how it works:
When compiler looking for extracting (in pattern matching) it looks at ::, sees that it's a subtype of List and has unapply method, and if unapply returns correct result it's choose this branch. The same way for Nil.
You could write it for Seq in general :
import scala.annotation.tailrec
def getString(ints :Seq[String]): String = {
#tailrec
def sumAccumulator(ints: Seq[String], accum: String): String = {
ints match {
case Seq(x) => accum
case Seq(x, xs#_*) => sumAccumulator(xs, accum + x)
}
}
sumAccumulator(ints, "[") + "]"
}
As WrappedArray is also a Seq, this will work
val seperator = " "
s"[${scala.collection.mutable.WrappedArray("I", "love", "coding").mkString(seperator)}]"
In case if you want to apply a filter, you can still use filter, and achieve the same.
<script src="https://scastie.scala-lang.org/lttp77S4Sout7VWkOXTEhQ.js"></script>
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
I am trying to convert a List of string to the form "rBrrBB", or "r r rb", or " rrB". The string must have length 6. If the this list is not full, then the list should be prefixed with an appropriate number of spaces
So far my code as below
def showColumn(xs: List[String]): String = xs match
{case List() => ""
case x::xs1 => x.format(" ") + showColumn(xs1)}
when I call this from
println (showColumn(List("","","b","b","r","b")))
it returns only "bbrb". It suppose to return " bbrb"
Any help would appreciate.
Try this:
def showColumn(xs: List[String]): String = xs.map(x => if(x == "") " " else x).mkString
or, alternatively:
def showColumn(xs: List[String]): String = xs.map(x => if(x.isEmpty) " " else x).mkString
Both work by changing empty strings in the list to spaces, then merging each string in the list into a single string.
If you absolutely must make this a recursive function, then a solution which is not tail-recursive would look like this:
def showColumn(xs: List[String]): String = xs match {
case Nil => ""
case x :: xs1 => (if(x.isEmpty) " " else x) + showColumn(xs1)
}
Finally, the tail-recursive version is a little more complex, as it employs a helper function:
import scala.annotation.tailrec
def showColumn(xs: List[String]): String = {
// Tail recursive helper function.
#tailrec
def nextStr(rem: List[String], acc: String): String = rem match {
case Nil => acc
case x :: xs1 => nextStr(xs1, acc + (if(x.isEmpty) " " else x))
}
// Start things off.
nextStr(xs, "")
}
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