2024 Clojure 状态调查! 中分享您的想法。

欢迎!请参阅 关于 页面,了解此工作方式的一些更多信息。

+2
集合
编辑

有时我想以相反的顺序迭代像 (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是错误的,只是提供一些思考。谢谢阅读!

1 回答

0
Avatar by

编辑:抱歉我以为这会显示为线程中的评论而不是“答案”,现在我似乎无法删除它,对此表示歉意。

我认为另一个想法是,应该让Range实现RandomAccess接口吗?由于Indexed扩展了Counted,它需要实现int count();方法,这对于无限Range来说是不可能的。但是并发接口RandomAccess仅承诺get(int i)操作是快速的。这可以在Range中轻松实现

public Object get(int i) {
    if (i < 0) {
        throw new IndexOutOfBoundsException();
    }
    final Object ret = Numbers.add(start,Numbers.multiply(step,i));
    if (boundsCheck.exceededBounds(ret)) {
        throw new IndexOutOfBoundsException();
    }
    return ret;
}

然后这将被Clojure的nth函数用于而不是通过遍历范围来找到第n个元素。

同样,这也可能是针对Repeat

public Object get(int i) {
    if (i < 0 || (count != INFINITE && i >= count)) {
        throw new IndexOutOfBoundsException();
    }
    return val;
}
by
虽然不一定反对这样的做法,但我很难理解实现可逆操作或随机访问来解决的问题。如果我们希望提高速度,Clojure已经有一些更适合快速.get和可逆操作的数据结构,尤其是向量。我不介意为这些中的一项或两项开一个票据进行考虑,但我强调,把它们围起来,用我们作为Clojure程序员体验到的缺失所带来的问题来界定,会比仅仅缺少它们更有帮助。
欢迎使用Clojure问答社区,在这里您可以提出问题,并从Clojure社区成员那里获得答案。
...