【问题标题】:Idiomatic Creation of Hash-Map哈希映射的惯用创建
【发布时间】:2016-06-10 18:38:26
【问题描述】:

我想创建一个散列映射,其中包含 n 个键值对,这些键值对以 3 个为一组创建,其中集合确实相交,例如[(34 false) (35 false) (36 false)] && [(24 false) (25 false) (26 false)] -> {34 false 35 false 36 false 24 false 25 false 26 false}


编辑:

为了玩/练习 Clojure,我正在尝试实现战舰棋盘游戏的惯用版本。我决定将战舰坐标存储在哈希图中,其中键是坐标,值是布尔值,指示该船的该部分是否已被击中。下面的具体代码是 supposed

  1. 选择一个轴(水平或垂直)
  2. 选择船头坐标
  3. 通过相应地增加 x 或 y 值来“建造”船的其余部分(总共 3 个坐标),例如{“10”假“11”假“12”假}。请注意,“10”转换为矩阵的第二行,第一列。
  4. 注意:在将船添加到坐标哈希图之前,必须检查新的船坐标以确保不存在交点。如果是这样,这艘船必须“重建”。

为此,我创建了以下代码。它有两个问题:

  1. 执行该函数会导致使用“acc”累加器出现以下异常:

    clojure.lang.LazySeq 不能转换为 clojure.lang.Associative

  2. 函数的结果不是单个哈希映射,而是n个哈希映射的列表

使用惯用的 clojure,我怎样才能实现我的目标?

(defn launch
  [n]
  (loop [cnt n acc {}]
    (if (= cnt 0)
      acc
    (recur
      (- cnt 1)
      ((fn []
        (let [axis (rand-int 2)]
            (if (= axis 0)
              (let [x (rand-int 8) y (rand-int 10)]
                (for [k (range 3)]
                  (assoc acc (str y (+ x k)) false)))
              (let [x (rand-int 10) y (rand-int 8)]
                (for [k (range 3)]
                  (assoc acc (str (+ y k) x) false)))))))))))

【问题讨论】:

    标签: clojure


    【解决方案1】:

    这就是我将如何重写它:

    (defn create-key [axis-val i]
      (if axis-val
        (str (rand-int 10) (+ (rand-int 8) i))
        (str (+ (rand-int 8) i) (rand-int 10))))
    
    (defn launch [n]
      (reduce (fn [acc axis]
                (reduce #(assoc % (create-key axis %2) false)
                        acc
                        (range 3)))
              {}
              (repeatedly n #(zero? (rand-int 2)))))
    

    在回复中:

    user> (launch 5)
    
    {"40" false, "07" false, "19" false, 
     "46" false, "87" false, "47" false, 
     "41" false, "62" false, "86" false}
    

    或者(如果你不喜欢reduce):

    (defn launch [n]
      (zipmap (mapcat #(map (partial create-key %) (range 3))
                      (repeatedly n #(zero? (rand-int 2))))
              (repeat false)))
    

    第三种变体是使用列表推导生成键:

    (defn launch [n]
      (zipmap (for [_ (range n)
                    :let [axis (zero? (rand-int 2))]
                    i (range 3)]
                (create-key axis i))
              (repeat false)))
    

    我猜这三个都是惯用的,所以你可以根据自己喜欢的编程风格选择一个。

    请注意,生成的键在地图内被打乱,因为未排序的地图不保持顺序。如果很重要,你应该使用sorted-map

    你的变种呢,产生错误的一个是这样的:

    (for [k (range 3)] (assoc acc (str y (+ x k)) false))

    它不会将所有键都放在一个映射中,而是生成一个由三个项目组成的序列,等于(assoc acc k false)

    (let [acc {}]
      (for [k (range 3)] (assoc acc k false)))
    ;;=> ({0 false} {1 false} {2 false})
    

    要做你想做的事,你可以使用reduce:

    (let [acc {}]
      (reduce #(assoc %1 %2 false) acc (range 3)))
    ;;=> {0 false, 1 false, 2 false}
    

    【讨论】:

    • 感谢有见地的代码示例。鉴于原始帖子中添加的信息,您会更改示例中的任何内容吗?
    【解决方案2】:

    leetwinski 给出了一个更简洁的答案,但我想我还是会发布这个,因为我基本上保持你的结构完好无损,这可能会帮助你更清楚地看到错误。

    首先,我不确定您为什么要将 acc 重新绑定到匿名函数调用的值。你的let 会很高兴地返回一个结果;所以,你或许应该思考一下为什么你认为有必要创建一个匿名函数。

    其次,问题在于for 返回一个惰性序列,而您将其绑定到您认为的映射数据结构。这解释了为什么它适用于案例 0 和 1,但是当您使用值 2 时它会失败。

    由于我并不完全了解您要完成的工作,因此这是您的原始代码,经过修改后可以正常工作。免责声明——这不是真正的惯用语,也不是我的写作方式,但我发帖是因为与原版相比,它可能会有所帮助,因为它确实有效。

    (defn launch
      [n]
      (loop [cnt n
             acc {}]
        (if (= cnt 0)
          acc
        (recur
          (dec cnt)
          (into acc
            (let [axis (rand-int 2)]
              (if (= axis 0)
                (let [x (rand-int 8) y (rand-int 10)]
                  (map #(hash-map (str y (+ x %)) false) (range 3)))
                (let [x (rand-int 10) y (rand-int 8)]
                  (map #(hash-map (str (+ y %) x) false) (range 3))))))))))
    

    【讨论】:

    • 感谢分享更正的代码示例。鉴于原始帖子中添加的信息,您将如何惯用地解决问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-06
    • 1970-01-01
    • 2021-06-11
    相关资源
    最近更新 更多