Haskell-基于嵌套列表理解中的测试排除列表

时间:2019-03-20 22:50:59

标签: haskell filter list-comprehension

我想根据一般规格创建一系列可能的方程式:

test = ["12", "34=", "56=", "78"]

每个字符串(例如“ 12”)代表该位置的可能字符,在这种情况下为“ 1”或“ 2”。) 因此,来自测试的可能方程式为“ 13 = 7”或“ 1 = 68”。 我知道我给出的示例并不均衡,但这是因为我故意给出了一个简化的短字符串。 (我也知道我可以使用“序列”来搜索所有可能性,但我想变得更聪明,因此需要下面说明的另一种方法。)

我想要的是尝试依次固定每个等式,然后删除等式中的所有其他等式。所以我想要:

[["12","=","56","78"],["12","34","=","78”]]

我写了这个嵌套列表理解: (它需要:{-#LANGUAGE ParallelListComp#-})

fixEquals :: [String] -> [[String]]
fixEquals re
  = [
      [
        if index == outerIndex then equals else remain 
        | equals <- map (filter (== '=')) re
        | remain <- map (filter (/= '=')) re
        | index <- [1..]
      ]

      | outerIndex <- [1..length re]
    ]

这将产生:

[["","34","56","78"],["12","=","56","78"],["12","34","=","78"],["12","34","56","”]]

但是我想过滤掉其中任何带有空列表的东西。即在这种情况下,第一个和最后一个。

我可以做到:

countOfEmpty :: (Eq a) => [[a]] -> Int 
countOfEmpty = length . filter (== [])

fixEqualsFiltered :: [String] -> [[String]]
fixEqualsFiltered re = filter (\x -> countOfEmpty x == 0) (fixEquals re)

以便“ fixEqualsFiltered测试”给出:

[["12","=","56","78"],["12","34","=","78”]]

这是我想要的,但看起来并不优雅。 我不禁想到还有另一种方法可以将其过滤掉。 毕竟,无论何时要在if语句中使用“等于”并且为空时,我们都要删除等于,因此构建列表似乎很浪费(例如[“”,“ 34”,“ 56”,“ 78” ],然后将其抛弃。)

任何想法都很感激。

4 个答案:

答案 0 :(得分:3)

xs = [["","34","56","78"],["12","=","56","78"],["12","34","=","78"],["12","34","56",""]]

filter (not . any null) xs

会给予

[["12","=","56","78"],["12","34","=","78"]]

如果您想要列表理解,那就

[x | x <- xs, and [not $ null y | y <- x]]

答案 1 :(得分:3)

我不知道这是否比您的代码更干净,但是使用递归可能会更清晰,更有效:

fixEquals = init . f
f :: [String] -> [[String]]
f [] = [[]]
f (x:xs) | '=' `elem` x = ("=":removeEq xs) : map (removeEq [x] ++) (f xs)
         | otherwise    = map (x:) (f xs)

removeEq :: [String] -> [String]
removeEq = map (filter (/= '='))

它的工作方式是,如果当前字符串中有一个'=',那么它将返回值分成两部分,即使不是递归调用也是如此。 init是必需的,因为返回的最后一个元素在任何字符串中都不相等。

最后,我相信您可以找到一个更好的数据结构来完成所需的工作,而不是使用字符串列表

答案 2 :(得分:2)

我想我可能会这样做。首先,我已经写了很多次的入门文章,现在几乎已经被我的手指烫了:

zippers :: [a] -> [([a], a, [a])]
zippers = go [] where
    go _ [] = []
    go b (h:e) = (b,h,e):go (h:b) e

在ghci中运行一两次可能会比我能做的任何英语著作更清楚地说明其作用:

> zippers "abcd"
[("",'a',"bcd"),("a",'b',"cd"),("ba",'c',"d"),("cba",'d',"")]

换句话说,它提供了一种依次选择列表中每个元素的方式,给出了选择点之前和之后的“剩余”。有了该工具,这就是我们的计划:我们将不确定地选择一个String作为我们的等号,再次检查我们是否首先有一个等号,然后从中清除等号。其他。所以:

fixEquals ss = do
    (prefix, s, suffix) <- zippers ss
    guard ('=' `elem` s)
    return (reverse (deleteEquals prefix) ++ ["="] ++ deleteEquals suffix)

deleteEquals = map (filter ('='/=))

让我们尝试一下:

> fixEquals ["12", "34=", "56=", "78"]
[["12","=","56","78"],["12","34","=","78"]]

完美!但这只是实际生成方程式的垫脚石,对吗?事实证明,跳过这一中间步骤并不困难。让我们做到这一点:

equations ss = do
    (prefixes, s, suffixes) <- zippers ss
    guard ('=' `elem` s)
    prefix <- mapM (filter ('='/=)) (reverse prefixes)
    suffix <- mapM (filter ('='/=)) suffixes
    return (prefix ++ "=" ++ suffix)

我们可以在ghci中尝试它:

> equations ["12", "34=", "56=", "78"]
["1=57","1=58","1=67","1=68","2=57","2=58","2=67","2=68","13=7","13=8","14=7","14=8","23=7","23=8","24=7","24=8"]

答案 3 :(得分:1)

实现所需的最简单的方法是创建所有组合并过滤具有含义的组合:

Prelude> test = ["12", "34=", "56=", "78"]
Prelude> sequence test
["1357","1358","1367","1368","13=7","13=8","1457","1458","1467","1468","14=7","14=8","1=57","1=58","1=67","1=68","1==7","1==8","2357","2358","2367","2368","23=7","23=8","2457","2458","2467","2468","24=7","24=8"
Prelude> filter ((1==).length.filter('='==)) $ sequence test
["13=7","13=8","14=7","14=8","1=57","1=58","1=67","1=68","23=7","23=8","24=7","24=8","2=57","2=58","2=67","2=68"]

您指出了一个缺点:假设我们有以下字符串列表:["=", "=", "0123456789", "0123456789"]。我们将生成100个组合并将其全部删除。

您可以将组合视为一棵树。对于["12", "34"],您具有:

  /  \
 1    2
/ \  / \
3 4  3 4

您可以修剪树:当路径上有两个=时,只需忽略子树。 让我们尝试去做。首先,一个简单的combinations函数:

Prelude> :set +m
Prelude> let combinations :: [String] -> [String]
Prelude|     combinations [] = [""]
Prelude|     combinations (cs:ts) = [c:t | c<-cs, t<-combinations ts]
Prelude|
Prelude> combinations test
["1357","1358","1367","1368","13=7","13=8","1457","1458","1467","1468","14=7","14=8","1=57","1=58","1=67","1=68","1==7","1==8","2357","2358","2367","2368","23=7","23=8","2457","2458","2467","2468","24=7","24=8", ...]

第二,我们需要一个变量来存储遇到的=个符号的当前数量:

  • 如果找到第二个=符号,只需放下子树
  • 如果我们在没有=的情况下到达组合的结尾,请删除组合

即:

Prelude> let combinations' :: [String] -> Int -> [String]
Prelude|     combinations' [] n= if n==1 then [""] else []
Prelude|     combinations' (cs:ts) n = [c:t | c<-cs, let p = n+(fromEnum $ c=='='), p <= 1, t<-combinations' ts p]
Prelude|
Prelude> combinations' test 0
["13=7","13=8","14=7","14=8","1=57","1=58","1=67","1=68","23=7","23=8","24=7","24=8","2=57","2=58","2=67","2=68"]
  • 我们使用p作为路径上新的=符号:如果p>1,则删除子树。
  • 如果n为零,则路径中没有任何=符号,请删除组合。

您可以使用变量n存储更多信息,例如最后一个字符的类型(避免使用+*序列)。