2

I'm new to C# and I'm making an application with arrays. I have an array with the numbers shown below:

int[] array2 = new int[] { 1, 3, 5, 7, 9 };

What I need to do is change the order of these numbers in the array without repetitions, because when I use Random Function, this shows me repeated numbers.

I saw this method, but do not know how to apply it with numbers: http://www.dotnetperls.com/shuffle

Cœur
  • 37,241
  • 25
  • 195
  • 267
user1974277
  • 77
  • 1
  • 3
  • 11
  • what you're trying to achieve here, your example array doesn't have any repetitions? – Nasmi Sabeer Jan 13 '13 at 15:20
  • Isn't `var array2 = new int[] { 1, 3, 5, 7, 9 }.OrderBy(_ => rnd.Next())` enough? – I4V Jan 13 '13 at 15:29
  • 1
    Do you require a guarantee that *every possible ordering is equally likely*? And do you also require a guarantee that *it is not possible to deduce future shuffles from past shuffles*? (The latter requirement is necessary for games of chance; if you can deduce future decks from current decks then online poker gets a lot easier.) If the answer to either question is yes then none of the answers posted here are correct. If you don't care about small bias or ability of attackers to predict your internal state then the answers posted here are reasonable. – Eric Lippert Jan 13 '13 at 16:56

4 Answers4

5

You can use the following LINQ chain:

int[] array2 = new int[] { 1, 3, 5, 7, 9 };
var random = new Random();
var total = (int)array2.
    OrderBy(digit => random.Next()).
    Select((digit, index) => digit*Math.Pow(10, index)).
    Sum();

First, it orders the elements randomly, then it selects each element multiplied by 10 raised to the power of its index, then sums them together and casts the result to an integer. Also, please note that I didn't provide an useful seed for your Random instance. You might want to do that, to produce pseudo-random results.

You might also want to use a method for exponentiation described here, to avoid having to cast to an integer.

EDIT: As Rhumborl pointed out, you may just need the shuffled array. In that case:

var shuffledArray = array2.OrderBy(n => random.Next()).
   ToArray();

Should work for you.

Community
  • 1
  • 1
e_ne
  • 8,340
  • 32
  • 43
  • I think the OP wants to still have an array at the end of it, in which case just `var random = new Random(); array2 = array2.OrderBy(n => random.Next()).ToArray();` would do. – Rhumborl Jan 13 '13 at 15:19
  • @Rhumborl I've based my answer on the question's title, but judging from its content what you say may be true. I'll edit my post. – e_ne Jan 13 '13 at 15:20
  • Still a great post tho, keep the detail in – Rhumborl Jan 13 '13 at 15:21
  • 4
    Note that this algorithm, though correct as shown here, has two problems. First, if the list is extremely long then this is an O(n lg n) operation, not an O(n) operation as the Knuth shuffle. Second, this depends on the fact that the orderby implementation evaluates the sequence of ordering elements exactly once. An implementation that re-evaluates the ordering clause would obviously end up with two completely different random results, and that could cause problems with the sorting algorithm. My point is, be extremely careful when using randomness in ordering to shuffle. – Eric Lippert Jan 13 '13 at 16:42
  • 1
    The great benefit of this technique, however, is that it does not destroy the original list, as the Knuth shuffle does. – Eric Lippert Jan 13 '13 at 16:42
  • @EricLippert Thank you for your comment. I didn't know about the problem caused by the implementation of `OrderBy`. – e_ne Jan 13 '13 at 16:44
  • Just to clarify: your solution is correct, and the implementation of OrderBy provided by the framework is good. My point is that people who attempt to use randomness to turn a sort into a shuffle often get it wrong, because they end up in a situation where the random function is giving inconsistent instructions to the sort algorithm. Your code is correct, but this code `myList.Sort((x, y) => random.Next(-1, 2));` is deeply, deeply wrong. Do you see why? – Eric Lippert Jan 13 '13 at 16:52
  • @EricLippert I do see that creating a new instance of `Random` with the same (unspecified) seed will produce the same results for each evaluation, rendering the sorting non-random. However, something made me wonder just now, doesn't that happen to `OrderBy` as well, if a new instance is created inside of the lambda, rather than passed through a closure? If I modify my method to fit your example, it returns the original, unshuffled array (while still using `OrderBy`). – e_ne Jan 13 '13 at 17:03
  • 3
    @Eve: Indeed, if you say `OrderBy(digit => (new Random()).Next())` then you get the same seeded-to-the-current-time "random" numbers out if the sort happens to take less than the tick granularity, and the sort becomes a no-op. The problem with `myList.Sort((x, y) => random.Next(-1, 2));` is that the comparison can tell you that item A is bigger than item B, item B is bigger than item C, and item C is bigger than item A, and the sort algorithm is allowed to crash or loop forever if you give it such an ill-behaved comparison relation. – Eric Lippert Jan 13 '13 at 17:08
  • @EricLippert I think I understand better now. I understood quite the contrary before your last message -- I ignored the fact that `Sort` will not return until the collection's elements match the given predicate. Thank you for your useful explanation. – e_ne Jan 13 '13 at 17:12
