Random playlist algorithm

后端 未结 12 1542
北恋
北恋 2020-12-09 04:58

I need to create a list of numbers from a range (for example from x to y) in a random order so that every order has an equal chance.

I need this for a music player I

12条回答
  •  陌清茗
    陌清茗 (楼主)
    2020-12-09 05:47

    If you use a maximal linear feedback shift register, you will use O(1) of memory and roughly O(1) time. See here for a handy C implementation (two lines! woo-hoo!) and tables of feedback terms to use.

    And here is a solution:

    public class MaximalLFSR
    {
        private int GetFeedbackSize(uint v)
        {
            uint r = 0;
    
            while ((v >>= 1) != 0)
            {
              r++;
            }
            if (r < 4)
                r = 4;
            return (int)r;
        }
    
        static uint[] _feedback = new uint[] {
            0x9, 0x17, 0x30, 0x44, 0x8e,
            0x108, 0x20d, 0x402, 0x829, 0x1013, 0x203d, 0x4001, 0x801f,
            0x1002a, 0x2018b, 0x400e3, 0x801e1, 0x10011e, 0x2002cc, 0x400079, 0x80035e,
            0x1000160, 0x20001e4, 0x4000203, 0x8000100, 0x10000235, 0x2000027d, 0x4000016f, 0x80000478
        };
    
        private uint GetFeedbackTerm(int bits)
        {
            if (bits < 4 || bits >= 28)
                throw new ArgumentOutOfRangeException("bits");
            return _feedback[bits];
        }
    
        public IEnumerable RandomIndexes(int count)
        {
            if (count < 0)
                throw new ArgumentOutOfRangeException("count");
    
            int bitsForFeedback = GetFeedbackSize((uint)count);
    
            Random r = new Random();
            uint i = (uint)(r.Next(1, count - 1));
    
            uint feedback = GetFeedbackTerm(bitsForFeedback);
            int valuesReturned = 0;
            while (valuesReturned < count)
            {
                if ((i & 1) != 0)
                {
                    i = (i >> 1) ^ feedback;
                }
                else {
                    i = (i >> 1);
                }
                if (i <= count)
                {
                    valuesReturned++;
                    yield return (int)(i-1);
                }
            }
        }
    }
    

    Now, I selected the feedback terms (badly) at random from the link above. You could also implement a version that had multiple maximal terms and you select one of those at random, but you know what? This is pretty dang good for what you want.

    Here is test code:

        static void Main(string[] args)
        {
            while (true)
            {
                Console.Write("Enter a count: ");
                string s = Console.ReadLine();
                int count;
                if (Int32.TryParse(s, out count))
                {
                    MaximalLFSR lfsr = new MaximalLFSR();
                    foreach (int i in lfsr.RandomIndexes(count))
                    {
                        Console.Write(i + ", ");
                    }
                }
                Console.WriteLine("Done.");
            }
        }
    

    Be aware that maximal LFSR's never generate 0. I've hacked around this by returning the i term - 1. This works well enough. Also, since you want to guarantee uniqueness, I ignore anything out of range - the LFSR only generates sequences up to powers of two, so in high ranges, it will generate wost case 2x-1 too many values. These will get skipped - that will still be faster than FYK.

提交回复
热议问题