Scala 中的不可变向量或列表生成器

Immutable Vector or List Builder in Scala

我 运行 需要根据某些条件将某些条目提取到相应的列表中。这是我的代码

var keys = Vector[String]()
var data = Vector[String]()

for ((k, v) <- myMap) {
  if (v.endsWith("abc")) { keys = keys :+ v }
  if (v.endsWith("xyz")) { data = data :+ v }
}

在不将 keysdata 设为 var 的情况下实现此逻辑的最佳方法是什么? Scala 中有不可变列表生成器这样的东西吗?

例如番石榴中的ImmutableList.Builder(Java)https://google.github.io/guava/releases/21.0/api/docs/com/google/common/collect/ImmutableList.Builder.html

使用 foldLeft 怎么样?

val map: Map[Int, String] = Map(
    1 -> "abc",
    2 -> "xyz",
    3 -> "abcxyz",
    4 -> "xyzabc"
)

val r = map.foldLeft((Seq.empty[String], Seq.empty[String])) {
    case ((keys, data), (k, v)) =>
        if (v.endsWith("abc")) {
            (keys :+ v, data)
        }
        else if (v.endsWith("xyz")) {
            (keys, data :+ v)
        }
        else {
            (keys, data)
        }

}
r match { 
    case (keys, data) =>
        println(s"keys: $keys")
        println(s"data: $data")
}

如果您被迫使用 var 或可变集合(超出您的优化需要),您可能没有正确考虑问题。

假设我们有一张地图m

Map(1 -> "abc", 2 -> "xyz")

现在,我们可以使用递归来解决这个问题(我在这里以尾递归的形式完成):

  type Keys = Vector[String]
  type Data = Vector[String]
  def keyData(m: Map[Int, String]): (Keys, Data) = {
    def go(keys: Keys, data: Data, m: List[(Int, String)]): (Keys, Data) =
      m match {
        case (k, v) :: ks if v endsWith("abc") =>
          go(v +: keys, data, ks)
        case (k, v) :: ks if v endsWith("xyz") =>
          go(keys, v +: data, ks)
        case k :: ks =>
          go(keys, data, ks)
        case _ => (keys, data)
      }
    go(Vector.empty[String], Vector.empty[String], m.toList)
  }

这将获取一个映射并生成一对向量,其中包含与您列出的谓词匹配的字符串数据。现在,假设我们想要将地图元素抽象并划分为满足任意两个谓词 p: Int => Booleanq: Int => Boolean 的向量。然后,我们将得到如下所示的内容:

  type Keys = Vector[String]
  type Data = Vector[String]
  def keyData(m: Map[Int, String], p: Int => Boolean, q: Int => Boolean): (Keys, Data) = {
    def go(keys: Keys, data: Data, m: List[(Int, String)]): (Keys, Data) =
      m match {
        case (k, v) :: ks if p(v) =>
          go(v +: keys, data, ks)
        case (k, v) :: ks if q(v) =>
          go(keys, v +: data, ks)
        case k :: ks =>
          go(keys, data, ks)
        case _ => (keys, data)
      }
    go(Vector.empty[String], Vector.empty[String], m.toList)
  }

现在,我们可以对任何键和值类型 KV 进行抽象:

def partitionMapBy[K, V](m: Map[K, V], p: V => Boolean, q: V => Boolean): (Vector[V], Vector[V]) = {
    def go(keys: Vector[V], data: Vector[V], m: List[(K, V)]): (Vector[V], Vector[V]) =
      m match {
        case (k, v) :: ks if p(v) =>
          go(v +: keys, data, ks)
        case (k, v) :: ks if q(v) =>
          go(keys, v +: data, ks)
        case k :: ks =>
          go(keys, data, ks)
        case _ => (keys, data)
      }
    go(Vector.empty[V], Vector.empty[V], m.toList)
  }

您会注意到这里的递归并没有什么特别的地方。这意味着我们可以使用折叠来完成同样的事情。这是使用 foldLeft:

的实现
def partitionMapBy[K, V](m: Map[K, V])(p: V => Boolean)(q: V => Boolean): (Vector[V], Vector[V]) =
    m.foldLeft[(Vector[V], Vector[V])]((Vector.empty[V], Vector.empty[V])) {
      case (acc @ (keys: Vector[V], data: Vector[V]), (_, v: V)) =>
        if(p(v)) (v +: keys, data)
        else if(q(v)) (keys, v +: data)
        else acc
    }

你可以看到,对于 m,we get the this,如果你让 p_ endsWith("abc")q_ endsWith("xyz") ,那么你就会得到你想要的。 `

您可以根据需要对值进行分区。

val (keys, notKeys) = myMap.values.partition(_.endsWith("abc"))
val (data, _)       = notKeys.partition(_.endsWith("xyz"))

您的 keysdata collections 将是 List[String] 而不是 Vector,但如果需要,这很容易 mod。

每个 Scala 集合都带有仅附加构建器:

val keysB, dataB = Vector.newBuilder[String]

for ((k, v) <- myMap) {
  if (v.endsWith("abc")) { keysB += v }
  if (v.endsWith("xyz")) { dataB += v }
}

val keys = keysB.result()
val data = dataB.result()