【问题标题】:Scala - Get the nearest key value from both sidesScala - 从双方获取最近的键值
【发布时间】:2019-12-30 19:28:05
【问题描述】:

我正在努力使用给定的输入整数以两种方式获取最接近的键值。

例子:

定义的地图如下。

val m = Map(10 -> "W",20 -> "W",30 -> "I",40 -> "A",50 -> "P",60 -> "S",70 -> "A",80 -> "A",90 -> "A",100 -> "I",110 -> "A",120 -> "E")

键是整数,它们按顺序递增。 如果说 95 作为输入,那么我应该得到如下的元组输出 ((90->"A"), (100->"I"))

scala> m.map( x=> (x._1,x._2)).toList.sortBy(_._1).filter( _._1<=95 ).last
res74: (Int, String) = (90,A)

scala>  m.map( x=> (x._1,x._2)).toList.sortBy(_._1).filter( _._1>=95 ).head
res75: (Int, String) = (100,I)

scala>

地图大小在实际场景中会很大(1K),我想避免 sortBy()。是否有任何可用的 foldLeft 或 map 解决方案?

【问题讨论】:

    标签: scala


    【解决方案1】:

    这是一个非常直接的解决方案。无需排序。

    def nearest[V](m :Map[Int,V], k :Int) :Seq[(Int,V)] =
      m.get(k).fold {
        val (before, after) = m.keys.partition(_ < k)
        Seq(before.maxOption, after.minOption).flatten.map(x => (x,m(x)))
      }(v => Seq((k,v)))
    

    测试:

    val m = Map(10 -> "W",20 -> "W",30 -> "I",40 -> "A",50 -> "P",60 -> "S",70 -> "A",80 -> "A",90 -> "A",100 -> "I",110 -> "A",120 -> "E")
    nearest(m, -7)   //res0: Seq[(Int, String)] = List((10,W))
    nearest(m, 60)   //res1: Seq[(Int, String)] = List((60,S))
    nearest(m, 93)   //res2: Seq[(Int, String)] = List((90,A), (100,I))
    nearest(m, 121)  //res3: Seq[(Int, String)] = List((120,E))
    

    【讨论】:

      【解决方案2】:

      这是一种利用TreeMap 的排序和范围查询功能的方法,如下所示:

      def nearestValues(m: Map[Int, String], key: Int) = {
        import scala.collection.immutable.TreeMap
        val tm = TreeMap(m.toSeq: _*)
      
        Seq(tm.to(key).lastOption, tm.from(key).headOption).flatten.distinct
      }
      
      val m = Map(
        10 -> "W", 20 -> "W", 30 -> "I", 40 -> "A", 50 -> "P", 60 -> "S",
        70 -> "A", 80 -> "A", 90 -> "A", 100 -> "I", 110 -> "A", 120 -> "E"
      )
      
      nearestValues(m, 95)
      // res1: Seq[(Int, String)] = List((90,A), (100,I))
      
      nearestValues(m, 20)
      // res2: Seq[(Int, String)] = List((20,W))
      
      nearestValues(m, 125)
      // res3: Seq[(Int, String)] = List((120,E))
      

      请注意,上述方法返回 Seq 而不是 Tuple 以适应完全匹配或单边匹配的情况。要返回 Tuple,可以使用类似于以下内容的内容:

      def nearestValues(m: Map[Int, String], key: Int) = {
        import scala.collection.immutable.TreeMap
        val tm = TreeMap(m.toSeq: _*)
      
        Seq(tm.to(key).lastOption, tm.from(key).headOption) match {
          case Seq(None, None) => (0 -> "", 0 -> "")  // Default tuple for empty Map
          case Seq(x, None)    => (x.get, Int.MaxValue -> "")
          case Seq(None, y)    => (Int.MinValue -> "", y.get)
          case Seq(x, y)       => (x.get, y.get)
        }
      }
      
      nearestValues(m, 95)
      // res1: ((Int, String), (Int, String)) = ((90,A),(100,I))
      
      nearestValues(m, 20)
      // res2: ((Int, String), (Int, String)) = ((20,W),(20,W))
      
      nearestValues(m, 125)
      // res3: ((Int, String), (Int, String)) = ((120,E),(2147483647,""))
      

      更新

      启动 Scala 2.13TreeMap 的方法 tofrom 分别替换为 rangeTorangeFrom

      【讨论】:

      • TreeMap 在构建过程中仍然对集合进行排序。
      • @Illia Popov,这是正确的,如答案中所述。
      【解决方案3】:

      你可以使用 Leo C 提出的有序集合,但是这需要集合构造然后搜索,所以这种算法的复杂度将是O(n*log n)

      以算法的方式处理此任务,您可以计算键的差异,然后找到最接近 0 的负值和正值。在这种情况下,复杂度会更低O(n)

      以下示例返回从左侧和右侧最近的键,不包括完全匹配(您可以通过更改过滤器中的条件来更改它):

      val data = Map(1-> "a", 5->"b")
      val key = 4
      
      val diffs = data.keys.map(_ - key)
      val rightKeyOpt = diffs.filter(_ > 0).reduceOption(_ min _).map(_ + key)
      val leftKeyOpt = diffs.filter(_ < 0).reduceOption(_ max _).map(_ + key)
      
      val result = (leftKeyOpt.map(k=> k->data(k)), rightKeyOpt.map(k=> k->data(k)))
      
      println (leftKeyOpt, rightKeyOpt)
      println result
      

      如果您非常需要,我敢打赌这可以在一行中完成:)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-16
        • 1970-01-01
        • 1970-01-01
        • 2023-02-09
        • 1970-01-01
        • 2017-02-04
        • 2021-01-23
        • 2018-09-22
        相关资源
        最近更新 更多