Clojure For Comprehension example

2019-02-07 09:14发布

问题:

I am using docjure and it needs a column map for its select-columns function. I would like to grab all my columns without having to specify it manually. How do I generate the following as a lazy infinite vector sequence [:A :B :C :D :E ... :AA :AB :AC .... :ZZ ... :XFD]?

回答1:

Your question boils down to: "How do I convert a number to a base-26 string with the alphabet A-Z?".

Here's one way to do that - probably not the most concise way, but making it more elegant is left as an exercise for the reader :).

Assume that numbers 0-25 map to 'A'-'Z', 26 maps to 'AA', etcetera. First we define a function to-col that converts an integer to a column keyword. You can use that function to generate an infinite sequence.

(defn to-col [num]
  (loop [n num s ()]
    (if (> n 25)
      (let [r (mod n 26)]
        (recur (dec (/ (- n r) 26)) (cons (char (+ 65 r)) s)))
      (keyword (apply str (cons (char (+ 65 n)) s))))))

That gives you a way to generate an infinite sequence of column keywords:

(take 100 (map to-col (range)))
;; => (:A :B :C :D :E :F :G :H :I :J :K :L :M :N :O :P :Q :R :S :T :U :V :W
;; :X :Y :Z :AA :AB :AC :AD :AE :AF :AG :AH :AI :AJ :AK :AL :AM :AN :AO :AP
;; :AQ :AR :AS :AT :AU :AV :AW :AX :AY :AZ :BA :BB :BC :BD :BE :BF :BG :BH
;; :BI :BJ :BK :BL :BM :BN :BO :BP :BQ :BR :BS :BT :BU :BV :BW :BX :BY :BZ
;; :CA :CB :CC :CD :CE :CF :CG :CH :CI :CJ :CK :CL :CM :CN :CO :CP :CQ :CR
;; :CS :CT :CU :CV)


回答2:

The essential clojure function for corecursion (and "tying the knot" is about it, no?) is iterate:

(def abc (map (comp str char) (range 65 91)))
(defn cols [seed]
  (let [next #(for [x %] (for [y seed] (str x y)))]
    (->> (iterate #(apply concat (next %)) seed)
         (mapcat identity))))

(time (first (drop 475254 (cols abc))))
"Elapsed time: 356.879148 msecs"
"AAAAA"

(doc iterate)
-------------------------
clojure.core/iterate
([f x])
  Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects

EDIT: Generalization of the function to return the "ordered" subsets of a set

(defn ordered-combinations [seed]
  (->> (map list seed)
       (iterate #(for [x % y seed] (concat x [y])))
       (mapcat identity)))

(def cols
  (let [abc (map char (range 65 91))]
    (map #(apply str %) (ordered-combinations abc))))

user> (take 30  (map #(apply str %) cols))
("A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "AA" "AB" "AC" "AD")
user> (take 28 (ordered-combinations [0 1]))
((0) (1) (0 0) (0 1) (1 0) (1 1) (0 0 0) (0 0 1) (0 1 0) (0 1 1) (1 0 0) (1 0 1) (1 1 0) (1 1 1) (0 0 0 0) (0 0 0 1) (0 0 1 0) (0 0 1 1) (0 1 0 0) (0 1 0 1) (0 1 1 0) (0 1 1 1) (1 0 0 0) (1 0 0 1) (1 0 1 0) (1 0 1 1) (1 1 0 0) (1 1 0 1))


回答3:

This answer is wrong; hopefully in an educational way.

mathematically what you are asking for is a lazy sequence of all subsets of the infinite sequence of the alphabet.

(take 40 (map #(keyword (apply str %)) 
           (rest (combinatorics/subsets  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))))
(:A :B :C :D :E :F :G :H :I :J :K :L :M :N
 :O :P :Q :R :S :T :U :V :W :X :Y :Z :AB :AC 
 :AD :AE :AF :AG :AH :AI :AJ :AK :AL :AM :AN :AO)

foo.core> (nth (map #(keyword (apply str %)) 
                 (rest (combinatorics/subsets  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) 
               40000)
:BLOUZ

project.clj:

(defproject foo "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [ org.clojure/math.combinatorics "0.0.3"]]
  :dev-dependencies [[swank-clojure/swank-clojure "1.4.0"]]) ; swank)

using math.combanatorics:

(ns foo.core
  (:require [clojure.math.combinatorics :as combinatorics]))


回答4:

As mentioned by jneira iterate feels like the right way to do this.

Here's an improvement on his function that should be clearer to understand as it involves less intermediate types. It is fully lazy unlike some of the other solutions based around loop/recur:

(defn column-names-seq [alphabet]
  (->> (map str alphabet)
     (iterate (fn [chars]
                (for [x chars
                      y alphabet]
                  (str x y))))
     (apply concat)))

To use it simply provide it with an alphabet string e.g:

(take 30 (column-names-seq "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))  ;; => ("A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "AA" "AB" "AC" "AD")


回答5:

I think this may be the kind of thing you were looking for (if not, well, at least it is what I thought the "correct" answer should be ;o).

(defn stream [seed]
  (defn helper [slow]
    (concat (map #(str (first slow) %) seed) (lazy-seq (helper (rest slow)))))
  (declare delayed)
  (let [slow (cons "" (lazy-seq delayed))]
    (def delayed (helper slow))
    delayed))

(take 25 (stream ["a" "b" "c"]))
("a" "b" "c" "aa" "ab" "ac" "ba" "bb" "bc" "ca" "cb" "cc" "aaa" "aab" "aac" "aba" "abb" "abc" "aca" "acb" "acc" "baa" "bab" "bac" "bba")

Code in git. I suspect I am abusing def horribly, but it works.

The idea is pretty simple: I take the output from the sequence and feed it back on itself. For each value in the output (which is also the input), I generate a new output by appending each of the letters in the seed sequence. Since this is circular it just keeps on going (there's an initial "" which is in the input, but not the output, that helps avoid creating something from nothing).

The process of feeding the output into the input is called "tying the knot" in a fairly famous paper for Haskell. But it's harder to do in Clojure because it's an eager language (and even lazy sequences aren't "lazy enough") - the only solution I could find was that mess with def (I suspect someone might do better with delay and force, but I had no luck).

And maybe it could even be written as a map?

[updated 2012-07-19 with more compact code]

Related question with much better code in an answer at Tying the knot in Clojure: circular references without (explicit, ugly) mutation? (it's the same idea as jneira's answer).

For completeness, here's the final version using iterate:

(defn stream [seed]
  (defn helper [slow] (mapcat (fn [c] (map #(str c %) seed)) slow))
  (apply concat (iterate helper seed)))


回答6:

Probably there is a way to remove the "for" duplication, but here is something that works for me:

(def all-letters (map char (range 65 90)))
(defn kw [& args] (keyword (apply str args)))
(concat
  (for [l all-letters] (kw l))
  (for [l all-letters l2 all-letters] (kw l l2))
  (for [l all-letters l2 all-letters l3 all-letters] (kw l l2 l3)))


回答7:

#include<stdio.h>
int main()
{
int n=703;

char arr[26]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};

while(n){
printf("%c ",arr[(n)%26]);
n=(n)/26;

}
return 0;
}

guys is this as simple as this or am i missing something .... of course the above program prints the required atring in reverse we can avoid that by using recursion or store it in a string to reverse it ...