Clojure 中惯用的字符串旋转

Posted

技术标签:

【中文标题】Clojure 中惯用的字符串旋转【英文标题】:Idiomatic string rotation in Clojure 【发布时间】:2015-02-25 14:29:20 【问题描述】:

如何在 Clojure 中为 Burrows-Wheeler transform 惯用地旋转字符串?

我想出了这个,它使用(cycle "string"),但感觉有点必要:

(let [s (str "^" "banana" "|")
      l (count s)
      c (cycle s)
      m (map #(take l (drop % c)) (range l))]
  (apply map str m))
=> ("^banana|" "banana|^" "anana|^b" "nana|^ba" "ana|^ban" "na|^bana" "a|^banan" "|^banana")

我不确定这是否符合代码高尔夫的条件。有没有更清洁的方法来做到这一点?

【问题讨论】:

在效率的基础上,Criterium 的 quickbench 以 1:7:46 的粗略比例给出了 cgrand/status203/pate 答案的平均运行时间 @status20,@O-I 的 rotations 解决方案甚至快了 4 倍。 @pate rotations 返回一个惰性字符序列的惰性序列。当我强制评估时((dorun (apply str (rotations "^bananas|"))) vs (dorun (bwrot "bananas"))rotationsbwrot 慢 12 倍。 【参考方案1】:

我愿意:

(defn bwrot [s]
  (let [s (str "^" s "|")]
    (for [i (range (count s))]
      (str (subs s i) (subs s 0 i)))))

或:

(defn bwrot [s]
  (let [n (+ 2 (count s))
        s (str "^" s "|^" s "|")]
    (for [i (range n)]
      (subs s i (+ i n)))))

第二个应该分配更少(一个字符串而不是每次迭代三个)。

【讨论】:

从 Java 7 开始,子字符串不再是视图。 谢谢@Alex 我不知道那个变化(这显然发生在 7u6)【参考方案2】:

clojure.contrib.seq 中曾经有一个 rotations 函数,可能值得一看以获得灵感。 source 转载如下:

(defn rotations
  "Returns a lazy seq of all rotations of a seq"
  [x]
  (if (seq x)
    (map
     (fn [n _]
       (lazy-cat (drop n x) (take n x)))
     (iterate inc 0) x)
    (list nil)))

然后你可以这样做:

(apply map str (rotations "^banana|"))
; => ("^banana|" "banana|^" "anana|^b" "nana|^ba" "ana|^ban" "na|^bana" "a|^banan" "|^banana")

【讨论】:

【参考方案3】:

逐步调用partition 有效:

(defn bwt[s]
  (let [s' (str "^" s "|")
        c (cycle s')
        l (count s')]
    (map last (sort (apply map str (take l (partition l 1 c)))))))

(apply str (bwt "banana"))
=> "|bnn^aaa"

【讨论】:

这比 @O-I 的 rotations 解决方案慢 72 倍。【参考方案4】:

如果我不关心效率或字符数,我会写如下内容:

(defn rotate-string
 [s]
 (apply str (concat (drop 1 s) (take 1 s))))

(defn string-rotations
  [s] 
  (->> s
       (iterate rotate-string)
       (take (count s))))

(rotate-string "^banana|") ; "banana|^"
(string-rotations "^banana|") ; ("^banana|" "banana|^" "anana|^b" "nana|^ba" "ana|^ban" "na|^bana" "a|^banan" "|^banana")

特别是,将单次旋转分解为它自己的函数。

【讨论】:

【参考方案5】:

另一种实现旋转的方法是使用“双字符串”(即将字符串连接到自身)并使用子字符串。

(defn rotations [strng]
  (let [indices (range (count strng))
        doublestr (str strng strng)]
    (map #(subs doublestr % (+ % (count strng))) indices)))

(rotations "^banana|")
;;(^banana| banana|^ anana|^b nana|^ba ana|^ban na|^bana a|^banan |^banana)

“foo”的旋转:

取双字符串“foofoo” “foo”的长度 n = 3 旋转是 "foofoo" 的所有 n 子字符串,以 indices 0、1、2 开头并且具有相同的长度 n

【讨论】:

以上是关于Clojure 中惯用的字符串旋转的主要内容,如果未能解决你的问题,请参考以下文章

Clojure 中的惯用语:(drop 1 str)还是(rest str)?

在 Clojure 中添加向量的惯用方法是啥?

Clojure 中的惯用错误处理

什么是惯用的clojure:使用

通过 Clojure 中的集合进行递归的惯用方式

clojure 中的惯用配置管理?