如何部分减少scala中的列表

How to partially reduce list in scala

我有一个整数列表(代表手牌价值)

val values = List(1, 200, 3, 4, 45, 45, 7, 8)

以及此列表中的索引列表(代表参与底池的牌)

val indexes = List(0, 4, 5, 7)

我需要过滤索引,以便只保留手牌价值最高的索引,相同的最高手牌价值将全部包括在内。

在这种情况下,输出将是 List(4, 5),因为 4 和 5 的最高手牌值都是 45。

我会分几个步骤进行:

val tuples = indexes.map(index => index -> values(index)) // mapping index - value
val maxVal = tuples.map(_._2).max // get max value
val maxIndices = tuples.filter(t => t._2 == maxVal).map(_._1) // filter & map tuples

为简洁起见,我对元组使用了位置表示法(_1_2),这当然可以使用模式匹配来编写,使其更像 scala。

P.S.1:感谢@Joe K 简化聚合。

P.S.2:@Luis Miguel Mejía Suárez 注意到,最后一行可以压缩成一个 collect 模式匹配方法:

tuples.collect { case (i, v) if (v == maxVal) => i }
val indexedValues = values.view.zipWithIndex
val maxValue = indexedValues.toList.filter{case (v,i) => indexes.contains(i)}.maxBy(_._1)._1
indexedValues.filter{ case (v,i) => v==maxValue }.map(_._2).toList

在 Scala REPL 中:

Welcome to Scala 2.13.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_231).
Type in expressions for evaluation. Or try :help.

scala> val values = List(1, 200, 3, 4, 45, 45, 7, 8)
values: List[Int] = List(1, 200, 3, 4, 45, 45, 7, 8)

scala> val indexes = List(0, 4, 5, 7)
indexes: List[Int] = List(0, 4, 5, 7)

scala> val indexedValues = values.view.zipWithIndex
indexedValues: scala.collection.View[(Int, Int)] = View(<not computed>)

scala> val maxValue = indexedValues.toList.filter{case (v,i) => indexes.contains(i)}.maxBy(_._1)._1
maxValue: Int = 45

scala> indexedValues.filter{ case (v,i) => v==maxValue }.map(_._2).toList
res0: List[Int] = List(4, 5)

恕我直言,以自定义方式处理列表的最佳方法是编写尾递归算法。

def filterHighestIndexes(indexes: Set[Int])(data: List[Int]): List[Int] = {
  @annotation.tailrec
  def loop(remaining: List[Int], acc: List[Int], currentMax: Int, currentIdx: Int): List[Int] =
    remaining match {
      case x :: xs =>
        val (newAcc, newMax) = 
          if (!indexes.contains(currentIdx) || x < currentMax)
            acc -> currentMax
          else if(x == currentMax)
            (currentIdx :: acc) -> currentMax
          else
            List(currentIdx)  -> x

        loop(
          remaining = xs,
          newAcc,
          newMax,
          currentIdx + 1
        )

      case Nil =>
        acc.reverse
    }

  data match {
    case x :: xs =>
      loop(
        remaining = xs,
        acc = List(0),
        currentMax = x,
        currentIdx = 1
      )

    case Nil =>
      List.empty
  }
}

你可以这样测试

val values = List(1, 200, 3, 4, 45, 45, 7, 8)
val indexes = Set(0, 4, 5, 7)

filterHighestIndexes(indexes)(values)
// res: List[Int] = List(4, 5)

请注意,我使用 Set 而不是 List 作为索引只是为了使 contains 更有效率。如果你不能改变它,可以只使用 List.

我觉得这个就这么简单:

scala> indexes.groupBy(values(_)).maxBy(_._1)._2
res6: List[Int] = List(4, 5)