有些条件的随机播放列表

时间:2011-05-05 14:22:27

标签: c# algorithm sorting

我有一个可以使用Equals()轻松比较的元素列表。我必须洗牌,但洗牌必须满足一个条件:

第i个元素shuffledList[i]必须不等于i +/- 1的元素和i +/- 2的元素。该清单应视为循环;也就是说,列表中的最后一个元素后跟第一个元素,反之亦然。

另外,如果可能的话,我想检查一下是否可以进行随机播放。

注意:

我正在使用c#4.0。

编辑:

根据一些回复,我会再解释一下:

  • 该列表不会有超过200个元素,因此不需要良好的性能。如果计算它需要2秒钟,那不是最好的事情,但它也不是世界末日。将保存随机列表,除非真实列表发生更改,否则将使用随机列表。

  • 是的,这是一个“受控”的随机性,但我希望在这个方法上运行的几个会返回不同的混洗列表。

  • 在我尝试下面的一些回复后,我会做进一步的修改。

编辑2:

  • 两个样本列表因Sehe的实现而失败(两者都有解决方案):

的Sample1:

`List<int> list1 = new List<int>{0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,9,10};`

可能的解决方案:

List<int> shuffledList1 = new List<int> {9,3,1,4,7,9,2,6,8,1,4,9,2,0,6,5,7,8,4,3,10,9,6,7,8,5,3,9,1,2,7,8}

样本2:

`List<int> list2 = new List<int> {0,1,1,2,2,2,3,3,4,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,8,8,9,9,9,9,10};`
  • 验证:我正在使用这种方法,它不是我制作的最有效和最优雅的代码,但它确实有效:

    public bool TestShuffle<T>(IEnumerable<T> input)
    {
        bool satisfied = true;
        int prev1 = 0; int prev2 = 0;
        int next1 = 0; int next2 = 0;
        int i = 0;
        while (i < input.Count() && satisfied)
        {
            prev1 = i - 1; prev2 = i - 2; next1 = i + 1; next2 = i + 2;
            if (i == 0)
            {
                prev1 = input.Count() - 1;
                prev2 = prev1 - 1;
            }
            else if (i == 1)
                prev2 = input.Count() - 1;
            if (i == (input.Count() - 1))
            {
                next1 = 0;
                next2 = 1;
            }
            if (i == (input.Count() - 2))
                next2 = 0;
            satisfied =
                    (!input.ElementAt(i).Equals(input.ElementAt(prev1)))
                && (!input.ElementAt(i).Equals(input.ElementAt(prev2)))
                && (!input.ElementAt(i).Equals(input.ElementAt(next1)))
                && (!input.ElementAt(i).Equals(input.ElementAt(next2)))
            ;
            if (satisfied == false)
                Console.WriteLine("TestShuffle fails at " + i);
            i++;
        }
        return satisfied;
    }
    

编辑3:

有时失败的另一个测试输入:

List<int> list3 = new List<int>(){0,1,1,2,2,3,3,3,4,4,4,5,5,5,5,6,6,6,6,7,7,7,8,8,8,8,9,9,9,9,10,10};

4 个答案:

答案 0 :(得分:4)

优化版

