2011-12-07 88 views
4

这是我的问题"Recursively reverse a sequence in Clojure"的后续行动。我可以使用clojure'for'宏来反转一个字符串吗?

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

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

可能:

这里是代码,我从开始?如果是这样,我假设解决方案可能需要将for宏包装在某个表达式中,该表达式定义了一个可变var,或者for宏的body-expr以某种方式将某个序列传递给下一次迭代(类似于map) 。

回答

5

这里是你如何可以扭转与字符串:

(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)) 
+0

这是我一直在寻找的东西。无突变奖金。谢谢。 – noahlz

+3

如果String支持'rseq',那么这样可以很好,这样你就可以做到这一点,而不需要为了反转而遍历字符串的开销。 – amalloy

+0

@amalloy是的。当我写这个答案时,我发现你的邮件列表发布了这个问题,并且为* clojure字符串rseq *搜索了谷歌。尽管如此,颠倒一个字符串并不是你必须这么做的。 –

7

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

这些序列可能会或可能不会像向量一样暴露随机访问。因此,在一般情况下,如果没有遍历所有Clojure序列的最后一个元素,您将无法访问它的最后一个元素,从而无法通过相反的顺序来遍历它。

我assumming你脑子里想的是这样的(类似Java的伪代码):

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

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

顺便说一句,即使有一个 (可能非惯用语) *的方式来做到这一点,其时间复杂度会像为O(n^2)相比,更容易的解决方案,是不值得的努力在链接的文章中,列表的O(n^2)和矢量的O(n)好得多(而且它非常优雅和习惯,事实上,官方的reverse也有这个实现)。

编辑:

一般的建议是:不要试图做Clojure的命令式编程,它不适合它。尽管许多事情看起来很奇怪或者反直觉(与众所周知的命令式编程中的习惯用法相反),但是一旦习惯了功能性的处理方式,它很多,我的意思是很容易

特别为这个问题,尽管同名Java(和其他类C)for和Clojure for是不一样的东西!首先是一个实际的循环 - 它定义了一个流量控制。第二个是一个理解 - 看它在概念上作为一个序列的更高功能和功能˚F每个其元件,它返回F(元件) S的另一序列的。 Java for是一个声明,它不计算任何东西,Clojure for(以及Clojure中的其他任何东西)是一个表达式 - 它评估的是序列f(element) s。

可能最简单的方法是使用序列函数库:http://clojure.org/sequences。此外,您可以在http://www.4clojure.com/上解决一些问题。第一个问题非常简单,但随着你逐步完成,它们会逐渐变得更加困难。

*如亚历山大的回答所示,问题的解决方案实际上是惯用的,而且非常聪明。荣誉! :)

+0

明白了。但是我想你可能会变得像变异在'for'之外定义的变种一样粗糙。但是,'doseq'可能更适合。 – noahlz

+0

@noahz:我想你*可以做这样的事情。我的编辑是指该部分。但是,你在一个命令式算法中使用可变状态。在思考之后,您可以*在真正的Java中编写纯Java代码并通过interop调用它。我并不总是反对命令式的变异代码。我只是认为它更容易在原始Java中完成。特别是因为互操作性非常好。 –

+0

查看亚历山大的解决方案。性能很糟糕,但这正是我所需要的。 – noahlz

3

首先,正如Goran所说,for不是一个声明 - 它是一个表达式,即序列理解。它通过迭代其他序列来构建序列。所以在表单中它是用来表示它是纯粹的功能(没有副作用)。 for可以看作增强map注入filter。正因为如此,它不能用来保持迭代状态,例如, reduce做。其次,你可以使用for和可变状态来表示序列逆转,例如,可以使用for。使用一个原子,它是java变量的粗略等价(不考虑它的并发性)。但这样做,你面临几个问题:

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

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

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

+0

实际上,原子很快,在无争议的代码中,它们是一个单一的比较和设置的java调用。 https://开头github上。com/clojure/clojure/blob/master/src/jvm/clojure/lang/Atom.java在我的测试中,它们大约比(inc 1) – gtrak

+1

慢20倍:https://gist.github.com/1444977 – gtrak

+1

map +过滤器实际上还没有'for'那么强大。 'for'更像是一个(可能重复的)'mapcat'应用程序,其实际上它同样强大。考虑将'(for [x(range 10)y [:x x]] y)'作为'map'操作:你不能这样做,因为你想返回两倍于你给定的元素。 – amalloy

相关问题