【问题标题】:Replacement some element with next or previous element in Scala List用 Scala List 中的下一个或上一个元素替换某些元素
【发布时间】:2017-03-15 05:22:25
【问题描述】:

在了解了 scala 中的一些功能后,我尝试从列表中修补元素:

val aList: List[List[Int]] = List(List(7,2,8,5,2),List(8,7,3,3,3),List(7,1,4,8,8))

如果 8 的位置是头部,我想要做的是用右邻居元素替换 8,否则如果 8 是尾部,则用左邻居替换。

更新后的 aList 应该是:

List[List[Int]] = List(List(7,2,2,5,2),List(7,7,3,3,3),List(7,1,4,4,4))

我试过以下代码:

def f(xs: List[Int]) = xs match {
  case x0 :: x1 :: x2 :: x3 :: x4 => List(x0,x1,x2,x3,x4)
  case 8 :: x1 :: x2 :: x3 :: x4 => List(x1,x1,x2,x3,x4)
  case x0 :: 8 :: x2 :: x3 :: x4 => List(x0,x0,x2,x3,x4)
  case x0 :: x1 :: 8 :: x3 :: x4 => List(x0,x1,x1,x3,x4)
  case x0 :: x1 :: x2 :: 8 :: x4 => List(x0,x1,x2,x2,x4)
  case x0 :: x1 :: x2 :: x3 :: 8 => List(x0,x1,x2,x3,x3)
}

aList.flatMap(f)

类型不匹配,因为类型是Product with java.io.Serializable,但需要的是scala.collection.GenTraversableOnce

您能解释一下有什么区别以及它是如何工作的吗?

【问题讨论】:

  • “如果 8 的位置是头部,则将 8 替换为下一个前向元素,如果 8 是尾部,则将其替换为前一个元素” - 如果它不是第一个也不是最后一个,你想做什么?我看到您已替换 (7,2,8,5,2) -> (7,2,2,5,2) - 但您为什么要这样做?进行替换的规则是什么?请澄清。

标签: scala collections serializable flatmap


【解决方案1】:

问题出在最后一个匹配模式中:

case x0 :: x1 :: x2 :: x3 :: 8 => List(x0,x1,x2,x3,x3)

您将8 放在列表尾部的位置,因此它必须具有List[Int] 类型(或者更一般的GenTraversableOnce,正如编译器告诉您的那样)。如果您有固定长度的内部列表,则应将模式更改为最后有 :: Nil

case 8 :: x1 :: x2 :: x3 :: x4 :: Nil => List(x1,x1,x2,x3,x4)
...
case x0 :: x1 :: x2 :: x3 :: 8 :: Nil => List(x0,x1,x2,x3,x3)

另一种选择是

case List(8, x1, x2, x3, x4) => List(x1,x1,x2,x3,x4)
...
case List(x0, x1, x2, x3, 8) => List(x0,x1,x2,x3,x3)

另外,您的第一个模式意味着永远无法到达其他模式,它只是保持列表不变。

如果您的内部列表不一定是固定大小的,您需要一个更通用的解决方案。请澄清一下,如果是这样的话。

另外,如果你想将List[List[Int]] 映射到List[List[Int]],你应该使用.map(f) 而不是flatMap

编辑

我注意到,在您的示例中,在最后一个子列表中,您有两个 8s 被左侧的 4 替换。如果你想实现这一点,你可以让你的函数递归并添加一个默认情况(当所有8s 被替换时)。

def f(xs: List[Int]) = xs match {
  case 8  :: x1 :: x2 :: x3 :: x4 :: Nil => f(List(x1,x1,x2,x3,x4))
  case x0 :: 8  :: x2 :: x3 :: x4 :: Nil => f(List(x0,x0,x2,x3,x4))
  case x0 :: x1 :: 8  :: x3 :: x4 :: Nil => f(List(x0,x1,x1,x3,x4))
  case x0 :: x1 :: x2 :: 8  :: x4 :: Nil => f(List(x0,x1,x2,x2,x4))
  case x0 :: x1 :: x2 :: x3 :: 8  :: Nil => f(List(x0,x1,x2,x3,x3))
  case _ => xs
}

但是即使通过这种方式进行了这些修复,f 也会在一个开头有两个 8s 和一些其他边缘情况的列表上循环。所以这里有一个更通用的模式匹配解决方案:

def f(xs: List[Int]): List[Int] = {
  // if there are only 8s, there's nothing we can do
  if (xs.filter(_ != 8).isEmpty) xs
  else xs match {
    // 8 is the head => replace it with the right (non-8) neighbour and run recursion
    case 8 :: x :: tail if x != 8 => x :: f(x :: tail)
    // 8 is in the middle => replace it with the left (non-8) neighbour and run recursion
    case x :: 8 :: tail if x != 8 => x :: f(x :: tail)
    // here tail either starts with 8, or is empty
    case 8 :: tail => f(8 :: f(tail))
    case x :: tail => x :: f(tail)
    case _ => xs
  }
}

【讨论】:

  • 谢谢,这对我有用。只要意识到如果有两个 8,f 就会混淆。你的逻辑很棒。
  • 您的代码完全适用于逻辑,但我仍然很困惑为什么我们不能使用exist(8) 而不是.isEmpty?
  • exists 方法采用谓词,您可以改为使用 if (!xs.exists(_ != 8)) xs else ...。它有点短,但我不喜欢在if 条件下使用一元否定,这有点容易出错。
【解决方案2】:

适用于任意长度的 xs:

  def f(xs: List[Int]) = {
    if (xs.length <= 1) xs else
    (for {
      i <- 0 until xs.length
    } yield {
      xs(i) match {
        case 8 => if (i == 0) xs(1) else xs(i - 1)
        case _ => xs(i)
      }
    }).toList
  }

【讨论】:

  • 这使用索引对列表元素的非惯用访问,并且将在List(8) 上使用IndexOutOfBoundsException 失败
  • 已编辑以修复 xs.length 的解决方案
猜你喜欢
  • 2011-07-01
  • 2013-03-14
  • 2015-11-09
  • 1970-01-01
  • 2019-12-20
  • 2021-02-19
  • 2013-05-29
  • 2010-09-08
  • 2017-07-05
相关资源
最近更新 更多