4

我有一个用例,我需要计算很多集合之间的相似性以创建一个简单的推荐引擎。我正在查看 Jaccard 系数和其他相似系数公式,但它们之间有一个共同点:集合内的项目不能重复(如果我在这里错了,请纠正我)。

我在 PHP 中编写了自己的函数来执行自定义哈希交集,其逻辑是:

  1. arr1:一个数组,其键是项目的 id,值是它们对应的数量。这代表用户的愿望清单。
  2. arr2: 相同,arr1但它代表另一个用户的库存。
  3. 对于这个自定义交集,我需要的逻辑是愿望清单的所有者不关心卖家是否有 100 个 item1。如果他只想要 4 个,则只计算 4 个。

我需要一种非常快速的方法来使集合相交,但是通常的相似系数公式涉及集合的相交和并集,当将一个集合与 200k 其他集合进行比较时,这可能不像我想要的那么快。这是我目前所处的位置:

function my_similarity_coefficient ($arr1, $arr2) {

    $matches = 0;
    $total = 0;

    if (count($arr2) == 0)
        return 0;

    foreach ($arr1 as $id => $qty) {

        $total += $qty;

        if (!array_key_exists($id, $arr2))
            continue;

        $matches += min($qty, $arr2[$id]); // do not match more than what user wants

    }

    return $matches / $total;

}

我尝试在 PHP 中交叉两个带红色的散列。尺寸分别是arr1[67]arr2[231]。该系数是在出色的 61.98 微秒(最坏情况下高达 266.075 微秒)下计算得出的。如果我尝试将数据从 Redis 获取到 PHP,这个数字会膨胀到 905.037µsec-3337.86µsec。

我想让瓶颈远离将数据从 redis 传输到 PHP,所以我想知道是否可以在 lua(甚至可能是 c++)中对这个自定义交集进行编程,如果可能的话,它不会受到同样的影响瓶颈,因为它也从pointA到pointB获取它,或者它不会因为数据已经是本地的而遭受获取瓶颈?

我对 lua 不熟悉,但我不希望被准确的代码提供。由于互联网上关于我真正想要实现的目标的 lua 资源很少,所以我想在搜索时先在这里挑选一些大脑。

4

1 回答 1

6

让我们来看看。首先,这是您的 PHP 代码直接翻译成 Lua。我在这里保留了相同的变量名,但你在 PHP 中称为“数组”的东西在 Lua 中称为“表”。

local my_similarity_coefficient = function(arr1, arr2)

  local matches = 0
  local total = 0

  if next(arr2) == nil then
    return 0
  end

  for id, qty in pairs(arr1) do

    total = total + qty

    if arr2[id] then
      matches = matches + math.min(qty, arr2[id])
    end

  end

  return matches / total

end

请注意,如果为空,此代码可以除以零arr1,但您的代码也可以。

让我们尝试一下:

local arr1 = {
  a = 3,
  b = 5,
  c = 8,
}

local arr2 = {
  a = 2,
  c = 10,
  d = 7,
  e = 21,
}

print(my_similarity_coefficient(arr1, arr2)) -- 0.625

现在让我们使用 Redis。首先,让我们创建测试数据。

redis 127.0.0.1:6379> hmset arr1 a 3 b 5 c 8
OK
redis 127.0.0.1:6379> hmset arr2 a 2 c 10 d 7 e 21
OK

这个脚本做你想做的事,不是以最有效的方式(可能会减少对 的调用redis.call),而是以一种简单的方式,所以你可以理解它并在需要时对其进行优化:

local k1, k2 = KEYS[1], KEYS[2]
local matches, total = 0, 0

if not redis.call("exists", k2) then return 0 end

local qty, qty2
for _, id in ipairs(redis.call("hkeys", k1)) do
  qty = tonumber(redis.call("hget", k1, id))
  total = total + qty
  qty2 = tonumber(redis.call("hget", k2, id) or 0)
  matches = matches + math.min(qty, qty2)
end

return tostring(matches / total)

让我们称之为:

$ redis-cli eval "$(cat the_script.lua)" 2 arr1 arr2
"0.625"

成功!

需要注意的重要一点是类型转换:值(数量)通过tonumber(Redis 返回字符串)转换为整数,我们将结果转换为字符串,因为如果我们返回浮点数,Redis 会将其截断为整数(这里 0)。

编辑-好的,谈论优化而不是说如何不好,所以这是一个简单的:

local k1, k2 = KEYS[1], KEYS[2]
local matches, total = 0, 0

if not redis.call("exists", k2) then return 0 end

local t1 = redis.call("hgetall", k1)

local id, qty, qty2
for i=1,#t1,2 do
  id, qty = t1[i], tonumber(t1[i+1])
  total = total + qty
  qty2 = tonumber(redis.call("hget", k2, id) or 0)
  matches = matches + math.min(qty, qty2)
end

return tostring(matches / total)
于 2013-11-06T10:19:48.463 回答