2

I have a message with 3 attributes: type, currency and amount.

I have a rule with 4 attributes, a destination, a message type, a currency and an amount.

I want to go through my rules and find a match to the message on the message type and return the destination, or null if there was no match

I'm using a vector for the fixed positions of the fields in the message and rule, I've defined them as follows:

user=> (def msg [100, "USD", 100])
#’user/msg

user=> (def rules [["FAL" 100 "UKP" 100] ["FBC" 101 "USD" 100]])
#’user/rules

Then I define some functions that extract the message type from a rule and a message:

user=>(defn rule-mt [[_ mt]] mt)
#’user/rule-mt

user=>(defn msg-mt [[mt]] mt)
#’user/msg-mt

I've defined a function to match the message types as follows:

user=>(defn match-mt [ msg rule ] ( = ( rule-mt rule ) ( msg-mt msg ) ) )
#’user/match-mt

So I can call this directly as follows to check if it matches the first rule:

user=>(match-mt msg (rules 0))
true

And then to see if it matches the second rule:

(match-mt msg (rules 1))
false

How do I iterate over my rules (vector of vectors) calling my match function and then return the destination field of a matching rule (the first field of the rule) ?

David
  • 14,047
  • 24
  • 80
  • 101

2 Answers2

3

Just do it! Here's a solution that finds all the rules that match a message using a for and then returns the first of them using first. However because of laziness, only one succesful match needs to be computed.

(defn dest-of-rule [rule] (first rule))
(defn get-dest [msg]
   (first
      (for [r rules
            :when (match-mt msg r)]
         (dest-of-rule r))))

Here's an alternative solution that does the same using filter:

(defn get-dest [msg]
   (dest-of-rule (first (filter #(match-mt msg %) rules))))

The first + filter idiom is very common and that's why there's a name for it: find-first. It is available e.g. in the package seq-utils (see here).

opqdonut
  • 5,119
  • 22
  • 25
3

Is there a specific reason why you are using vectors instead of maps as your data types? By using maps, you could get rid of the helper functions and produce more readable code, in my opinion.

Here's how I would do it:

(def msg {:type 100 :currency "USD" :amount 100})
(def rules [{:destination "FAL" :type 100 :currency "UKP" :amount 100}
            {:destination "FBC" :type 101 :currency "USD" :amount 100}])

The match-mt function then becomes:

(defn match-mt [msg rule] (= (:type msg) (:type rule)))

To get the destination of the first matching rule (similar to the answer provided by opqdonut):

(defn get-dest [msg]
  (:destination (first (filter (partial match-mt msg) rules))))

You could also write a generic function to check equality of a certain field for two (or more) inputs, and define get-dest in terms of that:

(defn field= [key & inputs]
  (apply = (map key inputs)))
(defn get-dest [msg]
  (:destination (first (filter (partial field= :type msg) rules))))
Christian Berg
  • 14,246
  • 9
  • 39
  • 44