微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

稍微改变种子时稍微改变随机列表洗牌的输出

如何解决稍微改变种子时稍微改变随机列表洗牌的输出

问题:

我想随机打乱一个列表,但使用种子值 s 以便,当 s 只稍微改变时,shuffle 的输出 只稍微改变。

详情:

我有一个元素的排序列表,例如:

[0,1,3,5,7]

这个列表应该被打乱多次,每次都使用一个种子值 s。当两个种子值 s1s2 = s1 + d 彼此靠近时,混洗列表也应该是“相似的”。

“相似”是指新混洗列表中的元素要么与使用原始种子值 s1 时的相同,要么仅被替换为与它们接近的值原始列表(例如它们在原始列表中的直接邻居)。

编辑:输出应该仍然是确定性的,即在混洗相同的输入列表时使用相同的种子 s1 应该导致相同的混洗列表。

上述列表的示例(注意添加小值 d 只会轻微扰乱列表,即许多值保持不变,如果它们发生变化,它们通常会被原始列表中的邻居替换。如偏移量增加,列表之间的“差异”可能会进一步增加,并且也可能会选择邻居之外的值):

种子: 输出
s [5,7,3]
s + d [5,1]
s + 2d [7,1]
s + 3d [7,5]

有没有这方面的算法?此问题是否有通用名称(或我可以使用的其他搜索词)?

编辑2: 输出也不应该取决于原始种子是什么,即如果 s = s3 + d = s4 - 0.5*d 那么无论我使用 ss3 + d 还是 s4 - 0.5*d 作为混洗,混洗结果都应该相同一粒种子。换句话说,我只将最终种子传递给算法,而不是原始种子 s3s4。原因是我想在种子之间进行插值,结果应该是列表排列之间的“插值”。

到目前为止我的想法:

我可以使用单工/柏林噪声对元素进行采样:我生成一个介于 0 和 1 之间的数字,然后使用它从列表中选择下一个元素(0 表示我选择第一个元素,1 表示我选择最后一个元素) .由于这些噪声类型可能是“平滑的”,添加 d 只会稍微改变随机值,这意味着它通常会选择与添加 d 之前选择的元素相近的元素。但是,我很难为噪声选择一个“好的”频率(高频会消除我需要的平滑度,低频会在添加 d 时没有变化)。此外,一旦我选择元素时列表缩小,d 的效果就会降低,因为 0 到 1 的范围将映射到更少的元素 - 我真的不知道这是否是一个问题......?>

解决方法

生成并留出列表的随机洗牌R。然后,每次使用种子 s:

调用生成器
  1. s = s % factorial(N) 其中 N 是列表的长度(或者只要求 s 应该介于 0 和 N!-1 之间)。
  2. Find the factorial representation 个,共 s
  3. Convert the factorial representation to a permutation p
  4. p 应用于 R 并返回结果。

结构是这样的,排列 p(s)p(s+1) 在字典顺序上是相邻的。这些步骤可以有效地实施。

您可以将相同的想法用于排列集合的其他排序,这可以最大限度地减少更改,例如 Heap ordering or the Steinhaus-Johnson-Trotter ordering,但我不知道在这些情况下是否可以有效地实施第 3 步.

,

如果您可以忍受将“相似性”的定义更改为排列元素之间的交换次数,那么我们可以使用 Trotter-Johnson 排列顺序,其中连续排列的差异仅在于 perms 中两个项目的一次交换。

有一些算法可以将连续的整数等级与顺序的连续 perms 相关联,还有其他算法可以从等级生成 perm,反之亦然。使用种子作为等级值,那么两个相差 n 的种子将需要 n 个项目交换才能在它们各自的 perms 之间进行。

很多搜索都提到了这本书:

D.L.克雷尔和 D.R. Stinson,组合算法:生成, 枚举和搜索,CRC 出版社,1999。

我已经把它变成了下面的 Python:

(defn render-location-details
  [cur-location]
  [:div
   [:h3 (:Desc cur-location)]
   [:h4 "Address"]
   [:p (:Address cur-location)]
   (when-not (clojure.string/blank? (:Comment cur-location))
     [:div
      [:h4 "Comment"]
      [:p (:Comment cur-location)]])])

n == 4 的示例输出

