时间一些时髦的SQL:前缀总和计算

这个Stack Overflow问题再次让我感到讨厌

[找到]数组中由于执行所有M运算而导致的最大元素

这是John在寻找Java解决方案的问题:

在将N个元素的数组初始化为0的情况下,我们得到了一系列(p; q; r)的M个操作。 运算(p; q; r)表示应将整数r添加到所有数组元素A[p];A[p + 1]; : : : ;A[q] A[p];A[p + 1]; : : : ;A[q] 。 您将输出执行所有M运算将导致的数组中的最大元素。 有一个简单的解决方案,它简单地执行所有操作,然后返回最大值,这需要O(MN)时间。 我们正在寻找一种更有效的算法。

有趣。 确实,一个幼稚的解决方案仅会按要求执行所有操作。 另一个幼稚但不太幼稚的解决方案会将所有(p; r)和所有(q + 1; -r)的运算转换为(x; y)形式的信号。 换句话说,我们可以像这样简单地实现我提出的解决方案

// This is just a utility class to model the ops
class Operation {
    final int p;
    final int q;
    final int r;

    Operation(int p, int q, int r) {
        this.p = p;
        this.q = q;
        this.r = r;
    }
}

// These are some example ops
Operation[] ops = {
    new Operation(4, 12, 2),
    new Operation(2,  8, 3),
    new Operation(6,  7, 1),
    new Operation(3,  7, 2)
};

// Here, we're calculating the min and max
// values for the combined values of p and q
IntSummaryStatistics stats = Stream
    .of(ops)
    .flatMapToInt(op -> IntStream.of(op.p, op.q))
    .summaryStatistics();

// Create an array for all the required elements using
// the min value as "offset"
int[] array = new int[stats.getMax() - stats.getMin()];

// Put +r and -r "signals" into the array for each op
for (Operation op : ops) {
    int lo = op.p     - stats.getMin();
    int hi = op.q + 1 - stats.getMin();

    if (lo >= 0)
        array[lo] = array[lo] + op.r;

    if (hi < array.length)
        array[hi] = array[hi] - op.r;
}

// Now, calculate the prefix sum sequentially in a
// trivial loop
int maxIndex = Integer.MIN_VALUE;
int maxR = Integer.MIN_VALUE;
int r = 0;

for (int i = 0; i < array.length; i++) {
    r = r + array[i];
    System.out.println((i + stats.getMin()) + ":" + r);

    if (r > maxR) {
        maxIndex = i + stats.getMin();
        maxR = r;
    }
}

System.out.println("---");
System.out.println(maxIndex + ":" + maxR);

上面的程序将打印出:

2:3
3:5
4:7
5:7
6:8
7:8
8:5
9:2
10:2
11:2
---
6:8

因此,最大值在位置6生成,值为8。

Java 8中更快的计算

使用Java 8的新Arrays.parallelPrefix()操作可以更快地计算出该值。 而不是最后循环,只需编写:

Arrays.parallelPrefix(array, Integer::sum);
System.out.println(
    Arrays.stream(array).parallel().max());

很棒,因为它可以比顺序O(M+N)解决方案运行得更快。 在此处阅读有关前缀总和的信息

现在向我展示承诺的SQL代码

在SQL中,可以轻松地重新实现天真的顺序复杂度和线性复杂度解决方案,而我正在展示PostgreSQL的解决方案。

我们该怎么做? 我们在这里使用了几个功能。 首先,我们使用通用表表达式(也称为WITH子句) 。 我们使用它们来声明表变量。 第一个变量是op表,其中包含我们的操作指令,例如Java:

WITH 
  op (p, q, r) AS (
    VALUES
      (4, 12, 2),
      (2,  8, 3),
      (6,  7, 1),
      (3,  7, 2)
  ),
  ...

这是微不足道的。 实际上,我们只是在生成几个示例值。

第二个表变量是信号表,在这里我们使用前面描述的优化方法:在所有p位置放置一个+r信号,在所有q + 1位置放置一个-r信号:

WITH 
  ...,
  signal(x, r) AS (
    SELECT p, r
    FROM op
    UNION ALL
    SELECT q + 1, -r
    FROM op
  )
...

运行时:

SELECT * FROM signal ORDER BY x

您将得到:

x   r
------
2   3
3   2
4   2
6   1
8  -2
8  -1
9  -3
13 -2

现在我们需要做的就是计算一个运行总计(基本上与前缀和相同) ,如下所示:

SELECT x, SUM(r) OVER (ORDER BY x)
FROM signal 
ORDER BY x
x   r
------
2   3
3   5
4   7
6   8
8   5
8   5
9   2
13  0

现在只需找到r的最大值,就可以设置好了。 我们将通过使用ORDER BYLIMIT来使用快捷方式:

SELECT x, SUM(r) OVER (ORDER BY x) AS s
FROM signal 
ORDER BY s DESC
LIMIT 1

我们回来了:

x   r
------
6   8

完善! 这是完整的查询:

WITH 
  op (p, q, r) AS (
    VALUES
      (4, 12, 2),
      (2,  8, 3),
      (6,  7, 1),
      (3,  7, 2)
  ),
  signal(x, r) AS (
    SELECT p, r
    FROM op
    UNION ALL
    SELECT q + 1, -r
    FROM op
  )
SELECT x, SUM(r) OVER (ORDER BY x) AS s
FROM signal 
ORDER BY s DESC
LIMIT 1

您能否击败此SQL解决方案的简洁性? 我敢打赌你不能。 挑战者应在评论部分中写其他选择。

翻译自: https://www.javacodegeeks.com/2016/03/time-funky-sql-prefix-sum-calculation.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值