Use foldLeft to replace occurrences of character in String - scala

I'm trying to learn functional Scala and working on a simple problem - replace occurrences of \' or \\ contained in a String:
Here is my code so far:
val data : String = "\' this is a test \\ "
data.toCharArray.foldLeft(""){ (x, y) => x match {
case Nil => y :: Nil
case head :: tail =>
if head == '\'' ''
else if head == '\\' ''
else head :: tail
}
There are multiple errors:
I've not understood something fundamental with fold?
Simple examples of foldLeft such as:
val sum = prices.foldLeft(0.0)(_ + _)
are understandable but I'm unsure how to use foldLeft in a context where there is conditions. In the problem I posted the condition being matching on a character.

There are several issues here, starting with some syntactic problems, like missing parentheses around the conditionals. The first real substantive issue is that the initial value (the "" in foldLeft("")) must be the same type as the accumulator, and as the return type. You seem to want a List[Char] as the return type, so you'll need to use something like List.empty[Char] as the initial value.
Next I'd strongly recommend using names like acc and c instead of x and y to indicate more clearly which is the accumulator and which is the current value.
Another issue is that '' also isn't valid Scala syntax—there is no empty character literal. I'll use '_' as the replacement just for the sake of example.
A working implementation might look like this:
val data: String = "\' this is a test \\ "
data.toCharArray.foldLeft(List.empty[Char]) { (acc, c) =>
c match {
case '\'' => acc :+ '_'
case '\\' => acc :+ '_'
case other => acc :+ other
}
}
Which yields:
val data: String = "' this is a test \ "
val res1: List[Char] = List(_, , t, h, i, s, , i, s, , a, , t, e, s, t, , _, )
Which I think is what you're aiming for?
As a footnote, I'm assuming this is just an exercise, but it's worth noting that using a left fold for an operation like this is extremely inefficient, since you're building up a list by appending.

There are several errors in this code:
you haven't closed lambda's bracket
you use List pattern matching on... well string because
x here is result so far (so "" initially) and y are elements of data (chars)
This code should look like this:
val data : String = "\' this is a test \\ "
data.toCharArray.foldLeft("") { (result, ch) =>
if (ch == '\'' || ch == '\\') result
else result + ch
}

Related

How to get more functional Scala based code when manipulating string by counting its length?

I am quite new to Scala and functional programming.
I wrote the simple codes as below, which manipulates the string by counting the word.
When the 4th comma-delimitted part is empty then, I concated only three columns, otherwise I concated all the columns including the values as code above.
But I think that it is not quite proper to the functional programming. Because I used the if statement to see the input value contains the value or not.
How to change it to the more scala-like code?
str = "aa,bb,1668268540040,34.0::aa,bb,1668268540040"
val parts = str.split("::")
for (case <- parts) {
val ret = case.map(c => if (c.value.isEmpty) {
c.columnFamily + "," + c.qualifier + "," + c.ts
} else {
c.columnFamily + "," + c.qualifier + "," + c.ts + "," + c.value
})
}
str = "aa,bb,1668268540040,34.0::aa,bb,166826434343"
val parts = str.split("::")
for (part <- parts) {
val elem = part.split(",", 4)
if (elem.length == 4) {
val Array(f, q, t, v) = elem
state.put(f + ":" + q, (v, t.toLong))
} else {
val Array(f, q, t) = elem
state.put(f + ":" + q, ("", t.toLong))
}
}
#LeviRamsey's comment tells you actually everything, but just to make your code more "scala-ish", you should avoid mutable data structures in the first place (what you're doing with state, which I think is a Map object), and use immutable data structures. About your if-else part, it's actually okay in FP, but in Scala, you can use pattern matching on a list, rather than manual length checking and using Arrays. Something like this:
parts.foldLeft(Map.empty[String, (String, Long)]) {
case (state, part) =>
part.split(",", 4).toList match {
case f :: q :: t :: v :: Nil =>
state.updated(f + ":" + q, (v, t.toLong))
case f :: q :: t :: Nil =>
state.updated(f + ":" + q, ("", t.toLong))
case _ => state // or whatever thing you want to do, in case neither 4 nor 3 elements are splitted
}
}

How not to count space as a char using foldLeft

I have an exercise where I have to check how many DIFFERENT chars are used in a string.
I don't know how not to count space as a char. I wanted to put if inside the foldLeft, like:
str.foldLeft(lista)((acc, char) => if (char != ' ') char :: acc)
but then it says that required List[Char], found: Any.
What do you generally also think about my function? Could I do it faster/more effectivly?
def countChars(str: String): Int = {
val lista = List[Char]()
val letters = str.foldLeft(lista)((acc, char) => char :: acc)
val result = letters.toSet.size
return result
}
println(countChars("hello world"))
I am not sure if this is "faster or more efficiently", maybe a little bit, but certainly reads better and is more idiomatic :)
str.iterator.filterNot(_.isWhitespace).distinct.size
Or alternatively
str.iterator.distinct.count(!_.isWhitespace)
If you are set on using foldLeft then folding into Set would save you a few ticks compared to creating a list first, and then converting it into set:
str.foldLeft(Set.empty[Char]) {
case (s, ' ') => s
case (s, c) => s + c
}.size
Or you could get rid of the space afterwards:
(str.foldLeft(Set.empty[Char])(_ + _) - ' ').size
First of all you can use Set as accumulator from the start. Then you can use if-else and return current accumulator if current char should not be counted:
val set = Set[Char]()
val uniqueCount = str.foldLeft(set)((acc, char) => if (char != ' ') acc + char else acc)
.size
You can use pattern matching. Then you don't need the if...else.
def countChars(str: String): Int =
str.foldLeft(Set[Char]()){
case (acc, ' ') => acc
case (acc, c) => acc + c
}.size