令我失望的是,我的优化功能仅比LINQ'简单'版本快了7倍。未经优化的LINQ 1m43s 优化 14.7s

  • linux 32-bit
  • 使用-optimize+
  • 的Mono 2.11(C#4.0)编译器
  • 1,000,000 TESTITERATIONS
  • VERBOSE不是#define - d

已经优化的内容:

  • 假设输入和输出数组
  • 在输入数组上就地工作
  • '手动'分析相同值的运行,而不是使用GroupBy(使用ValueRun struct)
  • 在数组中使用这些ValueRun结构而不是Enumerable(List);排序/随机播放
  • 使用unsafe块和指针(无法辨别......
  • 使用模数索引而不是MAGIC Linq代码
  • 通过迭代追加而不是嵌套LINQ生成输出。 这可能效果最好。实际上,如果我们能够快速通过这个计数订购了一个集合收集的ValueRun,它会更好,这似乎很容易做到;然而,转置索引(循环约束所需)使事情变得复杂。无论如何,以更大的输入和许多独特的值以及一些高度重复的值,以某种方式应用此优化的收益将更大。

以下是具有优化版本的代码。 _通过去除RNG的种子可以获得额外的速度增益;这些仅用于对输出进行回归测试。

  

[... old code removed as well ...]


原始回复(部分)

如果我找对了你,你试图设计一个shuffle来防止重复在输出中连续结束(最少交错2个元素)。

在一般情况下,这是不可解决的。想象一下只输入相同的元素:)

更新:陷入困境

就像我在笔记中提到的那样,我认为我并不是一直都在正确的轨道上。要么我应该调用图论(任何人?),要么使用简单的'bruteforcey'算法,而不是Erick的长期建议。

无论如何,你可以看到我一直在做什么,以及问题是什么(使随机样本能够快速查看问题):

#define OUTPUT       // to display the testcase results
#define VERIFY       // to selfcheck internals and verify results
#define SIMPLERANDOM
// #define DEBUG        // to really traces the internals
using System;
using System.Linq;
using System.Collections.Generic;

public static class Q5899274
{
    // TEST DRIVER CODE
    private const int TESTITERATIONS = 100000;
    public static int Main(string[] args)
    {
        var testcases = new [] {
            new [] {0,1,1,2,2,2,3,3,4,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,8,8,9,9,9,9,10},
            new [] {0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,9,10},
            new [] { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41, 42, 42, 42, },
            new [] {1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4},
        }.AsEnumerable();

        // // creating some very random testcases
        // testcases = Enumerable.Range(0, 10000).Select(nr => Enumerable.Range(GROUPWIDTH, _seeder.Next(GROUPWIDTH, 400)).Select(el => _seeder.Next(-40, 40)).ToArray());

        foreach (var testcase in testcases)
        {
            // _seeder = new Random(45); for (int i=0; i<TESTITERATIONS; i++) // for benchmarking/regression
            {
                try
                {
                    var output = TestOptimized(testcase);
#if OUTPUT
                    Console.WriteLine("spread\t{0}", string.Join(", ", output));
#endif
#if VERIFY
                    AssertValidOutput(output);
#endif
                } catch(Exception e)
                {
                    Console.Error.WriteLine("Exception for input {0}:", string.Join(", ", testcase));
                    Console.Error.WriteLine("Sequence length {0}: {1} groups and remainder {2}", testcase.Count(), (testcase.Count()+GROUPWIDTH-1)/GROUPWIDTH, testcase.Count() % GROUPWIDTH);
                    Console.Error.WriteLine("Analysis: \n\t{0}", string.Join("\n\t", InternalAnalyzeInputRuns(testcase)));
                    Console.Error.WriteLine(e);
                }
            }
        }

        return 0;
    }

#region Algorithm Core
    const int GROUPWIDTH = 3; /* implying a minimum distance of 2
                                 (GROUPWIDTH-1) values in between duplicates
                                 must be guaranteed*/

    public static T[] TestOptimized<T>(T[] input, bool doShuffle = false)
        where T: IComparable<T>
    {
        if (input.Length==0)
            return input;

        var runs = InternalAnalyzeInputRuns(input);
#if VERIFY
        CanBeSatisfied(input.Length, runs); // throws NoValidOrderingExists if not
#endif
        var transpositions = CreateTranspositionIndex(input.Length, runs);

        int pos = 0;
        for (int run=0; run<runs.Length; run++)
            for (int i=0; i<runs[run].runlength; i++)
                input[transpositions[pos++]] = runs[run].value;

        return input;
    }

    private static ValueRun<T>[] InternalAnalyzeInputRuns<T>(T[] input)
    {
        var listOfRuns = new List<ValueRun<T>>();
        Array.Sort(input);
        ValueRun<T> current = new ValueRun<T> { value = input[0], runlength = 1 };

        for (int i=1; i<=input.Length; i++)
        {
            if (i<input.Length && input[i].Equals(current.value))
                current.runlength++;
            else
            {
                listOfRuns.Add(current);
                if (i<input.Length)
                    current = new ValueRun<T> { value = input[i], runlength = 1 };
            }
        }

#if SIMPLERANDOM
        var rng = new Random(_seeder.Next());
        listOfRuns.ForEach(run => run.tag = rng.Next()); // this shuffles them
#endif
        var runs = listOfRuns.ToArray();
        Array.Sort(runs);

        return runs;
    }

    // NOTE: suboptimal performance 
    //   * some steps can be done inline with CreateTranspositionIndex for
    //   efficiency
    private class NoValidOrderingExists : Exception { public NoValidOrderingExists(string message) : base(message) { } }
    private static bool CanBeSatisfied<T>(int length, ValueRun<T>[] runs)
    {
        int groups = (length+GROUPWIDTH-1)/GROUPWIDTH;
        int remainder = length % GROUPWIDTH;

        // elementary checks
        if (length<GROUPWIDTH)
            throw new NoValidOrderingExists(string.Format("Input sequence shorter ({0}) than single group of {1})", length, GROUPWIDTH));
        if (runs.Length<GROUPWIDTH)
            throw new NoValidOrderingExists(string.Format("Insufficient distinct values ({0}) in input sequence to fill a single group of {1})", runs.Length, GROUPWIDTH));

        int effectivewidth = Math.Min(GROUPWIDTH, length);

        // check for a direct exhaustion by repeating a single value more than the available number of groups (for the relevant groupmember if there is a remainder group)
        for (int groupmember=0; groupmember<effectivewidth; groupmember++)
        {
            int capacity = remainder==0? groups : groups -1;

            if (capacity < runs[groupmember].runlength)
                throw new NoValidOrderingExists(string.Format("Capacity exceeded on groupmember index {0} with capacity of {1} elements, (runlength {2} in run of '{3}'))",
                            groupmember, capacity, runs[groupmember].runlength, runs[groupmember].value));
        }

        // with the above, no single ValueRun should be a problem; however, due
        // to space exhaustion duplicates could end up being squeezed into the
        // 'remainder' group, which could be an incomplete group; 
        // In particular, if the smallest ValueRun (tail) has a runlength>1
        // _and_ there is an imcomplete remainder group, there is a problem
        if (runs.Last().runlength>1 && (0!=remainder))
            throw new NoValidOrderingExists("Smallest ValueRun would spill into trailing incomplete group");

        return true;
    }

    // will also verify solvability of input sequence
    private static int[] CreateTranspositionIndex<T>(int length, ValueRun<T>[] runs)
        where T: IComparable<T>
    {
        int remainder = length % GROUPWIDTH;

        int effectivewidth = Math.Min(GROUPWIDTH, length);

        var transpositions = new int[length];
        {
            int outit = 0;
            for (int groupmember=0; groupmember<effectivewidth; groupmember++)
                for (int pos=groupmember; outit<length && pos<(length-remainder) /* avoid the remainder */; pos+=GROUPWIDTH)
                    transpositions[outit++] = pos;

            while (outit<length)
            {
                transpositions[outit] = outit;
                outit += 1;
            }

#if DEBUG
            int groups = (length+GROUPWIDTH-1)/GROUPWIDTH;
            Console.WriteLine("Natural transpositions ({1} elements in {0} groups, remainder {2}): ", groups, length, remainder);
            Console.WriteLine("\t{0}", string.Join(" ", transpositions));
            var sum1 = string.Join(":", Enumerable.Range(0, length));
            var sum2 = string.Join(":", transpositions.OrderBy(i=>i));
            if (sum1!=sum2)
                throw new ArgumentException("transpositions do not cover range\n\tsum1 = " + sum1 + "\n\tsum2 = " + sum2);
#endif
        }

        return transpositions;
    }

#endregion // Algorithm Core

#region Utilities
    private struct ValueRun<T> : IComparable<ValueRun<T>>
    {
        public T value;
        public int runlength;
        public int tag;         // set to random for shuffling

        public int CompareTo(ValueRun<T> other) { var res = other.runlength.CompareTo(runlength); return 0==res? tag.CompareTo(other.tag) : res; }
        public override string ToString() { return string.Format("[{0}x {1}]", runlength, value); }
    }

    private static /*readonly*/ Random _seeder = new Random(45);
#endregion // Utilities

#region Error detection/verification
    public static void AssertValidOutput<T>(IEnumerable<T> output)
        where T:IComparable<T>
    {
        var repl = output.Concat(output.Take(GROUPWIDTH)).ToArray();

        for (int i=1; i<repl.Length; i++) 
            for (int j=Math.Max(0, i-(GROUPWIDTH-1)); j<i; j++)
                if (repl[i].Equals(repl[j]))
                    throw new ArgumentException(String.Format("Improper duplicate distance found: (#{0};#{1}) out of {2}: value is '{3}'", j, i, output.Count(), repl[j]));
    }
#endregion

}

答案 1 :(得分:3)

您的要求消除了真正的改组替代方案:没有随机性,或者存在受控随机性。 这是一种特殊的方法

  1. 对原始列表L
  2. 进行排序
  3. 找到模式M及其频率n
  4. 如果n是偶数,n ++。
  5. 创建k(= 5 * n - 1)列表(素数为n,5倍nL 1 < / sub>通过L k
       (选择5以避免前两个元素和两个下一个元素)
  6. 以循环方式将所有元素分配到k列表
  7. 单独调整所有k列表。
  8. 按以下顺序整理k个列表的内容:      一个。随机选择+5或-5作为x      湾选择一个随机数j      C。重复k次:
             一世。添加L j 中的所有内容          II。 j&lt; - (j + x)mod k
  9. [5,6,7,7,8,8,9,10,12,13,13,14,14,14,17,18,18,19,19,20,21,21,21, 21,24,24,26,26,26,27,27,27,29,29,30,31,31,31,31,32,32,32,33,35,35,37,38,39, 40,42,43,44,44,46,46,47,48,50,50,50,50,51,52,53,54,55,56,57,57,58,60,60,60, 61,62,63,63,64,64,65,65,65,68,71,71,72,72,73,74,74,74,74,75,76,76,76,77,77, 77,78,78,78,79,79,80,81,82,86,88,88,89,89,90,91,92,92,92,93,93,94,94,95,96, 99,99,100,102,102,103,103,105,106,106,107,108,113,115,116,118,119,123,124,125,127,127,127,128,131, 133,133,134,135,135,135,137,137,137,138,139,141,143,143,143,145,146,147,153,156,157,158,160,164,166, 170,173,175,181,181,184,185,187,188,190,200,200,215,217,234,238,240]

    模式频率= 4,因此有19个时隙(#0 - #18)

    0:[7,21,32,50,65,77,93,115,137,173]
    1:[8,21,33,51,65,78,93,116,137,175]
    2:[8,24,35,52,65,78,94,118,138,181]
    3:[9,24,35,53,68,78,94,119,139,181]
    4:[10,26,37,54,71,79,95,123,141,184]
    5:[12,26,38,55,71,79,96,124,143,185]
    6:[13,26,39,56,72,80,99,125,143,187]
    7:[13,27,40,57,72,81,99,127,143,188]
    8:[14,27,42,57,73,82,100,127,145,190]
    9:[14,27,43,58,74,86,102,127,146,200]
    10:[14,29,44,60,74,88,102,128,147,200]
    11:[17,29,44,60,74,88,103,131,153,215]
    12:[18,30,46,60,74,89,103,133,156,217]
    13:[18,31,46,61,75,89,105,133,157,234]
    14:[19,31,47,62,76,90,106,134,158,238]
    15:[19,31,48,63,76,91,106,135,160,240]
    16:[5,20,31,50,63,76,92,107,135,164]
    17:[6,21,32,50,64,77,92,108,135,166]
    18:[7,21,32,50,64,77,92,113,137,170]

    随机抽取单个列表,并选择5个插槽列表(从#16开始随机):

    16:[31,135,92,76,107,5,164,63,20,50]
    2:[52,24,35,78,181,8,138,94,118,65]
    7:[57,143,99,81,40,13,127,72,188,27]
    12:[46,30,60,89,133,74,156,18,103,217]
    17:[64,50,135,92,21,32,108,77,166,6]
    3:[9,94,181,119,24,35,139,68,53,78]
    8:[145,27,14,57,42,100,190,82,73,127]
    13:[89,18,75,61,157,234,133,105,31,46]
    18:[113,21,7,92,64,32,137,50,170,77]
    4:[71,10,37,26,123,54,184,79,95,141]
    9:[27,74,86,14,102,146,127,43,58,200]
    14:[62,106,158,134,19,47,238,31,76,90]
    0:[7,77,65,21,50,93,173,115,32,137]
    5:[96,79,26,185,12,71,124,143,55,38]
    10:[29,14,147,60,128,88,74,44,102,200]
    15:[106,240,63,48,91,19,160,31,76,135]
    1:[65,33,21,51,137,8,175,93,116,78]
    6:[143,26,13,56,99,72,39,80,187,125]
    11:[103,88,29,60,74,44,17,153,131,215]

    [31,135,92,76,107,5,164,63,20,50,52,24,35,78,181,8,138,94,118,65,57,143,99, 81,40,13,127,72,188,27,46,30,60,89,133,74,156,18,103,217,64,50,135,92,21,32,108,77, 166,6,9,94,181,119,24,35,139,68,53,78,145,27,14,57,42,100,190,82,73,127,89,18,75, 61,157,234,133,105,31,46,113,21,7,92,64,32,137,50,170,77,71,10,37,26,123,54,184,79, 95,141,27,74,86,14,102,146,127,43,58,200,62,106,158,134,19,47,238,31,76,90,7,77,65, 21,50,93,173,115,32,137,96,79,26,185,12,71,124,143,55,38,29,14,147,60,128,88,74,44, 102,200,106,240,63,48,91,19,160,31,76,135,65,33,21,51,137,8,175,93,116,78,143,26,13, 56,99,72,39,80,187,125,103,88,29,60,74,44,17,153,131,215]

答案 2 :(得分:2)

答案 3 :(得分:1)

  • 列出有效的5个列表,如果没有无法解决的话
  • 从列表中删除排列
  • 创建一个5
  • 的循环图
  • 通过新图表中有效位置的计数来对列表(剩余列表)进行排序(如果您不这样做,最终可能会出现错误的不可解决方案,因为放置可以进入更多位置的项目会增加项目的可能位置数量少用)
  • 继续挑选列表中的项目,在有效位置添加项目到周期图
  • 如果图表或图表中没有有效位置,则无法继续下一步
  • 如果创建的图形还原为已创建图形的开始迭代
  • 继续创建其他图表
  • 将所有完整图表保存在列表中
  • 从该列表中随机选择
  • 如果列表为空无法解决