如何根据具体标准优雅地提取列表范围?

时间:2013-09-13 09:44:31

标签: scala collections

我想从列表中提取元素范围,满足以下要求:

  • 范围的第一个元素必须是元素匹配特定条件之前的元素
  • 范围的最后一个元素必须是匹配特定条件的元素旁边的元素
  • 示例:对于列表(1,1,1,10,2,10,1,1,1)和条件x >= 10我想得到(1,10,2,10,1)

这是非常简单的命令式编程,但我只是想知道是否有一些智能的Scala功能方式来实现它。是吗?

4 个答案:

答案 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

从主题问题中提取两个更简单的子问题很容易:

  1. 获取匹配后的所有元素的列表,前面有此匹配元素, 前面有一个元素。

  2. 获取所有元素的列表,直到最新匹配的元素,然后是匹配的元素和 之后的元素。

  3. 指定的问题很简单,无法实现适当的功能,因此不需要细分。

    这是第一个函数的实现:

    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
  }
}

这比递归解决方案效率低,但使测试更简单,更清晰。在递归解决方案中匹配正确的模式序列可能很困难,因为模式通常表示所选实现的形状而不是原始数据。通过简单的功能解决方案,每个元素都可以清晰简单地与其邻居进行比较。