Functional Way of handling corner case in Folds

I've a list of nodes (String) that I want to convert into something the following.
create X ({name:"A"}),({name:"B"}),({name:"B"}),({name:"C"}),({name:"D"}),({name:"F"})
Using a fold I get everything with an extra "," at the end. I can remove that using a substring on the final String. I was wondering if there is a better/more functional way of doing this in Scala ?
val nodes = List("A", "B", "B", "C", "D", "F")
val str = nodes.map( x => "({name:\"" + x + "\"}),").foldLeft("create X ")( (acc, curr) => acc + curr )
println(str)
//create X ({name:"A"}),({name:"B"}),({name:"B"}),({name:"C"}),({name:"D"}),({name:"F"}),
Solution 1
You could use the mkString function, which won't append the seperator at the end.
In this case you first map each element to the corresponding String and then use mkString for putting the ',' inbetween.
Since the "create X" is static in the beginning you could just prepend it to the result.
val str = "create X " + nodes.map("({name:\"" + _ + "\"})").mkString(",")
Solution 2
Another way to see this: Since you append exactly one ',' too much, you could just remove it.
val str = nodes.foldLeft("create X ")((acc, x) => acc + "({name:\"" + x + "\"}),").init
init just takes all elements from a collection, except the last.
(A string is seen as a collection of chars here)
So in a case where there are elements in your nodes, you would remove a ','. When there is none you only get "create X " and therefore remove the white-space, which might not be needed anyways.
Solution 1 and 2 are not equivalent when nodes is empty. Solution 1 would keep the white-space.
Joining a bunch of things, splicing something "in between" each of the things, isn't a map-shaped problem. So adding the comma in the map call doesn't really "fit".
I generally do this sort of thing by inserting the comma before each item during the fold; the fold can test whether the accumulator is "empty" and not insert a comma.
For this particular case (string joining) it's so common that there's already a library function for it: mkString.
Move "," from map(which applies to all) to fold/reduce
val str = "create X " + nodes.map( x => "({name:\"" + x + "\"})").reduceLeftOption( _ +","+ _ ).getOrElse("")

Replace if-else with functional code

