有时候,我想像 (range 100)
一样迭代一个范围,但是以相反的顺序。为此,存在两种现有的选择:
1) 程序员思考一下应该有哪些起始/结束/步长来构造反转的范围,并根据这些构造相应的范围,步长为负数
(range 99 -1 -1)
2) 使用 reverse
,例如:(reverse (range 100))
解决方案 1 不太好,因为它更难考虑,因为您必须改变起始/结束,因为反转了界限(包含/不包含界限)在起始和结束处。在步长不是 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);
}
然后可以使用 rseq
函数在 Clojure 中执行反转
(rseq (range 10))
=> (9 8 7 6 5 4 3 2 1 0)
这种方法的问题和/或挑战
仍然不能用于“空范围”,因为当你做 (range 0 0)
实际上你会得到一个空列表(并不是一个范围对象)。由于列表不是可逆的,当你尝试 rseq 它时会抛出异常。这可能可以通过允许存在一个空的范围对象来修复,或者也许可以通过使空列表可逆?无论如何,这可能是一个过于激进的更改。我实际上认为允许空的范围没有问题,对于空的范围是否必须是列表并不清楚,例如,空的向量不是列表,空集合/映射也不是空列表,所以有一个空的范围似乎并不奇怪。
对于浮点数范围来说,在计算逆范围算法上需要更多的思考,因为上面的算法在逆序时不产生完全相同的结果。例如
(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错了或其他什么,只是提供一些思考的食物。感谢阅读!