javascript - 社区标准和利弊?破坏性与非破坏性迭代
问题描述
我见过两种基本的方法来迭代一组项目(例如 a list
、array
等)。一种是增加或减少索引,这往往是许多“内置”(例如for
python中的循环)的默认值。另一种是对集合进行切片并始终评估第一项。
这两种方法可以“工作”(但可能并不理想),IIRC,在许多常见的网络语言中,包括 Ruby、Javascript 和 Python。因此,正如@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: []
显然 ONE 是保存数据所必需的,如果存储空间有限并且迭代后不再需要数据,则 THE OTHER 会提供一些优势。除了这些场景之外,我认为 ONE 通常更有用(并且不太可能引入错误)。我还认为 ONE可能总是或几乎总是更便宜以获得相同的结果,但我想测试上述假设。我感兴趣的是:
- 有哪些常见用例(如果有),其中“THE OTHER”优于“ONE”?为什么?
- 可以说 ONE总是或几乎总是比 THE OTHER 更可取吗?
- 是否有围绕这类事情的公认标准或约定?
- 一种方法是否更昂贵?
- 上述答案是普遍正确的,还是特定于语言的?
解决方案
我会建议一个评分标准:
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 个元素才能到达最后一个元素,加上n -1 才能获得到倒数第二个,加上...)。如果您的“本机”列表类型基于数组或向量,那么使用子列表将花费 O(n^2) 时间(您必须复制所有尚未处理的n -1 项)。对此没有特定的通用跨语言答案。
推荐阅读
- python - Splitting dataframes based on a cell string
- wordpress - 根据 WooCommerce 产品库存自动更新产品自定义字段
- javascript - NODEJS - 如何通过在内存中保存缓冲区来提高性能
- asp.net-core - 在 .NetCore 包中使用 NToastNotify 包会引发以下故障
- android - Java.Lang.UnsatisfiedLinkError: '没有找到实现
- python - numpy,将单个参数传递给 np.vectorized 函数
- javascript - 将 Leaflet Deflate 与 Shiny 集成
- c# - 如何过滤来自服务总线主题消息的响应
- python - Scrapy HTTP 状态码未处理或不允许
- python - 使用命名参数定义但可以采用 **kwargs 的函数的 MyPy 类型提示?