" local redis_prefix = ARGV[1]\n local input_matrices = cjson.decode(ARGV[2])\n local similarity_limit = tonumber(ARGV[3])\n local item = ARGV[4]\n local keys = {}\n\n for name, options in pairs(input_matrices) do\n local key = table.concat({redis_prefix, name, 'sets', item}, ':')\n local sets = redis.call('SMEMBERS', key)\n for _, set in ipairs(sets) do\n table.insert(keys, table.concat({redis_prefix, name, 'items', set}, ':'))\n end\n end\n\n -- Account for empty tables.\n if next(keys) == nil then\n return nil\n end\n\n local related_items = redis.call('SUNION', unpack(keys))\n\n local function add_similarity_if_necessary(item, similarity, score)\n local store = true\n local key = table.concat({redis_prefix, 'similarities', item}, ':')\n\n if similarity_limit ~= nil then\n local zrank = redis.call('ZRANK', key, similarity)\n\n if zrank ~= nil then\n local zcard = redis.call('ZCARD', key)\n\n if zcard >= similarity_limit then\n -- Similarity is not already stored and we are at limit of similarities.\n\n local lowest_scored_item = redis.call('ZRANGEBYSCORE', key, '0', '+inf', 'withscores', 'limit', 0, 1)\n\n if #lowest_scored_item > 0 then\n -- If score is less than or equal to the lowest score, don't store it. Otherwise, make room by removing the lowest scored similarity\n if score <= tonumber(lowest_scored_item[2]) then\n store = false\n else\n redis.call('ZREM', key, lowest_scored_item[1])\n end\n end\n end\n end\n end\n\n if store then\n redis.call('ZADD', key, score, similarity)\n end\n end\n\n for i, related_item in ipairs(related_items) do\n -- Disregard the current item.\n if related_item ~= item then\n local score = 0.0\n\n for name, matrix in pairs(input_matrices) do\n local s = 0.0\n\n local key_1 = table.concat({redis_prefix, name, 'sets', item}, ':')\n local key_2 = table.concat({redis_prefix, name, 'sets', related_item}, ':')\n\n if matrix.measure == 'jaccard_index' then\n local x = tonumber(redis.call('SINTERSTORE', 'temp', key_1, key_2))\n local y = tonumber(redis.call('SUNIONSTORE', 'temp', key_1, key_2))\n redis.call('DEL', 'temp')\n\n if y > 0 then\n s = s + (x / y)\n end\n elseif matrix.measure == 'sorensen_coefficient' then\n local x = redis.call('SINTERSTORE', 'temp', key_1, key_2)\n local y = redis.call('SCARD', key_1)\n local z = redis.call('SCARD', key_2)\n\n redis.call('DEL', 'temp')\n\n local denom = y + z\n if denom > 0 then\n s = s + (2 * x / denom)\n end\n else\n error(\"Bad matrix.measure: \" .. matrix.measure)\n end\n\n score = score + (s * matrix.weight)\n end\n\n if score > 0 then\n add_similarity_if_necessary(item, related_item, score)\n add_similarity_if_necessary(related_item, item, score)\n else\n redis.call('ZREM', table.concat({redis_prefix, 'similarities', item}, ':'), related_item)\n redis.call('ZREM', table.concat({redis_prefix, 'similarities', related_item}, ':'), item)\n end\n end\n end\n"