How to skip keys in map function on map in scala - scala

Given a map of Map[String, String].
I want to know how to skip a key from map
val m = Map("1"-> "1", "2"-> "2")
m.map[(String, String), Map[String, String]].map{
case(k,v)=>
if (v == "1") {
// Q1: how to skip this key
// Do not need to return anything
} else {
// If the value is value that I want, apply some other transformation on it
(k, someOtherTransformation(v))
}
}

.collect is doing exactly what you want, it takes partial function, if function is not defined for some element (pair for Map), that element is dropped:
Map("1"-> "1", "2"-> "2").collect { case (k, v) if v != "1" => (k, v * 2) }
//> scala.collection.immutable.Map[String,String] = Map(2 -> 22)
Here partial function is defined for v != "1" (because of guard), hence element with v == "1" is dropped.

You could put a "guard" on your case clause ...
case (k,v) if v != "1" => // apply some transformation on it
case (k,v) => (k,v) // leave as is
... or simply leave the elements you're not interested in unchanged.
case (k,v) => if (v == "1") (k,v) else // apply some transformation on it
The output of map is a new collection the same size as the input collection with all/some/none of the elements modified.

Victor Moroz's answer is good for this case, but for cases where you can't make the decision on whether to skip immediately in the pattern match, use flatMap:
Map("1"-> "1", "2"-> "2").flatMap {
case (k,v) =>
val v1 = someComplexCalculation(k, v)
if (v1 < 0) {
None
} else {
// If the value is value that I want, apply some other transformation on it
Some((k, someOtherTransformation(v1)))
}
}

Why not .filterNot to remove all unwanted values(according to your condition) and then a .map?
Sample code:
Map("1"-> "1", "2" -> "2").filterNot(_._2 == "1").map(someFunction)
//someFunction -> whatever you would implement

Related

Scala Spark: how to compare two list of tuple

I would like to find if each Tuple in my firstArray exists in my secondArray of tuple.
If it is not the case, i would like to return all tuple that doesn't match and which element of tuple exactly doesn't match.
It could be something like :
for each element (x,y) in firstArray:
for each element (k,z) in secondArray:
if (x != k) print(something)
return (x,y)
if (y != z) print(something)
return (x,y)
Example:
val firstArray: Array[(String,String)] = Array(("elem1","elem2"), ("elem3","elem4"))
val secondArray: Array[(String,String)] = Array(("elem1","elem2"), ("elem5","elem4"), ("elem3","elem7"))
Desired output
Output:
("elem3","elem4") is eliminated because elem4 doesn't match elem7
val result: Array[(String,String)] = Array(("elem3","elem4"))
you can try something like
val res = firstArray.filterNot(secondArray.contains(_))
It will return the elements of first array that are not present in the second.
Edit
The following code will loop over the two arrays, and compare the tuples
for {
(i,j) <- firstArray
(k,l) <- secondArray
}
{
println((i,j) match {
case (a,b) if (a == k && b ==l) => "Tuple found"
case (a,_) if (a == k)=> "First elem only found."
case (_,b) if (b ==l)=> "Second elem only found."
case _ => "No match"
})
}
Hope this will help

Create a Map from the elements of the same set based on condition in Scala

I have a Hashset of the following form, it might grow big:
var hs = HashSet(("fox", "name"),
("animal", "type"),
("gender", "type"),
("x", "test"),
("x", "nottest"),
("z", "test"),
("z", "nottest"))
What is the best way to have a Map from it with the following form:
HashMap (("x", "test")-> ("x", "nottest"),("z", "test") ->("z", "nottest"))
i.e Mapping the tuples from the same set where they have the same first element and the second element is prefixed with "not".
You can create all possible pairs and filter out the ones, that are not present in the original set:
hs.map { case(k, v) => (k, v) -> (k, "not" + v) }
.filter { case(pos, neg) => hs.contains(neg) }
.toMap
Edit:
If the set grows really large, then we can easily change the ordering - first check and filter only pairs with negations, then map:
hs.filter { case(k, v) => hs((k, "not" + v)) }
.map { case(k, v) => (k, v) -> (k, "not" + v) }
.toMap

Partially filter on a map to get key : Scala

