【问题标题】:Clojure - idiomatic way to do dynamically nested loopsClojure - 做动态嵌套循环的惯用方式
【发布时间】:2017-07-10 21:39:23
【问题描述】:

此代码旨在循环遍历提供的字符串中的每个字母并继续直到 z,然后递增前一个字母并在 a 处重新开始(例如,从“bde”开始,下一个将是“bdf”,以“zzz”结束" - 与典型循环的工作方式相同。)

我当然可以做一个嵌套的理解,因为这只有三个层次,但如果层次任意深,我传统上的方法是使用递归(如下所示),基本上相当于一个深度优先搜索。

这种方法的问题是任何非平凡大小的输入都会破坏堆栈(例如,“abcd”),我想不出没有递归的好方法。在 clojure 代码下实现的 Python 中的类似方法(有一些小的差异,例如在可变列表中累积结果)不会达到输入“abcd”的堆栈限制。

我尝试使用循环/递归,但这个结构似乎在 for 宏中不起作用,因为调用必须在下一次循环迭代时暂停,因此不在尾部位置(至少我相信这是原因) .

解决这类问题最惯用的方法是什么?

;;; example using for macro
(defn gen-pw [pw r]
  (cond (empty? pw) r
        :else (flatten (for [x (range (int(first pw)) 123)]
                            (gen-pw (rest pw) (str r (char x)))))))


;;; example using map instead of for macro
(defn gen-pw [pw r]
  (cond (empty? pw) r
        :else (flatten (map #(gen-pw (rest pw) (str r (char %)))
                            (range (int(first pw)) 123))))) 

(gen-pw "bde" "") 
def gen_pw(pw,r='',final=[]):
    if not pw:
        final.append(r)
        return 
    for letter in range(ord(pw[0]),123):
        gen_pw(pw[1:],r + chr(letter))
    return final

print(gen_pw('abcd'))

【问题讨论】:

  • 不清楚您要完成什么。这个过程是否应该为给定的字符串创建一个字典顺序的列表,即("bde" "bdf" "bdg" ... "zzz")
  • 是的,这就是目的。

标签: python clojure


【解决方案1】:

通过评估类似这样的内容,您正在生成一个过度嵌套的列表:

(for [...]
  (for [...]
    (for [...]
      ...)))

然后尝试使用flatten 修复意外嵌套,这当然必须递归地进入你的巨大结构,然后爆炸。相反,首先生成一个平面列表。最简单的方法就是使用您的map 版本,将map 替换为mapcat,并删除现在不需要的flatten

(defn gen-pw [pw r]
  (cond (empty? pw) [r]
        :else (mapcat #(gen-pw (rest pw) (str r (char %)))
                      (range (int(first pw)) 123))))

您还必须将基本情况从 r 调整为 [r],就像我在这里所做的那样:您正在生成一个有效密码的列表,而不仅仅是一个密码,并且所以返回类型应该总是一个列表。

【讨论】:

  • 我之前确实尝试过 mapcat,但在 [r] 部分犯了错误,所以我得到了单个字符 - 这就是我所缺少的。这是好消息,坏消息是它仍然会爆仓。
  • 哦,当然。因为(mapcat (mapcat (mapcat ... 也是一个问题,只是一个不同类型的问题。 edit:实际上,不,我不同意。 (count (gen-pw "aaaaa" "")) 返回 11881376 就好了,你不可能有一千万的堆栈帧来玩。
  • 嗯,让我在我的机器上再次测试这个 - 因为我不在它附近,所以在在线 REPL 上使用它,它在那里窒息。以前连“abcd”都不会运行。
  • 顺便说一句,如果所需的字符串长度非常大,这仍然会耗尽堆栈:它会消耗许多与字符串长度成线性关系的堆栈帧。所以如果你想生成一千个字符长的密码,你就会遇到麻烦。当然,您的 Python 版本也是如此。
  • 出于好奇,您为什么在这里使用cond 而不仅仅是if
【解决方案2】:

我认为这个问题陈述是关于计算笛卡尔积,所以我倾向于只推荐在clojure.math.combinatorics 中找到的the lazy mutual-recursion implementation of that。使用它变成这样:

(ns loops
  (:require [clojure.math.combinatorics :refer [cartesian-product]]
            [clojure.string :refer [join]]))

(defn chars-from [start]
  (map char (range (int start) 123)))

(defn gen-pw [pw]
  (map join (apply cartesian-product (map chars-from pw))))

(gen-pw "bde")
;;=> ("bde" "bdf" "bdg" ... "bee" "bef" "beg" ...

【讨论】:

  • 是的 - 这将是 itertools.product 的对应物 - 我喜欢它。感谢您的添加。
【解决方案3】:

一种方法是使用iterate

(defn transition [[text n]]
  (let [c (nth text n)
        nxt (if (= c \z) \z (-> c int inc char))
        nxt-str (str (subs text 0 n) nxt (subs text (inc n) (dec (count text))))]
    (if (= \z nxt)
      [nxt-str (inc n)]
      [nxt-str n])))

(defn ongoing? [[text n]]
  (not (every? #(= \z %) text)))

(->> (iterate transition ["zza" 2])
     (take-while ongoing?)
     (map first))

请注意,对于["zza" 2]\a 位于第三位(因此为 2),而对于 ["dzs" 0],\d 位于第一位(因此为 0)。

【讨论】:

    【解决方案4】:

    如果您达到递归限制,请将您的进程设为iterative rather than recursive

    你是对的,当递归过程调用是更大表达式的一部分(即不在尾部位置)时,它将产生一个递归过程。因此,请确保递归调用代表过程的全部值。

    (defn gen-pw [pw r]
      (let (s (successor pw))
        (if (nil? s)                ; (successor "zzz") is equal to nil
          r
          (gen-pw s (cons s r)))))  ; (successor "bfe") is equal to "bff"
    

    【讨论】:

    • 这是一个在 scheme 中运行良好的解决方案,但在 clojure 中不起作用,因为没有自动尾调用优化。你可以通过使用recur 来解决这个问题,但你的方法仍然是严格/急切的,懒惰的解决方案会好得多。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-16
    • 2018-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-09
    • 1970-01-01
    相关资源
    最近更新 更多