我见过两种基本方法来迭代一组项目(例如list
,array
等)。 一个是通过增加或减少索引,这通常是许多“内置”(例如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可能总是或几乎总是便宜以获得相同的结果,但是我想检验上述假设。我感兴趣的是:
答案 0 :(得分: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]);
}
}
不同的语言对具有不同特征的列表具有不同的默认实现。如果“列表”是单链接列表(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
在现代硬件上,昂贵的东西是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项)。没有针对此的特定通用跨语言答案。