I have a map:
val mapTest = Map("Haley" -> Map("Deran" -> 0.4, "Mike" -> 0.3), "Jack" -> Map("Deran" -> 0.3, "Mike" -> 0.3))
I want to retrieve the key based on a value. Given the value "Deran"-> 0.4 I should get "Haley".
I have tried using this:
mapTest.filter(_._2 == Map("Deran" -> 0.4))
but it doesn't work as filter selects all the values at a time. That's the first question. My second question is what If two keys verify that predicates such as the case for "Jack" and "Haley" for "Mike"
Maybe you want something like this:
val toSearch = List("Deran - > 0.4," Mike" -> 0.3)
mapTest.collectFirst {
case (key, values) if (toSearch.forall { case (k, v) => values.get(k).contains(v) }) => key
}
This could probably solve it:
def filter[K, NK, NV](m: Map[K, Map[NK, NV]])(p: ((NK, NV)) => Boolean): Vector[K] =
m.view.collect { case (k, v) if v.exists(p) => k }.toVector
Where NK is a generic type for a nested key and NV a generic type for a nested value.
This works as follows with the following inputs and outputs
val in1: (String, Double) = "Deran" -> 0.4
val out1: Vector[String] = Vector("Haley")
val in2: (String, Double) = "Mike" -> 0.3
val out2: Vector[String] = Vector("Haley", "Jack")
assert(filter(mapTest)(_ == in1) == out1)
assert(filter(mapTest)(_ == in2) == out2)
You can play around with this code here on Scastie.
Using a predicate you can be very generic but note that the complexity grows proportionally to the size of both the map and the nested maps contained therein.
If you can be less generic and simply check for equality, you can drop the predicate and use this to your advantage to make the nested check run in constant time:
def filter[K, NK, NV](m: Map[K, Map[NK, NV]])(p: (NK, NV)): Vector[K] =
m.view.collect { case (k, v) if v.get(p._1).contains(p._2) => k }.toVector
assert(filter(mapTest)(in1) == out1)
assert(filter(mapTest)(in2) == out2)
This variant is also available here on Scastie.

Scala alternative to series of if statements that append to a list?

I have a Seq[String] in Scala, and if the Seq contains certain Strings, I append a relevant message to another list.
Is there a more 'scalaesque' way to do this, rather than a series of if statements appending to a list like I have below?
val result = new ListBuffer[Err]()
val malformedParamNames = // A Seq[String]
if (malformedParamNames.contains("$top")) result += IntegerMustBePositive("$top")
if (malformedParamNames.contains("$skip")) result += IntegerMustBePositive("$skip")
if (malformedParamNames.contains("modifiedDate")) result += FormatInvalid("modifiedDate", "yyyy-MM-dd")
...
result.toList
If you want to use some scala iterables sugar I would use
sealed trait Err
case class IntegerMustBePositive(msg: String) extends Err
case class FormatInvalid(msg: String, format: String) extends Err
val malformedParamNames = Seq[String]("$top", "aa", "$skip", "ccc", "ddd", "modifiedDate")
val result = malformedParamNames.map { v =>
v match {
case "$top" => Some(IntegerMustBePositive("$top"))
case "$skip" => Some(IntegerMustBePositive("$skip"))
case "modifiedDate" => Some(FormatInvalid("modifiedDate", "yyyy-MM-dd"))
case _ => None
}
}.flatten
result.toList
Be warn if you ask for scala-esque way of doing things there are many possibilities.
The map function combined with flatten can be simplified by using flatmap
sealed trait Err
case class IntegerMustBePositive(msg: String) extends Err
case class FormatInvalid(msg: String, format: String) extends Err
val malformedParamNames = Seq[String]("$top", "aa", "$skip", "ccc", "ddd", "modifiedDate")
val result = malformedParamNames.flatMap {
case "$top" => Some(IntegerMustBePositive("$top"))
case "$skip" => Some(IntegerMustBePositive("$skip"))
case "modifiedDate" => Some(FormatInvalid("modifiedDate", "yyyy-MM-dd"))
case _ => None
}
result
Most 'scalesque' version I can think of while keeping it readable would be:
val map = scala.collection.immutable.ListMap(
"$top" -> IntegerMustBePositive("$top"),
"$skip" -> IntegerMustBePositive("$skip"),
"modifiedDate" -> FormatInvalid("modifiedDate", "yyyy-MM-dd"))
val result = for {
(k,v) <- map
if malformedParamNames contains k
} yield v
//or
val result2 = map.filterKeys(malformedParamNames.contains).values.toList
Benoit's is probably the most scala-esque way of doing it, but depending on who's going to be reading the code later, you might want a different approach.
// Some type definitions omitted
val malformations = Seq[(String, Err)](
("$top", IntegerMustBePositive("$top")),
("$skip", IntegerMustBePositive("$skip")),
("modifiedDate", FormatInvalid("modifiedDate", "yyyy-MM-dd")
)
If you need a list and the order is siginificant:
val result = (malformations.foldLeft(List.empty[Err]) { (acc, pair) =>
if (malformedParamNames.contains(pair._1)) {
pair._2 ++: acc // prepend to list for faster performance
} else acc
}).reverse // and reverse since we were prepending
If the order isn't significant (although if the order's not significant, you might consider wanting a Set instead of a List):
val result = (malformations.foldLeft(Set.empty[Err]) { (acc, pair) =>
if (malformedParamNames.contains(pair._1)) {
acc ++ pair._2
} else acc
}).toList // omit the .toList if you're OK with just a Set
If the predicates in the repeated ifs are more complex/less uniform, then the type for malformations might need to change, as they would if the responses changed, but the basic pattern is very flexible.
In this solution we define a list of mappings that take your IF condition and THEN statement in pairs and we iterate over the inputted list and apply the changes where they match.
// IF THEN
case class Operation(matcher :String, action :String)
def processInput(input :List[String]) :List[String] = {
val operations = List(
Operation("$top", "integer must be positive"),
Operation("$skip", "skip value"),
Operation("$modify", "modify the date")
)
input.flatMap { in =>
operations.find(_.matcher == in).map { _.action }
}
}
println(processInput(List("$skip","$modify", "$skip")));
A breakdown
operations.find(_.matcher == in) // find an operation in our
// list matching the input we are
// checking. Returns Some or None
.map { _.action } // if some, replace input with action
// if none, do nothing
input.flatMap { in => // inputs are processed, converted
// to some(action) or none and the
// flatten removes the some/none
// returning just the strings.

Scala Filter a map for based on unique values within Map values

In Scala, I'm trying to filter a map based on a unique property with the Map values.
case class Product(
item: Item,
)
productModels: Map[Int, Product]
How can I create a new Map (or filter productModels) to only contain values where Product.Item.someproperty is unique within the Map?
I've been trying foldLeft on productModels, but can't seem to get it. I'll keep trying but want to check with you all as well.
Thanks
You can do it the following way:
productModels
.groupBy(_._1) // produces Map[Product, Map[Int, Product]]
.filter {case (k,v) => v.size == 1} // filters unique values
.flatMap {case (_,v) => v}
The easiest way to do that is to transform your map into another map, where keys are desired fields of Item:
case class Product(item:String)
val productModels =
Map(
1 -> Product("a"),
2 -> Product("b"),
3 -> Product("c"),
4 -> Product("a")
)
// here I'm calculating distinct by Product.item for simplicity
productModels.map { case e#(_, v) => v.item -> e }.values.toMap
Result:
Map(4 -> Product(a), 2 -> Product(b), 3 -> Product(c))
Note, that the order of the elements is not guaranteed, as generic Map doesn't have particular order of keys. If you use Map that has item order, such as ListMap and want to preserve order of elements, here is the necessary adjustment:
productModels.toList.reverse.map { case e#(_, v) => v.item -> e }.toMap.values.toMap
Result:
res1: scala.collection.immutable.Map[Int,Product] = Map(1 -> Product(a), 3 -> Product(c), 2 -> Product(b))
case class Item(property:String)
case class Product(item:Item)
val xs = Map[Int, Product]() // your example has this data structure
// just filter the map based on the item property value
xs filter { case (k,v) => v.item.property == "some property value" }
Here is implementation with foldLeft:
productModels.foldLeft(Map.empty[Int, Product]){
(acc, el) =>
if (acc.exists(_._2.item.someproperty == el._2.item.someproperty)) acc
else acc + el
}