I have two Map[String, T]s, where T is an instance of subtype of Fruit. I need to construct new Map from two Maps, where the key is the common key names from the two maps, and the value is the Seq[Fruit] iff the values from the two maps shares the same type.
class Fruit
case class Apple() extends Fruit
case class Banana(num: Int) extends Fruit
case class Orange() extends Fruit
For example, if I have following two maps:
val map1 = Map("first" -> Apple(),
"second" -> Banana(3),
"third" -> Orange())
val map2 = Map("first" -> Orange(),
"second" -> Banana(4),
"third" -> Orange())
I need the result map, map3 which has following members:
generateMap(map1: Map[String, Fruit], map2: Map[String, Fruit]): Map[String, Seq[Fruit]]
=> results a map look like
Map("second" -> Seq(Banana(3), Banana(4)),
"third" -> Seq(Orange(), Orange())
I'm not sure how to write a function, generateMap. Could anyone help me to implement that? (using Scala 2.11.x)
Note that the class definitions (Fruits and others) are fixed, so I cannot modify them.
scala> val r: Map[String, Seq[Fruit]] = (map1.toList ++ map2.toList).
groupBy(x => x._1).
mapValues(lst => lst.map(x => x._2)).
.filter {
case (key, lst) => lst.forall(x =>
x.getClass == lst.head.getClass)
}
r: Map[String, Seq[Fruit]] = Map(third -> List(Orange(), Orange()),
second -> List(Banana(3), Banana(4)))
val m3 = (map1.toSeq ++ map2.toSeq). // Combine the maps
groupBy (x=>x._1). //Group by the original keys
map{case (k,lst)=> (k, lst.map(x=> x._2))}. //Strip the keys from the grouped sequences
filter{case (_, lst) => lst.forall(i => lst.head.getClass == i.getClass)}. //Filter out hetergeneous seqs
toMap // Make a map
Without forall:
(map1.toList ++ map2.toList).groupBy(_._1).mapValues(_.map(_._2))
.filter(_._2.map(_.getClass).toSet.tail.isEmpty)
Map(third -> List(Orange(), Orange()), second -> List(Banana(3), Banana(4)))
This version requires a little more (but still linear inside filter) CPU and memory than version with forall, so you should use it only for small collections.
Related
I have a JSON string that I need to process and I am mapping it in the following manner:
val jsonMap = mapper.readValue[Map[String, Object]](jsonString)
My jsonMap contains the following value:
Map(k1 -> List(Map(k2 -> v2), Map(k3 -> v3))
The desired value for newJsonMap is:
Map(k1 -> Map(k2 -> v2))
So in a nutshell, I want to convert the value of the k1 key from a List to a Map. I first started to implement according to this question: Scala - Flatten a List of Maps to Map
But then I realized that I was dealing with different datatypes, in this case Map[String, Object] which means I can't perform operations such as using the scala in-built flatten method to lists.
Is there any way I can convert this Map into a Scala Map so I can apply the necessary transformations to the JSON? Any thoughts on how I should proceed?
Thanks in advance.
The simple answer is to do this
val jsonMap = mapper.readValue[Map[String, List[Map[String, String]]]](jsonString)
and then use the answer from the previous question.
If you want to do it by hand, something like this should work:
val map: Map[String, Map[String, String]] =
jsonMap.collect {
case (k, v: List[_]) =>
val list: List[Map[String, String]] =
v.collect {
case m: Map[_, _] =>
m.collect {
case (k: String, v: String) =>
k -> v
}
}
k -> list.headOption.getOrElse(Map.empty)
}
This will ignore any elements in Object that are not the required type. Nested collect expressions are required because type erasure prevents matching on a nested type in one operation.
This is assuming that you want the head of the List[Map] inside the Object, but it should be clear how to change this to a different transformation.
In Scala, given a list of lists, how can I create one nested HashMap from the elements? I would like to create the HashMap as a hierarchical tree such that for an element at index i, the element at index i - 1 is its parent.
Example for lists of known length:
val lst = List (
List(34, 56, 78),
List(34, 56,79),
List (87, 23, 12),
List(87, 90, 78),
List(1, 45, 87)
)
scala> lst.groupBy(l => l(0))
.mapValues(l => l.groupBy(x => x(1)))
.mapValues{ case x => x.mapValues(y => y.map (z => z(2))) }
res2: scala.collection.immutable.Map[Int,scala.collection.immutable.Map[Int,List[Int]]] = Map(34 -> Map(56 -> List(78, 79)), 1 -> Map(45 -> List(87)), 87 -> Map(23 -> List(12), 90 -> List(78)))
This method works when the length of the elements are known but does not work for an arbitrary length N. Is there any solution that can create this nested map for lists of any length where every list has the same length?
Some preliminary tests seem to indicate that this might work.
def nest(lli: List[List[Int]]): Traversable[_] =
if (lli.head.size == 1)
lli.flatten.distinct
else
lli.groupBy(_.head)
.mapValues(vs => nest(vs.map(_.tail)))
private def buildPartitionTree(partitionValues: List[List[Any]]): Map[Any, Any] = {
val valuesAsNestedMaps = partitionValues.map(_.foldRight(Map[Any,Map[Any,_]]()) { case (partitionValue, map) =>
Map(partitionValue.toString -> map)
}).map(_.asInstanceOf[Map[Any, Any]])
valuesAsNestedMaps.reduce[Map[Any, Any]] { case (map1: Map[Any, Any], map2: Map[Any, Any]) => mergeMaps(map1, map2) }
}
private def mergeMaps(map1 : Map[Any, Any], map2 : Map[Any, Any]) = (map1.keySet ++ map2.keySet).map(key =>
key -> mergeMapValues(map1.get(key), map2.get(key))
).toMap
private def mergeMapValues(o1 : Option[Any], o2 : Option[Any]): Any = (o1, o2) match {
case (Some(v1: Map[Any, Any]), Some(v2: Map[Any, Any])) => mergeMaps(v1, v2)
case (None, Some(x)) => x
case (Some(y), None) => y
}
val nestedMap = buildPartitionTree(lst)
Since the size of sublists is arbitrary you cannot specify the result type of desired function. Consider introducing recursive data structure like this:
trait Tree[A]
case class Node[A](key:A, list:List[Tree[A]]) extends Tree[A]
case class Leaf[A](value:A) extends Tree[A]
Now you can create function producing desired result in terms of trees:
def toTree[A](key:A, list:List[List[A]]):Tree[A] =
if (list.exists(_.isEmpty)) Leaf(key)
else Node(key, list.groupBy(_.head).map {case (k,v) => toTree(k, v.map(_.tail))}.toList)
Since you don't have 'root' value for key, you can call toTree function with some fake key:
toTree(-1, lst)
res1: Node(-1,List(Node(34,List(Node(56,List(Leaf(79), Leaf(78))))), Node(1,List(Node(45,List(Leaf(87))))), Node(87,List(Node(23,List(Leaf(12))), Node(90,List(Leaf(78)))))))
I expect to return a map containing value of different datatypes such as
(key -> String) and (key -> Int), but i can have Map either of
Map[String,String] or Map[String,Int].
I can't use class because number and order of keys are not fixed.
Is there any way to wrap String and Int to a generic class so that i can return map as Map[String,Any]
You can use HMap as #Harnish suggested, but there is an alternative in the scala library: Map[String, Either[Int, String]]. It applies only if you know that the types either one or another and nothing more.
The type Either[Int, String] can be created either by Left(5) or Right("Hello"). Then you can use match to test the value:
x match {
case Left(n) => println(s"$n is a number")
case Right(s) => println(s"$s is a string")
}
Updated
Example:
val dict = scala.collection.mutable.Map[String, Either[String, Int]]()
dict += ("a" -> Right(5))
dict += ("b" -> Left("Hello"))
dict map {
case (key, Right(n)) => println(s"For $key: $n is integer")
case (key, Left(s)) => println(s"For $key: $s is string")
}
I'm not sure if you can do this with the standard collections library, however it is possible using shapeless HMap (Heterogenous map). This is the example given in the docs, which closely matches what you have described:
// Key/value relation to be enforced: Strings map to Ints and vice versa
class BiMapIS[K, V]
implicit val intToString = new BiMapIS[Int, String]
implicit val stringToInt = new BiMapIS[String, Int]
val hm = HMap[BiMapIS](23 -> "foo", "bar" -> 13)
//val hm2 = HMap[BiMapIS](23 -> "foo", 23 -> 13) // Does not compile
scala> hm.get(23)
res0: Option[String] = Some(foo)
scala> hm.get("bar")
res1: Option[Int] = Some(13)
Note, it doesn't give you an Any, instead you have to specify what is valid in your key/value pairs. I'm not sure if that's helpful to you or not...
Having val mapList: List[Map[String, Int]], I want to do something like:
val map = mapList foldLeft (Map[String, Int]()) ( _ ++ _ )
or
val map = mapList foldLeft (Map[String, Int]())
( (m1: Map[String, Int], m2: Map[String, Int]) => m1 ++ m2 )
Neither option is compiled (first says "missing parameter type for expanded function (x, y) => x ++ y" and second says "type mismatch; found (Map[String, Int], Map[String, Int]) => Map[String, Int]; required: String").
I want to achieve a classical solution for concatenating a list of immutable maps such as List( Map("apple" -> 5, "pear" -> 7), Map("pear" -> 3, "apricot" -> 0) ) would produce a Map("apple" -> 5, "pear" -> 10, "apricot" -> 0).
Using scala 2.10.5.
You need to add a dot before foldLeft. You can only use spaces instead of dots under specialized conditions, such as for methods with exactly 1 parameter (arity-1 methods):
val map = mapList.foldLeft(Map[String, Int]()) ( _ ++ _ )
You can read more about method invocation best practices here.
You might also be interested in the reduce methods, which are specialized versions of the fold methods, where the return type is the same as the type of the elements of the collection. For example reduceLeft uses the first element of the collection as a seed for the foldLeft. Of course, since this relies on the first element's existence, it will throw an exception if the collection is empty. Since reduceLeft takes only 1 parameter, you can more easily use a space to invoke the method:
mapList.reduceLeft( _ ++ _)
mapList reduceLeft(_ ++ _)
Finally, you should note that all you are doing here is merging the maps. When using ++ to merge the maps, you will just override keys that are already present in the map – you won't be adding the values of duplicate keys. If you wanted to do that, you could follow the answers provided here, and apply them to the foldLeft or reduceLeft. For example:
mapList reduceLeft { (acc, next) =>
(acc.toList ++ next.toList).groupBy(_._1).toMap.mapValues(_.map(_._2).sum)
}
Or slightly differently:
mapList.map(_.toSeq).reduceLeft(_ ++ _).groupBy(_._1).toMap.mapValues(_.map(_._2).sum)
And, if you're using Scalaz, then most concisely:
mapList reduceLeft { _ |+| _ }
If I have a collection c of type T and there is a property p on T (of type P, say), what is the best way to do a map-by-extracting-key?
val c: Collection[T]
val m: Map[P, T]
One way is the following:
m = new HashMap[P, T]
c foreach { t => m add (t.getP, t) }
But now I need a mutable map. Is there a better way of doing this so that it's in 1 line and I end up with an immutable Map? (Obviously I could turn the above into a simple library utility, as I would in Java, but I suspect that in Scala there is no need)
You can use
c map (t => t.getP -> t) toMap
but be aware that this needs 2 traversals.
You can construct a Map with a variable number of tuples. So use the map method on the collection to convert it into a collection of tuples and then use the : _* trick to convert the result into a variable argument.
scala> val list = List("this", "maps", "string", "to", "length") map {s => (s, s.length)}
list: List[(java.lang.String, Int)] = List((this,4), (maps,4), (string,6), (to,2), (length,6))
scala> val list = List("this", "is", "a", "bunch", "of", "strings")
list: List[java.lang.String] = List(this, is, a, bunch, of, strings)
scala> val string2Length = Map(list map {s => (s, s.length)} : _*)
string2Length: scala.collection.immutable.Map[java.lang.String,Int] = Map(strings -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4)
In addition to #James Iry's solution, it is also possible to accomplish this using a fold. I suspect that this solution is slightly faster than the tuple method (fewer garbage objects are created):
val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
This can be implemented immutably and with a single traversal by folding through the collection as follows.
val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }
The solution works because adding to an immutable Map returns a new immutable Map with the additional entry and this value serves as the accumulator through the fold operation.
The tradeoff here is the simplicity of the code versus its efficiency. So, for large collections, this approach may be more suitable than using 2 traversal implementations such as applying map and toMap.
Another solution (might not work for all types)
import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)
this avoids the creation of the intermediary list, more info here:
Scala 2.8 breakOut
What you're trying to achieve is a bit undefined.
What if two or more items in c share the same p? Which item will be mapped to that p in the map?
The more accurate way of looking at this is yielding a map between p and all c items that have it:
val m: Map[P, Collection[T]]
This could be easily achieved with groupBy:
val m: Map[P, Collection[T]] = c.groupBy(t => t.p)
If you still want the original map, you can, for instance, map p to the first t that has it:
val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) => p -> ts.head }
Scala 2.13+
instead of "breakOut" you could use
c.map(t => (t.getP, t)).to(Map)
Scroll to "View": https://www.scala-lang.org/blog/2017/02/28/collections-rework.html
This is probably not the most efficient way to turn a list to map, but it makes the calling code more readable. I used implicit conversions to add a mapBy method to List:
implicit def list2ListWithMapBy[T](list: List[T]): ListWithMapBy[T] = {
new ListWithMapBy(list)
}
class ListWithMapBy[V](list: List[V]){
def mapBy[K](keyFunc: V => K) = {
list.map(a => keyFunc(a) -> a).toMap
}
}
Calling code example:
val list = List("A", "AA", "AAA")
list.mapBy(_.length) //Map(1 -> A, 2 -> AA, 3 -> AAA)
Note that because of the implicit conversion, the caller code needs to import scala's implicitConversions.
c map (_.getP) zip c
Works well and is very intuitiv
How about using zip and toMap?
myList.zip(myList.map(_.length)).toMap
For what it's worth, here are two pointless ways of doing it:
scala> case class Foo(bar: Int)
defined class Foo
scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._
scala> val c = Vector(Foo(9), Foo(11))
c: scala.collection.immutable.Vector[Foo] = Vector(Foo(9), Foo(11))
scala> c.map(((_: Foo).bar) &&& identity).toMap
res30: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))
scala> c.map(((_: Foo).bar) >>= (Pair.apply[Int, Foo] _).curried).toMap
res31: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))
This works for me:
val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) {
(m, p) => m(p.id) = p; m
}
The Map has to be mutable and the Map has to be return since adding to a mutable Map does not return a map.
use map() on collection followed with toMap
val map = list.map(e => (e, e.length)).toMap