【问题标题】:SQL: generate N random non-negative integers adding up to a provided total MSQL:生成 N 个随机非负整数,总和为提供的 M
【发布时间】:2017-05-29 04:57:45
【问题描述】:

生成随机整数加起来等于给定总数的问题在不同的编程语言中不断出现,RJavaPython 都有 SO 解决方案。

此问题寻求一种“普通”SQL 解决方案,该解决方案仅限于带有可选公用表表达式 [CTE] 的单个 SELECT 语句。

输入是一个 1x2 表调用 inputs:单行,其中包含 int 类型的 MN 列。结果应该是一个 Nx1 表,其中包含单个列 i,表示加起来为 M 的整数。

【问题讨论】:

  • 感谢您的信息。但是在这里你应该问一个问题
  • +++ 这实际上是一个非常棘手的 SQL 问题。我会接受挑战的!
  • @RiggsFolly 这个问题要求使用特定语言功能的 SQL 解决方案。它指定输入和输出。它突出了其他三种语言的 SO 解决方案,以帮助回答问题。在您看来,究竟缺少什么?问号?
  • @Strawberry 为什么与作为复杂大数据分析管道一部分的选择抽样有关。细节与问题无关。事实上,我有目的地抽象了问题陈述,使其通用且对其他上下文有用。
  • 不,缺少的是任何为自己解决问题的尝试!在 SO,我们帮助您解决解决问题的尝试,我们不会免费为您编写代码

标签: mysql sql postgresql amazon-redshift


【解决方案1】:
create table inputs (m int,n int);
insert into inputs (m,n) values (100,10);

select      i-lag(i,1,0) over (order by i)  as i

from       (select      *
            from       (select      i
                        from        generate_series (1,(select m from inputs)) as gs(i)
                        order by    random()
                        limit       (select n from inputs)-1
                        ) t

            union all

            select      100
            ) t

 order by   i

示例结果

+----+
| i  |
+----+
| 1  |
+----+
| 1  |
+----+
| 2  |
+----+
| 3  |
+----+
| 4  |
+----+
| 12 |
+----+
| 13 |
+----+
| 16 |
+----+
| 18 |
+----+
| 30 |
+----+

访问权限非常有限,所以只是想法 -

  • 生成数字 1..n-1

  • 随机排序

  • 选择前 m-1 个数字 联合所有n

  • 对数字排序

  • 计算后面每两个数字之间的距离(LAG - 对于第一个数字,使用 0 作为前面的数字)

【讨论】:

  • 终于可以写代码了。请看一看。
【解决方案2】:

我提出以下查询(PostgreSQL):

WITH ZeroToOne (m, n, y) AS (
  SELECT m, n, random()
  FROM inputs
  CROSS JOIN generate_series(1, n)
), SumToM (m, n, y, x) AS (
  SELECT m, n, y, y * m / sum(y) OVER (PARTITION BY m, n)
  FROM zerotoone
), MissingToM (m, n, l) AS (
  SELECT m, n, m - sum(floor(x))
  FROM sumtom
  GROUP BY m, n
)
SELECT m, n, y, x, l,
  CASE
    WHEN row_number() OVER (PARTITION BY m, n ORDER BY x - floor(x) DESC) > l
    THEN floor(x)
    ELSE ceil(x)
  END AS v
FROM missingtom
NATURAL JOIN sumtom;

唯一有趣的值是 m、n 和 v;出于解释目的,我保留了其他值。

我将使用以下输入案例作为运行示例逐步完成查询:

SELECT * FROM inputs;
 m  | n 
----+---
 20 | 4
 30 | 4
 42 | 3
(3 rows)

第一个 CTE (ZeroToOne) 为每个输入案例计算 [0, 1] 范围内的 n 随机值,并将这些值称为 y

 m  | n |          y          
----+---+---------------------
 20 | 4 |   0.374425032641739 
 20 | 4 |   0.644279096741229 
 20 | 4 |   0.626386553514749 
 20 | 4 |   0.320786282420158 
 30 | 4 |   0.848764919675887 
 30 | 4 |   0.268079651053995 
 30 | 4 |   0.250213726423681 
 30 | 4 |   0.497460773680359 
 42 | 3 |   0.571454062592238 
 42 | 3 | 0.00338772451505065 
 42 | 3 |   0.139226260595024 

第二个 CTE (SumToM) 将每个 y 值乘以 m,然后将结果除以输入案例的值之和。因此,将输入对 (m, n) 的所有x 相加得到m

 m  | n |          y          |         x         
