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

欢迎!请参阅 关于 页面以了解更多有关这个网站的信息。

+2
集合
编辑

有时候,我想像 (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错了或其他什么,只是提供一些思考的食物。感谢阅读!

1 答案

0

编辑:对不起,我以为这会显示为 threading 的注释,而不是“答案”,现在我似乎无法删除它,抱歉。

我认为另一个想法是,应该让 Range 实现 RandomAccess 吗?由于 Indexed extends Counted,它要求实现 int count();,这无法为无限的范围实现。但是 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;
}
虽然不一定反对这类事,但我正在努力理解实现可逆性和随机访问所解决的具体问题。如果我们希望获得速度,Clojure 已经有更适合快速 .get 和可逆性的结构,最值得注意的是向量。我并不介意为其中之一甚至两者开设一个待处理事项,但我强调,最好是以我们作为 Clojure 程序员因缺失而体验到的实际问题来制定这些项目的框架,而不仅仅是它们的存在。
...