我们有一个带有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
}
可以避免定义标志变量吗?他们如何用纯函数式语言解决这个问题?
答案 0 :(得分:7)
您可以使用drop
/ tail
,dropWhile
,takeWhile
函数:
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
修改强>
我将解决方案放回到前面,这会在BEGIN
和END
之间删除(过滤掉)值。保留它们:
list1.dropWhile("BEGIN" !=).tail.takeWhile("END"!=)
编辑2
在这里迎接挑战......我将允许多个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
我们可以在最后轻松使用foldLeft
和reverse
结果列表:
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)
}