Let's say I have:
val m: Map[String, Int] = Map("one" -> 1, "five" -> 5, "six" -> 6, "nine" -> 9)
and I have two functions:
def isNotDivisibleByTwo(i: Int): ValidatedNec[String, Int] = Validated.condNec(i%2!=0, i, s"$i is divisible by 2.")
def isNotDivisibleByThree(i: Int): ValidatedNec[String, Int] = Validated.condNec(i%3!=0, i, s"$i is divisible by 3.")
I want a function that gives me:
def sanitize(m: Map[String, Int]):Map[String, Validated[NonEmptyList[String], Int]] = ???
i.e. It should return all the numbers that satisfy the said two functions, and a mapping of all the failing numbers and their associated faults.
e.g. For the given list m, I want to get:
val result = Map(
"one" -> Valid(1),
"five -> Valid(5),
"nine" -> Invalid(NonEmptyList("9 is dividible by 3")),
"six" -> Invalid(NonEmptyList("6 is dividible by 2", "6 is dividible by 3"))
)
This is what I currently have:
import cats.data._
val m: Map[String, Int] = Map("one" -> 1, "five" -> 5, "six" -> 6, "nine" -> 9)
def isNotDivisibleByTwo(i: Int): ValidatedNec[String, Unit] = Validated.condNec(i%2!=0, (), s"$i is divisible by 2.")
def isNotDivisibleByThree(i: Int): ValidatedNec[String, Unit] = Validated.condNec(i%3!=0, (), s"$i is divisible by 3.")
def sanitize(m: Map[String, Int]): Map[String, Validated[NonEmptyChain[String], Int]] = {
m.mapValues{
i =>
isNotDivisibleByTwo(i).product(
isNotDivisibleByThree(i)
).map(_ => i)
}
}
But, I am not happy with the way I am "composing" the validations.
How can I do this in the most catsy way?
You were so close.
Remember that the correct way to combine multiple Validates is using the Applicative syntax.
import cats.data.{Validated, ValidatedNec}
import cats.syntax.apply._
type ErrorsOr[A] = ValidatedNec[String, A]
def isNotDivisibleByTwo(i: Int): ErrorsOr[Int] =
Validated.condNec((i % 2) != 0, i, s"$i is divisible by 2.")
def isNotDivisibleByThree(i: Int): ErrorsOr[Int] =
Validated.condNec((i % 3) != 0, i, s"$i is divisible by 3.")
val map: Map[String, Int] = Map("one" -> 1, "five" -> 5, "six" -> 6, "nine" -> 9)
def sanitize(m: Map[String, Int]): Map[String, ErrorsOr[Int]] =
m.view.mapValues { i =>
(
isNotDivisibleByTwo(i),
isNotDivisibleByThree(i)
).tupled.map(_ => i)
}.toMap
sanitize(map)
// res: Map[String, ErrorsOr[Int]] = Map(
// "one" -> Valid(1),
// "five" -> Valid(5),
// "six" -> Invalid(Append(Singleton("6 is divisible by 2."), Singleton("6 is divisible by 3."))),
// "nine" -> Invalid(Singleton("9 is divisible by 3."))
// )
However, you may make the code even more general, to work with any number of validations. By using traverse.
(In this case, you do not need any syntax import).
import cats.data.NonEmptyList
val validations: NonEmptyList[Int => ErrorsOr[Int]] = NonEmptyList.of(isNotDivisibleByTwo, isNotDivisibleByThree)
def sanitize[K, V](map: Map[K, V])
(validations: NonEmptyList[V => ErrorsOr[V]]): Map[K, ErrorsOr[V]] =
map.view.mapValues(i => validations.traverse(f => f(i)).map(_ => i)).toMap
sanitize(map)(validations)
// res: Map[String, ErrorsOr[Int]] = Map(
// "one" -> Valid(1),
// "five" -> Valid(5),
// "six" -> Invalid(Append(Singleton("6 is divisible by 2."), Singleton("6 is divisible by 3."))),
// "nine" -> Invalid(Singleton("9 is divisible by 3."))
// )
The reason why I use .view.mapValues(...).toMap is because on Scala 2.13 mapValues is deprecated.
Related
I'd like to create a map on which the key is the string and the value is the number of how many times the string appears on the list. I tried the groupBy method, but have been unsuccessful with that.
Required Answer
scala> val l = List("abc","abc","cbe","cab")
l: List[String] = List(abc, abc, cbe, cab)
scala> l.groupBy(identity).mapValues(_.size)
res91: scala.collection.immutable.Map[String,Int] = Map(cab -> 1, abc -> 2, cbe -> 1)
Suppose you have a list as
scala> val list = List("abc", "abc", "bc", "b", "abc")
list: List[String] = List(abc, abc, bc, b, abc)
You can write a function
scala> def generateMap(list: List[String], map:Map[String, Int]) : Map[String, Int] = list match {
| case x :: y => if(map.keySet.contains(x)) generateMap(y, map ++ Map(x -> (map(x)+1))) else generateMap(y, map ++ Map(x -> 1))
| case Nil => map
| }
generateMap: (list: List[String], map: Map[String,Int])Map[String,Int]
Then call the function as
scala> generateMap(list, Map.empty)
res1: Map[String,Int] = Map(abc -> 3, bc -> 1, b -> 1)
This also works:
scala> val l = List("abc","abc","cbe","cab")
val l: List[String] = List(abc, abc, cbe, cab)
scala> l.groupBy(identity).map(x => (x._1, x._2.length))
val res1: Map[String, Int] = HashMap(cbe -> 1, abc -> 2, cab -> 1)
What would be a functional way to zip two dictionaries in Scala?
map1 = new HashMap("A"->1,"B"->2)
map2 = new HashMap("B"->22,"D"->4) // B is the only common key
zipper(map1,map2) should give something similar to
Seq( ("A",1,0), // no A in second map, so third value is zero
("B",2,22),
("D",0,4)) // no D in first map, so second value is zero
If not functional, any other style is also appreciated
def zipper(map1: Map[String, Int], map2: Map[String, Int]) = {
for(key <- map1.keys ++ map2.keys)
yield (key, map1.getOrElse(key, 0), map2.getOrElse(key, 0))
}
scala> val map1 = scala.collection.immutable.HashMap("A" -> 1, "B" -> 2)
map1: scala.collection.immutable.HashMap[String,Int] = Map(A -> 1, B -> 2)
scala> val map2 = scala.collection.immutable.HashMap("B" -> 22, "D" -> 4)
map2: scala.collection.immutable.HashMap[String,Int] = Map(B -> 22, D -> 4)
scala> :load Zipper.scala
Loading Zipper.scala...
zipper: (map1: Map[String,Int], map2: Map[String,Int])Iterable[(String, Int, Int)]
scala> zipper(map1, map2)
res1: Iterable[(String, Int, Int)] = Set((A,1,0), (B,2,22), (D,0,4))
Note using get is probably preferable to getOrElse in this case. None is used to specify that a value does not exist instead of using 0.
As an alternative to Brian's answer, this can be used to enhance the map class by way of implicit methods:
implicit class MapUtils[K, +V](map: collection.Map[K, V]) {
def zipAllByKey[B >: V, C >: V](that: collection.Map[K, C], thisElem: B, thatElem: C): Iterable[(K, B, C)] =
for (key <- map.keys ++ that.keys)
yield (key, map.getOrElse(key, thisElem), that.getOrElse(key, thatElem))
}
The naming and API are similar to the sequence zipAll.
Inspired by this question, I'd like to implement a Multiset in Scala. I'd like a MultiSet[A] to:
Support adding, removing, union, intersection and difference
Be an A => Int, providing the count of each element
Here's one approach, extending Set:
import scala.collection.immutable.Map
import scala.collection.immutable.Set
import scala.collection.SetLike
import scala.collection.mutable.Builder
class MultiSet[A](private val counts: Map[A, Int] = Map.empty[A, Int])
extends SetLike[A, MultiSet[A]] with Set[A] {
override def +(elem: A): MultiSet[A] = {
val count = this.counts.getOrElse(elem, 0) + 1
new MultiSet(this.counts + (elem -> count))
}
override def -(elem: A): MultiSet[A] = this.counts.get(elem) match {
case None => this
case Some(1) => new MultiSet(this.counts - elem)
case Some(n) => new MultiSet(this.counts + (elem -> (n - 1)))
}
override def contains(elem: A): Boolean = this.counts.contains(elem)
override def empty: MultiSet[A] = new MultiSet[A]
override def iterator: Iterator[A] = {
for ((elem, count) <- this.counts.iterator; _ <- 1 to count) yield elem
}
override def newBuilder: Builder[A,MultiSet[A]] = new Builder[A, MultiSet[A]] {
var multiSet = empty
def +=(elem: A): this.type = {this.multiSet += elem; this}
def clear(): Unit = this.multiSet = empty
def result(): MultiSet[A] = this.multiSet
}
override def seq: MultiSet[A] = this
}
object MultiSet {
def empty[A]: MultiSet[A] = new MultiSet[A]
def apply[A](elem: A, elems: A*): MultiSet[A] = MultiSet.empty + elem ++ elems
def apply[A](elems: Seq[A]): MultiSet[A] = MultiSet.empty ++ elems
def apply[A](elem: (A, Int), elems: (A, Int)*) = new MultiSet((elem +: elems).toMap)
def apply[A](elems: Map[A, Int]): MultiSet[A] = new MultiSet(elems)
}
Extending Set is nice, because it means that MultiSet automatically gets definitions for union, difference, etc. All the following will hold:
// add
assert(
MultiSet("X" -> 3, "Y" -> 1) + "X" ===
MultiSet("X" -> 4, "Y" -> 1))
assert(
MultiSet("X" -> 3, "Y" -> 1) + "Z" ===
MultiSet("X" -> 3, "Y" -> 1, "Z" -> 1))
// remove
assert(
MultiSet("a" -> 2, "b" -> 5) - "b" ===
MultiSet("a" -> 2, "b" -> 4))
assert(
MultiSet("a" -> 2, "b" -> 5) - "c" ===
MultiSet("a" -> 2, "b" -> 5))
// add all
assert(
MultiSet(10 -> 1, 100 -> 3) ++ MultiSet(10 -> 1, 1 -> 7) ===
MultiSet(100 -> 3, 10 -> 2, 1 -> 7))
// remove all
assert(
MultiSet("a" -> 2, "b" -> 5) -- MultiSet("a" -> 3) ===
MultiSet("b" -> 5))
However, I would have to override some inherited methods like union and intersect because they would do the wrong things for multisets, e.g. the following would not hold:
// union (takes max of values)
assert(
MultiSet(10 -> 5, 1 -> 1).union(MultiSet(10 -> 3, 1 -> 7)) ===
MultiSet(10 -> 5, 1 -> 7))
// intersection (takes min of values)
assert(
MultiSet(10 -> 5, 100 -> 3).intersect(MultiSet(10 -> 1, 1 -> 7)) ===
MultiSet(10 -> 1))
Another problem with extending Set is that then I can't have MultiSet be an A => Int because I'll get the error: illegal inheritance; class MultiSet inherits different type instances of trait Function1: A => Int and A => Boolean. I could work around this by declaring a separate, say, count method, but I'd really prefer that the class just be an A => Int.
Another approach would be to inherit from Map[A, Int], which would give me the A => Int that I want, but then I would have to define all of my own ++, --, etc. since in Map these would be defined over (A, Int) pairs, but for a multiset they need to be defined over As.
I guess a third approach would be to give up on both Set and Map, and just implement a new subclass of Iterable or whatever.
What would you recommend? What's the best way to fit a MulitSet within the Scala collections framework?
However, I would have to override some inherited methods like intersect because they would do the wrong things for multisets
I think you'll have to bite the bullet and do just that.
Another problem with extending Set is that then I can't have MultiSet be an A => Int
Indeed, you cannot inherit twice the same trait (here Function1) with different type parameters. And actually in this case, this is not just an annoying technical limitation, but doing so would in fact not make much sense because when calling apply there would be no way to know which overload you want to call: def apply( key: A ): Boolean or def apply( key: A ): Int? You can't tell as the argument lists are the same.
However, what you could do is add an implicit conversion from MultiSet[A] to A => Int. This way, a MultiSet is treated by default as a A => Boolean (as any set), but can be coerced to a A => Int when nedded (in particular it could be passed directly to a function expecting a A => Int function).
class MultiSet[A] ... {
...
def count(elem: A): Int = counts.getOrElse( elem, 0 )
}
object MultiSet {
...
implicit def toCountFunc[A]( ms: MultiSet[A] ): A => Int = {
(x: A) => ms.count( x )
}
}
Some test i the REPL:
scala> val ms = MultiSet("a" -> 2, "b" -> 5)
ms: MultiSet[String] = Set(a, a, b, b, b, b, b)
scala> ms("a")
res17: Boolean = true
scala> ms("c")
res18: Boolean = false
scala> def testExists( f: String => Boolean, keys: String *) {
| println( keys.map( f ).toList )
| }
testExists: (f: String => Boolean, keys: String*)Unit
scala> testExists( ms, "a", "c" )
List(true, false)
scala> def testCounts( f: String => Int, keys: String *) {
| println( keys.map( f ).toList )
| }
testCounts: (f: String => Int, keys: String*)Unit
scala> testCounts( ms, "a", "c" )
List(2, 0)
I have an unordered map:
class O(val a: Int)
Map[String, List[O]]
which I'd like to turn into:
SortedMap[String, SortedMap[Int, O]]
with the child SortedMap keyed on the O field.
I'm sure there must be a more idiomatic code than the below...
class O(val a: Int)
val a: Map[String, List[O]] = Map[String, List[O]]( ("b" -> List(new O(3), new O(2))), "a" -> List(new O(1), new O(2)))
val key1s = a map (_._1)
val oMapsList = ListBuffer[SortedMap[Int, O]]()
for (key1 <- key1s) {
val oList = a(key1)
val key2s = oList map (_.a)
val sortedOMap = SortedMap[Int, O]() ++ (key2s zip oList).toMap
oMapsList += sortedOMap
}
val sortedMap = SortedMap[String, SortedMap[Int, O]]() ++ (key1s zip oMapsList).toMap
Expected sortedMap contents is:
"a" -> ( (1 -> O(1)),(2 -> O(2)) )
"b" -> ( (2 -> O(2)),(2 -> O(3)) )
Firstly, the setup:
scala> case class O(i: Int)
defined class O
scala> Map("a" -> List(O(1), O(2)), "b" -> List(O(2), O(3)))
res0: scala.collection.immutable.Map[java.lang.String,List[O]] = Map(a -> List(O(1), O(2)), b -> List(O(2), O(3)))
Now, import SortedMap:
scala> import collection.immutable._
import collection.immutable._
Now for the answers!
Using breakOut (1 line of code)
Use breakOut - but it involves some unwelcome repetition of types:
scala> res0.map({ case (s, l) => s -> (l.map(o => o.i -> o)(collection.breakOut): SortedMap[Int, O]) })(collection.breakOut): SortedMap[String, SortedMap[Int, O]]
res4: scala.collection.immutable.SortedMap[String,scala.collection.immutable.SortedMap[Int,O]] = Map(a -> Map(1 -> O(1), 2 -> O(2)), b -> Map(2 -> O(2), 3 -> O(3)))
Using a separate method (2 lines of code)
Or a second approach would be to involve a sort method:
scala> def sort[K: Ordering, V](m: Traversable[(K, V)]) = SortedMap(m.toSeq: _ *)
sort: [K, V](m: scala.collection.immutable.Traversable[(K, V)])(implicit evidence$1: Ordering[K])scala.collection.immutable.SortedMap[K,V]
And so:
scala> sort(res0.mapValues(l => sort(l.map(o => o.i -> o)) ))
res13: scala.collection.immutable.SortedMap[java.lang.String,scala.collection.immutable.SortedMap[Int,O]] = Map(a -> Map(1 -> O(1), 2 -> O(2)), b -> Map(2 -> O(2), 3 -> O(3)))
What is the most succinct Scala way to reverse a Map? The Map may contain non-unique values.
EDIT:
The reversal of Map[A, B] should give Map[B, Set[A]] (or a MultiMap, that would be even better).
If you can lose duplicate keys:
scala> val map = Map(1->"one", 2->"two", -2->"two")
map: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,one), (2,two), (-2,two))
scala> map.map(_ swap)
res0: scala.collection.immutable.Map[java.lang.String,Int] = Map((one,1), (two,-2))
If you don't want access as a multimap, just a map to sets, then:
scala> map.groupBy(_._2).mapValues(_.keys.toSet)
res1: scala.collection.immutable.Map[
java.lang.String,scala.collection.immutable.Set[Int]
] = Map((one,Set(1)), (two,Set(2, -2)))
If you insist on getting a MultiMap, then:
scala> import scala.collection.mutable.{HashMap, Set, MultiMap}
scala> ( (new HashMap[String,Set[Int]] with MultiMap[String,Int]) ++=
| map.groupBy(_._2).mapValues(Set[Int]() ++= _.keys) )
res2: scala.collection.mutable.HashMap[String,scala.collection.mutable.Set[Int]]
with scala.collection.mutable.MultiMap[String,Int] = Map((one,Set(1)), (two,Set(-2, 2)))
scala> val m1 = Map(1 -> "one", 2 -> "two", 3 -> "three", 4 -> "four")
m1: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,one), (2,two), (3,three), (4,four))
scala> m1.map(pair => pair._2 -> pair._1)
res0: scala.collection.immutable.Map[java.lang.String,Int] = Map((one,1), (two,2), (three,3), (four,4))
Edit for clarified question:
object RevMap {
def
main(args: Array[String]): Unit = {
val m1 = Map("one" -> 3, "two" -> 3, "three" -> 5, "four" -> 4, "five" -> 5, "six" -> 3)
val rm1 = (Map[Int, Set[String]]() /: m1) { (map: Map[Int, Set[String]], pair: (String, Int)) =>
map + ((pair._2, map.getOrElse(pair._2, Set[String]()) + pair._1)) }
printf("m1=%s%nrm1=%s%n", m1, rm1)
}
}
% scala RevMap
m1=Map(four -> 4, three -> 5, two -> 3, six -> 3, five -> 4, one -> 3)
rm1=Map(4 -> Set(four, five), 5 -> Set(three), 3 -> Set(two, six, one))
I'm not sure this qualifies as succinct.
How about:
implicit class RichMap[A, B](map: Map[A, Seq[B]])
{
import scala.collection.mutable._
def reverse: MultiMap[B, A] =
{
val result = new HashMap[B, Set[A]] with MultiMap[B, A]
map.foreach(kv => kv._2.foreach(result.addBinding(_, kv._1)))
result
}
}
or
implicit class RichMap[A, B](map: Map[A, Seq[B]])
{
import scala.collection.mutable._
def reverse: MultiMap[B, A] =
{
val result = new HashMap[B, Set[A]] with MultiMap[B, A]
map.foreach{case(k,v) => v.foreach(result.addBinding(_, k))}
result
}
}