有时我想以相反的顺序迭代像 (range 100)
这样的范围。为此,有两种现有的选择
1) 编程人员考虑将要构建反向范围的正确的起始/结束/步长,并相应地构建范围,使用负步长
(range 99 -1 -1)
2) 使用 reverse
,例如 (reverse (range 100))
解决方案1并不理想,因为它更难以思考,你必须为了反向的范围的起始/结束的包含/不包含范围而更改起始/结束位置。它还要求进行比我喜欢的更复杂的心理计算,特别是在 step
不是 1 的情况下。例如,快速解决如何反向 (range 23 96 7)
!答案是 (range 93 22 -7)
,但真的不希望强迫这些数学在你的脑海中,并将其提交到代码中,实际上你真正想要的是 (reverse (range 23 96 7))
。例如,如果你的明天想将下限从包括23改为包括24,你必须重新计算上限并更改代码到 (range 94 23 -7)
。
对于使用非整数范围的编程人员来说,这会更困难/不可能
(range 1.5 5.4 1.23)
解决方案2也不理想,因为它需要 O(n) 空间和时间
(defn reverse
"Returns a seq of the items in coll in reverse order. Not lazy."
{:added "1.0"
:static true}
[coll]
(reduce1 conj () coll))
那么接下来是什么?
可逆
也许 clojure.lang.Range
/clojure.lang.LongRange
对象可以实现 clojure.lang.Reversible
,因为它们可以在 O(1) 时间和空间内反转,例如
public ISeq rseq() {
final Number difference = Numbers.minus(end, start);
final Number remainder = Numbers.remainder(difference, step);
final Number last = Numbers.isZero(remainder) ?
Numbers.minus(end, step) : Numbers.minus(end, remainder);
final Number reverseStep = Numbers.minus(step);
BoundsCheck bc;
Object newEnd;
if (Numbers.isPos(reverseStep)) {
newEnd = Numbers.inc(start);
bc = positiveStep(newEnd);
} else {
newEnd = Numbers.dec(start);
bc = negativeStep(newEnd);
}
return new Range(last, newEnd, reverseStep, bc);
}
然后可以使用 clojure 中的 rseq
函数进行反转
(rseq (range 10))
=> (9 8 7 6 5 4 3 2 1 0)
这种方法的缺点和/或挑战
还在“空范围”上无法工作,因为你执行(range 0 0)
时实际上得到一个空列表(实际上不是一个范围对象)。因此,由于列表不是可逆的,当尝试rseq时,你会得到一个异常。这可以通过允许存在一个空的Range对象来解决,或者也许可以通过使空列表可逆?无论如何,可能太过激进。实际上,我认为允许空范围没问题,对我来说,不清楚空范围是否特别需要是列表,因为例如,空向量不是列表,空集合/映射也不是空列表,所以一个空的Range看起来并不奇怪。
浮点范围为计算反向范围时需要更多地思考算法,因为上面的算法在反转时不会产生完全相同的结果。例如...
(range 1.5 5.4 1.23)
=> (1.5 2.73 3.96 5.1899999999999995)
(rseq (range 1.5 5.4 1.23))
=> (5.1899999999999995 3.9599999999999995 2.7299999999999995 1.4999999999999996)
如果不是那样的话,也许可以有:reverse true
关键字参数
也许可逆性更适合在range
函数中
(range 10 :reverse true)
或类似。
无论如何,我并不想说Clojure是错误的,只是提供一些思考。谢谢阅读!