我想从列表中提取元素范围,满足以下要求:
x >= 10
我想得到(1,10,2,10,1)这是非常简单的命令式编程,但我只是想知道是否有一些智能的Scala功能方式来实现它。是吗?
答案 0 :(得分:3)
将它保存在scala标准库中,我会使用递归来解决这个问题:
def f(_xs: List[Int])(cond: Int => Boolean): List[Int] = {
def inner(xs: List[Int], res: List[Int]): List[Int] = xs match {
case Nil => Nil
case x :: y :: tail if cond(y) && res.isEmpty => inner(tail, res ++ (x :: y :: Nil))
case x :: y :: tail if cond(x) && res.nonEmpty => res ++ (x :: y :: Nil)
case x :: tail if res.nonEmpty => inner(tail, res :+ x)
case x :: tail => inner(tail, res)
}
inner(_xs, Nil)
}
scala> f(List(1,1,1,10,2,10,1,1,1))(_ >= 10)
res3: List[Int] = List(1, 10, 2, 10, 1)
scala> f(List(2,10,2,10))(_ >= 10)
res4: List[Int] = List()
scala> f(List(2,10,2,10,1))(_ >= 10)
res5: List[Int] = List(2, 10, 2, 10, 1)
也许在这个解决方案中我没有想到的东西,或者我错过了一些东西,但我认为你会得到基本的想法。
答案 1 :(得分:2)
良好的功能算法设计实践就是将复杂问题分解为更简单的问题。 该原则称为Divide and Conquer。
从主题问题中提取两个更简单的子问题很容易:
获取匹配后的所有元素的列表,前面有此匹配元素, 前面有一个元素。
获取所有元素的列表,直到最新匹配的元素,然后是匹配的元素和 之后的元素。
指定的问题很简单,无法实现适当的功能,因此不需要细分。
这是第一个函数的实现:
def afterWithPredecessor
[ A ]
( elements : List[ A ] )
( test : A => Boolean )
: List[ A ]
= elements match {
case Nil => Nil
case a :: tail if test( a ) => Nil // since there is no predecessor
case a :: b :: tail if test( b ) => a :: b :: tail
case a :: tail => afterWithPredecessor( tail )( test )
}
由于第二个问题可以看作是第一个问题的直接反转,因此可以通过反转输入和输出轻松实现:
def beforeWithSuccessor
[ A ]
( elements : List[ A ] )
( test : A => Boolean )
: List[ A ]
= afterWithPredecessor( elements.reverse )( test ).reverse
但这是这个的优化版本:
def beforeWithSuccessor
[ A ]
( elements : List[ A ] )
( test : A => Boolean )
: List[ A ]
= elements match {
case Nil => Nil
case a :: b :: tail if test( a ) =>
a :: b :: beforeWithSuccessor( tail )( test )
case a :: tail =>
beforeWithSuccessor( tail )( test ) match {
case Nil => Nil
case r => a :: r
}
}
最后,将上述函数组合在一起以产生解决问题的函数变得非常简单:
def range[ A ]( elements : List[ A ] )( test : A => Boolean ) : List[ A ]
= beforeWithSuccessor( afterWithPredecessor( elements )( test ) )( test )
scala> range( List(1,1,1,10,2,10,1,1,1) )( _ >= 10 )
res0: List[Int] = List(1, 10, 2, 10, 1)
scala> range( List(1,1,1,10,2,10,1,1,1) )( _ >= 1 )
res1: List[Int] = List()
scala> range( List(1,1,1,10,2,10,1,1,1) )( _ == 2 )
res2: List[Int] = List(10, 2, 10)
第二个测试返回一个空列表,因为满足谓词的最外层元素没有前导(或后继)。
答案 2 :(得分:1)
def range[T](elements: List[T], condition: T => Boolean): List[T] = {
val first = elements.indexWhere(condition)
val last = elements.lastIndexWhere(condition)
elements.slice(first - 1, last + 2)
}
scala> range[Int](List(1,1,1,10,2,10,1,1,1), _ >= 10)
res0: List[Int] = List(1, 10, 2, 10, 1)
scala> range[Int](List(2,10,2,10), _ >= 10)
res1: List[Int] = List(2, 10, 2, 10)
scala> range[Int](List(), _ >= 10)
res2: List[Int] = List()
答案 3 :(得分:1)
压缩并映射到救援
val l = List(1, 1, 1, 10, 2, 1, 1, 1)
def test (i: Int) = i >= 10
((l.head :: l) zip (l.tail :+ l.last)) zip l filter {
case ((a, b), c) => (test (a) || test (b) || test (c) )
} map { case ((a, b), c ) => c }
那应该有用。我只有我的智能手机,距离我可以测试的地方几英里,所以对任何拼写错误或轻微的语法错误表示道歉
编辑:现在有效。我希望很明显,我的解决方案将列表向右和向左移动以创建两个新列表。当这些被压缩并再次使用原始列表压缩时,结果是一个元组列表,每个元组包含原始元素和其邻居的元组。这对于过滤和映射回简单列表来说是微不足道的。
将其变为更通用的功能(并使用collect
而不是过滤器 - > map)......
def filterWithNeighbours[E](l: List[E])(p: E => Boolean) = l match {
case Nil => Nil
case li if li.size < 3 => if (l exists p) l else Nil
case _ => ((l.head :: l) zip (l.tail :+ l.last)) zip l collect {
case ((a, b), c) if (p (a) || p (b) || p (c) ) => c
}
}
这比递归解决方案效率低,但使测试更简单,更清晰。在递归解决方案中匹配正确的模式序列可能很困难,因为模式通常表示所选实现的形状而不是原始数据。通过简单的功能解决方案,每个元素都可以清晰简单地与其邻居进行比较。