Clojure的:造型简洁多对多的关系

时间:2019-02-02 22:29:50

标签: clojure modeling

由于我正在学习西班牙语,此刻我正在做一个死的简单快闪程序。

该应用程序有两个概念:

  1. 的卡本身。两个字符串,一个正面,一个在后面。另外,每张卡都标记有0-m标签。例如。给定卡的标签可以是["spanish" "verb"]
  2. 个人资料。该配置文件存储两件事情:哪些卡是通过定义标签包括,和“知识得分”每张卡。

该应用程序的工作原理很简单,只需选择要练习的个人资料,即可为您提供知识得分最低的卡片正面。当用户就绪时,它显示了背面。然后,用户输入是否他想起卡其改变该牌的知识分数。

对于任何其任何闪卡应用程序之前,使用时,该超级琐碎的东西。

我的问题是:如何在Clojure中惯用地对此建模?我要面对的挑战是个人资料和卡片之间的多对多关系。

我可以创建一个状态映射是这样的:

{:card-universe [
  {:front "Correr" :back "To run" :tags ["spanish" "verb"]}
  {:front "Querer" :back "To want" :tags ["spanish" "verb"]}
  {:front "La mesa" :back "The table" :tags ["spanish" "noun"]}]

 :profiles [
  {
   :name "Spanish verbs"
   :tags ["spanish" "verb"] 
   :cards [{:front "Correr" :back "To want" :score 7}
           {:front "Querer" :back "To want" :score 10}]
  }
  {
   :name "Spanish"
   :tags ["spanish"] 
   :cards [{:front "Correr" :back "To run" :score 8}
           {:front "Querer" :back "To want" :score 3}
           {:front "La mesa" :back "The table" :score 2}]
  }
 ]
}

这对我来说似乎很愚蠢。说我编辑的卡片,因为我犯了一个错误,那我就必须去通过所有的配置文件和更新它们。我可以通过为所有卡创建身份(某种程度上)来解决此问题,而只是使用它来引用卡:

{:card-universe [
  {:id "c1" :front "Correr" :back "To run" :tags ["spanish" "verb"]}
  {:id "c2" :front "Querer" :back "To want" :tags ["spanish" "verb"]}
  {:id "c3" :front "Mesa" :back "Table" :tags ["spanish" "noun"]}]

 :profiles [
  {
   :name "Spanish verbs"
   :tags ["spanish" "verb"] 
   :cards [{:id "c1" :score 7}
           {:id "c2" :score 10}]
  }
  {
   :name "Spanish words"
   :tags ["spanish"] 
   :cards [{:id "c1" :score 8}
           {:id "c2" :score 3}
           {:id "c3"  :score 2}]
  }
 ]
}

这也许是一个更好一点,但它仍然意味着,如果我在一个给定的标签添加更多的卡,我将不得不获取所有的牌。卡宇宙和:我之间基本上是外联接。卡配置文件中的

下一个问题弹出被存储的状态。我当然可以只是这种状态下出正确的存储文件,但如果我是通过创建Web应用程序的SQL数据库将是我去将其扩展到多用户。在我看来,我应该能够全部编写代码并在开始时将其存储到文件中,以后再交换掉我存储数据的方式,而无需触及应用程序用来运行的数据结构。

任何提示和经验将不胜感激!

我觉得应用程序太简单了,无法获得Clojure的任何好处。特别是在引入数据库时​​-基本上只会使它成为CRUD应用程序。

2 个答案:

答案 0 :(得分:2)

我可能会先把东西拆开

(def card-data
  [{:id "c1" :front "Correr" :back "To run" :tags #{"spanish" "verb"}}
   {:id "c2" :front "Querer" :back "To want" :tags #{"spanish" "verb"}}
   {:id "c3" :front "Mesa" :back "Table" :tags #{"spanish" "noun"}}])

(defn spanish-words [cards]
  (filter #(-> % :tags (every? ["spanish"])) cards))

(defn spanish-verbs [cards]
  (filter #(-> % :tags (every? ["spanish" "verb"])) cards))

然后使用可以在其中存储状态的函数制作一个小原子db进行测试。你可以稍后抽象此功能无论DB你最终使用了。

(def db (atom {}))

(defn remembered! [scores-db card]
  (swap! scores-db update (:id card) #(if % (inc %) 0)))

现在我们可以对其进行测试了。

#_user=> (->> card-data spanish-verbs first (remembered! db))
{"c1" 0}
#_user=> (->> card-data spanish-verbs second (remembered! db))
{"c1" 0, "c2" 0}
#_user=> (->> card-data spanish-verbs first (remembered! db))
{"c1" 1, "c2" 0}

那行得通。但是我们可以将过滤进一步抽象到select-tags函数中。

(defn select-tags [cards & tags]
  (filter #(-> % :tags (every? (->> tags flatten (remove nil?)))) cards))

(defn spanish [cards & tags]
  (select-tags cards "spanish" tags))

(defn verbs [cards & tags]
  (select-tags cards "verb" tags))

#_user=> (spanish (verbs card-data))
({:id "c1", :front "Correr", :back "To run", :tags #{"verb" "spanish"}} {:id "c2", :front "Querer", :back "To want", :tags #{"verb" "spanish"}})
#_user=> (verbs (spanish card-data))
({:id "c1", :front "Correr", :back "To run", :tags #{"verb" "spanish"}} {:id "c2", :front "Querer", :back "To want", :tags #{"verb" "spanish"}})

现在,我们可以只编写他们。

(defn spanish-verbs [cards & tags]
  ((comp spanish verbs) cards tags))
;; or (apply spanish cards "verb" tags)
;; or even (apply select-tags cards "verb" "spanish" tags)

#_user=> (->> card-data spanish-verbs first (remembered! db))
{"c1" 2, "c2" 0}

答案 1 :(得分:0)

如果您熟悉SQL,则应立即从Walkable sql库和sqlite开始: http://walkable.gitlab.io 您将从SQL的规范化中受益匪浅。 Walkable使得轻轻松松获取数据成为树状结构,只需按几次按键即可进行过滤。不要浪费时间与原子作战,领域并不复杂,花时间制作CRUD原型也不值得。