Reputation: 1006
Suppose I've a scala map like this -
Map[String, List[String]] = Map(
"A"->List(1,2,3),
"B"->List(2,4),
"C"->List(4,5),
"D"->List(7,8,9),
"E"->List(8,9),
"F"->List(4,5,7),
"G"->List(1,3,2)
)
I want to group the keys such that their intersection is not empty. For example the result should be like -
Map(
List("A","B","G")->List(2),
List("C","F")->List(4,5),
List("D","E")->List(8,9)
)
Note that there can be multiple outputs for eg there can be List("B","C")->List(4). I just need to select anyone as long as all the keys are present AND the keys should not be repeated, for eg - 'B' and 'C' are present in the map already so later even if we still found any pair for intersection is not empty, for instance we found 'B' and 'C', we should NOT add it. My way to approach this problem includes converting list of values to a set and then iterate through all entries of map such that it keeps on intersecting its value with the next value and if intersection is not empty, we will group the keys.
But I want to know if there is any other way to do this.
EDIT -
The point is the key list should be the large and its value should have at least one element.
Upvotes: 1
Views: 711
Reputation: 51271
I turned the Map's values into List[Int]
just to make it easier to load into the IDE. That can be easily undone.
val m :Map[String, List[Int]] = Map(
"A"->List(1,2,3),
"B"->List(2,4),
"C"->List(4,5),
"D"->List(7,8,9),
"E"->List(8,9),
"F"->List(4,5,7),
"G"->List(1,3,2))
Then I fold
over the keys to see which ones intersect
.
m.keys.foldLeft(List.empty[(List[String],List[Int])]){case (acc,k) =>
val (not, has) = acc.partition(_._2.intersect(m(k)).isEmpty)
has.headOption.fold((List(k),m(k)) :: acc){case (s,v) =>
(k::s, v.intersect(m(k))) :: has.tail ::: not
}
}.toMap
//res0: Map[List[String],List[Int]] = Map(List(D, E) -> List(8, 9)
// ,List(C, F) -> List(4, 5)
// ,List(B, G, A) -> List(2))
It's a bit of a "brute force" approach, getting the intersection once for the partition()
and again when building the new entry to the accumulator, and the original Map m
is dereferenced multiple times for each k
value.
explanation
From the Map's keys I'm building a List
of (List[String],List[Int])
tuples. For each Map key, the accumulated list is partitioned between those elements where there is an intersection with the new key's value (has
), and those where there is no intersection (not
).
I grab only the 1st element from the has
group. If there is none then this key, and its value, are prepended to the result, else the key is prepended to the has.head
key list and the value list is adjusted to reflect the current intersection. That new element is prepended to the has.tail
(the intersections being ignored) and combined with everything in the not
list.
Upvotes: 2
Reputation: 3638
You can follow this approach for getting the desired output.
Lets say you have an input of the following format.
val map1: Map[String, List[Int]] = Map(
"A"->List(1,2,3),
"B"->List(2,4),
"C"->List(4,5),
"D"->List(7,8,9),
"E"->List(8,9),
"F"->List(4,5,7)
)
To get unique values we convert the List of values to a set so that we get the intersection between values
val map2 = map1.map(x => (x._1,x._2.toSet))
val result: Map[List[String], Set[Int]] =
for(a <- map2;
b <- map2 if( (a._1!=b._1) && (a._2 intersect b._2).nonEmpty))
yield( List(a._1,b._1) -> (a._2 intersect b._2) )
This function will give you result as
result: Map[List[String],Set[Int]] = Map(List(D, F) -> Set(7), List(C, F) -> Set(4, 5), List(E, D) -> Set(8, 9), List(B, C) -> Set(4), List(B, A) -> Set(2), List(A, B) -> Set(2), List(F, C) -> Set(4, 5), List(C, B) -> Set(4), List(D, E) -> Set(8, 9), List(F, D) -> Set(7), List(F, B) -> Set(4), List(B, F) -> Set(4))
Please let me know if you have any queries regarding this approach. Thanks
Upvotes: 0