【问题标题】:Scala group by mapping keysScala 通过映射键分组
【发布时间】:2018-02-16 06:22:31
【问题描述】:

假设我有一张这样的地图:

val m: Map[Int, String] = Map(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d", ...)

假设我有一个函数:def f(i: Int): String,我想将其应用于此地图的键。然后我想按结果 f(key) 进行分组,并连接属于同一个 f(key) 组的所有字符串(值)。

是否有一种有效的方法来“groupByKey”,同时在普通 scala 中映射键(无火花)。

【问题讨论】:

  • 您的样本数据 m 都是不同的键。你想在那里分组? f(i) 是否为某些 (i: Int) 返回相同的结果,并且您希望根据该结果进行分组?

标签: scala


【解决方案1】:

如果我了解情况,你有一张地图……

val m: Map[K,V] = ...

...以及转换键的功能/方法...

def f(k: K):J = ...  //K and J can be same type

...您想将Map 中的键分组,然后将Map 中的所有值收集到它们的新组中。

m.keys                 //Iterable[K]
 .toList               //List[K]
 .groupBy(f)           //Map[J,List[K]]
 .mapValues(_.map(m))  //Map[J,List[V]]

如果您原来的V 本身就是List,那么您可以使用flatMap() 而不是map(),以使结果变平。

toList 步骤是可选的。没有它,结果是Map[J,Iterable[V]]

【讨论】:

    【解决方案2】:

    因为 1 和 3 都被映射到 'odd' 并且 Map 必须强制键的唯一性,所以一对将被丢​​弃。一个简单的解决方案是将原始 Map[Int, String] 转换为不强制唯一性的 Seq[Int, String]。例如:

    m.toSeq.map {
    case (k, v) => (if (k % 2 == 0) "even" else "odd", v)
    }.groupBy(_._1).mapValues(
      values => values.map(_._2)
    )
    

    【讨论】:

      【解决方案3】:

      我建议使用foldLeft 来执行此操作:

      val m: Map[Int, String] = Map(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d")
      m: Map[Int,String] = Map(1 -> a, 2 -> b, 3 -> c, 4 -> d)
      
      m.foldLeft(Map.empty[String, String]) {
          case (accum, (i, s)) if i % 2 == 0 => accum + ("even" -> accum.get("even").fold(s)(existing => existing + s))
          case (accum, (i, s)) => accum + ("odd" -> accum.get("odd").fold(s)(existing => existing + s))
      }
      
      //scala.collection.immutable.Map[String,String] = Map(odd -> ac, even -> bd)
      

      编辑:

      我有很多案例,而不仅仅是“偶数”和“奇数”。如果我想以 1000 为模怎么办,所以有 1000 个不同的组。我无法在 foldLeft 函数中将它们全部列举为案例。有没有更通用的方法来做到这一点?

      为了概括这一点,我们只需要使用比“偶数”或“奇数”更好的键。在这种情况下,听起来密钥将基于i。因此,让我们仅以模块 10 为例来避免大型控制台打印输出。让我们用一堆东西制作一张新地图:

      import scala.util.Random
      val testMap = (0 to 30).map(i => i -> Random.alphanumeric.filter(_.isLetter).take(Random.nextInt(3)).mkString("")).toMap
      

      这可以使我们的每个条目的每个地方都有一些随机字母的示例地图。

      然后它实际上只有一次,因为我们的键控功能现在只是 i 本身模块 10。

      testMap.foldLeft(Map.empty[Int, String]) {
          case (accum, (i, s)) => accum + (i % 10 -> accum.get(i % 10).fold(s)(existing => existing + s))
      }
      

      或者,为了让事情更明显:

      testMap.foldLeft(Map.empty[Int, String]) {
          case (accum, (i, s)) => {
              val key = i % 10
              accum + (key -> accum.get(key).fold(s)(existing => existing + s))
          }
      }
      

      这是一个示例运行:

      testMap: scala.collection.immutable.Map[Int,String] = Map(0 -> q, 5 -> Ax, 10 -> a, 24 -> AX, 25 -> "", 14 -> U, 20 -> "", 29 -> R, 1 -> hB, 6 -> ky, 28 -> ch, 21 -> dk, 9 -> v, 13 -> BR, 2 -> R, 17 -> "", 22 -> h, 27 -> "", 12 -> "", 7 -> "", 3 -> "", 18 -> "", 16 -> Qu, 11 -> XO, 26 -> gS, 23 -> "", 8 -> "", 30 -> fn, 19 -> "", 4 -> "", 15 -> Br)
      res6: scala.collection.immutable.Map[Int,String] = Map(0 -> fn, 5 -> Br, 1 -> XO, 6 -> gS, 9 -> "", 2 -> "", 7 -> "", 3 -> "", 8 -> "", 4 -> U)
      
      scala> testMap.foldLeft(Map.empty[Int, String]) {
           | case (accum, (i, s)) => accum + (i % 10 -> accum.get(i % 10).fold(s)(existing => existing + s))
           | }
      res7: scala.collection.immutable.Map[Int,String] = Map(0 -> qafn, 5 -> AxBr, 1 -> hBdkXO, 6 -> kyQugS, 9 -> Rv, 2 -> Rh, 7 -> "", 3 -> BR, 8 -> ch, 4 -> AXU)
      

      【讨论】:

      • 这对我的问题来说没问题,但在实践中,我有很多案例,而不仅仅是“偶数”和“奇数”。如果我想以 1000 为模怎么办,所以有 1000 个不同的组。我无法在 foldLeft 函数中将它们全部列举为案例。有没有更通用的方法来做到这一点?
      • @user3685285 我已经用广义折叠更新了答案
      【解决方案4】:

      如果我正确理解您更改的要求,您是否正在尝试连接 Map 的所有值,其键可被给定整数整除?如果是这样,这是一种方法:

      val m: Map[Int, String] = Map(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d", 5 -> "e", 6 -> "f", 7 -> "g")
      
      def f(m: Map[Int, String], n: Int): String =
        m.filter{
          case (k, v) => k % n == 0
        }.toSeq.map{
          case (k, v) => (n, v)
        }.groupBy(_._1).
        mapValues(_.map(_._2).mkString).
        getOrElse(n, "")
      
      f(m, 3)
      // res1: String = fc
      

      【讨论】:

      • @user3685285,您原来的要求似乎发生了重大变化。请查看我的更新答案。
      【解决方案5】:

      只是为了测试,我们假设 f 是某个东西,这导致不同 i 的 f(i) 相同:

      def f(i: Int) : String = if (i % 2 == 0) "even" else "odd" 
      f: (i: Int)String
      

      这样我们就可以分组了。

      val m: Map[Int, String] = Map(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d")
      

      映射:

      m.map {case (k,v) => ((k, (f(k), v)))}
      res82: scala.collection.immutable.Map[Int,(String, String)] = Map(1 -> (odd,a), 2 -> (even,b), 3 -> (odd,c), 4 -> (even,d))
      

      分组:

      scala> m.map {case (k,v) => ((k, (f(k), v)))}.groupBy {case (k,(fs, v)) => fs} 
      res83: scala.collection.immutable.Map[String,scala.collection.immutable.Map[Int,(String, String)]] = Map(odd -> Map(1 -> (odd,a), 3 -> (odd,c)), even -> Map(2 -> (even,b), 4 -> (even,d)))
      

      一个函数,用于从地图中提取字符串并将它们连接起来:

      scala> def myconcat (m: Map [Int, (String, String)]) : String = m.values.foldLeft ("") {(z, s2) => z + s2._2}
      myconcat: (m: Map[Int,(String, String)])String
      

      结合上面的映射和分组:

      scala> m.map {case (k,v) => ((k, (f(k), v)))}.groupBy  {case (k,(fs, v)) => fs}.map (fsv => myconcat (fsv._2)) 
      res90: scala.collection.immutable.Iterable[String] = List(ac, bd)
      

      相当复杂。 :)

      【讨论】:

        猜你喜欢
        • 2022-01-26
        • 1970-01-01
        • 2018-12-11
        • 1970-01-01
        • 1970-01-01
        • 2013-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多