【问题标题】:Can I use the clojure 'for' macro to reverse a string?我可以使用 clojure 'for' 宏来反转字符串吗?
【发布时间】:2011-12-07 18:27:56
【问题描述】:

这是对我的问题"Recursively reverse a sequence in Clojure" 的跟进。

是否可以使用 Clojure 的“for”宏来反转序列?我试图更好地理解这个宏的限制和用例。

这是我开始的代码:

((defn reverse-with-for [s] 
    (for [c s] c))

可能吗?

如果是这样,我假设解决方案可能需要将 for 宏包装在一些定义可变 var 的表达式中,或者 for 宏的 body-expr 将以某种方式将序列传递给下一次迭代(类似于 @ 987654324@).

【问题讨论】:

    标签: clojure


    【解决方案1】:

    Clojure for 宏正在与任意 Clojure 序列一起使用。

    这些序列可能会也可能不会像向量那样暴露随机访问。因此,在一般情况下,如果不一直遍历到 Clojure 序列的最后一个元素,您就无法访问它,这将导致无法以相反的顺序通过它。

    我假设你有这样的想法(类似 Java 的伪代码):

    for(int i = n-1; i--; i<=0){
       doSomething(array[i]);
    }
    

    在这个例子中,我们预先知道数组大小n,我们可以通过它的索引来访问元素。对于 Clojure 序列,我们不知道这一点。在 Java 中,使用数组和 ArrayList 来做这件事是有意义的。然而,Clojure 序列更像链表——你有一个元素,以及对下一个元素的引用。

    顺便说一句,即使有一个 (可能是非惯用的)* 方法来做到这一点,它的时间复杂度也会像 O(n^2) 这样的努力比较不值得在链接的帖子中更简单的解决方案是 O(n^2) 用于列表和更好的 O(n) 用于向量(它非常优雅和惯用。事实上,官方reverse 有这个实现)。

    编辑:

    一般建议:不要尝试在 Clojure 中进行命令式编程,它不是为它设计的。尽管许多事情看起来很奇怪或违反直觉(与命令式编程中众所周知的习语相反),但一旦您习惯了函数式的做事方式,它就会变得很多,我的意思是很多更容易.

    专门针对这个问题,尽管 Java(和其他 C 类)for 和 Clojure for 同名,但它们并不是一回事!首先是一个实际的循环——它定义了一个流控制。第二个是理解 - 从概念上看它是一个序列的高级函数和一个函数f 来完成for 它的每一个元素,它返回另一个 f(element) 序列。 Java for 是一个语句,它不计算任何东西,Clojure for(以及 Clojure 中的任何其他东西)是一个表达式 - 它计算为 f(element) 的序列秒。

    可能最简单的方法是使用序列函数库:http://clojure.org/sequences。另外,您可以在http://www.4clojure.com/ 上解决一些问题。第一个问题很容易,但随着你的进展,它们会逐渐变得更难。

    *正如亚历山大的回答所示,这个问题的解决方案实际上是惯用的并且非常聪明。为此点赞! :)

    【讨论】:

    • 明白。但我想你可以像改变一个在for 之外定义的变量这样粗糙的东西。但是,doseq 可能更(不)合适。
    • @noahz:我猜你可以做这样的事情。我的编辑指的是那部分。但是随后您在命令式算法中使用可变状态。循着这个思路,您也可以用实际的 Java 编写纯 Java 代码并通过互操作调用它。我并不总是反对命令式变异代码。我只是认为在原始 Java 中更容易完成。尤其是互操作效果如此之好。
    • 查看 Alexandre 的解决方案。性能很糟糕,但这正是我想要的。
    【解决方案2】:

    以下是使用 for 反转字符串的方法:

    (defn reverse-with-for [s] 
        (apply str
            (for [i (range (dec (count s)) -1 -1)]
                (get s i))))
    

    请注意,此代码是无突变的。同理:

    (defn reverse-with-map [s] 
        (apply str
            (map (partial get s) (range (dec (count s)) -1 -1))))
    

    一个更简单的解决方案是:

    (apply str (reverse s))
    

    【讨论】:

    • 这是我一直在寻找的东西。无突变的奖励。谢谢。
    • 如果 String 支持 rseq 那就太好了,这样您就可以在没有遍历字符串进行反转的开销的情况下执行此操作。
    • @amalloy 是的。我在写这个答案并用谷歌搜索 clojure string rseq 时发现了你的邮件列表帖子。不过,反转字符串并不是您必须经常做的事情。
    【解决方案3】:

    首先,正如 Goran 所说,for 不是一个语句——它是一个表达式,即序列理解。它通过迭代其他序列来构造序列。因此,在它被使用的形式中,它是纯函数(没有副作用)。 for 可以看作是增强的map 注入了filter。因此,它不能用于保持迭代状态,例如reduce 做。

    其次,您可以使用for 和可变状态来表达序列反转,例如使用一个原子,它是java变量的粗略等价物(不考虑它的并发属性)。但是这样做你会面临几个问题:

    1. 您正在打破主要语言范式,因此您的代码肯定会变得更糟。
    2. 由于所有 clojure 可变状态单元都被设计为线程安全的,它们都使用了某种非法并发修改保护,并且没有能力将其移除。因此,您将获得较差的性能特征。
    3. 在这种特殊情况下,正如 Goran 所说,序列是广泛使用的 Clojure 抽象之一。例如,有惰性序列,它可能是无限的,所以你不能把它们走到最后。尝试使用命令式技术处理此类序列肯定会遇到困难。

    所以不要这样做,至少在 Clojure 中:)

    编辑:我忘了提。 for 返回惰性序列,因此您必须以某种方式对其进行评估,以便应用您在其中执行的所有状态突变。不这样做的另一个原因:)

    【讨论】:

    • 实际上,原子速度非常快,在非竞争代码中,它们是一个单独的比较和设置 java 调用。 github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/… 在我的测试中,它们比 (inc 1) 慢了大约 20 倍
    • map+filter 实际上仍然没有for 强大。 for 更像是mapcat 的(可能重复的)应用程序,它实际上同样强大。考虑将(for [x (range 10) y [:x x]] y) 写成map 操作:你不能这样做,因为你想返回两倍于你给定的元素。
    • 感谢您的澄清。我不知道for 的这种拼接功能。我写了关于 for 和 map+filter 的等价性,考虑了 Scala 中的类似结构,我读到它相当于 map+filter。
    猜你喜欢
    • 2021-04-25
    • 1970-01-01
    • 1970-01-01
    • 2017-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-06
    • 1970-01-01
    相关资源
    最近更新 更多