按差异总和排序

时间:2017-12-16 19:05:10

标签: django postgresql django-models

我有一个模型,它有一个带浮动列表的属性:

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))

如果我的条目数很小,这种方法很快。但是我害怕有大量的条目,所有条目的比较和排序都会变慢,因为它们必须从数据库加载。有没有办法提高效率?

2 个答案:

答案 0 :(得分:0)

最好的方法是自己编写,现在你在列表上迭代4次以上!

虽然这种方法看起来很漂亮,但并不好。

你可以做的一件事是:

  1. 有一个名为last_diff的变量,并将其设为0

  2. 遍历所有entries

  3. 迭代每个entry.values

  4. 从i = 0到列表末尾,计算abs(entry.values[i]-list2[i])

  5. 在名为new_diff

  6. 的变量中对这些值求和
  7. 如果new_diff > last_diff从内循环中断并将entry推到正确位置(称为Insertion Sort,请将其检出!)

  8. 通过这种方式,在平均情况下,时间复杂度远低于您现在所做的!

    也许你也必须有创意。我要分享一些想法,亲自检查一下,确保它们没问题。

    假设:

    1. values列表元素总是正浮动。
    2. 所有list2
    3. entries始终相同。
    4. 那么你可以说,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个查询将被完全索引,因此速度极快。