Lua 可能与redis+进行哈希相交;卢阿?
我有一个用例,我需要计算许多集合之间的相似性来创建一个简单的推荐引擎。我在看Jaccard系数和其他相似系数公式,但它们之间有一个共同点:集合中的项目不能重复(如果我在这里错了,请纠正我) 我用PHP编写了自己的函数来进行自定义哈希交叉,逻辑如下:Lua 可能与redis+进行哈希相交;卢阿?,lua,redis,lua-table,Lua,Redis,Lua Table,我有一个用例,我需要计算许多集合之间的相似性来创建一个简单的推荐引擎。我在看Jaccard系数和其他相似系数公式,但它们之间有一个共同点:集合中的项目不能重复(如果我在这里错了,请纠正我) 我用PHP编写了自己的函数来进行自定义哈希交叉,逻辑如下: arr1:一个数组,其键是项的ID,值是其对应的数量。 这表示用户的愿望列表 arr2:与arr1相同,但它表示另一个用户的库存 对于这个自定义交叉点,我需要的逻辑是,愿望列表的所有者不关心卖家是否有100个item1。如果他只想要4个,那么只算4个
arr1
:一个数组,其键是项的ID,值是其对应的数量。
这表示用户的愿望列表arr2
:与arr1
相同,但它表示另一个用户的库存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中相交两个redish哈希。尺寸分别为arr1[67]
和arr2[231]
。该系数是在未完成的61.98µs(最坏情况下可达266.075µs)下计算的。如果我尝试将数据从Redis获取到PHP,这个数字会膨胀到905.037µsec-3337.86µsec
我想避开将数据从redis传输到PHP的瓶颈,所以我想知道是否有可能用lua(或者甚至是c++)编程这个自定义交集,如果有可能,它是否也会遇到同样的瓶颈,因为它也在从点A到点B获取数据,还是不会遇到获取瓶颈,因为数据已经是它的本地数据
我不熟悉lua,但我不想被一匙一匙地灌输确切的代码。由于互联网上几乎没有关于lua的资源与我真正想要实现的目标相关,所以我想在搜索时先在这里挑选一些大脑。让我们看看。首先,这里是直接翻译成Lua的PHP代码。我在这里保留了相同的变量名,但在PHP中称为“Array”的变量在Lua中称为“Table” 请注意,如果
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)
看起来不错。看来你已经为我做了这项工作。有没有可能在一次行程中把一个hgetall放进一张桌子?你是说hgetall k2?只有当两个文档之间有很多交叉点时,这才是一个好主意。此外,hgetall返回
{key1,val1,key2,val2}
而不是{key1=val1,key2=val2}
,因此必须进行转换。当前代码只执行一次网络跳闸,但在Redis内部执行Lua-C跳闸,因此它可能更快,也可能更快。总而言之:这是可能的,但你应该对它进行基准测试。好的,非常感谢!如果我能投两次票,我会的!:)结果真的很奇怪。我做了一次1:40k的运行:第一次从redis一个接一个地执行所有arr2,并用原生PHP进行系数计算。另一个是每次迭代调用$redis->eval()。原生PHP=16s PHP lua eval=21s,这是一致的分数。你认为我可能做错了什么吗?或者这是意料之中的?好吧,我想我终于搞定了。这只需要3秒钟:我认为这很好。除非你能看到我可以优化它的地方。:)
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)