【问题标题】:Function in order to show a TicTacToe board : can it be simplified and more readable?显示井字棋盘的功能:可以简化并更具可读性吗?
【发布时间】:2013-08-24 10:56:14
【问题描述】:

在学习 clojure 时,我正在尝试构建一个没有 ia 的简单井字游戏。我开始使用一种方法来显示板,但它对我来说似乎很丑:我正在使用内部函数,以便使它们本地化到 show-board 方法,这样它就不能在外部实例化。也许是长得不好的原因。

这是函数(按我的意愿工作):

(defn show-board [board]
    "Prints the board on the screen.
     Board must be a list of three lists of three elements with
     :cross for a cross and :circle for a circle, other value for nothing"
    (let [convert-elem (fn [elem] 
                            (cond
                                    (= elem :cross) "X"
                                    (= elem :circle) "O"
                                    :other "_"))
          convert-line (fn [elems-line]
                            (reduce str (map convert-elem elems-line)))]
         (doseq [line board]
            (println (convert-line line)))))

这是一个用例:

(show-board (list (list :cross :circle :none) (list :none :circle :none) (list :cross :none :none)))

抱歉代码太丑了,这是因为我来自 Java,我是从 Clojure 开始的。 (我想我真的会从学习 Clojure 中受益,并用它制作游戏,所以我不能就这样离开它。

我想简化它的另一个原因是代码维护和可读性。

提前致谢

【问题讨论】:

    标签: clojure


    【解决方案1】:

    让底层编写器缓冲输出而不是创建中间字符串(即使您通过StringBuilder 创建它们)怎么样?

    => (defn show-board [board]
         (doseq [line board]
           (doseq [item line]
             (print ({:cross \X :circle \O} item \_)))
           (newline)))
    

    如果您想获取字符串而不是打印出来,只需使用with-out-str

    【讨论】:

      【解决方案2】:

      使用内部函数非常好,但在这种情况下使用letfn 可能看起来更好。此外,convert-elem 可以用case 简化,convert-line 应该使用apply 而不是reduce,原因我在my answer 中对Clojure: reduce vs. apply SO 问题进行了解释(简而言之,使用apply a使用单个StringBuilder,结果过程是线性的;使用reduce,每个步骤都涉及分配一个新的StringBuilder,并且过程是二次的;对于像这样的小情况,这并没有太大区别,但它是尽管如此,使用正确方法的风格更好)。

      这是修改后的函数:

      (defn show-board [board]
        "Prints the board on the screen.
         Board must be a list of three lists of three elements with
         :cross for a cross and :circle for a circle, other value for
         nothing."
        (letfn [(convert-elem [elem] 
                  (case elem
                    :cross "X"
                    :circle "O"
                    "_"))
                (convert-line [elems-line]
                  (apply str (map convert-elem elems-line)))]
          (doseq [line board]
            (println (convert-line line)))))
      

      附带说明一下,这个函数实际上接受任意 seqable 的 seqable,不一定是列表的列表。 Clojure 中更常见的具体类型选择是使用向量:

      user> (show-board [[:cross :circle :none]
                         [:none :circle :none]
                         [:cross :none :none]])
      XO_
      _O_
      X__
      nil
      

      【讨论】:

      • convert-element 函数可以替换为地图{:cross \X :circle \O :none \_}。恕我直言,它更简洁。
      • @NikitaBeloglazov 是的,只要将规范(如文档字符串中所示)更改为要求 :none 作为“空”值。或者我们可以使用{:cross \X :circle \O} 并使用(get convert-elem elem \_) 查找结果。
      • 是的。我可以添加,但您可以在没有 get 的情况下添加:(convert-elem elem \_)
      • 哇 :) 非常感谢。不知道 letfn :会在适当的时候使用它。在处理字符串时不知道 reduce 的复杂性。也感谢“\”读者的 cmets。
      • @NikitaBeloglazov 确实。感谢cmets! @LaurentBERNABE要在答案中再补充一句,虽然我认为在这种情况下在一个函数中做所有事情都很好(因为它真的很简单),如果这个逻辑变得更加复杂,那么把事情分开可能是有意义的——将内部函数放在顶层(可能将它们标记为私有),制作一个单独的函数构造一个可用于打印的板表示(一个字符串或字符串序列),然后让show-board调用该函数。这样一来,尽管有额外的复杂性,它都将保持清晰和可测试。
      【解决方案3】:
      (defn show-board
        [board]
        (let [convert (fn [el] (get {:cross \X :circle \O} el \_))]
          (doseq [line board]
            (doseq [el line]
              (print (convert el)))
            (println))))
      

      【讨论】:

      • 除了美观之外,这也让我想起了类 Java 语言的嵌套 for(比我们之前的尝试更简单)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多