# -*- coding: utf-8 -*-
"""
Created on Thu Jun  3 08:44:56 2021

@author: Paddy3118
"""

from typing import List

Perm = List[int]

_fact = [1]     # factorials cache


def print_perm(T: Perm) -> None:
    print(T)

def tj_unrank(n: int,r: int) -> Perm:
    "Returns the r-ranked Trotter-Johnson permutation of integers 0..n-1"
    global _fact

    for i in range(len(_fact),n+2):    # Extend factorial cache if necessary.
        _fact.append(_fact[i - 1] * i)

    pi: Perm = [0] * (n+2)
    pi[1] = 1
    r2 = 0
    for j in range(2,n+1):
        r1 = (r * _fact[j]) // _fact[n]
        k = r1 - j*r2
        if ((r2 % 2) == 0):
            for i in range(j-1,j - k - 1,-1):
                pi[i+1] = pi[i]
            pi[j-k] = j
        else:
            for i in range(j - 1,k,-1):
                pi[i+1] = pi[i]
            pi[k + 1] = j
        r2 = r1

    return [i - 1 for i in pi[1:-1]]

def tj_rank(n: int,p: Perm) -> int:
    "Returns the ranking of the Trotter-Johnson permutation p,of integers 0..n-1"
    assert set(p) == set(range(n)),f"Perm {p} not a perm of 0..{n-1}."

    pi = [0] + [i+1 for i in p] + [0]
    r = 0
    for j in range(2,n + 1):
        i = k = 1
        while pi[i] != j:
            if (pi[i] < j):
                k += 1
            i += 1
        if ((r % 2) == 0 ):
            r = j*r+j-k
        else:
            r = j*r+k-1

    return r

def tj_parity(p: Perm) -> int:
    "Returns the 0/1 parity of the Trotter-Johnson permutation p,of integers 0..n-1"
    n = len(p)
    assert set(p) == set(range(n)),f"Perm {p} not a perm of 0..{n-1}."

    pi = [0] + [i+1 for i in p] + [0]
    a,c = [0] * (n + 1),0
    for j in range(1,n+1):
        if a[j] == 0:
            c += 1
            a[j] = 1
            i = j
            while ( pi[i] != j ):
                i = pi[i]
                a[i] = 1

    return (n-c) % 2


if __name__ == '__main__':
    from sys import argv
    import re

    if not (len(argv) == 2 and re.match(r'\d+',argv[1])):
        raise SystemExit('ERROR. Call needs an integer >0 argument')
    n = int(argv[1])
    if n <=0:
        raise SystemExit('ERROR. Call needs one integer >0 argument')

    print(f"Testing rank/unrank n={n}.\n");
    for i in range(len(_fact),n+2):    # Extend factorial cache if necessary.
        _fact.append(_fact[i - 1] * i)

    for r in range(_fact[n]):
        p = tj_unrank(n,r)
        rank = tj_rank(n,p)
        parity = tj_parity(p)
        print(f"  Rank: {r:4} to perm: {p},parity: {parity} to rank: {rank}")

(注意连续烫发的区别在于上面烫发中的两个项目的一次交换)。

,

在这种情况下,很难给出“相似”的度量标准。 [例如:Apple 在他们的第一次 shuffle 实现中遇到了问题 - 人们觉得歌曲混合得不够随机]。

我会以不同的方式重新表述这个问题“我们如何修改一些简单的改组算法来达到预期的结果?”。

这是我的想法:

  1. 我们将需要一个基线种子 s -(较大的,与您的案例/数据中通常使用的列表大小相当,该值可以硬编码或作为参数传递,但应保持固定,如果我们不同s1)
  2. d = abs(s1 - s) -(距离s1有多远s
  3. 使用 s 作为种子进行默认 shuffle -(您选择的语言的随机库中的任何函数都可以。)
  4. 现在遍历列表并将概率为 p (0,1) 的任何元素与距您的位置距离为 d 的任何其他元素交换。
    例如:d = 2
    1 2 3 4 5 6 7
    元素 5 可以与 3,4,6 或 7 交换。
    基本上在位置 i 的元素将与此范围 [i-d,i+d] 中的任何元素(也可以随机选择)交换概率
  5. 操纵 p 和 s,直到满足您的“相似性”分数/感觉。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。