SomeAnonymousPerson
SomeAnonymousPerson

Reputation: 3281

Get random values from an Array

I'm trying to change the elements of an array randomly, by changing the indexes. Thats ok. Now the problem is that as the random is always random, I can get two results that are the same.

for example:

Monday:

song 1

song 2

song 3

Tuesday:

song 2

song 1

song 3

Wednesday:

song 1

song 2

song 3

And so on...

And the list from

Monday

and

Wednesday

in this case is the same. I need to control that, but as you can see on the code, once I get the list from one day, I just print it. I thought about putting it on an array or Tuples and check if that tuple exists, but I think its too complicated. I thought that maybe I can make my own random function. But still, I'm not sure about that solution either. Any ideas of how can I solve this situation? Thanks!

Here is the code I have so far:

    static string[] songs = new string[] { "song1", "song2", "song3" };
    static string[] days = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
    private static Random random = new Random();

/* Random number between lower and higher, inclusive */
    public static int rand(int lower, int higher)
    {
        int r = lower + (int)(random.Next(0, 2) * (higher - lower));

        return r;
    }

    /* pick M elements from original array. Clone original array so that
   7 * we don’t destroy the input. */
    public static string[] pickMRandomly()
    {
        string[] subset = new string[songs.Length];
        string[] array = (string[])songs.Clone();
        for (int j = 0; j < songs.Length; j++)
        {
            int index = rand(j, array.Length - 1);
            subset[j] = array[index];
            array[index] = array[j]; // array[j] is now “dead”
        }
        return subset;
    }

    public static void playListCreation()
    {
        for (int j = 0; j < days.Length; j++)
        {
            var result =pickMRandomly();
            System.Console.WriteLine(days[j]);
            foreach (var i in result)
            {
                System.Console.WriteLine(i + " ");
            }
            System.Console.WriteLine("/n");
        }
    }
}

Upvotes: 0

Views: 192

Answers (3)

sujith karivelil
sujith karivelil

Reputation: 29036

Consider that you have 3 Songs in hand and want to assign unique Combination for each day of a week( 7days). It is not possible since you can made only six unique combinations with these three. So definitely there may be one repeating sequence. You will get 24 unique song sequence if you add another song(let it be "song4") to this collection. I have included a snippet that help you to get these combination of unique sequence of songs.

string[] songs = new string[] { "song1", "song2", "song3", "song4" };       
int numberOfSongs = songs.Count();
var collection = songs.Select(x => x.ToString()); ;
for (int i = 1; i < numberOfSongs; i++)
{
    collection = collection.SelectMany(x => songs, (x, y) => x + "," + y);                            
}
List<string> SongCollections = new List<string>();
SongCollections.AddRange(collection.Where(x => x.Split(',')
                                   .Distinct()
                                   .Count() == numberOfSongs)
                                   .ToList());

Now the SongCollections will contains 24 unique sequence of 4 songs. (if you choose 3 songs then you will get 6 unique sequences). You can apply Random selection of sequence from these collection and assign to days as you wish.

Now Let me use a Dictionary<string, int> dayCollectionMap to map a collection to a day(Note : Here i use a collection of 4 songs since 3 is not enough for 7 days). Consider the snippet below:

Dictionary<string, int> dayCollectionMap = new Dictionary<string, int>();
string[] days = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
Random randomCollection = new Random();
foreach (string day in days)
{
    int currentRandom = randomCollection.Next(0, SongCollections.Count());
    if (!dayCollectionMap.Any(x => x.Value == currentRandom))
    {
        dayCollectionMap.Add(day, currentRandom);
    }
    else
    {
        // The collection is already taken/ Add another random sequence
        while (true)
        {
            currentRandom = randomCollection.Next(0, SongCollections.Count());
            if (!dayCollectionMap.Any(x => x.Value == currentRandom))
            {
                dayCollectionMap.Add(day, currentRandom);
                break;
            }

        }
    }

}

So that you can select the song collection for Wednesday by using the code

  var songCollectionForWed = SongCollections[dayCollectionMap["Wednesday"]];

Upvotes: 1

Dan Roberts
Dan Roberts

Reputation: 2339

If I understand you correctly, you don't just want a random arrangement of songs for each day, you want a unique (and random) arrangement of songs each day.

The only way that I can think of to guarantee this is to work out all of the possible combinations of songs and to randomly sort them - then to pick out a different combination from the list for each day.

using System;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflowAnswer
{
    class Program
    {
        static string[] songs = new string[] { "song1", "song2", "song3" };
        static string[] days = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };

        static void Main(string[] args)
        {
            var rnd = new Random();
            var allCombinationsInRandomOrder = GetCombinations(songs, songs.Length)
                .Select(combination => new { Combination = combination, Order = rnd.Next() })
                .OrderBy(entry => entry.Order)
                .Select(entry => entry.Combination);

            var dayIndex = 0;
            foreach (var combination in allCombinationsInRandomOrder)
            {
                var day = days[dayIndex];
                Console.WriteLine(day);
                Console.WriteLine(string.Join(", ", combination));

                dayIndex++;
                if (dayIndex >= days.Length)
                    break;
            }
            Console.ReadLine();
        }

        private static IEnumerable<IEnumerable<string>> GetCombinations(IEnumerable<string> songs, int numberOfSongsInGeneratedLists)
        {
            if (songs == null)
                throw new ArgumentNullException(nameof(songs));
            if (numberOfSongsInGeneratedLists <= 0)
                throw new ArgumentOutOfRangeException(nameof(numberOfSongsInGeneratedLists));
            if (numberOfSongsInGeneratedLists > songs.Count())
                throw new ArgumentOutOfRangeException("can't ask for more songs in the returned combinations that are provided", nameof(numberOfSongsInGeneratedLists));

            if (numberOfSongsInGeneratedLists == 1)
            {
                foreach (var song in songs)
                    yield return new[] { song };
                yield break;
            }

            foreach (var combinationWithOneSongTooFew in GetCombinations(songs, numberOfSongsInGeneratedLists - 1))
            {
                foreach (var song in songs.Where(song => !combinationWithOneSongTooFew.Contains(song)))
                    yield return combinationWithOneSongTooFew.Concat(new[] { song });
            }
        }
    }
}

Upvotes: 2

keyboardP
keyboardP

Reputation: 69372

From what I understand, you want to create a random playlist and if this playlist has been created before, you want to generate another (until it's unique). One way you could do this is to add a hash of some sort to a HashSet and see if it's previously been generated. For example,

bool HashSet<int> playlistHashes = new HashSet<int>();
private bool CheckIfUnique(string[] playlist)
{
    //HashSet returns false if the hash already exists 
    //(i.e. playlist already likely to have been created)
    return playlistHashes.Add(string.Join("",playlist).GetHashCode());
}

Then once you've generated your playlist, you can call that method and see if it returns false. If it returns false, that playlist order has been created before and so you can generate again. Using the technique above means that song1, song2, song3 is different from song3, song2, song1 so the order is important.

As mentioned in my comment on the question, if you're testing with 3 songs, there are only 6 different permutations and 7 days of the week so you're going to get a duplicate.

Side note, GetHashCode can throw 'false-positives' but it's up to you to determine how likely it is, and if the impact is actually of any significance since a new playlist is generated anyway. Good thread for more information here. There are numerous hashing techniques possible with lower collision chances if GetHashCode would not suffice here.

Upvotes: 1

Related Questions