等价于具有停止条件的生成器的 Haskell

Equivalent in Haskell of generator with stopping condition

假设我想在 Haskell 中构建一个列表 iterate step x0,但包含终止条件。所以在 Python 中,这将是 list(my_gen),例如

def my_gen():
    x = x0
    while not done(x):
        x = step(x)
        yield x

(编辑: 如果我想包含 x0,这应该在循环之前有一个额外的 yield x。)

一种方法是写我自己的 takeWhileInclusive 然后说

takeWhileInclusive (not . done) . iterate step x0

这是 Haskell-y 方式(或 a Haskell-y 方式)来完成这个吗?当 done x 为真时尝试为 step x 添加一些标记值然后使用 takeWhile.

似乎是不自然的

特别是我想到了 LeetCode 上的 container with most water problem,并用

之类的东西解决了它
maxWith volume . smartSteps (0, n)
 where smartSteps = takeWhileInclusive (\(i,j) -> j - i > 1) . iterate step

step 增加 i 或减少 j(或两者),根据哪个索引具有更高的行。

当然这里只使用 takeWhile j > i 会很容易,但我想想想我将如何处理没有自然的“你走得太远”条件的情况,只是“你完成了”条件。

编辑: 这个问题已被标记为重复(我在问题中链接到的 question),但事实并非如此。问题不是如何写 takeWhileInclusive,实际上这个问题明确地把 takeWhileInclusive 当作给定的。它是关于如何完成一项可能使用或不使用 takeWhileInclusive.

的任务

是的,这是一种 Haskell-y 方式。

另一种 Haskell-y 方式(或 Haskell-y 方式来实现 takeWhileInclusive)是稍后一步压缩 iterated 值。

myGen done step = map snd . takeWhile (not . done . fst) . ap zip tail . iterate step

N.B。与 iterate 不同(但与 my_gen 相似)这不会发出初始 x 值作为步骤之一。

您可以使用unfoldr生成序列:

unfoldr (\x -> if done x then Nothing else Just (x, step x)) x0

例如,

> import Data.List
> step = (+1)
> done = (> 10)
> x0 = 0
> unfoldr (\x -> if done x then Nothing else Just (x, step x)) x0
[0,1,2,3,4,5,6,7,8,9,10]

unfoldrx0 上调用它的函数开始。当函数returnsNothingunfoldr停止时。当函数 returns Just (x, y) 时,它将 x 附加到结果并在 y.

上再次调用该函数

将您的生成器与 unfoldr 的 Python 实现进行比较:

def unfoldr(f, x):
    while True:
        if (y := f(x)) is None:
            return
        else:
            yield y[0]
            x = y[1]

list(unfoldr(lambda x: None if done(x) else (x, step(x)), x0))

你可以定义

takeUntil done xs = 
  foldr (\x r -> if done x then [x] else x : r) [] xs

然后像

一样使用它
takeUntil done $ iterate step x0

例如takeUntil (> 9) [1..] == [1..10].

使用 foldr 指定最终元素很容易(如此处所示),但使用编码“变形”的 unfoldr 更麻烦,用 列表作为哨兵。使用“同构”可以指定非空尾,这似乎是完成此任务的合适工具。