代码之家  ›  专栏  ›  技术社区  ›  NotAnAmbiTurner

社区标准和优缺点?破坏性迭代与非破坏性迭代

  •  -1
  • NotAnAmbiTurner  · 技术社区  · 8 年前

    有两种基本的方法我已经看到在一个项目集合上迭代(例如 list , array 一个 是通过增加或减少索引,这往往是许多“内置”的默认值(例如。 for 其他的

    这两种方法可以 工作

    例如,使用Python:

    一个

    collection_1 = [0, 1, 2, 3, 4]
    x = 0
    while x < len(collection_1):
      print(collection_1[x])
      x += 1
    print("collection_1: {}".format(collection_1))
    

    0
    1
    2
    3
    4
    collection_1: [0, 1, 2, 3, 4]
    

    其他的

    collection_2 = [0, 1, 2, 3, 4]
    while len(collection_2) > 0:
      print(collection_2[0])
      collection_2 = collection_2[1:]
    print("collection_2: {}".format(collection_2))
    

    0
    1
    2
    3
    4
    collection_2: []
    

    显然,一个是保存数据所必需的,另一个是在存储空间有限且迭代后不再需要数据的情况下提供一些优势。除了这些场景之外,我认为一个通常更有用(而且不太可能引入bug)。我也认为其中之一是 可能

    • “另一个”比“一个”更可取的一些常见用例(如果有的话)是什么?为什么?
    • 可以这么说吗 总是 几乎总是 比另一个更好?
    • 这类事情有公认的标准或惯例吗?
    • 一种方法要贵得多吗?
    1 回复  |  直到 8 年前
        1
  •  2
  •   David Maze    8 年前

    我建议一个准则:

    一。匹配现有代码库的样式。

    在一个大的项目中,如果事情是以一致的风格写的,它会很有帮助。对于超低级的事情,比如列表迭代,这不一定是件大事,但是如果大多数事情都是“一种”方式来完成相同的任务,“另一种”方式会让后来的人感到困惑。

    2。如果您的语言有本机列表迭代(如“map”函数),请使用它。

    # Python
    for x in collection_1:
        print(x)
    
    // Javascript
    collection_1.forEach(x => console.log(x))
    
    -- Haskell
    mapM_ putStrLn collection1
    
    // Go
    for _, x := range collection1 {
            fmt.Printf("%d\n", x)
    }
    

    在C和语言中,索引数组是习惯用法,所以 如果这是那种语言的惯例

    /* C */
    void printList(int *l, int len) {
        int i;
        for (i = 0; i < len; i++) {
            printf("%d\n", l[i]);
        }
    }
    

    三。了解“列表”是什么类型。

    不同的语言对具有不同特性的列表有不同的默认实现。如果“list”是一个单独链接的列表(Lisp,Haskell),那么一次遍历一个元素并传递“列表的其余部分”实际上是正确的。

    -- Haskell
    -- printAList is a function that takes a list parameter
    -- and returns an IO action producing no value in particular.
    printAList :: [Int] -> IO ()
    -- To print an empty list, do nothing.
    printAList [] = return ()
    -- To print a non-empty list:
    printAList (x:xs) = do
      -- Print the first element;
      putStrLn (show x)
      -- Then print the list of things starting at the second element.
      printAList xs
    

    不过,由于它是一个单独链接的列表,所以按索引获取一个元素可能需要( n个 )时间和你绝对不想这样做。

    在某些语言中,“list”是一个起始位置加上一个长度(C中的数组,Go中的切片),在这种情况下,“create a list of the rest”并不昂贵,尽管看起来有点奇怪。

    /* C programmers will look at you funny */
    void printList2(int *l, int len) {
        for (; len > 0; l++, len--)
            printf("%d\n", l[0]); /* or *l */
        }
    }
    
    // Go: also strange, but not wildly inefficient
    func printList(l []int) void {
            while (len(l) > 0) {
                    fmt.Printf("%d\n", l[0])
                    l = l[1:len(l)]
            }
    }
    

    但是,如果“一个列表”是一个项目块,并且更改它需要复制整个列表(Python,如@Ry-comments中建议的那样),那么您肯定不希望这样;但是按索引查找项目相对便宜。

    # Python; this is common to update a list in place
    for i in range(len(container_1)):
        container_1[i] += 1
    

    四。温和地选择非破坏性的行动。

    在现代硬件上,昂贵的东西是I/O、网络调用和糟糕的算法。小效率基本上无关紧要。以下两项之间是否存在实际差异:

    # Python
    
    def doubleListInPlace(l):
      for i in range(len(l)):
          l[i] *= 2
      return l
    
    def doubleListAndCopy(l):
      return [2 * x for x in l]
    
    def main():
      l = [1, 2, 3]
      ll = doubleList...(l)
      lll = doubleList...(ll)
      print("Doubled list: " + repr(ll))
      print("Quadrupled list: " + repr(lll))
    

    一种方法要贵得多吗?

    如果您的“原生”列表类型是一个单独链接的列表,那么尝试通过索引遍历它需要( n个 n个 最后一个元素,加上 -1到倒数第二个,加上。如果“本机”列表类型基于数组或向量,则使用子列表将花费O(n^2)时间(必须全部复制 -1个您尚未处理的项目)。对此没有一个通用的跨语言答案。