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

时间:2018-10-19 23:18:22

标签: javascript python ruby iteration mutation

我见过两种基本方法来迭代一组项目(例如listarray等)。 一个是通过增加或减少索引,这通常是许多“内置”(例如python中的for循环)的默认值。 另一个是对集合进行切片,并始终评估第一项。

在许多常见的网络语言(包括Ruby,Javascript和Python)中,这两种方法都可以在IIRC中“起作用”,但可能并不理想。因此,正如@DavidMaze指出的那样,我在抽象地询问非语言特定的方法时,这个问题可能并不适用于每种语言,或者可能存在特定于语言的巨大差异。

例如,使用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: []

显然,保留一个数据是必要的,而如果存储空间有限并且在迭代之后不再需要数据,则OTHER会提供一些优势。除了这些情况之外,我认为ONE通常更有用(而且引入错误的可能性较小)。我也认为ONE可能总是或几乎总是便宜以获得相同的结果,但是我想检验上述假设。我感兴趣的是:

  • 在哪些常见用例(如果有)中,“ THE OTHER”比“ ONE”更可取?为什么?
  • 可以说一个总是(em)还是几乎总是?
  • 关于这种事情是否存在公认的标准或约定?
  • 一种方法要贵得多吗?
  • 以上这些答案是正确的还是特定于语言的?

1 个答案:

答案 0 :(得分:2)

我建议一个标题:

1。匹配您现有代码库的样式。

在一个大型项目中,如果以一致的方式编写内容可能会有所帮助。对于诸如列表迭代之类的超低级事情,这不一定有什么大问题,但是,如果大多数事情都是“一种”方式完成相同任务,那么“另一种”方式会使以后的人们感到困惑。

2。如果您的语言具有本地列表迭代功能(例如“地图”功能),请使用它。

大多数较新的语言都有某种方法可以对整个列表进行操作,或者有一些标准的方法可以遍历列表。

# 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]);
    }
}

3。了解“列表”的类型。

不同的语言对具有不同特征的列表具有不同的默认实现。如果“列表”是单链接列表(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

但是,由于它是一个单链表,因此按索引获取元素可能需要O( n )时间,并且您绝对不想这样做。

在某些语言中,“列表”是起始位置加上长度(C中的数组,Go中的切片),在这种情况下,“创建其余列表”并不那么昂贵,尽管它看上去有点奇怪。

/* 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-在注释中建议的那样),那么您绝对不希望那样做。但是按索引查找商品相对便宜。

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

4。轻轻地喜欢非破坏性行动。

在现代硬件上,昂贵的东西是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))

不可变(“ ... andCopy”)表单肯定会分配一个额外的列表,但是当您以后使用结果时,等待的惊喜会减少,并且您(通常)不会注意到额外的分配。使用更不变的样式在测试(您可以一次创建一个测试夹具并在测试中重复使用)和维护撤消堆栈(这是Javascript Redux框架中“时间旅行调试器”的核心)方面也非常有帮助。实例)。

  

一种方法的成本明显更高吗?

如果您的“本地”列表类型是单链接列表,则尝试通过索引对其进行迭代需要O( n ^ 2)时间(您必须遍历所有 n < / em>元素到最后一个,再加上 n -1到倒数第二个,再加上...)。如果您的“本地”列表类型基于数组或向量,则使用子列表将花费O(n ^ 2)时间(您必须复制尚未处理的所有 n -1项)。没有针对此的特定通用跨语言答案。