【问题标题】:d2: assigning ranges/iterators to array slicesd2:将范围/迭代器分配给数组切片
【发布时间】:2012-03-29 22:26:30
【问题描述】:

考虑以下代码:

enum size = 16;
double[size] arr1 = [...];
double[size] arr2 = [...];
process = (double x) { return (x + 1); };

arr2[] = map!(process)(arr1[]); // here

我无法将 map 的结果转换回我的普通数组。问题不仅适用于map,还适用于takerepeat 以及来自std.algorithmstd.range 的所有在范围上运行的优秀工具。

在这个任务中,我得到了Error: cannot implicitly convert expression (map(arr1[])) of type Result to double[]。如何在不使用的情况下将范围评估为数组

uint i = 0;
foreach (x; map!(process)(arr1[])) {
    arr2[i] = x;
    i++;
}

?

另外,有人可以解释一下,为什么我必须用静态数组调用map!(process)(arr1[]) 而不是map!(process)(arr1)?静态数组不应该与动态的迭代方式兼容,还是我什么都没有?

此外,简单的枚举语法foreach (index, item; sequence) 似乎不适用于范围 - 是否有解决方法?我猜原因与为什么不能将范围分配给数组切片的原因相同。

【问题讨论】:

    标签: iterator d range


    【解决方案1】:

    诸如mapfilter 之类的函数返回范围,而不是数组,因此简单地分配给数组不会比将string 分配给wstring 更有效。他们是不同的类型。对于许多基于范围的函数(包括mapfilter),它们返回的范围实际上是惰性的,以避免不必要的计算,这使得它们与数组的兼容性大大降低。解决方案是使用std.array.array,它接受一个范围并从中创建一个动态数组。所以,你可以这样做

    auto arr = array(map!process(origArray));
    

    但是,我建议不要在实际需要之前将范围转换为数组,因为这会导致不必要的计算,这意味着分配一个新数组。如果您确实需要一个数组,那么一定要使用 std.array.array 来转换范围,但是如果您不需要实际的数组,对范围进行操作通常会更有效。但是,如果您想将结果转换为 static 数组而不是动态数组,则最好只在循环中分配每个元素(并且可能完全跳过 map),因为使用 std.array.array 将分配一个动态数组,一旦分配给静态数组就不会使用。太浪费内存了。

    另外,请注意,将静态数组与基于范围的函数一起使用可能会带来风险,因为它们必须对静态数组进行切片以获得动态数组以供基于范围的函数进行处理,并且如果该动态数组超出了声明了静态数组,然后您正在泄漏对不再存在的数据的引用。例如,

    auto func()
    {
        int[5] arr;
        return map!process(arr[]);
    }
    

    会很糟糕。但是,只要在您退出包含静态数组的范围之前,您已完成使用切片并且没有任何内容引用它(包括可能已创建的任何范围),您应该没问题。这需要小心的。

    关于必须对静态数组进行切片的问题,您确实应该将其作为一个单独的问题提出,但与之相关的两个现有问题是this onethis one。它几乎可以归结为 IFTI(隐式函数模板实例化)使用它给出的确切类型进行实例化,并且静态数组既不是动态数组也不是范围,因此任何特别需要动态数组或range 将无法使用静态数组进行编译。编译器隐式切片静态数组以将它们转换为动态数组,用于显式采用动态数组的函数,但模板实例化不会发生这种隐式转换,因此您必须显式切片静态数组以将它们传递给基于范围的函数。

    关于将 foreach 与索引和范围一起使用的问题,同样,您不应该在同一个问题中提出多个问题。请针对您遇到的每个问题发布单独的问题。归根结底是

    foreach(elem; range)
    {
        //stuff
    }
    

    降低到接近的东西

    for(; !range.empty; range.popFront())
    {
        auto elem = range.front;
        //stuff
    }
    

    这根本不涉及索引。 可以更改为您创建一个索引变量,但是对于范围而言,让它们的索引在每次迭代时都像这样迭代一个并不总是有意义的(就像它通常会很好) ,所以这还没有完成。不过,添加自己的计数器很简单。

    {
        size_t i;
        foreach(elem; range)
        {
            //stuff
            ++i;
        }
    }
    

    opApply 确实支持在 foreach 中使用索引,但它不是一个范围,也不适用于基于范围的函数。

    【讨论】:

    • 谢谢你,乔纳森!我知道范围和数组是不同的类型,但认为 D 可以实现某种隐式转换,因为范围可以转换为数组,只要它们是有限的并且它们的元素与数组的元素类型兼容。我知道堆栈和堆分配的差异,以及数组化的计算和内存开销,但还是感谢您的警告:) 另外,感谢您与 IFTI 明确说明-如果我理解正确,将来这可能会改变允许隐式切片静态数组,但由于不完善,现在我们应该使用这种方式。
    • IFTI 可能 被更改为将静态数组隐式转换为动态数组,但我对此表示怀疑。仍然需要有可能让模板化函数采用静态数组(因此总是将它们转换为模板实例化会很糟糕),并且 IFTI 在找到有效的隐式转换之前不会继续尝试(这将是昂贵的并且会改变许多模板的语义)。因此,我希望您始终必须对静态数组进行切片以将它们传递给基于范围的函数,但在某些时候可能会进行改进以至少减少问题。
    • 我建议尽可能使用staticArray 而不是array,因为它不会分配GC 内存。编程的有效方法是在可能的情况下设置一个堆栈分配的数组(仅用于处理任务的情况)并将范围内的所有元素分配给数组。我不知道自己如何在 D 中做到这一点,但我想知道。如果这样的范围就像一个迭代器一样,将数组作为后台存储,那就太好了,这样您就可以从范围内检索数组而无需任何性能开销。
    猜你喜欢
    • 2021-05-30
    • 2016-10-04
    • 2021-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-14
    • 1970-01-01
    • 2021-02-11
    相关资源
    最近更新 更多