用 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
。
编辑
我注意到,在您的示例中,在最后一个子列表中,您有两个 8
s 被左侧的 4
替换。如果你想实现这一点,你可以让你的函数递归并添加一个默认情况(当所有8
s 被替换时)。
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
也会在一个开头有两个 8
s 和一些其他边缘情况的列表上循环。所以这里有一个更通用的模式匹配解决方案:
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 中的下一个或上一个元素替换某些元素的主要内容,如果未能解决你的问题,请参考以下文章