Separate OR AND lookups

Our first pass at a lookup function looks fruitful. We can call it with a number of constraints and get back expected results. However, this function assumes that all our conditions are logical ORs This means a function should satisfy the a OR b OR c condition. But this will satisfy only some of our needs. Often, we'll want more than one constraint to be satisfied at the same time. This is a logical AND. Let's implement this function now. While we're at it, we can refactor our first lookup function and reuse some of its parts:

(defn generate-input-list [constraints]
  (if (some #{:source} constraints)
    (let [source-source (comp (partial filter #(= :source (first     %1)))
                              (partial partition 2))
          source-value (comp second source-source)
          source-key (comp first source-source)]

      (if (string? source-key)
        (load-directory (source-key constraints))
        source-value))

    (load-directory "data/")))

(defn generate-constraint-pairs [constraints]
  (->> constraints
       (partition 2)
       (remove #(= :source (first %)))))

(defn find-lookup-fn [inp]
  (case inp
    :time specific-time-pred
    :time-after time-after-pred
    :time-before time-before-pred
    :price specific-price-pred
    :price-abouve price-abouve-pred
    :price-below price-below-pred))

;; refactor some code to clean up
(defn lookup-refactored [& constraints]

  ;; ensure constraints are in pairs -> Preconditions
  {:pre [(even? (count constraints))]}

  ;; map over pairs - find predicate fn based on keyword -   partially apply fn with arg
  (let [files (generate-input-list constraints)
        constraint-pairs (generate-constraint-pairs constraints)

        constraint-pairs-A (map (fn [x]
                                  [(find-lookup-fn (first x))                                   (second x)])
                                constraint-pairs)

        lookupfn-fns-A (map (fn [x]
                                  (fn [y]
                                    (lookupfn y ((first x) (second                                     x)))))
                            constraint-pairs-A)]

    ;; apply all fns with args
    (apply concat ((apply juxt lookupfn-fns-A)
                   files))))

(defn lookup-and [& constraints]

  ;; ensure constraints are in pairs -> Preconditions
  {:pre [(even? (count constraints))]}

  ;; map over pairs - find predicate fn based on keyword -   partially apply fn with arg
  (let [files (generate-input-list constraints)
        constraint-pairs (generate-constraint-pairs constraints)

        constraint-pairs-B (map (fn [x]
                                  [(find-lookup-fn (first x))                                   (second x)])
                                constraint-pairs)

        constraint-predicates (map (fn [x]
                                     ((first x) (second x)))
                                   constraint-pairs-B)

        ;; lookupfn
        ;; constraint-predicates ;; (f1 f2 ...)
        ;; files

        pred-fn (fn [input-tick]
                  (every? (fn [pred]
                            (pred input-tick))
                          constraint-predicates))]

(lookupfn files pred-fn)))

The generate-input-list, generate-constraint-pairs, and find-lookup-fn functions are pulled out of the let block from the original lookup function. The lookup-refactored function is now a lot smaller and we can reuse the first three functions in our AND implementation. The lookup-and expression also uses the let block to create an input file list and pairs of input constraints. The constraint-pairs-B function replaces a constraint key with a corresponding predicate function. The lookup-and function differs slightly thereon.

The Var, constraint-predicates, is a Var of the result of each predicate function applied with a supplied input. Recall that predicates themselves returned yet another function, for example, (defn time-after-pred [time] #(.after (:last-trade-time %) time)). So, the result of this will be in the form of a list of predicate functions.

Given that this lookup function implements a logical AND, we want all of the predicates to hold true for each input tick. Therefore, when lookupfn is finally called with a file list (files), we want a function that holds true for all conditions. This is where pred-fn comes in.

The pred-fn function is a function that takes input ticks as its arguments. For each input tick, we apply the every? function, for example, (every? pos? '(10 20 30)) ;; true. This expression asks "does every one of the predicate functions hold true for this one tick?" A true or false answer determines what's finally returned by lookupfn:

(count (lookup-or :time-after #inst "2015-08-15T17:18:00.000-00:00" :price-abouve 20))
;; 2126

(count (lookup-and :time-after #inst "2015-08-15T17:18:00.000-00:00" :price-abouve 20))
;; 424

(count (lookup-and :time-after #inst "2015-08-15T17:18:00.000-00:00"
                   :time-before #inst "2015-08-15T17:19:00.000-00:00"
                   :price-abouve 20))
;; 47

With the logical AND function implemented, we can try it out and compare the results we get from what was returned with our original OR implementation. The same constraints using an OR condition return 2126 ticks, where the use of an AND condition returns only 424 results. Constraining our first AND further (prices that are only above 20) again shrinks the number of ticks that satisfy all constraints.

So far, we've made progress. However, it would be nice to further simplify our overall interface to use either AND or OR. When comparing only a pseudocode outline of code to an OR AND function where each implementation becomes larger, we'd like to implement some features:

(defn lookup-combined' [mode & constraints]

  ;; ensure constraints are in pairs -> Preconditions
  {:pre [(even? (count constraints))]}


  ;; map over pairs - find predicate fn based on keyword - partially apply fn with arg
  (let [files (generate-input-list constraints)
        constraint-pairs (generate-constraint-pairs constraints)

        ;; OR
        constraint-pairs-A (map (fn [x]
                                  [(find-lookup-fn (first x)) (second x)])
                                constraint-pairs)

        lookupfn-fns-A (map (fn [x]
                                  (fn [y]
                                    (lookupfn y ((first x) (second x)))))
                            constraint-pairs-A)

        ;; AND
        constraint-pairs-B (map (fn [x]
                                  [(find-lookup-fn (first x)) (second x)])
                                constraint-pairs)

        constraint-predicates (map (fn [x]
                                     ((first x) (second x)))
                                   constraint-pairs-B)

        pred-fn (fn [x]
                  (every? (fn [pfn]
                            (pfn x))
                          constraint-predicates))]

    ;; OR
    (apply concat ((apply juxt lookupfn-fns-A)
                   files))

    ;; AND
    (lookupfn files pred-fn)))
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.166.131