2011-12-07 84 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

相關問題