Vector 更新clojure中地图向量中的一个地图

Vector 更新clojure中地图向量中的一个地图,vector,clojure,hashmap,Vector,Clojure,Hashmap,我在这方面做了很多研究,但没有找到我的具体问题的答案。作为背景,我正在参加一个关于Java的编码训练营,我们正在学习Java旁边的JavaScript和Clojure。课程内容大致为Java:50%/JavaScript 30%/Clojure 20%。我已经超越了我的同班同学,正在应对来自老师的挑战 我在Clojure中构建了一个应用程序来管理动物收容所。我使用hashmaps向量作为我的中央数据存储。我成功做到的事情: 从hashmaps的硬编码数组加载样本数据 创建新动物并将其哈希映射添加

我在这方面做了很多研究,但没有找到我的具体问题的答案。作为背景,我正在参加一个关于Java的编码训练营,我们正在学习Java旁边的JavaScript和Clojure。课程内容大致为Java:50%/JavaScript 30%/Clojure 20%。我已经超越了我的同班同学,正在应对来自老师的挑战

我在Clojure中构建了一个应用程序来管理动物收容所。我使用hashmaps向量作为我的中央数据存储。我成功做到的事情:

  • 从hashmaps的硬编码数组加载样本数据
  • 创建新动物并将其哈希映射添加到数组中
  • 列出动物的整个“表”(每行一个hashmap中的数据)
  • 显示一个动物记录的多行详细视图
  • 我目前正在努力解决的是我的编辑功能。它应该显示所选动物的现有数据,并进行用户想要进行的任何编辑,然后更新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)
    
    正如我在评论中记录的那样,下一行有两种方式失败

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


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

    我想你想把
    动物

    animals (assoc animals index animal)) 
    
    if
    语句的末尾,返回函数的结果

    (assoc animals index animal)
    
    或者展示它们

    (display-animals (assoc animals index animal))
    

    简而言之,您必须返回绑定在最内层let中的动物(它的索引>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风格。首先,我将使用独立函数的输入提取动物的更新,以删除使
    let
    绑定混乱的
    name、name2、breed、breed2…
    绑定。(
    upd和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)))))
    
    然后我会考虑将收集用户输入的整个部分与实际更新动物收集分离。

    行:

    animals (assoc animals index animal))
    
    它不做你认为它做的事情——它不在let绑定向量中

    首先,干得好,你问问题的方式正确(举例说明等)。祝贺你上了编码课程。我的建议是继续做你正在做的事情,并学会用clojure以一种与java截然不同的方式思考。它们都是值得的,只是方法不同而已。在代码中,您正在执行许多“临时分配”(例如
    name2
    )。事实上,let绑定向量有11对,这表明你做得太多了。
    let
    动物中的第二项特别奇怪

    相反,试着考虑表达式的计算,而不是赋值
    name2=name1+…
    是一条语句,而不是表达式。它没有任何作用。相反,在声明式语言中,几乎所有内容都是表达式。在下面的代码中(我只是您所做工作的扩展,不一定是我从头开始做的),请注意没有本地绑定被重新绑定(没有任何内容被多次“分配到”)
    let
    允许我们在词汇上将
    name
    绑定到表达式的结果,然后使用
    name
    实现其他功能<代码>非空
    允许我们比使用
    名称
    名称2
    做得更好,而这正是您所做的

    (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的时候更加地道一点,摆脱命令式思维。这样做之后,您就可以专注于它的设计部分了


    继续努力,问更多的问题

    您需要重新构造代码,因为您不返回更新后的值,而是返回初始值。记住集合是不可变的,我同意我需要重新构造代码。我只是不知道该怎么做。。。我尝试将最后一行
    animals(assoc animals index animal))
    移到big
    (let[]…0
    语句中,但得到了完全相同的结果。简言之,您必须返回绑定在最内层
    let
    (it index>0)中的
    animals
    ,否则显示当前的
    动物
    并返回它。因此,它将是这样的:
    (如果(>索引0)(让[…](关联动物索引动物))(do(显示动物)动物))
    但我更愿意对其进行进一步的重组,使其更具可读性,减少必要性。我对这个问题的评论感激不尽!我赞同letwinski的解决方案,但发现leetwinski和Josh的贡献对解决这个问题和另一个我从未发布过的问题极为有价值,因为我最终能够解决这个问题谢谢。你给了我一些糊涂的东西。我同意我没有把事情做得很完美。这是我的第一个Culjury项目,我知道我还有一条路要走,在我真正精通之前。很好的工作。顺便说一下,在这里至少考虑一下你的答案,你已经发现我们帮助了提姆。整个上午我都在上课。我的老师确实把这篇文章带到了学校