展平嵌套的生成器表达式

时间:2016-12-22 18:35:02

标签: python generator itertools

我正在尝试压平嵌套的生成器生成器,但是我得到了意想不到的结果:

>>> g = ((3*i + j for j in range(3)) for i in range(3))
>>> list(itertools.chain(*g))
[6, 7, 8, 6, 7, 8, 6, 7, 8]

我希望结果如下:

[0, 1, 2, 3, 4, 5, 6, 7, 8]

我认为我得到了意想不到的结果,因为在外部生成器已经迭代完毕之前,内部生成器没有被评估,将i设置为2.我可以通过强制评估内部生成器使用列表推导而不是生成器表达式:

>>> g = ([3*i + j for j in range(3)] for i in range(3))
>>> list(itertools.chain(*g))
[0, 1, 2, 3, 4, 5, 6, 7, 8]

理想情况下,我想要一个完全懒惰的解决方案,并且在使用之前不会强制评估内部嵌套元素。

有没有办法压缩任意深度的嵌套生成器表达式(可能使用itertools.chain以外的其他东西)?

修改

不,我的问题不是Variable Scope In Generators In Classes的重复。老实说,我不知道这两个问题是如何相关的。也许主持人可以解释为什么他认为这是重复的。

此外,我的问题的两个答案都是正确的,因为它们可用于编写一个能够正确展平嵌套生成器的函数。

def flattened1(iterable):
    iter1, iter2 = itertools.tee(iterable)
    if isinstance(next(iter1), collections.Iterable):
        return flattened1(x for y in iter2 for x in y)
    else:
        return iter2

def flattened2(iterable):
    iter1, iter2 = itertools.tee(iterable)
    if isinstance(next(iter1), collections.Iterable):
        return flattened2(itertools.chain.from_iterable(iter2))
    else:
        return iter2

据我所知timeit,他们的表现相同。

>>> timeit(test1, setup1, number=1000000)
18.173431718023494
>>> timeit(test2, setup2, number=1000000)
17.854709611972794

我不确定从风格的角度来看哪一个更好,因为x for y in iter2 for x in y有点大脑扭曲,但可以说比itertools.chain.from_iterable(iter2)更优雅。感谢投入。

令人遗憾的是,我只能将两个同样好的答案之一标记为正确。

3 个答案:

答案 0 :(得分:7)

您可以使用chain.from_iterable

,而不是使用chain(*g)
>>> g = ((3*i + j for j in range(3)) for i in range(3))
>>> list(itertools.chain(*g))
[6, 7, 8, 6, 7, 8, 6, 7, 8]
>>> g = ((3*i + j for j in range(3)) for i in range(3))
>>> list(itertools.chain.from_iterable(g))
[0, 1, 2, 3, 4, 5, 6, 7, 8]

答案 1 :(得分:4)

这个怎么样:

[x for y in g for x in y]

哪个收益率:

[0, 1, 2, 3, 4, 5, 6, 7, 8]

答案 2 :(得分:2)

猜猜你已经有了答案,但这是另一个观点。

问题在于,当创建每个内部生成器时,值生成表达式在外部变量i上关闭,因此即使第一个内部生成器开始生成值,它也会使用&# 34;电流"值i。如果外部生成器已被完全使用,那么它将具有值i=2(并且在chain(*g)调用中的参数被评估之后,恰好就是chain实际上是g = ((3*i1 + j for i1 in [i] for j in range(3)) for i in range(3)) 之前的情况叫)。

以下狡猾的技巧将解决这个问题:

i

请注意,这些内部生成器不会在for上关闭,因为[i]子句是在生成器创建时计算的,因此单例列表i评估及其价值"冻结"面对from_iterable的价值的进一步变化。

这种方法优于chain.from_iterable答案,如果你想在g = ((3*i1 + j for i1 in [i] for j in range(3)) for i in range(3)) g1 = next(g) g2 = next(g) g3 = next(g) 电话之外使用它,它会更加通用 - 它总会产生"正确"内部发电机,无论外部发电机在使用内部发电机之前是部分还是完全消耗。例如,在以下代码中:

list(g1)
list(g2)
list(g3)

你可以插入这些行:

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }

        return GetAttributesToIncludeInEqualityCheck()
            .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());

        return hash;
    }
}

在定义相应内部生成器之后的任何时间点的任何顺序,您将得到正确的结果。