在clojure中的地图矢量中更新一个地图

时间:2016-08-18 03:14:34

标签: vector clojure hashmap

我已经做了很多搜索,没有找到我的具体问题的答案。作为背景,我正在Java上编写一个编码训练营,我们正在学习JavaScript和Clojure以及Java。课程内容大致为Java:50%/ JavaScript 30%/ Clojure 20%。在我的同学之后取得了进步,我正在接受教练的挑战。

我在Clojure中构建了一个应用程序来管理动物收容所。我使用一个哈希映射矢量作为我的中央数据存储。我成功完成的事情:

  1. 从硬编码的哈希映射数据中加载样本数据。
  2. 创建一个新动物并将其hashmap添加到数组中。
  3. 列出动物的整个“表格”(每行一个散列图的数据)
  4. 显示一只动物记录的多线详细视图。
  5. 我目前正在努力的是我的编辑功能。它应该显示所选动物的现有数据并进行用户想要进行的任何编辑,然后更新hashmap的工作副本,最后更新数组的工作副本。

    (defn edit-animals [animals]
      ;; ask which animal to edit index is 1 based from the UI
      (let [index (wait-for-int "Please enter the idex of the animal to edit. Enter 0 to display the list of animals" 0 (count animals))]
    
        ;; if the user typed a valid index
        (if (> index 0)
          ;; then edit the animal
          (let [index (- index 1)  ;; index is 0 based for indexing the array
                animals animals
                animal (nth animals index)
    
                ;; prompt with existing data and ask for new data
                name (wait-for-string (str "Name: " (:name animal) "\n") false)
                species (wait-for-string (str "Species: " (:species animal "\n")) false)
                breed (wait-for-string (str "Breed: " (:breed animal "\n")) false)
                description (wait-for-string (str "Description: " (:description animal "\n")) false)
    
                ;; check for null returns from user
                name2 (if (= name "") (:name animal) name)
                species2 (if (= species "") (:species animal) species)
                breed2 (if (= breed "") (:breed animal) breed)
                description2 (if (= description "") (:description animal) description)
    
                ;; update local copy of animal
                animal (assoc animal :name name2 :species species2 :breed breed2 :description description2)]
            ;; assoc fails to update animals
            ;; assoc-in crashes at runtime
            animals (assoc animals index animal))
    
          ;;(wait-for-enter "\nPress enter to return to the menu")
    
          ;; else dispolay the list of animals
          (display-animals animals)))
      animals)
    

    我已经在我的调试器中运行了这段代码,并验证了所有内容都按预期工作:

    animal (assoc animal :name name2 :species species2 :breed breed2 :description description2)
    

    下一行以两种方式之一失败,正如我在评论中记录的那样。

    我知道原子可能是一种更好的方法,但到目前为止,不断传递的地图矢量是有效的,所以我想找到一个解决当前问题的方法,不涉及使用原子。一旦我解决了这个问题,我计划将项目切换到原子数据结构。但这是另一天的项目。

    如果我在这里错过了相关讨论,请指出我正确的方向!

3 个答案:

答案 0 :(得分:1)

简而言之,你必须返回你最里面的动物(它的索引> 0),否则显示当前动物并返回它。所以它会是这样的:

(defn edit-animals []
  (let [index ...]
    (if (> index 0) 
      ;; for an acceptable index you query for input, modify animals, and return modified collection
      (let [...] 
        (assoc animals index animals)) 
      ;; otherwise display initial data and return it
      (do (display animals) 
          animals))))

但我会更多地重构代码,使其更具clojure风格。首先,我将使用独立函数的输入提取动物的更新,以删除使name, name2, breed, breed2...绑定混乱的let绑定。 (upd-with-input),并将assoc替换为update。像这样:

(defn edit-animals [animals]
  (letfn [(upd-with-input [animal field prompt]
            (let [val (wait-for-string (str prompt (field animal) "\n") false)]
              (if (clojure.string/blank? val)
                animal
                (assoc animal field val))))]
    (let [index (dec (wait-for-int "enter index" 0 (count animals)))]          
      (if (contains? animals index)
        (update animals index
                (fn [animal]
                  (-> animal
                      (upd-with-input :name "Name: ")
                      (upd-with-input :species "Species: ")
                      (upd-with-input :breed "Breed: ")
                      (upd-with-input :description "Description: "))))
        (do (when (== -1 index) (display animals))
            animals)))))

然后我会考虑将收集用户输入的整个部分与实际更新的动物集合分开。

答案 1 :(得分:1)

该行:

animals (assoc animals index animal))

不会按照您的想法执行操作 - 它不在let绑定向量中。

首先,干得好,你用正确的方式提出问题(用例子等)。祝贺你的编码课程。我的建议是继续做你正在做的事情,并学会以与你在java中想到的完全不同的方式思考clojure。它们都是值得的,只是在方法上有所不同。在您的代码中,您正在进行大量的临时任务" (例如name2)。你的let绑定向量有11对的事实是一个红旗,你做得太多了。 letanimals animals中的第二项特别奇怪。

相反,尝试考虑表达式的评估而不是值的赋值。 name2 = name1 + ...是一个陈述,而不是表达。它没有做任何事情。相反,在声明性语言中,几乎所有东西都是表达式。在下面的代码中(我只是你已经做过的扩展而不一定是我从头开始做的扩展),请注意没有重新绑定本地绑定(没有任何内容是"已分配至少不止一次。 let允许我们在词法上将name绑定到表达式的结果,然后我们使用name来实现其他目的。 not-empty让我们比使用namename2做得更好,这就是你所做的。

(def animals [{:name "John" :species "Dog" :breed "Pointer" :description "Best dog"}
              {:name "Bob"  :species "Cat" :breed "Siamese" :description "Exotic"}])

(defn edit-animals
     [animals]
     (if-let [index (dec (wait-for-int "Please enter the index of the animal to edit. Enter 0 to display the list of animals" 0 (count animals)))]
       (let [animal (nth animals index)
             name        (or (not-empty (wait-for-string (str "Name: " (:name animal) "\n") false)) 
                             (:name animal))
             species     (or (not-empty (wait-for-string (str "Species: " (:species animal) "\n") false)) 
                             (:species animal))
             breed       (or (not-empty (wait-for-string (str "Breed: " (:breed animal) "\n") false)) 
                             (:breed animal))
             description (or (not-empty (wait-for-string (str "Description: " (:description animal) "\n") false)) 
                             (:description animal))
             animal {:name name :species species :breed breed :description description}]
         (println "showing new animal: " animal)
         (assoc animals index animal))
       animals))

(def animals (edit-animals animals))

请注意,除了重构代码之外,这并没有真正实现。它确实做得太多,并不是一个功能如何做好一件事的好例子。但是我认为你现在的目标应该是更加惯用,并且当你写下你的clojure时,摆脱命令式的心态。完成后,您可以专注于它的设计部分。

保持良好的工作,并提出更多问题!

答案 2 :(得分:0)

我认为您要从

中删除animals
animals (assoc animals index animal)) 

if语句的末尾,而是返回函数的结果

(assoc animals index animal)

或显示它们

(display-animals (assoc animals index animal))