List 筛选Haskell中具有相同数量不同元素的列表

List 筛选Haskell中具有相同数量不同元素的列表,list,haskell,filter,List,Haskell,Filter,我对Haskell很陌生,我有数据data Instruction=Add | Sub | Mul | Div | Dup | Pop派生(Eq,Ord,Show,Generic)和Dup与mapM(const[Mul,Dup])[1..n])大小为n的所有可能组合 我只需要以Dup开头,以Mul结尾的列表,所以我使用了filter(==Mul.last)(filter(==Dup.head)(mapM(const[Mul,Dup])[1..n])但我也只希望列表中有相同数量的Mul和Dup,但

我对Haskell很陌生,我有数据
data Instruction=Add | Sub | Mul | Div | Dup | Pop派生(Eq,Ord,Show,Generic)
Dup
mapM(const[Mul,Dup])[1..n])
大小为n的所有可能组合

我只需要以
Dup
开头,以
Mul
结尾的列表,所以我使用了
filter(==Mul.last)(filter(==Dup.head)(mapM(const[Mul,Dup])[1..n])
但我也只希望列表中有相同数量的
Mul
Dup
,但我似乎想不出一种方法来做到这一点。我该如何过滤这些信息?有没有更有效的方法来过滤这些信息,因为随着列表越来越大,可能会有大量的组合


示例列表如下所示:
[Dup,Mul,Dup,Mul]
[Dup,Dup,Mul,Mul]
对于大小为4的列表。

应该可以定义一个函数
countPred::A->[A]>Int
,该函数统计列表中与第一个参数相等的项数;然后,您可以执行
筛选(\l->countPred Mul l==countPred Dup l)
(或者如果您更喜欢无点形式,也可以执行
筛选(==)countPred Mul countPred Dup)
)。我认为另一种方法可能是执行
(==0)。总和map(\case{Mul->1,Dup->(-1)})
,但我觉得这比必要的稍微复杂一些。

虽然你的方法是正确的,但我认为它不是最有效的方法。您可以生成
2^N
列表,然后过滤掉其中的许多列表。忘记了保持计数简单的其他要求,通过要求我们有尽可能多的
Mul
s和
Dup
s,我们只得到
choose(N,N/2)
列表(大小
N/2
1..N
的子集的数量),这是一个小得多的数字

相反,我们可以尝试避免过滤,只在第一时间生成通缉名单。我建议使用以下方法,您可以根据需要修改它以满足其他需求

我们定义了一个函数
sameMulDup
,它接受两个整数
m
d
,并生成带有
m Mul
s和
d Dup
s的所有列表

sameMulDup :: Int -> Int -> [[Instruction]]
sameMulDup 0 d = [replicate d Dup]
sameMulDup m 0 = [replicate d Mul]
sameMulDup m d = do
    -- generate the first element
    x <- [Dup, Mul]
    -- compute how many m and d we have left
    let (m', d') = case x of
           Dup -> (m  , d-1)
           Mul -> (m-1, d  )
    -- generate the other elements
    xs <- sameMulDup m' d'
    return (x:xs)

不管怎样,给定sameMuldup,你应该能够解决你的全部任务。

我喜欢chi的答案,但在一篇评论中,我提到它没有实现尽可能多的共享。我推测,如果您多次迭代指令列表,共享将是有益的,但如果您只迭代一次,则更糟糕。从经验上看,无论您迭代多少次,共享版本似乎都会更快,但内存权衡正如预测的那样:一次迭代越差,多次迭代越好。所以我觉得展示它可能会很有趣

这是它的样子。我们将列出无限多的答案。第一个索引是指令列表的长度;第二个是有多少个
Mul
s(尽管我将使用
True
False
而不是
Mul
Dup
)。因此:

为了完整起见,下面是如何编写一个与chi的
sameMulDup
签名相同的函数,并计算相同的答案(直到交换到
Bool
):

我的机器上的一些计时,对于编译时的
m=d=12
-O2:

sameMulDup , one iteration  1.35s    6480Kb
sameMulDup', one iteration  1.11s  226476Kb
sameMulDup , two iterations 4.26s 2135368Kb
sameMulDup', two iterations 1.97s  620880Kb
以下是我用来获取这些号码的驱动程序代码:

main :: IO ()
main = do
    [sharing, twice, m, d] <- getArgs
    let answer = (if read sharing then sameMulDup' else sameMulDup) (read m) (read d)
    if read twice
       then do
           print . sum . map (sum . map fromEnum) $ answer
           print . sum . map (sum . map (fromEnum . not)) $ answer
        else print . sum . map (sum . map fromEnum) $ answer
main::IO()
main=do

[sharing,tweep,m,d]我必须说,我真的很喜欢你如何
mapM(const[Mul,Dup])[1..n]
-这是一种生成这些列表的非常优雅的方式,也是我自己找不到的方式!也许
sequenceA(replicate 3[Mul,Dup])
可能更容易阅读,但我肯定喜欢你的操作方式。@bradrn
replicate 3[Mul,Dup]
也可以工作。不过,与其生成所有列表,然后过滤掉错误的列表,不如先只生成想要的列表。谢谢@chi,不知何故,我完全忘记了
replicitem
!这确实是一个更好的方法。谢谢你的回答。这会完美地过滤列表,但我也会尝试提高效率,只生成所需的列表。@GeorgiSpasov我建议现在您可能只想解决问题,而不必担心效率。如果您担心这一点,您可以稍后对其进行基准测试,以尝试改进最有效的方法。感谢您的回答,这对于创建包含相同数量不同元素的列表非常有用。我只需要弄清楚如何编辑它,以便只生成第一个元素
Dup
和最后一个元素
Mul
@GeorgiSpasov从所需的
m
d
中减去1的列表,然后将两个缺少的元素添加到它们的已知位置。我被以下观察结果狠狠地揍了一顿:
sameMulDup
在运行过程中会被多次调用,使用相同的参数。例如,在生成前缀
Mul:Dup:…
Dup:Mul:…
之后,这两个
可以共享,但在这个实现中将重新计算。制作一个具有建议共享的版本是相当简单的,但是书呆子的讽刺是:完全共享的版本真的更好吗?如果你只打算在列表上重复一次,那么分享更多可能会更糟,但两次或两次以上会更好。@DanielWagner很好的观察。事实上,这取决于消费者。简而言之:动态编程被认为是有害的(嗯,至少有时是有害的)。@chi在ghci中,或者在没有优化的情况下编译,结果证明共享版本速度更快,内存消耗更少!但是有了
-O2
,它就和我们的机器人差不多了
sameMulDup' :: Int -> Int -> [[Bool]]
sameMulDup' m d = bits !! (m+d) !! m
sameMulDup , one iteration  1.35s    6480Kb
sameMulDup', one iteration  1.11s  226476Kb
sameMulDup , two iterations 4.26s 2135368Kb
sameMulDup', two iterations 1.97s  620880Kb
main :: IO ()
main = do
    [sharing, twice, m, d] <- getArgs
    let answer = (if read sharing then sameMulDup' else sameMulDup) (read m) (read d)
    if read twice
       then do
           print . sum . map (sum . map fromEnum) $ answer
           print . sum . map (sum . map (fromEnum . not)) $ answer
        else print . sum . map (sum . map fromEnum) $ answer