用 Scala List 中的下一个或上一个元素替换某些元素

Posted

技术标签:

【中文标题】用 Scala List 中的下一个或上一个元素替换某些元素【英文标题】:Replacement some element with next or previous element in 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) - 但您为什么要这样做?进行替换的规则是什么?请澄清。 【参考方案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 的解决方案

以上是关于用 Scala List 中的下一个或上一个元素替换某些元素的主要内容,如果未能解决你的问题,请参考以下文章

Scala groupBy 项目列表中的所有元素

Scala中的列表可以添加元素吗?

scala中的List

如何从 Scala 中的映射键中获取值的常见元素?

Scala语言之高阶函数

选择组中的下一个单选按钮