----+---+---------------------+-------------------
 20 | 4 |   0.374425032641739 |  3.80924177094873 
 20 | 4 |   0.644279096741229 |  6.55462277759638 
 20 | 4 |   0.626386553514749 |  6.37259161753762 
 20 | 4 |   0.320786282420158 |  3.26354383391728 
 30 | 4 |   0.848764919675887 |  13.6565766414436 
 30 | 4 |   0.268079651053995 |   4.3133855037604 
 30 | 4 |   0.250213726423681 |  4.02592384820881 
 30 | 4 |   0.497460773680359 |  8.00411400658722 
 42 | 3 |   0.571454062592238 |  33.6117414945302 
 42 | 3 | 0.00338772451505065 | 0.199258922297338 
 42 | 3 |   0.139226260595024 |  8.18899958317244 

很明显,m 大于x 值的整数部分之和。也很容易看出两个和之间的差(x 值的总和和 x 值的整数部分的总和)小于n。所以现在的想法是计算 hom 有多少数字必须四舍五入,多少数字必须四舍五入。第三个CTE(MissingToM)的l值是要四舍五入的值的个数:

 m  | n | l 
----+---+---
 20 | 4 | 2 
 30 | 4 | 1 
 42 | 3 | 1 

为确保数字的分布保持均匀,我们将具有最高小数部分的数字与最终查询一起四舍五入:

 m  | n |          y          |         x         | l | v  
----+---+---------------------+-------------------+---+----
 20 | 4 |   0.374425032641739 |  3.80924177094873 | 2 |  4
 20 | 4 |   0.644279096741229 |  6.55462277759638 | 2 |  7
 20 | 4 |   0.626386553514749 |  6.37259161753762 | 2 |  6
 20 | 4 |   0.320786282420158 |  3.26354383391728 | 2 |  3
 30 | 4 |   0.848764919675887 |  13.6565766414436 | 1 | 14
 30 | 4 |   0.268079651053995 |   4.3133855037604 | 1 |  4
 30 | 4 |   0.250213726423681 |  4.02592384820881 | 1 |  4
 30 | 4 |   0.497460773680359 |  8.00411400658722 | 1 |  8
 42 | 3 |   0.571454062592238 |  33.6117414945302 | 1 | 34
 42 | 3 | 0.00338772451505065 | 0.199258922297338 | 1 |  0
 42 | 3 |   0.139226260595024 |  8.18899958317244 | 1 |  8

由于输入表中多次出现相同的配置(m,n),查询会失败,所以我在其上添加一个主键约束:

ALTER TABLE inputs ADD PRIMARY KEY (m, n);

【讨论】:

  • 不错!您创建了一个超越问题的解决方案,同时解决了多个唯一 (m, n) 对的问题。选择性向上/向下舍入逻辑也是一个不错的选择。
【解决方案3】:

这似乎可以做到(Postgres):

with recursive inputs (n,m) as (
  values (10,100)
), worker (i, total, rn) as (

   select val, val as total, 1 as rn
   from ( 
     select floor(random() * (m/n - 1) + 1)
     from inputs
   ) as x (val)

   union all

   select c.val, p.total + c.val, p.rn + 1
   from worker p
     join lateral (
       select floor(random() * (i.m - p.total - 1) + 1)
       from inputs i
     ) c (val) on p.rn < (select n from inputs)
)
select *
from worker
order by rn;

但是,这可能被视为作弊,因为大多数情况下,在 6 或 7 行之后(有时更早,有时更晚),值的总和(上例中为 100)已经达到。这意味着最后的“随机”数字不再那么随机了。

其中一个好的结果是:

  i | total | rn
----+-------+---
  3 |     3 |  1
  1 |     4 |  2
 40 |    44 |  3
 33 |    77 |  4
 11 |    88 |  5
  2 |    90 |  6
  4 |    94 |  7
  3 |    97 |  8
  2 |    99 |  9
  1 |   100 | 10

但有时它就像:

  i | total | rn
----+-------+---
  7 |     7 |  1
 59 |    66 |  2
 23 |    89 |  3
 10 |    99 |  4
  1 |   100 |  5
  0 |   100 |  6
  0 |   100 |  7
  0 |   100 |  8
  0 |   100 |  9
  0 |   100 | 10

但我没有看到随机值必须是唯一的任何要求。

在线示例:http://rextester.com/VRBV22166

【讨论】:

  • 在这里很好地使用了横向连接。我喜欢解决方案的简约。我给出了 Fabian 尝试的答案,因为他的解决方案最后没有显示零的累积。
猜你喜欢
  • 2014-09-10
  • 1970-01-01
  • 1970-01-01
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-25
  • 1970-01-01
相关资源
最近更新 更多