【问题标题】:Shuffling Elements in a List (randomly re-arrange List Elements)洗牌列表中的元素(随机重新排列列表元素)
【发布时间】:2012-02-07 16:26:20
【问题描述】:

我的程序的一部分要求我能够随机打乱列表元素。我需要一个函数,当我给它一个列表时,它会伪随机地重新排列列表中的元素。
排列的变化必须在每次调用时都可以看到列表。

我的实现似乎工作得很好,但我觉得它相当长并且正在增加我的代码库,而且我觉得它不是执行此操作的最佳解决方案。所以我需要一个更短的实现。这是我的实现:

-模块(随机播放)。 -出口([列表/1])。 -定义(RAND(X),随机:均匀(X))。 -define(TUPLE(Y,Z,E),erlang:make_tuple(Y,Z,E))。 列表(L)-> Len = 长度(L), 数字=列表:seq(1,Len), tuple_to_list(?TUPLE(Len,[],shuffle(Nums,L,[])))。 洗牌([],_,缓冲区)->缓冲区; shuffle(Nums,[Head|Items],Buffer)-> {Pos,NewNums} = pick_position(Nums), 洗牌(NewNums,项目,[{Pos,Head}|缓冲区])。 pick_position([N])-> {N,[]}; pick_position(Nos)-> T = 列表:最大(编号), 选择(编号,T)。 选择(从,最大)-> 随机:种子(开始 (案例随机:种子(现在())的 未定义-> NN =元素(3,现在()), {?RAND(NN),?RAND(NN),?RAND(NN)}; 任何 -> 任何 结尾) 结尾 ), T2 = 随机:均匀(最大), 案例列表:成员(T2,From)的 假 -> 挑选(从,最大); 真 -> {T2,来自 - [T2]} 结尾。

在 shell 中运行它:

F:\> 错误 Eshell V5.8.4(使用 ^G 中止) 1> c(随机播放)。 {好的,随机播放} 2> 洗牌:列表([a,b,c,d,e])。 [c,b,a,e,d] 3> 洗牌:列表([a,b,c,d,e])。 [e,c,b,d,a] 4> 洗牌:列表([a,b,c,d,e])。 [a,b,c,e,d] 5> 洗牌:列表([a,b,c,d,e])。 [b,c,a,d,e] 6> 洗牌:列表([a,b,c,d,e])。 [c,e,d,b,a] 我的动机是 STDLIB 中没有这样的功能。在我的游戏中的某个地方,我需要重新整理,还需要找到解决问题的最佳有效解决方案,而不仅仅是一个有效的解决方案。

有人可以帮助构建一个较短版本的解决方案吗?可能更有效率?谢谢

【问题讨论】:

    标签: erlang


    【解决方案1】:

    请注意,karl's answer 更加简洁明了。


    这是一个相当简单的解决方案,虽然不一定是最有效的:

    -module(shuffle).
    
    -export([list/1]).
    
    list([])     -> [];
    list([Elem]) -> [Elem];
    list(List)   -> list(List, length(List), []).
    
    list([], 0, Result) ->
        Result;
    list(List, Len, Result) ->
        {Elem, Rest} = nth_rest(random:uniform(Len), List),
        list(Rest, Len - 1, [Elem|Result]).
    
    nth_rest(N, List) -> nth_rest(N, List, []).
    
    nth_rest(1, [E|List], Prefix) -> {E, Prefix ++ List};
    nth_rest(N, [E|List], Prefix) -> nth_rest(N - 1, List, [E|Prefix]).
    

    例如,可以取消nth_rest/3 中的++ 操作。您无需在每次调用random 时都为随机算法播种。启动程序时首先播种,如下所示:random:seed(now())。如果您为每次调用 uniform/1 播种它,您的结果就会出现偏差(尝试使用 [shuffle:list([1,2,3]) || _ <- lists:seq(1, 100)])。

    【讨论】:

    • 感谢@Adam 很好的解决方案。我喜欢它
    • 嗨。我可以补充一点,您应该在使用random:uniform 之前播种吗?否则你会在不同的 VM 执行中得到相同的结果,而且在许多应用程序中这是不需要的。
    • @user601836 好点!请注意,您只需为每个进程执行一次,而不是每次运行 shuffle:list/1
    • 很好的二次复杂度,但我想要 karlsolution 的 O(N*logN)。
    • 我还要补充一点,大多数时候最好使用 LYSE 中提出的种子:{A:32,B:32,C:32} = crypto:rand_bytes(12) as random:seed({A,B,C})
    【解决方案2】:
    1> L = lists:seq(1,10).
    [1,2,3,4,5,6,7,8,9,10]
    

    通过创建元组列表 {R, X} 将随机数 R 与 L 中的每个元素 X 相关联。对这个列表进行排序并解包元组以获得一个打乱版本的 L。

    1> [X||{_,X} <- lists:sort([ {random:uniform(), N} || N <- L])].
    [1,6,2,10,5,7,9,3,8,4]
    2>     
    

    【讨论】:

    • 投票了!我也喜欢这个!谢谢@karl
    • 这种洗牌方法会产生有偏差的结果,除非您可以保证不会生成重复的随机数(random:uniform/0 无法做到这一点。
    • 好点@jvf,虽然我认为值得指出的是,由于random:uniform/0 返回一个浮点数,重复应该非常罕见。很高兴意识到这种事情,但我敢打赌,对于许多应用程序来说,如此微小的偏差在实践中不会成为问题。
    • 有更好的随机函数吗?
    • @quantumpotato 不确定您所说的“更好”是什么意思,但新的改进版本是rand:uniform()
    【解决方案3】:
    -module(shuffle).
    
    -compile(export_all).
    
    shuffle(L) ->
        shuffle(list_to_tuple(L), length(L)).
    
    shuffle(T, 0)->
        tuple_to_list(T);
    shuffle(T, Len)->
    Rand = random:uniform(Len),
    A = element(Len, T),
    B = element(Rand, T),
    T1 = setelement(Len, T,  B),
    T2 = setelement(Rand,  T1, A),
    shuffle(T2, Len - 1).
    

    main()-> 随机播放(列表:序列(1, 10))。

    【讨论】:

      【解决方案4】:

      这会比上面的方案快一点,这里列为do2进行时序比较。

      -module(shuffle).
      -export([
          time/1,
          time2/1,
          do/1,
          do2/1
      ]).
      
      time(N) ->
          L = lists:seq(1,N),
          {Time, _} = timer:tc(shuffle, do, [L]),
          Time.
      time2(N) ->
          L = lists:seq(1,N),
          {Time, _} = timer:tc(shuffle, do2, [L]),
          Time.
      do2(List) ->
          [X||{_,X} <- lists:sort([ {rand:uniform(), N} || N <- List])].
      do(List) ->
          List2 = cut(List),
          AccInit = {[],[],[],[],[]},
          {L1,L2,L3,L4,L5} = lists:foldl(fun(E, Acc) -> 
              P = rand:uniform(5), 
              L = element(P, Acc),
              setelement(P, Acc, [E|L])
          end, AccInit, List2),
          lists:flatten([L1,L2,L3,L4,L5]).
      
      cut(List) ->
          Rand=rand:uniform(length(List)),
          {A,B}=lists:split(Rand, List),
          B++A.
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-08
        • 1970-01-01
        • 2017-11-10
        • 1970-01-01
        • 2016-01-20
        • 2014-06-12
        相关资源
        最近更新 更多