How do I use core.logic to search for valid nested maps in a database of maps?

146 views Asked by At

I'm currently working on a problem that asks to look for certain vendors that can fulfill an order. The vendors are represented as maps, as is the order. A vendor can fulfill an order if it can serve the order's zipcode, the order's service level, the order's requested vehicle, and has the appropriate extra accommodations. I've been trying to use core.logic for this problem, so I have the vendors stored in a logic database using core.logic.pldb as fleets. Following my research on how to use core.logic with Clojure maps, I've been trying to use a combination of featurec, membero and PMap to solve the problem. The code shown below is the current state of my efforts.

(pldb/db-rel fleet fl)

(def f1 {:id 1
         :zipcodes #{"02138" "33135" "33157" "02139"}
         :services #{"standard" "through_door" "to_curb" "to_door"}
         :vehicles #{"any" "sedan" "suv" "wav"}
         :extra {:covid-19 true}
         :cost 20
         :weight 100})


(def f2 {:id 2
         :zipcodes #{"33157" "02139"}
         :services #{"standard" "through_door"}
         :vehicles #{"sedan" "suv"}
         :extra {:covid-19 true}
         :cost 40
         :weight 80})


(def f3 {:id 3
         :zipcodes #{"02138" "33135"}
         :services #{"to_curb" "to_door"}
         :vehicles #{"any" "sedan"}
         :extra {:covid-19 true}
         :cost 60
         :weight 120})


(def ex_m {:ride {:zipcodes "02138"
                  :appointment-time "0630"
                  :services "to_curb"
                  :vehicles "sedan"}
           :accessibility {:extra {:covid-19 true}}})


(def fleets
  (pldb/db
    [fleet (map->PMap f1)]
    [fleet (map->PMap f2)]
    [fleet (map->PMap f3)]))


 (defn find-fleets [m] 
   (run-db* fleets [fl]
            (fresh [fl_zipcodes fl_services fl_vehicles fl_covid
                    ride_info ride_zip ride_service ride_vehicle ride_covid]
                   (membero ride_zip fl_zipcodes)
                   (membero ride_service fl_services)
                   (membero ride_vehicle fl_vehicles)
                   (== fl_covid ride_covid)
                   (featurec fl {:zipcodes fl_zipcodes
                                 :services fl_services
                                 :vehicles fl_vehicles
                                 :extra {:covid-19 fl_covid}})
                   (featurec m {:ride ride_info
                                :accessibility {:extra {:covid-19 ride_covid}}})
                   (featurec ride_info {:zipcodes ride_zip
                                        :services ride_service
                                        :vehicles ride_vehicle}))))

However, when I try to run the find-fleets function above, like so,

   (find-fleets (map->PMap ex_m))

I get the following error and trace:

   ; Error printing return value (StackOverflowError) at clojure.lang.KeywordLookupSite$1/get (KeywordLookupSite.java:45).
   ; null

Is there something I'm missing here about core.logic and how to use it with Clojure maps? Am I just missing some stupid error?

Thank you in advance!

1

There are 1 answers

0
Alan Thompson On

If you reformulate your problem a bit, you can solve this using the submatch? function from the Tupelo library. From the docs:

(submatch? smaller larger)

  Returns true if the first arg is (recursively) a 
  subset/submap/subvec of the 2nd arg

An example

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require [tupelo.core :as t]))

(def shops
  [{:id       1
    :zipcodes #{"02138" "33135" "33157" "02139"}
    :services #{"standard" "through_door" "to_curb" "to_door"}
    :vehicles #{"any" "sedan" "suv" "wav"}
    :extra    {:covid-19 true}
    :cost     20
    :weight   100}

   {:id       2
    :zipcodes #{"33157" "02139"}
    :services #{"standard" "through_door"}
    :vehicles #{"sedan" "suv"}
    :extra    {:covid-19 true}
    :cost     40
    :weight   80}

   {:id       3
    :zipcodes #{"02138" "33135"}
    :services #{"to_curb" "to_door"}
    :vehicles #{"any" "sedan"}
    :extra    {:covid-19 true}
    :cost     60
    :weight   120}])

Define the repair job to match the structure of the shops (a map with values that are sets or maps).

(def job
  {:zipcodes #{"02138"}
   :services #{"to_curb"}
   :vehicles #{"sedan"}
   :extra    {:covid-19 true}})

A unit test shows the submatch? function in action.

(dotest
  (let [matches (t/keep-if #(t/submatch? job %) shops)]  ; synonym for `filter`
    (is= matches
      [{:id       1,
        :zipcodes #{"02138" "02139" "33157" "33135"},
        :services #{"to_curb" "standard" "to_door" "through_door"},
        :vehicles #{"wav" "any" "suv" "sedan"},
        :extra    {:covid-19 true},
        :cost     20,
        :weight   100}
       {:id       3,
        :zipcodes #{"02138" "33135"},
        :services #{"to_curb" "to_door"},
        :vehicles #{"any" "sedan"},
        :extra    {:covid-19 true},
        :cost     60,
        :weight   120}])
    ))