操作数组, 使得其中相同的元素的距离>=k - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
icySoda
V2EX    算法

操作数组, 使得其中相同的元素的距离>=k

  •  
  •   icySoda 2021-12-23 11:50:02 +08:00 1830 次点击
    这是一个创建于 1441 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近业务上的一个需求. 真实场景是这样, 0-31 一共 32 个数, 给定长度 n, 返回一个长度为 n 的随机数组. 使得:

    1. 以下元素出现的概率为 50%, 0,7,8,15,16,23,24,31
    2. 以下元素出现的概率为 45%: 1~6,25~30, 9,14,17,22
    3. 其他元素出现的概率为 5%: 1014, 18~21
    4. 数组中相同元素的位置距离>=6

    前 3 点很容易满足, 第 4 点没什么思路, 请老师们指教

    28 条回复    2021-12-23 23:24:16 +08:00
    Mutoo
        1
    Mutoo  
       2021-12-23 12:26:51 +08:00
    1 号桶 10 个;
    2 号桶 9 个;
    3 号桶 1 个;
    每个桶对应一个序列,序列里是上面所指定的数字;
    初始化的时候将三个序列洗牌;
    每回合随机取一个桶,获得相应序列中的首个数字,输出,并将数字放回到序列末尾;
    每个序列被取 6 ( k )次后重新洗牌;
    lysS
        2
    lysS  
       2021-12-23 12:40:41 +08:00
    先按前三个要求写一个 rand, rand 每次返回一个值,检查这个值是否符合条件 4 ,不符合重新生成,符合就 append
    icySoda
        3
    icySoda  
    OP
       2021-12-23 13:07:47 +08:00
    @Mutoo 这个并不能保证 4. 前一个回合输出的最后数字是 x, 下一个回合第一个输出的数字也可能是 x.
    @lysS 最后一个值运气不好的话, 可能 cpu 冒烟也不一定能得到结果
    Mutoo
        4
    Mutoo  
       2021-12-23 13:37:50 +08:00 via iPhone
    洗牌只发生在初始化和每 k 次 /序列。取的时候只取序列首个数,FIFO 。只要你的序列长度大于 k 就可以保证 4 。
    Mutoo
        5
    Mutoo  
       2021-12-23 13:39:20 +08:00 via iPhone
    更正一下:每号桶对应一个序列,不是每个桶对应一个序列。总的就三个序列。
    ipwx
        6
    ipwx  
       2021-12-23 13:46:30 +08:00
    @Mutoo 不用放到末尾,加个 next 指针就行。洗牌后,每个桶内每次取 next 指针的数字就行。当 next 指针用完整个桶在重新洗牌。。。

    @icySoda 不过这其实和原问题的分布略有不同,但是我觉得足够相近了。这套重新洗牌是最快的实现方案,不然你就得维护过去六个元素的集合,如果下一个采样到的元素属于这个集合就重新采样。。。不过大概也不会太慢。

    每个点上重新采样一次的概率是 6/32 ,大概 1/5 。期望采样 2 次就能拿到一个合适的数字。倒也不是不行
    ipwx
        7
    ipwx  
       2021-12-23 13:52:05 +08:00
    Mutoo
        8
    Mutoo  
       2021-12-23 14:11:34 +08:00
    写了个代码验证一下,好像确实不能保证 4 。这个规则只有首位数字能保证 k 次不重复。

    想了一下,如果有个序列有 7 个数,要保正条件 4 ,生成的结果中每 6 个数都不重复的话,序列就无法随机了。
    Maboroshii
        9
    Maboroshii  
       2021-12-23 14:32:31 +08:00
    最简单的想到,如果 n<=6 ,那么不允许出现重复数字
    也就是这个结果和 n 的大小相关的,这个完全影响了随机出现

    一个蠢办法,离线算出来出符合第 4 点的有限多的数组,最后从这些数组里面随机一个拿出来用就行了
    Maboroshii
        10
    Maboroshii  
       2021-12-23 14:41:44 +08:00
    想到一个生成的办法

    初始化一个空数组(长度大大于 n)
    do {
    随机一个数 a
    判断这个数是否在数组中
    if 存在数 a0 == a {
    把这个数放在 a0 位置的 6 位以后 (如果数组长度不够,扩展数组长度)
    } else {
    放在第一个不为空的位置
    }
    } while 从数组开始能取到长度为 n 的元素都不为空的片段

    这样可以搞一个长度为 n 的窗口,可以一直得到符合要求的数组
    GuuJiang
        11
    GuuJiang  
       2021-12-23 15:15:16 +08:00
    每生成一个数,就在接下来的 6 次随机中从随机池中暂时移除,6 次以后再加回来
    生成每个数时如果随机到了一个已被移除的数则重试
    ipwx
        12
    ipwx  
       2021-12-23 15:23:23 +08:00
    @Maboroshii
    @Mutoo
    @GuuJiang

    你看我 7L 的代码,还有 6L 的分析。从期望这只要多采样一倍就行。。。估计是最合适的方案了

    加上计数器统计总共的采样次数:

    https://ideone.com/e1Ek8y

    可以知道这次实验,生成 100 个数字只需要采样 129 次,丢弃率只有不到 30%
    icySoda
        13
    icySoda  
    OP
       2021-12-23 15:23:35 +08:00
    @Maboroshii 这样没法保证数组的前 n 个符合概率要求吧?
    @ipwx 这是个办法, 就是概率有点出入, 不过我可以离线算几组概率和要求比较近的拿来用
    ipwx
        14
    ipwx  
       2021-12-23 15:24:16 +08:00
    @icySoda 我的代码方法没有出入,完全按照楼主的要求来。
    ipwx
        15
    ipwx  
       2021-12-23 15:25:03 +08:00
    GuuJiang
        16
    GuuJiang  
       2021-12-23 15:27:21 +08:00
    “移除”和“加回来”只是形象的说法,实际实现时只需要记录每个数字最后出现位置,随机结果中检查距离最后出现位置是否小于 6 ,如果是则重试
    预判到可能会有人怀疑这样改变了概率,其实不会,根据规则定义,6 次之内出现过的数据概率本来就只能为 0 ,而其他的数仍然是原始的概率,如果不相信的话思考下下面这个简化的例子就明白了
    只用一个 6 面骰子,如何产生 1-5 的均匀分布随机数,答案就是如果扔到 6 就不算,重新扔一次,通过条件概率可以很容易计算出这样操作产生的随机数就是 1-5 均匀分布,简单地说就是对于一个随机试验,抛弃某些特定的结果不会影响未被抛弃的那些结果的概率
    icySoda
        17
    icySoda  
    OP
       2021-12-23 15:27:42 +08:00
    @ipwx 你的循环以"是否满足第四点"为跳出的条件, 会导致数量最多的第 2 点的数据概率偏大.
    我离线算了几组, 第 2 点所列的元素概率大体都在 50%左右
    Mutoo
        18
    Mutoo  
       2021-12-23 15:36:45 +08:00 via iPhone
    重采样必然会影响概率。至于影响多少很难讲。
    @ipwx
    icySoda
        19
    icySoda  
    OP
       2021-12-23 15:40:09 +08:00
    @GuuJiang 我要消化一下
    GuuJiang
        20
    GuuJiang  
       2021-12-23 15:45:22 +08:00
    @Mutoo 不会影响的,我给的方案也是同一个思路,用条件概率公式简单算一下就知道了
    ipwx
        21
    ipwx  
       2021-12-23 15:54:46 +08:00
    @Mutoo icySoda 你们说得对,我把一个重采样的丢弃操作放错位置了。

    https://ideone.com/fx3OHz

    @icySoda 这个就符合条件了。

    reject sampling 是有理论证明的,无需怀疑。如果结果不符合,一定是 reject sampling 没写对(滑稽)
    ipwx
        22
    ipwx  
       2021-12-23 15:56:05 +08:00
    ('accept rate:', 0.6822678583611926)
    [0.50017, 0.45079, 0.04904]

    接受率在 0.7 左右,比我想的 0.5 要好 hhh
    ipwx
        23
    ipwx  
       2021-12-23 15:57:02 +08:00
    @icySoda 另外你的 原问题,14 这个数同时出现在 Group B 和 group C 了。我把它放进 group C 了
    GuuJiang
        24
    GuuJiang  
       2021-12-23 15:58:04 +08:00
    @icySoda 如果你计算生成的结果频率和你期望的不一致,说明你对概率的计算不对,而且我大概能猜到不对在哪,以 5%这组为例,假设你第一次生成了 10 ,那么接下来的 6 次中不可能再有 10 ,但是 11-14 ,18-21 中每个数出现的概率仍然是 5%/8 ,但是你不能再去统计整组的概率,换句话说,前三条规则表面上说的是三个组整体的概率,实际上说的是其中每个元素的概率,拒绝采样保证没有被丢弃的元素的概率不变,但是如果你要让整组的概率仍然维持不变,这和第 4 条是天然相悖的
    ipwx
        25
    ipwx  
       2021-12-23 16:00:45 +08:00   1
    @GuuJiang 21L 我 reject sampling 修正了,22L 是实验结果
    icySoda
        26
    icySoda  
    OP
       2021-12-23 17:52:58 +08:00
    @ipwx 看了结果, 概率确实大体相近

    同样的逻辑, 我用 js 的出来的概率就是差很多, 百思不得其解

    https://ideone.com/bp66nl
    crs0910
        27
    crs0910  
       2021-12-23 20:58:36 +08:00
    @icySoda while true 写错位置了
    icySoda
        28
    icySoda  
    OP
       2021-12-23 23:24:16 +08:00
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1253 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 75ms UTC 17:16 PVG 01:16 LAX 09:16 JFK 12:16
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86