我有一个模型,它有一个带浮动列表的属性:
values = ArrayField(models.FloatField(default=0), default=list, size=64, verbose_name=_('Values'))
目前,我正在获取我的参赛作品,并根据所有差异的总和与另一个列表进行排序:
def diff(l1, l2):
return sum([abs(v1-v2) for v1, v2 in zip(l1, l2)])
list2 = [0.3, 0, 1, 0.5]
entries = Model.objects.all()
entries.sort(key=lambda t: diff(t.values, list2))
如果我的条目数很小,这种方法很快。但是我害怕有大量的条目,所有条目的比较和排序都会变慢,因为它们必须从数据库加载。有没有办法提高效率?
答案 0 :(得分:0)
最好的方法是自己编写,现在你在列表上迭代4次以上!
虽然这种方法看起来很漂亮,但并不好。
你可以做的一件事是:
有一个名为last_diff
的变量,并将其设为0
遍历所有entries
。
迭代每个entry.values
从i = 0到列表末尾,计算abs(entry.values[i]-list2[i])
在名为new_diff
如果new_diff > last_diff
从内循环中断并将entry
推到正确位置(称为Insertion Sort
,请将其检出!)
通过这种方式,在平均情况下,时间复杂度远低于您现在所做的!
也许你也必须有创意。我要分享一些想法,亲自检查一下,确保它们没问题。
假设:
values
列表元素总是正浮动。list2
entries
始终相同。那么你可以说,values
中元素的总和越大,diff
值越大,无论list2
中的元素是什么
然后你可能会忘记整个diff
功能。 (测试一下!)
答案 1 :(得分:0)
使这一点变得更快的唯一方法是尽可能多地将数据移动到数据库,即计算和排序。这并不容易,但在this answer的帮助下,我设法在几乎纯粹的Django中为它编写了一个查询:
class Unnest(models.Func):
function = 'UNNEST'
class Abs(models.Func):
function = 'ABS'
class SubquerySum(models.Subquery):
template = '(SELECT sum(%(field)s) FROM (%(subquery)s) _sum)'
x = [0.3, 0, 1, 0.5]
pairdiffs = Model.objects.filter(pk=models.OuterRef('pk')).annotate(
pairdiff=Abs(Unnest('values')-Unnest(models.Value(x, ArrayField(models.FloatField())))),
).values('pairdiff')
entries = Model.objects.all().annotate(
diff=SubquerySum(pairdiffs, field='pairdiff')
).order_by('diff')
unnest
函数将values
的每个元素转换为一行。在这种情况下,它会发生两次,但会立即减去两个结果列并使其成为正数。尽管如此,每pk
行的行数与values
一样多。这些需要总结,但这并不像听起来那么容易。该列不能简单地聚合。这是迄今为止最棘手的部分 - 即使在摆弄了这么久之后,我仍然不太明白为什么Postgres需要这种间接性。在使用它的少数几个选项中,我相信子查询是Django中可以表达的唯一一个(并且只有1.11版本)。
请注意,上述行为与zip
完全相同,即当一个数组比另一个数组长时,其余部分将被忽略。
虽然当你不再需要检索所有行并在Python中循环它们时它已经快得多,但它还没有改变它导致全表扫描。每次都必须处理所有行。不过,你可以做得更好。请查看cube
扩展程序。使用它计算L 1 距离 - 至少,这似乎是您直接使用<#>
运算符计算的。这需要使用RawSQL
或自定义Expression
。然后在SQL表达式cube("values")
上添加GiST索引,或者如果您能够将类型从float[]
更改为cube
,则直接在该字段上添加。如果是后者,您可能还必须实现自己的CubeField
- 我还没有找到提供它的任何软件包。无论如何,在所有这些情况下,最低距离的前N个查询将被完全索引,因此速度极快。