6

I'm sure this has been asked a million times, but when I searched all the examples didn't quite fit, so I thought I should ask it anyway.

I have two arrays which will always contain 6 items each. For example:

string[] Colors=
    new string[] { "red", "orange", "yellow", "green", "blue", "purple" };

string[] Foods=
    new string[] { "fruit", "grain", "dairy", "meat", "sweet", "vegetable" };

Between these two arrays, there are 36 possible combinations(e.g. "red fruit", "red grain").

Now I need to further group these into sets of six unique values.

For example:

meal[0]=
    new Pair[] { 
        new Pair { One="red", Two="fruit" }, 
        new Pair { One="orange", Two="grain" }, 
        new Pair { One="yellow", Two="dairy" }, 
        new Pair { One="green", Two="meat" }, 
        new Pair { One="blue", Two="sweet" }, 
        new Pair { One="purple", Two="vegetable" } 
    };

where meal is

Pair[][] meal;

No element can be repeated in my list of "meals". So there is only ever a single "Red" item, and a single "meat" item, etc.

I can easily create the pairs based on the first two arrays, but I am drawing a blank on how best to then group them into unique combinations.

Ken Kin
  • 4,503
  • 3
  • 38
  • 76
Peter Lange
  • 2,786
  • 2
  • 26
  • 40
  • 1
    Does the order matter, or only which item is paired with which? Assuming the latter, you can just fix Colors and each permutation of Foods corresponds to a set of Color/Food matches. – grossvogel Feb 27 '13 at 00:31
  • No, the order doesn't really matter, so long as the overall meal combinations are unique and every possible combination can be arrived at. – Peter Lange Feb 27 '13 at 00:45
  • 2
    Seems to me that you'll get a lot more than 36 combinations. You can pair `red` with 6 foods, then `orange` with the 5 that are left, then `yellow` with the remaining 4... and so on. You'll get 6 * 5 * 4 * 3 * 2 * 1 combinations (6!), which equals 720. – Daniel A.A. Pelsmaeker Feb 27 '13 at 01:03
  • And can be there 2 meals (set of 6 pairs) with `One = "red", Two = "fruit"` ? In other words do you really need to group 36 combinations into 6 groups OR you need all unique sets of 6 elements where first and second component do not repeat (6! groups)? – Alexei Levenkov Feb 27 '13 at 01:03
  • There are 720 possible combinations that meet your requirements. Do you want to enumerate *all* of them, or *choose one at random*, or what? – Eric Lippert Feb 27 '13 at 01:05
  • 1
    @Virtlink: What he's saying is that there are 36 possible combinations for *a single pair*. You are correct that there are 720 possible combinations for *six pairs where there is no repetition in either the first or second item* – Eric Lippert Feb 27 '13 at 01:06
  • If you want all possible combinations, isn't this basically calculating the permutations of 123456 after fixing an order on the colors and 1-6 representing which class of food each color is paired up with? Calculating permutations should be relatively straightforward. – G. Bach Feb 27 '13 at 01:27
  • @G.Bach: Correct. Though calculating all the permutations is a bit tricky; there's not an out-of-the-box solution that ships with the base class libraries unfortunately. (Though there might be one in Microsoft Solver Foundation now that I think about it. I should investigate that.) – Eric Lippert Feb 27 '13 at 01:28
  • 1
    @EricLippert It seems there are a reasonable number of implementations that calculate the permutations of any array, some in C, some in Java; those shouldn't be hard to adapt to C#. I don't think something like this requires external libraries. – G. Bach Feb 27 '13 at 01:30

3 Answers3

6

OK, you want a sequence containing all 720 possible sequences. This is a bit trickier but it can be done.

The basic idea is the same as in my previous answer. In that answer we:

  • generated a permutation at random
  • zipped the permuted second array with the unpermuted first array
  • produced an array from the query

Now we'll do the same thing except instead of producing a permutation at random, we'll produce all the permutations.

Start by getting this library:

http://www.codeproject.com/Articles/26050/Permutations-Combinations-and-Variations-using-C-G

OK, we need to make all the permutations of six items:

Permutations<string> permutations = new Permutations<string>(foods);

What do we want to do with each permutation? We already know that. We want to first zip it with the colors array, turning it into a sequence of pairs, which we then turn into an array. Instead, let's turn it into a List<Pair> because, well, trust me, it will be easier.

IEnumerable<List<Pair>> query = 
    from permutation in permutations
    select colors.Zip(permutation, (color, food)=>new Pair(color, food)).ToList();

And now we can turn that query into a list of results;

List<List<Pair>> results = query.ToList();

And we're done. We have a list with 720 items in it. Each item is a list with 6 pairs in it.

The heavy lifting is done by the library code, obviously; the query laid on top of it is straightforward.

