Question

This is complicated, and I'm hoping there's a simpler way to do it.

I'm comparing a freshly generated list of "suggested connections" for a social networking site against a "blocked suggestions" list. The first list looks something like this:

((12 :mutuals 8 :ranking 8)(43 :mutuals 2 :mutual-groups (2) :ranking 4) ... )

The first value is the user id, and the plist in the cdr of each sublist is essentially the "reasons" why the person is suggested.

The second list looks like:

((12 . 2) (3 . 4) (43 . 3) ...)

The car is the user id and the cdr is the ranking they had when they were "blocked" from the user's suggestions.

I want to find a way, for each sublist in the first list, to compare it against the list of blocked suggestions. There are three possible outcomes:

  • there will be no corresponding entry => leave the suggestion in the list.
  • there is a corresponding entry and the ranking field is 5 or more higher => leave the suggestion in the list and remove the blocked suggestion from its index.
  • there is a corresponding entry but the ranking is the same or within 5 => remove the suggestion from the list of suggestions.

My current code that does this uses LOOP. This was the best way for me to spell it out as literally as possible.

(this is in a LET block for now but eventually will be in a defun. The function (remove-suggestion) is my own function that modifies a hash table.)

    (let ((userid 10753) ; my userid in this program, for example
          (suggestion-list '((12 :mutuals 8 :ranking 8)(43 :mutuals 2 :mutual-groups (2) :ranking 4) (4 :mutuals 10 :ranking 10)))
          (blocked-list '((12 . 2) (3 . 4) (43 . 3)))
      (remove nil
        (loop for suggestion in suggestion-list
              for sug-id = (car suggestion)
              for sug-rank = (getf (cdr suggestion) :ranking)
              collect
                (loop for (block-id . block-rank) in blocked-list
                      until (= block-id sug-id)
                      finally
                       (if (/= block-id sug-id (return suggestion)
                         (when (>= (- sug-rank block-rank) 5)
                           (progn
                             (remove-suggestion block-id userid :blocked t)
                             (return suggestion))))))))

When I evaluate this in the REPL, I get:

((12 :mutuals 8 :ranking 8)) (4 :mutuals 10 :ranking 10))

Which is exactly right, since even though user 12 was blocked before, their ranking went up so they were retained. user 43 was removed because their ranking had not gotten high enough. User 4 was kept because there was no corresponding entry in the blocked list.

I'm really hoping there is a way to do this more cleanly. Perhaps using some combination of remove, remove-if, mapcar, and/or lambda?

I know I could just store the list in question with defparameter and then use

(remove suggested-contact <location> :key #'car)

which is what I did before, but I don't like that as much conceptually.

If you made it to the end, congrats!

Was it helpful?

Solution

OK, you can probably do that with 2 remove-ifs - 1 for each list.

For the first one:

(remove-if (lambda (e)
             (let ((blocked (assoc (first e)
                                   '((12 . 2) (3 . 4) (43 . 3) ...)))
               (and blocked (< (cdr blocked) 5))))
           '((12 :mutuals 8 :ranking 8)
             (43 :mutuals 2 :mutual-groups (2) :ranking 4)
             ...))

And for the second:

(remove-if (lambda (e)
             (and (member (car e)
                          '((12 :mutuals 8 :ranking 8)
                            (43 :mutuals 2 :mutual-groups (2) :ranking 4)
                            ...)
                          :key 'first)
                  (>= (cdr e) 5))
           '((12 . 2) (3 . 4) (43 . 3) ...))

OTHER TIPS

Thanks to Vsevolod, here is my shiny new function that, I am pretty sure, does everything I need it to in one go. I also used an anaphoric and to get rid of the let block.

(defun remove-blocked-suggestions (suggestion-list &optional (userid *userid*))

  (remove-if #'(lambda (suggestion)
                 (aand (assoc (car suggestion) (db userid :blocked-suggestions))
                       (if (< (- (getf (cdr suggestion) :ranking)
                                 (cdr it)) 5)
                         t
                         (progn
                           (remove-suggestion (car it) userid :blocked t)
                           nil))))
             suggestion-list))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top