函数式编程风格的过滤列表

时间:2011-01-23 17:14:51

标签: scala functional-programming

我们有一个带有BEGIN和END标记的字符串列表作为此列表的一部分。我们可以在函数式编程风格中过滤出BEGIN-END之间的元素吗?我只是在scala中使用这种常规(标记)方法出来。

val list1 =
  """992
  1010
  1005
  1112
  BEGIN
  1086
  1244
  1107
  1121
  END
  1223
  1312
  1319
  1306
  1469""".lines.toList

var flag = false
val filteredList = list1.filter{
  def f(x: String): Boolean = {
    if (x.contains("BEGIN")) {
      flag = true;
      return false
    } else if (x.contains("END")) {
      flag = false
    }
    flag
  }
  f
}

可以避免定义标志变量吗?他们如何用纯函数式语言解决这个问题?

6 个答案:

答案 0 :(得分:7)

您可以使用drop / taildropWhiletakeWhile函数:

val filteredList = list1.map(_.trim).dropWhile("BEGIN" !=).tail.takeWhile("END" !=)

修改

如评论tail中所述,如果列表为空,则会抛出异常,因此如果您希望保持安全,请使用drop(1)代替tail

val filteredList = list1.map(_.trim).dropWhile("BEGIN" !=).drop(1).takeWhile("END" !=)

这是我的算法版本,它处理了几个 BEGIN END 部分(一些疯狂的东西来自我 - 一个小状态机:)

var filteredList1 = list1.map(_.trim).foldLeft(List(None): List[Option[List[String]]]) {
  case (None :: rest, "BEGIN") => Some(Nil) :: rest
  case (Some(list) :: rest, "END") => None :: Some(list) :: rest
  case (Some(current) :: rest, num) => Some(num :: current) :: rest
  case (result, _) => result
}.flatten.reverse map (_.reverse)

返回List[List[String]]

答案 1 :(得分:3)

首先,列表中的每个字符串都包含行开头的空格。

这是您的代码中最大的问题,有两种方法可以解决它。

修剪线条......

val list1 =
  """992
  1010
  ...
  1306
  1469""".lines.map(_.trim).toList

...或者您可以在每行前加|并使用stripMargin

然后,应用takeWhile / dropWhile

只是一个小问题
list1.takeWhile("BEGIN" !=) ++ list1.dropWhile("END"!=).tail

或更有效率:

val (begin,middle) = list1.span("BEGIN" !=)
val end = middle.dropWhile("END" !=).tail
begin ++ end

修改

我将解决方案放回到前面,这会在BEGINEND之间删除(过滤掉)值。保留它们:

list1.dropWhile("BEGIN" !=).tail.takeWhile("END"!=)

编辑2

在这里迎接挑战......我将允许多个BEGIN / END块,但也要考虑输入可能严重错误。如果BEGIN没有相应的END怎么办?也许连续有两个BEGIN,或者列表在END之前耗尽。

定义一些规则:

  • 忽略没有相应BEGIN的END
  • BEGIN / END块不嵌套
  • 在已经在块中遇到的BEGIN启动新块
  • 如果列表中的列表用完,则假定隐式END

不用多说,首先创建一个标识输入中每个"BEGIN"的迭代器:

val blocksStarts =
  Iterator.iterate(list1)(_.dropWhile("BEGIN" !=).drop(1)).drop(1).takeWhile(Nil !=)

//This iterator tries to continue forever,
//returning Nils once the sequences are exhausted
//For this reason, we must use drop(1) instead of tail

给出列表的迭代器,每个列表从"BEGIN"

开始

然后从每个列表中获取元素,直到达到相应的"END"或其他"BEGIN",或者列表用尽:

val blocks = blockStarts map {
  _.takeWhile(x => x != "BEGIN" && x != "END")
} toList

最终toList是因为此时它仍然是Iterator。您现在有一个列表列表,每个列表对应于“块”中的一批元素,如前面的规则所定义。

答案 2 :(得分:2)

我正在扩展其他人的答案,以提供列表中有两个BEGIN ... END块的情况。

val list1 =
  """992
  1010
  1005
  1112
  BEGIN
  1086
  1244
  1107
  1121
  END
  1223
  1312
  BEGIN
  773
  990
  224
  END
  1319
  1306
  1469""".lines.map(_.trim).toList

我们将使用foldRight在迭代之间传递状态累加器。 请注意,我们正在使用foldRight来有效地构建结果列表,因此在遇到END之前我们会遇到BEGIN

case class StripStatus(list:List[String], retaincurrent:Boolean)

list1.foldRight(StripStatus(Nil,false)){ (curElem:String, curStatus:StripStatus) =>
   if (curElem == "END")
      StripStatus(curStatus.list,true)
   else if (curElem == "BEGIN")
      StripStatus(curStatus.list,false)
   else if (curStatus.retaincurrent)
      StripStatus(curElem::curStatus.list, true)
   else
      curStatus
}.list

我们可以在最后轻松使用foldLeftreverse结果列表:

list1.foldLeft(StripStatus(Nil,false)){ (curStatus:StripStatus, curElem:String) =>
   if (curElem == "BEGIN")
      StripStatus(curStatus.list,true)
   else if (curElem == "END")
      StripStatus(curStatus.list,false)
   else if (curStatus.retaincurrent)
      StripStatus(curElem::curStatus.list, true)
   else
      curStatus
}.list.reverse

答案 3 :(得分:1)

MMMM。这是我的看法:

def getInside(l: List[String]) = {
    def concat(in: List[String], out: List[String]): List[String] = in ::: off(out)

    def off(l: List[String]): List[String] = 
        if (l.isEmpty) Nil 
        else on(l dropWhile ("BEGIN" !=) drop 1)

    def on(l: List[String]): List[String] = 
        if (l.isEmpty) Nil
        else (concat _).tupled(l span ("END" !=))

    off(l)
}

答案 4 :(得分:0)

我不知道Scala,但您可以定义一个函数,该函数返回与子字符串匹配的下一个元素的列表中的索引,并返回找到子字符串的索引以及之前遇到的元素列表子串匹配。伪代码标头:findSubstr(list, startIndex)。然后构建表达式(更多伪代码):

beginIndex, preBeginElems = findSubstr(list, 0)
endIndex, inBetweenElems = findSubstr(list, beginIndex)
restElems = list[endIndex until the end]

如果有帮助,我可以在Haskell中写一下......:)

编辑:可能还有其他方法可以做到这一点

答案 5 :(得分:0)

同样,目标是在列表中处理多个BEGIN ... END范围。

def getBetweenBeginEnd(l:List[String]) = {
   def internal(l:List[String],accum:List[String]):List[String]={
      val (keep, keepChecking) = l.dropWhile("BEGIN" !=).drop(1).span("END" !=)
      if (keepChecking == Nil)
         accum:::keep
      else
         internal(keepChecking.tail,accum:::keep)
   }
   internal(l,Nil)
}