9

设置考虑以下电影和演员的 DataScript 数据库,数据从learndatalogtoday.org 窃取:以下代码可以在 JVM/Clojure REPL 或 ClojureScript REPL 中执行,只要project.clj包含[datascript "0.15.0"]作为依赖项。

(ns user
  (:require [datascript.core :as d]))

(def data 
  [["First Blood" ["Sylvester Stallone" "Brian Dennehy" "Richard Crenna"]]
   ["Terminator 2: Judgment Day" ["Linda Hamilton" "Arnold Schwarzenegger" "Edward Furlong" "Robert Patrick"]]
   ["The Terminator" ["Arnold Schwarzenegger" "Linda Hamilton" "Michael Biehn"]]
   ["Rambo III" ["Richard Crenna" "Sylvester Stallone" "Marc de Jonge"]]
   ["Predator 2" ["Gary Busey" "Danny Glover" "Ruben Blades"]]
   ["Lethal Weapon" ["Gary Busey" "Mel Gibson" "Danny Glover"]]
   ["Lethal Weapon 2" ["Mel Gibson" "Joe Pesci" "Danny Glover"]]
   ["Lethal Weapon 3" ["Joe Pesci" "Danny Glover" "Mel Gibson"]]
   ["Alien" ["Tom Skerritt" "Veronica Cartwright" "Sigourney Weaver"]]
   ["Aliens" ["Carrie Henn" "Sigourney Weaver" "Michael Biehn"]]
   ["Die Hard" ["Alan Rickman" "Bruce Willis" "Alexander Godunov"]]
   ["Rambo: First Blood Part II" ["Richard Crenna" "Sylvester Stallone" "Charles Napier"]]
   ["Commando" ["Arnold Schwarzenegger" "Alyssa Milano" "Rae Dawn Chong"]]
   ["Mad Max 2" ["Bruce Spence" "Mel Gibson" "Michael Preston"]]
   ["Mad Max" ["Joanne Samuel" "Steve Bisley" "Mel Gibson"]]
   ["RoboCop" ["Nancy Allen" "Peter Weller" "Ronny Cox"]]
   ["Braveheart" ["Sophie Marceau" "Mel Gibson"]]
   ["Mad Max Beyond Thunderdome" ["Mel Gibson" "Tina Turner"]]
   ["Predator" ["Carl Weathers" "Elpidia Carrillo" "Arnold Schwarzenegger"]]
   ["Terminator 3: Rise of the Machines" ["Nick Stahl" "Arnold Schwarzenegger" "Claire Danes"]]])

(def conn (d/create-conn {:film/cast {:db/valueType :db.type/ref
                                      :db/cardinality :db.cardinality/many}
                          :film/name {:db/unique :db.unique/identity
                                      :db/cardinality :db.cardinality/one}
                          :actor/name {:db/unique :db.unique/identity
                                       :db/cardinality :db.cardinality/one}}))
(def all-datoms (mapcat (fn [[film actors]]
                          (into [{:film/name film}] 
                                (map #(hash-map :actor/name %) actors)))
                        data))
(def all-relations (mapv (fn [[film actors]]
                           {:db/id [:film/name film]
                            :film/cast (mapv #(vector :actor/name %) actors)}) data))

(d/transact! conn all-datoms)
(d/transact! conn all-relations)

描述简而言之,该数据库中有两种实体——电影和演员(不区分性别的词)和三种 datom:

  • 电影实体:(:film/name唯一的字符串)
  • 电影实体:(:film/cast多个参考)
  • 演员实体:(:actor/name唯一字符串)

问题我想构建一个查询,询问:哪些电影有这些N演员,并且只有这些N演员作为唯一的明星出现,对于 N>=2?

例如,《机械战警》由南希·艾伦、彼得·韦勒、罗尼·考克斯主演,但没有一部电影只由前两位主演,艾伦和韦勒。因此,我希望以下查询产生空集:

(d/q '[:find ?film-name
       :where
       [?film :film/name ?film-name]
       [?film :film/cast ?actor-1]
       [?film :film/cast ?actor-2]
       [?actor-1 :actor/name "Nancy Allen"]
       [?actor-2 :actor/name "Peter Weller"]]
     @conn)
; => #{["RoboCop"]}

然而,这个查询是有缺陷的,因为我不知道如何表达任何匹配都应该排除任何不是 Allen 或 Weller 的演员——再次,我想找到只有 Allen 和 Weller 合作过而没有任何其他演员的电影,所以我想调整上述查询以生成空集。如何调整此查询以强制执行此要求?

4

2 回答 2

3

因为 DataScript 没有否定功能(截至 2016 年 5 月),我认为在“纯”Datalog 中使用一个静态查询是不可能的。

我要走的路是:

  1. 以编程方式构建查询以添加 N 个子句,声明演员必须包含 N 个演员
  2. 添加一个谓词函数,给定一部电影、数据库和一组演员 ID,使用 EAVT 索引来查找每部电影是否有一个不在集合中的演员。

这是一个基本的实现

(defn only-those-actors? [db movie actors]
  (->> (datoms db :eavt movie :film/cast) seq
    (every? (fn [[_ _ actor]]
                (contains? actors actor)))
    ))

(defn find-movies-with-exact-cast [db actors-names]
  (let [actors (set (d/q '[:find [?actor ...] :in $ [?name ...] ?only-those-actors :where
                           [?actor :actor/name ?name]]
                      db actors-names))
        query {:find '[[?movie ...]]
               :in '[$ ?actors ?db]
               :where
               (concat
                 (for [actor actors]
                   ['?movie :film/cast actor])
                 [['(only-those-actors? ?db ?movie ?actors)]])}]
    (d/q query db actors db only-those-actors?)))
于 2016-05-13T08:13:55.140 回答
0

您可以使用谓词 fun 和d/entitytogether 来按:film/cast实体的字段过滤数据。在 Datascript 不支持否定(不是运算符等)之前,这种方法看起来要简单得多。

在此处查看(= a (:age (d/entity db e))Datascript 的测试用例中的行

[{:db/id 1 :name "Ivan" :age 10}
 {:db/id 2 :name "Ivan" :age 20}
 {:db/id 3 :name "Oleg" :age 10}
 {:db/id 4 :name "Oleg" :age 20}]

...

(let [pred (fn [db e a]
             (= a (:age (d/entity db e))))]
  (is (= (q/q '[:find ?e
                :in $ ?pred
                :where [?e :age ?a]
                       [(?pred $ ?e 10)]]
              db pred)
         #{[1] [3]})))))

在您的情况下,谓词主体可能看起来像这样

(clojure.set/subset? actors (:film/cast (d/entity db e))

在性能方面,d/entity调用速度很快,因为它是按索引查找。

于 2017-12-19T14:43:47.727 回答