Shuffle list with some conditions

后端 未结 4 1213
余生分开走
余生分开走 2021-01-04 22:51

I have a list of elements which can be easily compared using Equals(). I have to shuffle the list, but the shuffle must satisfy one condition:

The i\'th

4条回答
  •  醉梦人生
    2021-01-04 23:50

    Optimized version

    To my disappointment, my optimized function only runs 7x faster than the LINQ 'straightforward' version. Unoptimized LINQ 1m43s Optimized 14.7s.

    • linux 32-bit
    • Mono 2.11 (C# 4.0) compiler with -optimize+,
    • 1,000,000 TESTITERATIONS
    • VERBOSE not #define-d

    What has been optimized:

    • assume arrays for input and output
    • work in-place on input array
    • 'manually' analyze runs of identical values, instead of using GroupBy (using ValueRun struct)
    • have these ValueRun structs in an Array instead of Enumerable (List); sort/shuffle in-place
    • use unsafe blocks and pointers (no discernable difference...)
    • use modulo indexing instead of MAGIC Linq code
    • generate the output by iterative append instead of nested LINQ. This probably had the most effect. Actually, it would be even better when we could shortcut the ValueRuns that have a countruns collection is being ordered by this count, it seemed pretty easy to do; however, the transposed indexing (needed for the cyclic constraints) is complicating things. The gain of somehow applying this optimization anyway will be bigger with larger inputs and many unique values and some highly duplicated values.

    Here is the code with optimized version. _Additional speed gain can be had by removing the seeding of RNGs; these are only there to make it possible to regression test the output.

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


    Original response (partial)

    If I'm getting you right, you are trying to devise a shuffle that prevents duplicates from ending up consecutive in the output (with a minimum interleave of 2 elements).

    This is not solvable in the general case. Imagine an input of only identical elements :)

    Update: Troubled state of affairs

    Like I mention in the notes, I think I wasn't on the right track all the time. Either I should invoke graph theory (anyone?) or use a simple 'bruteforcey' algorithm instead, much a long Erick's suggestion.

    Anyway, so you can see what I've been up to, and also what the problems are (enable the randomized samples to quickly see the problems):

    #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(T[] input, bool doShuffle = false)
            where T: IComparable
        {
            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[] InternalAnalyzeInputRuns(T[] input)
        {
            var listOfRuns = new List>();
            Array.Sort(input);
            ValueRun current = new ValueRun { value = input[0], runlength = 1 };
    
            for (int i=1; i<=input.Length; i++)
            {
                if (i { 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(int length, ValueRun[] runs)
        {
            int groups = (length+GROUPWIDTH-1)/GROUPWIDTH;
            int remainder = length % GROUPWIDTH;
    
            // elementary checks
            if (length1
            // _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(int length, ValueRun[] runs)
            where T: IComparable
        {
            int remainder = length % GROUPWIDTH;
    
            int effectivewidth = Math.Min(GROUPWIDTH, length);
    
            var transpositions = new int[length];
            {
                int outit = 0;
                for (int groupmember=0; groupmemberi));
                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 : IComparable>
        {
            public T value;
            public int runlength;
            public int tag;         // set to random for shuffling
    
            public int CompareTo(ValueRun 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(IEnumerable output)
            where T:IComparable
        {
            var repl = output.Concat(output.Take(GROUPWIDTH)).ToArray();
    
            for (int i=1; i

提交回复
热议问题