繁体   English   中英

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

[英]How to elegantly extract range of list based on specific criteria?

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

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

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

将它保存在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. 获取所有元素的列表,直到最新匹配的元素,然后是匹配元素和后面的元素。

命名问题很简单,无法实现适当的功能,因此不需要细分。

这是第一个函数的实现:

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)

第二个测试返回一个空列表,因为满足谓词的最外层元素没有前导(或后继)。

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()

拉链并映射到救援

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而不是过滤器 - >地图)...

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM