In the following code, inside the for comprehension, I can refer to the string and index using a tuple dereference:
val strings = List("a", "b", "c")
for (stringWithIndex <- strings.zipWithIndex) {
// Do something with stringWithIndex._1 (string) and stringWithIndex._2 (index)
}
Is there any way in the Scala syntax to have the stringWithIndex split into the parts (string and index) within the for comprehension header, so that readers of the code do not have to wonder at the values of stringWithIndex._1 and stringWithIndex._2?
I tried the following, but it would not compile:
for (case (string, index) <- strings.zipWithIndex) {
// Do something with string and index
}
You almost got it:
scala> val strings = List("a", "b", "c")
strings: List[java.lang.String] = List(a, b, c)
scala> for ( (string, index) <- strings.zipWithIndex)
| { println("str: "+string + " idx: "+index) }
str: a idx: 0
str: b idx: 1
str: c idx: 2
See, no need for case keyword.
strings.zipWithIndex.foreach{case(x,y) => println(x,y)}
res:
(a,0)
(b,1)
(c,2)
Related
I have a Scala list below :
val numList = List(1,2,3,4,5,1,2)
I want to get index of the same element pair in the list. The output should look like (0,5),(1,6)
How can I achieve using map?
def catchDuplicates(num : List[Int]) : (Int , Int) = {
val count = 0;
val emptyMap: HashMap[Int, Int] = HashMap.empty[Int, Int]
for (i <- num)
if (emptyMap.contains(i)) {
emptyMap.put(i, (emptyMap.get(i)) + 1) }
else {
emptyMap.put(i, 1)
}
}
Let's make the challenge a little more interesting.
val numList = List(1,2,3,4,5,1,2,1)
Now the result should be something like (0, 5, 7),(1, 6), which makes it pretty clear that returning one or more tuples is not going to be feasible. Returning a List of List[Int] would make much more sense.
def catchDuplicates(nums: List[Int]): List[List[Int]] =
nums.zipWithIndex //List[(Int,Int)]
.groupMap(_._1)(_._2) //Map[Int,List[Int]]
.values //Iterable[List[Int]]
.filter(_.lengthIs > 1)
.toList //List[List[Int]]
You might also add a .view in order to minimize the number of traversals and intermediate collections created.
def catchDuplicates(nums: List[Int]): List[List[Int]] =
nums.view
.zipWithIndex
.groupMap(_._1)(_._2)
.collect{case (_,vs) if vs.sizeIs > 1 => vs.toList}
.toList
How can I achieve using map?
You can't.
Because you only want to return the indexes of the elements that appear twice; which is a very different kind of transformation than the one that map expects.
You can use foldLeft thought.
object catchDuplicates {
final case class Result[A](elem: A, firstIdx: Int, secondIdx: Int)
private final case class State[A](seenElements: Map[A, Int], duplicates: List[Result[A]]) {
def next(elem: A, idx: Int): State[A] =
seenElements.get(key = elem).fold(
ifEmpty = this.copy(seenElements = this.seenElements + (elem -> idx))
) { firstIdx =>
State(
seenElements = this.seenElements.removed(key = elem),
duplicates = Result(elem, firstIdx, secondIdx = idx) :: this.duplicates
)
}
}
private object State {
def initial[A]: State[A] =
State(
seenElements = Map.empty,
duplicates = List.empty
)
}
def apply[A](data: List[A]): List[Result[A]] =
data.iterator.zipWithIndex.foldLeft(State.initial[A]) {
case (acc, (elem, idx)) =>
acc.next(elem, idx)
}.duplicates // You may add a reverse here if order is important.
}
Which can be used like this:
val numList = List(1,2,3,4,5,1,2)
val result = catchDuplicates(numList)
// result: List[Result] = List(Result(2,1,6), Result(1,0,5))
You can see the code running here.
I think returning tuple is not a good option instead you should try Map like -
object FindIndexOfDupElement extends App {
val numList = List(1, 2, 3, 4, 5, 1, 2)
#tailrec
def findIndex(elems: List[Int], res: Map[Int, List[Int]] = Map.empty, index: Int = 0): Map[Int, List[Int]] = {
elems match {
case head :: rest =>
if (res.get(head).isEmpty) {
findIndex(rest, res ++ Map(head -> (index :: Nil)), index + 1)
} else {
val updatedMap: Map[Int, List[Int]] = res.map {
case (key, indexes) if key == head => (key, (indexes :+ index))
case (key, indexes) => (key, indexes)
}
findIndex(rest, updatedMap, index + 1)
}
case _ => res
}
}
println(findIndex(numList).filter(x => x._2.size > 1))
}
you can clearly see the number(key) and respective index in the map -
HashMap(1 -> List(0, 5), 2 -> List(1, 6))
I am trying to run the following code:
def split(input: Int): List[Int] = {
val inputAsString = input.toString
val inputAsStringList = inputAsString.split("").toList
inputAsStringList.map(_.toInt).reverse
}
split(3122)
def increment(list: List[Int]): List[Int] = {
def loop(multiplier: Int, result: List[Int], list: List[Int]): List[Int] = list match {
case x :: xs =>
val newList = (x * multiplier) :: result
loop(multiplier * 10, newList, xs)
case Nil => result
}
loop(1, List(), list)
}
val result: List[Int] = for {
splited <- split(3122)
incremented <- increment(splited)
} yield incremented
But the line incremented <- increment(splited) is giving the following error:
Type mismatch, expected: List[Int], actual: Int
Why is this happening if both functions are returning the same data type?
Your increment function takes a List[Int], but splited is an Int while in the for comprehension. This is because at the line splited <- split(3122), you are really saying for every x: Int in split(y): List[Int]. If you want it to compile, you want your val result code to look like this:
...
val splited = split(3122)
val result: List[Int] = for {
incremented <- increment(splited)
} yield incremented
This returns result: List[Int] = List(2). Whether you expect this or not is another thing - I'm not sure what you expect increment to return.
Please consider the following example to understand what for-comprehension does:
//For comprehension:
for {
a <- aMonad
b <- bMonad
//....some more....
z <- zMonad
} yield {
???
}
//means:
aMonad.flatMap { a =>
bMonad.flatMap { b =>
//{{{...some more
//notice here it is .map instead of flatMap
zMonad.map { z =>
//something
???
}
//}}}
}
}
The monads aMonad, bMonad, .. zMonad should be monads of similar types, like, List[_], Future[_] etc to be used in a for-comprehension.
That means, aMonad of type List[Int] and bMonad of type Future[Int] cannot be used in a for-comprehension like in the above code, while there is no problem if they are of List[Int] and List[String] type.
Further readings:
https://docs.scala-lang.org/tutorials/FAQ/yield.html
https://darrenjw.wordpress.com/2016/04/15/first-steps-with-monads-in-scala/
I search a way to count the different chars from a string.
The problem is that's not allowed to use any functions from the scala-api or to use vars (only val).
I want same result like that
val fruit: String = "dasisteintest"
println(fruit.groupBy(identity).mapValues(_.size))
Map(e -> 2, s -> 3, n -> 1, t -> 3, a -> 1, i -> 2, d -> 1)
In every try I made, I have at the end a list[(Char,Int)] where I have to change the Int. But because it's an immutable list I can't change it.
How can I implement that counting char algorithm?
You can use the following snippet:
val fruit: String = "dasisteintest"
val map = scala.collection.mutable.HashMap.empty[Char, Int]
for (symbol <- fruit) {
if (map.contains(symbol))
map(symbol) = map(symbol) + 1
else
map.+=((symbol, 1))
}
println(map)
def countChars(str: String) = {
def loop(chars: List[Char], acc: Map[Char, Int]): Map[Char, Int] = {
chars match {
case Nil => acc
case char :: rest =>
loop(rest, acc + (char -> (acc(char) + 1)))
}
}
loop(str.toList, Map.empty[Char, Int] withDefaultValue 0)
}
Test:
# val fruit: String = "dasisteintest"
fruit: String = "dasisteintest"
# countChars(fruit)
res4: Map[Char, Int] = Map('e' -> 2, 's' -> 3, 'n' -> 1, 't' -> 3, 'a' -> 1, 'i' -> 2, 'd' -> 1)
Whatever you use here is from scala api, a Map.apply, or Map.empty or List.::. It would be difficult not to use any functions from scala api. My guess is that you aren't supposed to be using things like groupBy and you are supposed to do something a bit more low level. Folding is natural solution here, like foldLeft, but if that is considered "using a function from scala api", you can just implement foldLeft yourself, just like I did in my solution.
As for withDefaultValue you can replace it with explicit check if value is present and put a 1 there in that case.
You don't know how to change an in a list or map that is immutable? You just make a new list with that value changed.
For a map, given
val map = Map('a' -> 3)
you can update it doing
# map.updated('a', 4)
res6: Map[Char, Int] = Map('a' -> 4)
or
# map + ('a' -> 4)
res7: Map[Char, Int] = Map('a' -> 4)
Both do exact the same thing - insert or update - and return new map.
Here you can find how to update elements in a list
Replace element in List with scala
although you rarely want to access list by index, you rather just build a new list from old one while iterating over it somehow, e.g. with fold.
What do you mean by "no functions from Scala API"? Does that include no functions from the collections api? If so, then ignore my answer. However if we can't even use reduce methods, I don't see the point of this exercise.
Here's what I came up with:
val fruit: String = "dasisteintest"
fruit.foldLeft[Map[Char,Int]](Map.empty)((map, c) => map + (c -> (map.getOrElse(c, 0) + 1)))
Although can you expand what you mean by ""you have to change the Int"?
Here is the expected code.
First the function which returns the char from the list
def removeFromList(l: List[Char], l2: List[Char], toDelete: Char): List[Char] = {
if (l.isEmpty) l2
else {
if (l.head == toDelete)
removeFromList(l.tail, l2, toDelete)
else
removeFromList(l.tail, l2 :+ l.head, toDelete)
}
}
and then the function which counts the chars and calls removeFromList()
def zaehlZeichen(s: String): List[(Char, Int)] = {
val sl: List[Char] = s.toUpperCase().toList
if (sl.isEmpty) Nil
else {
val l: List[Char] = List()
val tupleList: List[(Char, Int)] = List();
val in: Int = countChar(sl, 0, sl.head)
val t: List[(Char, Int)] = tupleList :+ (sl.head, in)
val cutL: List[Char] = removeFromList(sl, l, sl.head)
t ::: zaehlZeichen(cutL.mkString);
}
}
object CountEachCharInString
{
//Counts the number of a specific character in the Input String
def characterCount(inputChar: Char, str: String): Unit =
{
var num: Int = 0
num = str.count(_ == inputChar);
/*
//Implementation of count method
for(i <- 0 to str.length - 1)
{
if(str(i) == inputChar)
{
num += 1
}
}
*/
println(s"$inputChar appears $num times")
}
def countAllChars(inputStr: String): Unit =
{
//To eliminate duplicates, need one loop inside the other. str(i) == str(j)
for(i <- 0 to inputStr.length - 1)
{
var occurence: Int = 0
for(j <- 0 to i-1)
{
if(inputStr(j) == inputStr(i))
occurence = occurence + 1
}
if(occurence == 0)
{
characterCount(inputStr(i), inputStr)
//var num = inputStr.count(_ == inputStr(i))
//println( inputStr(i) + s" appears $num times")
}
}
}
def main(args: Array[String]): Unit =
{
countAllChars("HelloforHello...^^&&&&")
}
}
I am new to scala, my objective is to iterate over list and check if items in the list exist the map as its keys and if they exist return the value for that key.
I did the following:
def getMatchingValues(listItmes: List[String]) = {
for (item <- listItems) {
theMap.keys.foreach { i =>
if (i.equals(item)) {
theMap(i)
}
"NoMatch"
}
}
}
I am trying to figure if there is a better way to do this in scala?
Map has a getOrElse method which does what you want:
def getMatchingValues(listItems: List[String]) = listItems map (theMap.getOrElse(_,"NoMatch"))
At least I think that is what you want. Here's an example:
scala> val theMap = Map("a"->"A", "b" -> "B")
theMap: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B)
scala> val listItems = List("a","b","c")
listItems: List[String] = List(a, b, c)
scala> listItems map (theMap.getOrElse(_,"NoMatch"))
res0: List[String] = List(A, B, NoMatch)
A possible solution with flatMap:
/* Return Some[String] if found, None if not found, then flatten */
def getMatchingValues(listItems: List[String], theMap: Map[String, String]): List[String] =
listItems.flatMap(item => theMap.get(item))
/* Same thing with some syntactic sugar */
def getMatchingValuesSmartass(listItems: List[String], theMap: Map[String, String]): List[String] =
listItems flatMap theMap.get
val l = List("1", "3", "5", "7")
val m = Map("5" -> "five", "2" -> "two", "1" -> "one")
getMatchingValues(l, m)
getMatchingValuesSmartass(l, m)
You could use the map.get method and handle the result with pattern matching
list.map { x => map.get(x) match {
case None => "No match"
case Some(i) => (x, i)
}}
The above code returns a list of pairs where each pair represents the elements of the list and the value associated in the map ("No match" if not found)
If I was you, I would do two steps. Given this Map:
val map = Map("a" -> "b", "b" -> "c", "c" -> "d", "d" -> "e")
and this List:
val list = List("a", "c", "e")
At first I would map an Option value to every item in the List. Giving you if there is a value for the item.
val mapped = list.map(item => item -> map.get(item))
This will give you this:
mapped: List[(String, Option[String])] =
List(("a",Some("b")), ("c",Some("d")), ("e", None))
Calling get on the map returns a wrapped result. If there is a result, you will get the result wrapped in a Some. Otherwise you will get a None. Both are subclasses of Option Option is a closure-construct, that provides you a null-value without having to deal with null. Now you are able to map again, to reach your goal.
val result = mapped.map(tuple => tuple._1 -> tuple._2.getOrElse("No match"))
result: List[(String, String)] = List(("a","b"), ("c","d"), ("e","No match"))
getOrElse extracts the value of a Some or falls back to the parameter if it is a None.
To make it look more professional, we can write this expression in one line ;)
val result = list.map(item => item -> map.get(item).getOrElse("No match"))
This will give you the exact same result.
I've got a two dimensional array and I want to apply a function to each value in the array.
Here's what I'm working with:
scala> val array = Array.tabulate(2,2)((x,y) => (0,0))
array: Array[Array[(Int, Int)]] = Array(Array((0,0), (0,0)), Array((0,0), (0,0)))
I'm using foreach to extract the tuples:
scala> array.foreach(i => i.foreach(j => println(i)))
[Lscala.Tuple2;#11d559a
[Lscala.Tuple2;#11d559a
[Lscala.Tuple2;#df11d5
[Lscala.Tuple2;#df11d5
Let's make a simple function:
//Takes two ints and return a Tuple2. Not sure this is the best approach.
scala> def foo(i: Int, j: Int):Tuple2[Int,Int] = (i+1,j+2)
foo: (i: Int,j: Int)(Int, Int)
This runs, but need to apply to array(if mutable) or return new array.
scala> array.foreach(i => i.foreach(j => foo(j._1, j._2)))
Shouldn't be to bad. I'm missing some basics I think...
(UPDATE: removed the for comprehension code which was not correct - it returned a one dimensional array)
def foo(t: (Int, Int)): (Int, Int) = (t._1 + 1, t._2 + 1)
array.map{_.map{foo}}
To apply to a mutable array
val array = ArrayBuffer.tabulate(2,2)((x,y) => (0,0))
for (sub <- array;
(cell, i) <- sub.zipWithIndex)
sub(i) = foo(cell._1, cell._2)
2dArray.map(_.map(((_: Int).+(1) -> (_: Int).+(1)).tupled))
e.g.
scala> List[List[(Int, Int)]](List((1,3))).map(_.map(((_: Int).+(1) -> (_: Int).+(1)).tupled))
res123: List[List[(Int, Int)]] = List(List((2,4)))
Dot notation required to force