1

if you work in C# you better use C# structs.

You can use this generic function

using System;
using System.Collections.Generic;

public static class ListExtensions
{
    public static void Shuffle<T>(this IList<T> list)
    {
        var randomNumber = new Random(DateTime.Now.Millisecond);
        var n = list.Count;
        while (n > 1)
        {
            n--;
            var k = randomNumber.Next(n + 1);
            var value = list[k];
            list[k] = list[n];
            list[n] = value;
        }
    }
}

and then your code should look like:

List<int> list2 = new List<int>(){1, 3, 5, 7, 9};
Shuffle(list2);
Roee Gavirel
  • 18,955
  • 12
  • 67
  • 94
  • Good implementation of Knuth shuffle. I note that arrays of int already are convertible to `IList`, so there's no need to use a `List` if you don't want to. – Eric Lippert Jan 13 '13 at 16:49
  • 1
    Shouldn't that be `list2.Shuffle()`? As you are creating an extension method. – comecme Jan 13 '13 at 19:37
0

So as you said, you already have an array with numbers to work with. So I am not going to show you how to make an array filled with unique numbers.

This is how you shuffle your array.

  1. Figure out how you can generate random numbers between 0 and the length of your array.
  2. Write a loop that goes from the length_of_the_array-1 to 0. (Use this index as idx1)

Inside the loop do the following:

a. Generate a random number between 0 and idx1 (inclusive) using your method from Step 1. (Let the random number be idx2.)
b. Swap the elements in your array at idx1 and idx2.

A simple swap can be done by doing something like this:

int tmp = array[idx1];
array[idx1] = array[idx2];
array[idx2] = tmp;

The loop ends and you are left with a shuffled array.

Sanchit
  • 2,240
  • 4
  • 23
  • 34
  • The shuffle algorithm you are describing introduces bias; it is the most common way to describe an incorrect Knuth Fischer Yates shuffle. Jeff has a good analysis describing why this algorithm is wrong here: http://www.codinghorror.com/blog/2007/12/shuffling.html – Eric Lippert Jan 13 '13 at 16:36
  • Roee Gaveril's answer gives a correct implementation of the shuffle algorithm. – Eric Lippert Jan 13 '13 at 16:38
  • I meant to use one random number not two. Then its identical to the one by Roee. I'm going to ignore the whole random number generated can be easily figured out part from the Jeff article but these are all pseudo random number generators that programs use. I'll edit my answer as well for correctness. – Sanchit Jan 13 '13 at 16:44
0

I am not quite sure what you mean by changing the order without repetition. If you just want to generate a random number that never repeats you could do something like this

private Random rand = new Random();
private List<int> used = new List<int>;
protected int randomNonrepeating() {
   int i = rand.next();
   while(used.contains(i))
       i = rand.next();
   used.add(i);
   return i;
}

My guess is, that is not quite what you are looking for though. If you just want to modify the algorithm at the supplied link to work with an array of integers as opposed to strings. You just need to change the types. Something like this

using System;

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

static class RandomStringArrayTool { static Random _random = new Random();

public static string[] RandomizeStrings(int[] arr)
{
List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>();
// Add all strings from array
// Add new random int each time
foreach (var s in arr)
{
    list.Add(new KeyValuePair<int, int>(_random.Next(), s));
}
// Sort the list by the random number
var sorted = from item in list
         orderby item.Key
         select item;
// Allocate new string array
int[] result = new string[arr.Length];
// Copy values to array
int index = 0;
foreach (KeyValuePair<int, int> pair in sorted)
{
    result[index] = pair.Value;
    index++;
}
// Return copied array
return result;
}

}

Hope this helps.

Chris Wininger
  • 651
  • 8
  • 13
  • Your first algorithm is not efficient if the list is big. It is instructive to work out how long your algorithm will run if the list has a thousand items in it. – Eric Lippert Jan 13 '13 at 16:43
  • Your second algorithm is good but you've written it to be about fifteen times longer than it needs to be. `return (from item in list orderby _random.Next() select item).ToArray();` will do nicely. – Eric Lippert Jan 13 '13 at 16:46