('ve been meaning to write a blog article for some time on ways to generate permutations in LINQ; I might use this as an example!)

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
4

There are 720 possible combinations that meet your needs. It is not clear from your question whether you want to enumerate all 720 or choose one at random or what. I'm going to assume the latter.

UPDATE: Based on comments, this assumption was incorrect. I'll start a new answer.


First, produce a permutation of the second array. You can do it in-place with the Fischer-Yates-Knuth shuffle; there are many examples of how to do so on StackOverflow. Alternatively, you could produce a permutation with LINQ by sorting with a random key.

The former technique is fast even if the number of items is large, but mutates an existing array. The second technique is slower, particularly if the number of items is extremely large, which it isn't.

The most common mistake people make with the second technique is sorting on a guid. Guids are guaranteed to be unique, not guaranteed to be random.

Anyway, produce a query which, when executed, permutes the second array:

Random random = new Random();
IEnumerable<string> shuffled = from food in foods 
                               orderby random.NextDouble() 
                               select food;

A few other caveats:

  • Remember, the result of a query expression is a query, not a set of results. The permutation doesn't happen until you actually turn the thing into an array at the other end.
  • if you make two instances of Random within the same millisecond, you get the same sequence out of them both.
  • Random is pseudo-random, not truly random.
  • Random is not threadsafe.

Now you can zip-join your permuted sequence to the first array:

IEnumerable<Pair> results = colors.Zip(shuffled, (color, food)=>new Pair(color, food));

Again, this is still a query representing the action of zipping the two sequences together. Nothing has happened yet except building some queries.

Finally, turn it into an array. This actually executes the queries.

Pair[] finalResults = results.ToArray();

Easy peasy.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    I think he wants _all_ permutations: an array `meal` with 36 elements, each element consisting of 6 pairs. Shuffling and random picking won't work for this. – Daniel A.A. Pelsmaeker Feb 27 '13 at 00:54
  • That would be correct. I need to arrive at every combination of possible 6 pair values. My math skills suck (which is why I have to get help, lol, but I believe we should see something like 600 or so possible meal combinations. – Peter Lange Feb 27 '13 at 01:10
  • @Goblyn27: There are 720 possible combinations that meet your requirements. Enumerating all of them is a bit tricky. I'll update my answer. – Eric Lippert Feb 27 '13 at 01:12
  • Opps, I see it was edited since I last updated. Yes, sorry for the confusion, but I am trying to arrive at the 720 combinations (see I told you my math sucked; I thought it would be 600) – Peter Lange Feb 27 '13 at 01:12
  • 1
    @Goblyn27: There are six possible red foods, right? Once you've chosen one of the foods to be red, it is not available to be orange, so there are only five possible orange foods. That's 6 x 5 which is 30. That leaves only four foods to be yellow, so that's 6 x 5 x 4 which is 120... keep going and we get 6 x 5 x 4 x 3 x 2 x 1 which is 720. – Eric Lippert Feb 27 '13 at 01:24
1

Upon request, I will be specific about how I view the problem in regards to sorting. I know that since C# is a higher level language there are tons of quick and easy libraries and objects that can be used to reduce this to minimal code. This answer is actually attempting the solve the question by implementing sorting logic.

When initially reading this question I was reminded of sorting a deck of cards. The two arrays are very similar to an array for suit and an array for face value. Since one way to solve a shuffle is to randomize the arrays and then pick a card combined of both, you could apply the same logic here.

Sorting as a possible solution

The Fisher-Yates sorting algorithm essentially loops through all the indices of the array swapping the current index with a random index. This creates a fairly efficient sorting method. So then how does this apply to the problem at hand? One possible implementation could be...

    static Random rdm = new Random();
    public string[] Shuffle(string[] c)
    {
        var random = rdm;
        for (int i = c.Length; i > 1; i--)
        {
            int iRdm = rdm.Next(i);
            string cTemp = c[iRdm];
            c[iRdm] = c[i - 1];
            c[i - 1] = cTemp;
        }
        return c;
    }

Source: Fisher-Yates Shuffle

The code above randomizes the positions of values within the string array. If you passed the Colors and Food arrays into this function, you would get unique pairings for your Pairs by referencing a specific index of both.

Since the array is shuffled, the pairing of the two arrays at index 0,1,2,etc are unique. The problem however asks for Pairs to be created. A Pair class should then be created that takes in a value at a specific index for both Colors and Foods. ie...Colors[3] and Foods[3]

 public class Pair
{
     public string One;
     public string Two;
     public Pair(string m1, string m2)
     {
         One = m1;
         Two = m2;
     }
}

Since we have sorted arrays and a class to contain the unique parings, we simply create the meal array and populate it with Pairs.

If we wanted to create a new pair we would have...

Pair temp = new Pair(Colors[0],Foods[0]);

With this information we can finally populate the meal array.

    Pair[] meal = new Pair[Colors.Length - 1];
    for (int i = 0; i < Colors.Length - 1; i++)
    {
        meal[i] = new Pair(Colors[i],Foods[i]);
    } 

This section of code creates the meal array and defines its number of indices by the length of Colors. The code then loops through the total number of Color values while creating new pair combos and dropping them in meal. This method assumes the length of the arrays are identical, a check could easily be made for the smallest array.

Full Code

private void Form1_Load(object sender, EventArgs e)
        {
            string[] Colors = new string[] { "red", "orange", "yellow", "green", "blue", "purple" };
            string[] Foods = new string[] { "fruit", "grain", "dairy", "meat", "sweet", "vegetable" };
            Colors = Shuffle(Colors);
            Foods = Shuffle(Foods);
            Pair[] meal = new Pair[Colors.Length - 1];
            for (int i = 0; i < Colors.Length - 1; i++)
            {
                meal[i] = new Pair(Colors[i],Foods[i]);
            }
        }
        static Random rdm = new Random();
        public string[] Shuffle(string[] c)
        {
            var random = rdm;
            for (int i = c.Length; i > 1; i--)
            {
                int iRdm = rdm.Next(i);
                string cTemp = c[iRdm];
                c[iRdm] = c[i - 1];
                c[i - 1] = cTemp;
            }
            return c;
        }
    }
    public class Pair
    {
         public string One;
         public string Two;
         public Pair(string m1, string m2)
         {
             One = m1;
             Two = m2;
         }
     }

-Original Post-

You can simply shuffle the array. This will allow for the same method to populate meal, but with different results. There is a post on Fisher-Yates shuffle Here

Community
  • 1
  • 1
MonkeyDoug
  • 453
  • 3
  • 17