I know that in functional style all if-else blocks replaced by pattern matching. But how I can handle Maps with pattern matching in Scala? For example how I can rewrite this code in more functional style?
val myMap= getMap()
if(myMap.contains(x) && myMap.contains(y)) Some(myMap(x) + myMap(y))
else if(myMap.contains(x + y)){
val z = myMap(x + y)
if (z % 2 == 0) Some(z)
else None
}
else None
Using if-else is totally acceptable for functional programming, because if-else in Scala is merely an expression. The reasons to decide between if-else and pattern-matching should be focused on improving readability, mainly.
Here's my try at rewriting your code. I'm actually not using pattern matching here, but a for-comprehension to sum the values.
def sumOfValues = for{
mx <- myMap.get(x)
my <- myMap.get(y)
} yield mx + my
def valueOfSumIfEven = myMap.get(x+y).filter(_ % 2 == 0)
sumOfValues orElse valueOfSumIfEven
To answer your question directly - you can filter the cases with an additional if:
getMap match {
case myMap if (myMap.contains(x) && myMap.contains(y)) =>
Some(myMap(x) + myMap(y))
case myMap if (myMap.contains(x+y)) =>
myMap(x+y) match {
case z if (z % 2 == 0) => Some(z)
case _ => None
}
case _ => None
}
([edit: there actually is] Although there is are "else-if's" in Scala, ) this is actually a way of doing if-else-if-else (looking at the produced class-files this is what it actually does whereas if-else is equivalent to the ternary ?: in java, returning Unit implicitly when the final else is missing).
How about:
myMap.get(x)
.zip(myMap.get(y))
.headOption.map(x => x._1 + x._2)
.orElse(myMap.get(x + y)
.filter(__ % 2 == 0))
Well, you could write your test as follows:
myMap.get(x).flatMap(xVal => myMap.get(y).map(_ + xVal))
.orElse(myMap.get(x+y).filter(_ % 2 == 0))
But what you have already may just be clearer to anyone trying to understand the intent. Note that the first line (from flatMap to the end) can also be written as a for-comprehension, as shown in #ziggystar's answer).
Maybe the modulo test part could be rewritten as a match, if that feels cleaner:
if(myMap.contains(x) && myMap.contains(y))
Some(myMap(x) + myMap(y))
else myMap.get(x + y) match {
case Some(z) if (z % 2 == 0) => Some(z)
case _ => None
}

Matching with custom combinations/operators

I know that you can do matching on lists in a way like
val list = List(1,2,3)
list match {
case head::tail => head
case _ => //whatever
}
so I started to wonder how this works. If I understand correctly, :: is just an operator, so what's to stop me from doing something like
4 match {
case x + 2 => x //I would expect x=2 here
}
If there is a way to create this kind of functionality, how is it done; if not, then why?
Pattern matching takes the input and decomposes it with an unapply function. So in your case, unapply(4) would have to return the two numbers that sum to 4. However, there are many pairs that sum to 4, so the function wouldn't know what to do.
What you need is for the 2 to be accessible to the unapply function somehow. A special case class that stores the 2 would work for this:
case class Sum(addto: Int) {
def unapply(i: Int) = Some(i - addto)
}
val Sum2 = Sum(2)
val Sum2(x) = 5 // x = 3
(It would be nice to be able to do something like val Sum(2)(y) = 5 for compactness, but Scala doesn't allow parameterized extractors; see here.)
[EDIT: This is a little silly, but you could actually do the following too:
val `2 +` = Sum(2)
val `2 +`(y) = 5 // y = 3
]
EDIT: The reason the head::tail thing works is that there is exactly one way to split the head from the tail of a list.
There's nothing inherently special about :: versus +: you could use + if you had a predetermined idea of how you wanted it to break a number. For example, if you wanted + to mean "split in half", then you could do something like:
object + {
def unapply(i: Int) = Some(i-i/2, i/2)
}
and use it like:
scala> val a + b = 4
a: Int = 2
b: Int = 2
scala> val c + d = 5
c: Int = 3
d: Int = 2
EDIT: Finally, this explains that, when pattern matching, A op B means the same thing as op(A,B), which makes the syntax look nice.
Matching with case head :: tail uses an infix operation pattern of the form p1 op p2 which gets translated to op(p1, p2) before doing the actual matching. (See API for ::)
The problem with + is the following:
While it is easy to add an
object + {
def unapply(value: Int): Option[(Int, Int)] = // ...
}
object which would do the matching, you may only supply one result per value. E.g.
object + {
def unapply(value: Int): Option[(Int, Int)] = value match {
case 0 => Some(0, 0)
case 4 => Some(3, 1)
case _ => None
}
Now this works:
0 match { case x + 0 => x } // returns 0
also this
4 match { case x + 1 => x } // returns 3
But this won’t and you cannot change it:
4 match { case x + 2 => x } // does not match
No problem for ::, though, because it is always defined what is head and what is tail of a list.
There are two ::s (pronounced "cons") in Scala. One is the operator on Lists and the other is a class, which represents a non empty list characterized by a head and a tail. So head :: tail is a constructor pattern, which has nothing to do with the operator.