I would like to know if this simulator works as it should because I don't think these are logical answers, but can't capture mistake either.
I have written a simulator for the following game(Given a deck of cards and 1 point) to find most optimal strategy(what is dealers highest card to continue game)
1. Dealer picks a card and shows it to you(Dealer can't pick Joker)
2. You decide whether to play or no
3.1. If you don't play you get current points and finish game
3.2. If you play you pick a Card
3.2.1. If your card is higher you get double points and go back to step 1
3.2.2. If your and dealer's cards are equal you go back to step 1
3.2.3. If dealer's card is higher you lose all points and finish
The simulation shows win coefficient for choosing each MAX card to play.It shows these numbers which is highly doubtful to me.I expected it to grow to 1.5 till 7 and then go back to 1.
(First-Win/number of simulations,Second-Max card dealer can get for you to continue game)
1 -1
1.0853817 0
1.1872532 1
1.3126581 2
1.4672619 3
1.6704736 4
1.9485809 5
2.2674231 6
2.9993735 7
3.5692085 8
4.3581477 9
4.0109722 10
2.3629856 11
0 12
Here's C# code:
using System;
namespace Codeforces
{
class Program
{
static int[] k = new int[54];
static Random rand = new Random();
static long Doubling(int i, long f)
{
int d = rand.Next(52);
if (k[d] > i) return f;
int ch = d;
while (ch == d) ch = rand.Next(54);
if (k[d] > k[ch]) return 0;
if (k[d] == k[ch]) return Doubling(i, f);
return Doubling(i, f * 2);
}
static void Main(string[] args)
{
for (int i = 0; i < 54; i++) k[i] = i / 4;
for (int i = -1; i < 13; i++)
{
long sum = 0;
for (int j = 0; j < 1e7; j++)
{
sum += Doubling(i, 1);
}
Console.WriteLine(sum / 1.0e7 + " " + i);
}
}
}
}
I'm not a C# programmer, but it looks like your basic approach is mostly correct. I would recommend using a loop rather than recursion.
Your problem description is vague regarding the value of jokers and whether dealer discards jokers when drawn or magically just doesn't draw them—you seem to have gone for the latter if I'm reading your code correctly.
It also appears that the way you implemented the recursion implicitly replaces cards in the deck after each play of the game rather than playing through the deck.
When I implemented this independently in another language, I got comparable results. Looks to me like your intuition is wrong.
This question already has answers here:
Card Shuffling in C#
(9 answers)
Closed 7 years ago.
I'm currently trying to write my first program so my coding isn't nearly as knowledgeable as others...
I am trying to shuffle a deck of cards without using the exact code given in the Fischer-Yates method or at least my version of it...
My plan is to to assign the cards to an enum and that will assign them a number from 0-51. Then I set the cards in a list and remove the card from the list that the RNG generates. After the card is removed from the list the counter deletes one from the possible random numbers and I set the number to a temporary variable that I will then pass to a new list that will be the shuffled deck.
My issue is in finding code that will explicitly convert my first list to an int. I think I know how to remove from the first list using .RemoveAt. Will I need to make another conversion or can i store the Random Number I find into the shuffledDeck?
enum Cards
{
AceSpade,
TwoSpade,
ThreeSpade,
FourSpade,
FiveSpade,
SixSpade,
SevenSpade,
EightSpade,
NineSpade,
TenSpade,
JackSpade,
QueenSpade,
KingSpade,
AceHeart,
TwoHeart,
ThreeHeart,
FourHeart,
FiveHeart,
SixHeart,
SevenHeart,
EightHeart,
NineHeart,
TenHeart,
JackHeart,
QueenHeart,
KingHeart,
AceDiamond,
TwoDiamond,
ThreeDiamond,
FourDiamond,
FiveDiamond,
SixDiamond,
SevenDiamond,
EightDiamond,
NineDiamond,
TenDiamond,
JackDiamond,
QueenDiamond,
KingDiamond,
AceClub,
TwoClub,
ThreeClub,
FourClub,
FiveClub,
SixClub,
SevenClub,
EightClub,
NineClub,
TenClub,
JackClub,
QueenClub,
KingClub
}
class DeckShuffle
{
static Random rndCard = new Random(DateTime.Now.Millisecond);
int currentCard;
int tempCard;
int totalCards = 51;
List<Cards> currentDeck = new List<Cards>();
List<int> shuffledDeck = new List<int>();
private void CardShuffler()
{
while (totalCards > -1)
{
tempCard = rndCard.Next(0, 51);
totalCards--;
}
}
}
So you have a list of cards, currentDeck.
I'd recommend a for loop to have an integer value like:
List<Cards> currentDeck = new List<Cards>();
List<Cards> shuffledDeck = new List<Cards>();
for(int i = 0; i < currentDeck.Count - 1; i++) {
//Grab a card, insert it into ShuffledDeck
int randoNum = rndCard.Next(0, 51-i); //This will grab a new number between 1 and however many cards are left
shuffledDeck.Add(currentDeck[randoNum]);
currentDeck.RemoveAt(i);
}
This way, every loop as i increases, the number of cards left in currentDeck decreases, and we grab one, and add it to the shuffled deck.
Edit: I'd probably recommend creating a Card class though (if you're that far into learning C#) - each "Card" object would have a numeric value (1-13, 11 for Jack, 12 for Queen, 13 for King, etc) and a suit.
That'll make most card-based games a lot easier.
This question already has answers here:
Random String Generator Returning Same String [duplicate]
(30 answers)
Closed 8 years ago.
First off I have my "Deck" class here. I've just put in the some basic methods, for testing purposes. It's my first go at creating a "card" program.
public class Deck
{
private int deckCounter = 0;
private List<Card> deckSize = new List<Card>();
private List<Card> shuffledDeck = new List<Card>();
private Random random = new Random();
public Deck()
{
}
public void Build()
{
for (int i = 1; i < 5; i++)
{
for (int k = 1; k < 14; k++)
{
deckSize.Add(new Card(k.ToString(), i));
}
}
}
public void Add(Card card)
{
deckSize.Add(card);
deckCounter++;
}
public Card RemoveCard()
{
Card cardToRemove = deckSize.First();
deckSize.RemoveAt(0);
return cardToRemove;
}
public void ShowContainedCards()
{
int cardCount = 0;
foreach (Card c in deckSize)
{
Console.WriteLine(c.ReturnCardInfo());
cardCount++;
}
Console.WriteLine(cardCount);
}
public void Shuffle()
{
while (deckSize.Count != 0)
{
int i = random.Next(deckSize.Count);
shuffledDeck.Add(deckSize[i]);
deckSize.RemoveAt(i);
}
deckSize = shuffledDeck;
}
public bool IsEmpty()
{
if (deckSize.Any())
{
return false;
}
else return true;
}
public List<Card> GetCardList()
{
return deckSize;
}
}
Basicly, what I do is, this:
Deck deck1 = new Deck();
Deck deck2 = new Deck();
deck1.Build();
deck1.Shuffle();
deck2.Build();
deck2.Shuffle();
After that, I get the exact same shuffle, for deck1 and deck2. Why is that? Also, I'm a newb at this, if you couldn't tell :)
Use the same Random class instance in both deck instances:
Random random = new Random();
Deck deck1 = new Deck(random);
Deck deck2 = new Deck(random);
So, in the constructor:
public class Deck
{
private int deckCounter = 0;
private List<Card> deckSize = new List<Card>();
private List<Card> shuffledDeck = new List<Card>();
private Random random;
public Deck(Random random)
{
this.random = random;
}
The current problem with your code, is that the two instances of Random that are created are seeded the same. This causes them to produce the same results. Using the same instance of Random means the second shuffle will build on top of the seeded results of the first shuffle.
From the docs:
The default seed value is derived from the system clock and has finite resolution. As a result, different Random objects that are created in close succession by a call to the default constructor will have identical default seed values and, therefore, will produce identical sets of random numbers.
The same docs also suggest a solution:
This problem can be avoided by using a single Random object to generate all random numbers.
Hence, one possible solution would be to make your random generator static, so all of your Deck instances share the same Random instance:
private static Random random = new Random();
That way, you would even avoid changing any part of the public interface of your Deck class.
Computers are inherently not random, so any random number generator will actually be using an algorithm to produce output that looks random. The thing with this is that there's always a starting point, and if you know where it' starting from, you can predict the outcome. Random number generators therefore have a "seed" which tells it where to start. The same seed will always give the same sequence of "random" numbers.
Both times, you're using new Random(), which uses the default seed. In some languages, you'd be advised to pass the current time as a seed, but C# does that for you. However, if you create two Random objects close together, they're likely to get the same time, which is what's happening here.
If you made your Random static, then all your random numbers would come from the same source, so the two decks would get successive random numbers, not the same ones in parallel.
Ok, the cards game I'm developing is pretty similar to Scopa if someone knows it.
The deck contains 40 cards divided into 4 different suits of 10 cards each (ace => value 1, two => value 2, three = ..., four, five, six, seven, knave, queen, king => value 10).
There are 2 players (actually an AI and a human player) and they have 4 cards in their hand.
There are 4 free cards to take on the table and players can take them only respecting the following rules:
1) Court cards (knave, queen and king) can only take identical court cards (for example, if I have a queen I can only take a queen from the table).
2) Numeric cards (from ace to seven) can take identical numeric cards or smaller numeric cards by sum (for example, if I have a seven I can take a seven or { an ace, a six } or {a three, a four } or { an ace, three two }).
Now the time has come to find which cards the AI can eventually take during it's turn:
private List<List<Card>> CalculateAITake()
{
List<Int32> handValues = new List<Int32>();
List<List<Card>> takes = new List<List<Card>>();
/* here i take every hand card value, in a unique way
* in order to avoid processing two or more times the
* same value
*/
foreach (Card card in m_AIHand)
{
Int32 cardValue = (Int32)card.Rank;
if (!handValues.Contains(cardValue))
handValues.Add(cardValue);
}
/* for each hand card value now, I calculate the
* combinations of cards I can take from table
*/
foreach (Int32 handValue in handValues)
{
// it's a court card, let's use a direct and faster approach
if (handValue >= 8)
{
foreach (Card card in m_CardsOnTable)
{
if ((Int32)card.Rank == handValue)
{
List<Card> take = new List<Card>();
take.Add(card);
takes.Add(take);
}
}
}
else
// it's a numeric card, let's use recursion
CalculateAITakeRecursion(takes, (new List<Card>(m_CardsOnTable)), 0, (new List<Card>()), handValue, 0);
}
return takes;
}
private void CalculateAITakeRecursion(List<List<Card>> takes, List<Card> cardsExcluded, Int32 cardsExcludedIndex, List<Card> cardsIncluded, Int32 sumWanted, Int32 sumPartial)
{
for (Int32 i = cardsExcludedIndex; i < cardsExcluded.Count; ++i)
{
Card cardExcluded = cardsExcluded[i];
Int32 sumCurrent = sumPartial + (Int32)cardExcluded.Rank;
/* the current sum is lesser than the hand card value
* so I keep on recursing
*/
if (sumCurrent < sumWanted)
{
List<Card> cardsExcludedCopy = new List<Card>(cardsExcluded);
cardsExcludedCopy.Remove(cardExcluded);
List<Card> cardsIncludedCopy = new List<Card>(cardsIncluded);
cardsIncludedCopy.Add(cardExcluded);
CalculateAITakeRecursion(takes, cardsExcludedCopy, ++cardsExcludedIndex, cardsIncludedCopy, sumWanted, sumCurrent);
}
/* the current sum is equal to the hand card value
* we have a new valid combination!
*/
else if (sumCurrent == sumWanted)
{
cardsIncluded.Add(cardExcluded);
Boolean newTakeIsUnique = true;
Int32 newTakeCount = cardsIncluded.Count;
/* problem: sometimes in my results i can find both
* { ace of hearts, two of spades }
* { two of spades, ace of hearts }
* not good, I don't want it to happens because there
* is still a lot of work to do on those results!
* Contains() is not enought to guarantee unique results
* so I have to do this!
*/
foreach (List<Card> take in takes)
{
if (take.Count == newTakeCount)
{
Int32 matchesCount = 0;
foreach (Card card in take)
{
if (cardsIncluded.Contains(card))
matchesCount++;
}
if (newTakeCount == matchesCount)
{
newTakeIsUnique = false;
break;
}
}
}
if (newTakeIsUnique)
takes.Add(cardsIncluded);
}
}
}
Do you think that this algorithm could be improved somehow?
I'm trying to shorten this code as much as I can so that it can be easy to debug and easy to maintain... also, if someone has a more elegant solution to avoid duplicate combinations I would really, really appreciate it (I don't want to get both { ace of hearts, two of spades } and { two of spades, ace of hearts }... only one of them).
Many, many thanks in advance!
Rather than considering each numeric card in your hand and looking for free cards that total it, I would consider each possible total of free cards and looking for a numeric card in your hand that matches it. You could use some sort of bitset to speed up the check for a matching card in your hand, and if you sort the free cards in ascending order, you could avoid adding a card matching one that you skipped, and you could stop adding cards if you exceeded the highest numeric card in your hand.
EDIT: pseudocode follows (sorry, I'm no good at naming variables):
call find_subset_sum(1, List<int>, 0)
// Passing the total because it's easy to calculate as we go
sub find_subset_sum(int value, List<int> play, total)
if total > max_hand_card
return // trying to pick up too many cards
if total in hand_set
call store_play(play)
if value > max_free_card
return // no more cards available to pick up
// try picking up higher value cards only
find_subset_sum(value + 1, play, total)
// now try picking up cards of this value
for each free card
if card value = value // only consider cards of this value
total += value
play.append(card)
find_subset_sum(value + 1, play, total)
// you could remove all the added cards here
// this would avoid having to copy the list each time
// you could then also move the first recursive call here too
It looks a bit odd but that's to ensure that if you only need one card of a particular value you don't unnecessarily consider picking up each available card of that value.
You can optimise this still further by sorting the array in ascending order.
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 write in C#, to create play lists in a random order.
Any ideas?
Thanks.
EDIT: I'm not interested in changing the original list, just pick up random indexes from a range in a random order so that every order has an equal chance.
Here's what I've wrriten so far:
public static IEnumerable<int> RandomIndexes(int count)
{
if (count > 0)
{
int[] indexes = new int[count];
int indexesCountMinus1 = count - 1;
for (int i = 0; i < count; i++)
{
indexes[i] = i;
}
Random random = new Random();
while (indexesCountMinus1 > 0)
{
int currIndex = random.Next(0, indexesCountMinus1 + 1);
yield return indexes[currIndex];
indexes[currIndex] = indexes[indexesCountMinus1];
indexesCountMinus1--;
}
yield return indexes[0];
}
}
It's working, but the only problem of this is that I need to allocate an array in the memory in the size of count. I'm looking for something that dose not require memory allocation.
Thanks.
This can actually be tricky if you're not careful (i.e., using a naïve shuffling algorithm). Take a look at the Fisher-Yates/Knuth shuffle algorithm for proper distribution of values.
Once you have the shuffling algorithm, the rest should be easy.
Here's more detail from Jeff Atwood.
Lastly, here's Jon Skeet's implementation and description.
EDIT
I don't believe that there's a solution that satisfies your two conflicting requirements (first, to be random with no repeats and second to not allocate any additional memory). I believe you may be prematurely optimizing your solution as the memory implications should be negligible, unless you're embedded. Or, perhaps I'm just not smart enough to come up with an answer.
With that, here's code that will create an array of evenly distributed random indexes using the Knuth-Fisher-Yates algorithm (with a slight modification). You can cache the resulting array, or perform any number of optimizations depending on the rest of your implementation.
private static int[] BuildShuffledIndexArray( int size ) {
int[] array = new int[size];
Random rand = new Random();
for ( int currentIndex = array.Length - 1; currentIndex > 0; currentIndex-- ) {
int nextIndex = rand.Next( currentIndex + 1 );
Swap( array, currentIndex, nextIndex );
}
return array;
}
private static void Swap( IList<int> array, int firstIndex, int secondIndex ) {
if ( array[firstIndex] == 0 ) {
array[firstIndex] = firstIndex;
}
if ( array[secondIndex] == 0 ) {
array[secondIndex] = secondIndex;
}
int temp = array[secondIndex];
array[secondIndex] = array[firstIndex];
array[firstIndex] = temp;
}
NOTE: You can use ushort instead of int to half the size in memory as long as you don't have more than 65,535 items in your playlist. You could always programmatically switch to int if the size exceeds ushort.MaxValue. If I, personally, added more than 65K items to a playlist, I wouldn't be shocked by increased memory utilization.
Remember, too, that this is a managed language. The VM will always reserve more memory than you are using to limit the number of times it needs to ask the OS for more RAM and to limit fragmentation.
EDIT
Okay, last try: we can look to tweak the performance/memory trade off: You could create your list of integers, then write it to disk. Then just keep a pointer to the offset in the file. Then every time you need a new number, you just have disk I/O to deal with. Perhaps you can find some balance here, and just read N-sized blocks of data into memory where N is some number you're comfortable with.
Seems like a lot of work for a shuffle algorithm, but if you're dead-set on conserving memory, then at least it's an option.
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<int> 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.
Personally, for a music player, I wouldn't generate a shuffled list, and then play that, then generate another shuffled list when that runs out, but do something more like:
IEnumerable<Song> GetSongOrder(List<Song> allSongs)
{
var playOrder = new List<Song>();
while (true)
{
// this step assigns an integer weight to each song,
// corresponding to how likely it is to be played next.
// in a better implementation, this would look at the total number of
// songs as well, and provide a smoother ramp up/down.
var weights = allSongs.Select(x => playOrder.LastIndexOf(x) > playOrder.Length - 10 ? 50 : 1);
int position = random.Next(weights.Sum());
foreach (int i in Enumerable.Range(allSongs.Length))
{
position -= weights[i];
if (position < 0)
{
var song = allSongs[i];
playOrder.Add(song);
yield return song;
break;
}
}
// trim playOrder to prevent infinite memory here as well.
if (playOrder.Length > allSongs.Length * 10)
playOrder = playOrder.Skip(allSongs.Length * 8).ToList();
}
}
This would make songs picked in order, as long as they haven't been recently played. This provides "smoother" transitions from the end of one shuffle to the next, because the first song of the next shuffle could be the same song as the last shuffle with 1/(total songs) probability, whereas this algorithm has a lower (and configurable) chance of hearing one of the last x songs again.
Unless you shuffle the original song list (which you said you don't want to do), you are going to have to allocate some additional memory to accomplish what you're after.
If you generate the random permutation of song indices beforehand (as you are doing), you obviously have to allocate some non-trivial amount of memory to store it, either encoded or as a list.
If the user doesn't need to be able to see the list, you could generate the random song order on the fly: After each song, pick another random song from the pool of unplayed songs. You still have to keep track of which songs have already been played, but you can use a bitfield for that. If you have 10000 songs, you just need 10000 bits (1250 bytes), each one representing whether the song has been played yet.
I don't know your exact limitations, but I have to wonder if the memory required to store a playlist is significant compared to the amount required for playing audio.
There are a number of methods of generating permutations without needing to store the state. See this question.
I think you should stick to your current solution (the one in your edit).
To do a re-order with no repetitions & not making your code behave unreliable, you have to track what you have already used / like by keeping unused indexes or indirectly by swapping from the original list.
I suggest to check it in the context of the working application i.e. if its of any significance vs. the memory used by other pieces of the system.
From a logical standpoint, it is possible. Given a list of n songs, there are n! permutations; if you assign each permutation a number from 1 to n! (or 0 to n!-1 :-D) and pick one of those numbers at random, you can then store the number of the permutation that you are currently using, along with the original list and the index of the current song within the permutation.
For example, if you have a list of songs {1, 2, 3}, your permutations are:
0: {1, 2, 3}
1: {1, 3, 2}
2: {2, 1, 3}
3: {2, 3, 1}
4: {3, 1, 2}
5: {3, 2, 1}
So the only data I need to track is the original list ({1, 2, 3}), the current song index (e.g. 1) and the index of the permutation (e.g. 3). Then, if I want to find the next song to play, I know it's third (2, but zero-based) song of permutation 3, e.g. Song 1.
However, this method relies on you having an efficient means of determining the ith song of the jth permutation, which until I've had chance to think (or someone with a stronger mathematical background than I can interject) is equivalent to "then a miracle happens". But the principle is there.
If memory was really a concern after a certain number of records and it's safe to say that if that memory boundary is reached, there's enough items in the list to not matter if there are some repeats, just as long as the same song was not repeated twice, I would use a combination method.
Case 1: If count < max memory constraint, generate the playlist ahead of time and use Knuth shuffle (see Jon Skeet's implementation, mentioned in other answers).
Case 2: If count >= max memory constraint, the song to be played will be determined at run time (I'd do it as soon as the song starts playing so the next song is already generated by the time the current song ends). Save the last [max memory constraint, or some token value] number of songs played, generate a random number (R) between 1 and song count, and if R = one of X last songs played, generate a new R until it is not in the list. Play that song.
Your max memory constraints will always be upheld, although performance can suffer in case 2 if you've played a lot of songs/get repeat random numbers frequently by chance.
you could use a trick we do in sql server to order sets in random like this with the use of guid. the values are always distributed equaly random.
private IEnumerable<int> RandomIndexes(int startIndexInclusive, int endIndexInclusive)
{
if (endIndexInclusive < startIndexInclusive)
throw new Exception("endIndex must be equal or higher than startIndex");
List<int> originalList = new List<int>(endIndexInclusive - startIndexInclusive);
for (int i = startIndexInclusive; i <= endIndexInclusive; i++)
originalList.Add(i);
return from i in originalList
orderby Guid.NewGuid()
select i;
}
You're going to have to allocate some memory, but it doesn't have to be a lot. You can reduce the memory footprint (the degree by which I'm unsure, as I don't know that much about the guts of C#) by using a bool array instead of int. Best case scenario this will only use (count / 8) bytes of memory, which isn't too bad (but I doubt C# actually represents bools as single bits).
public static IEnumerable<int> RandomIndexes(int count) {
Random rand = new Random();
bool[] used = new bool[count];
int i;
for (int counter = 0; counter < count; counter++) {
while (used[i = rand.Next(count)]); //i = some random unused value
used[i] = true;
yield return i;
}
}
Hope that helps!
As many others have said you should implement THEN optimize, and only optimize the parts that need it (which you check on with a profiler). I offer a (hopefully) elegant method of getting the list you need, which doesn't really care so much about performance:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
class Program
{
static void Main(string[] a)
{
Random random = new Random();
List<int> list1 = new List<int>(); //source list
List<int> list2 = new List<int>();
list2 = random.SequenceWhile((i) =>
{
if (list2.Contains(i))
{
return false;
}
list2.Add(i);
return true;
},
() => list2.Count == list1.Count,
list1.Count).ToList();
}
}
public static class RandomExtensions
{
public static IEnumerable<int> SequenceWhile(
this Random random,
Func<int, bool> shouldSkip,
Func<bool> continuationCondition,
int maxValue)
{
int current = random.Next(maxValue);
while (continuationCondition())
{
if (!shouldSkip(current))
{
yield return current;
}
current = random.Next(maxValue);
}
}
}
}
It is pretty much impossible to do it without allocating extra memory. If you're worried about the amount of extra memory allocated, you could always pick a random subset and shuffle between those. You'll get repeats before every song is played, but with a sufficiently large subset I'll warrant few people will notice.
const int MaxItemsToShuffle = 20;
public static IEnumerable<int> RandomIndexes(int count)
{
Random random = new Random();
int indexCount = Math.Min(count, MaxItemsToShuffle);
int[] indexes = new int[indexCount];
if (count > MaxItemsToShuffle)
{
int cur = 0, subsetCount = MaxItemsToShuffle;
for (int i = 0; i < count; i += 1)
{
if (random.NextDouble() <= ((float)subsetCount / (float)(count - i + 1)))
{
indexes[cur] = i;
cur += 1;
subsetCount -= 1;
}
}
}
else
{
for (int i = 0; i < count; i += 1)
{
indexes[i] = i;
}
}
for (int i = indexCount; i > 0; i -= 1)
{
int curIndex = random.Next(0, i);
yield return indexes[curIndex];
indexes[curIndex] = indexes[i - 1];
}
}