简体   繁体   English

根据Scala映射中的值对键进行分组

[英]Grouping keys based on values in scala map

Suppose I've a scala map like this - 假设我有一个这样的Scala地图-

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). 注意,可以有多个输出,例如可以有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. 我只需要选择任何人,只要所有键都存在且不应重复这些键即可,例如-地图中已经存在“ B”和“ C”,因此即使我们仍然发现有任何交叉对,不为空,例如我们找到了“ B”和“ C”,则不应添加它。 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. 解决该问题的方法包括将值列表转换为集合,然后遍历map的所有条目,以使其继续与下一个值相交,如果交集不为空,则将键分组。

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. 关键是密钥列表应该很大,其值应至少包含一个元素。

I turned the Map's values into List[Int] just to make it easier to load into the IDE. 我将Map的值转换为List[Int]只是为了使其更易于加载到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 . 然后我把钥匙fold起来看看哪个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. 这是一种“蛮力”方法,它一次为partition()获取交点,而在为累加器建立新条目时再次获取相交点,并且对于每个k值,原始Map m被多次取消引用。


explanation 说明

From the Map's keys I'm building a List of (List[String],List[Int]) tuples. 从地图的钥匙我建立一个List(List[String],List[Int])元组。 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 ). 对于每个Map键,累积列表都在与新键值相交的元素( has )和没有相交的元素( not )之间进行划分。

I grab only the 1st element from the has group. 我只从has组中获取第一个元素。 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. 如果没有,则将该键及其值添加到结果的前面,否则将该键添加到has.head键列表的前面,并调整值列表以反映当前的交集。 That new element is prepended to the has.tail (the intersections being ignored) and combined with everything in the not list. 该新元素在has.tail (相交被忽略)并与not列表中的所有内容组合在一起。

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 谢谢

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM