5

I want to take a List of strings with around 12 objects and split it into two List of strings but completely randomise it.

Example of List:

List 1:

EXAMPLE 1
EXAMPLE 2
EXAMPLE 3
EXAMPLE 4
EXAMPLE 5
EXAMPLE 6
EXAMPLE 7
EXAMPLE 8

Apply some logic here...

Result gives me two lists: List 1:

EXAMPLE 5
EXAMPLE 6
EXAMPLE 1
EXAMPLE 8

List 2:

EXAMPLE 2
EXAMPLE 3
EXAMPLE 4
EXAMPLE 7

I'm a newbie to C# MVC, so I've found some answers on Stack but none have been able to answer my question.

Edit: What I've tried so far gives me one random member of the team. I want to now expand on this and create the two lists as mentioned above.

  [HttpPost]
    public ActionResult Result(Models.TeamGenerator model)
    {
        var FormNames = model.Names;

        string[] lines = FormNames.Split(
            new[] { Environment.NewLine },
            StringSplitOptions.None);

        List<string> listOfLines = new List<string>();

        foreach (var i in lines)
        {
            listOfLines.Add(i);
        }
        string[] result1 = listOfLines.Where(item => item != string.Empty).ToArray();

        Random genRandoms = new Random();
        int aRandomTeam = genRandoms.Next(listOfLines.Count);

        string currName = listOfLines[aRandomTeam];

        return View();
    }

*EDIT** Thanks for the solution! I've now got my application working and managed to publish it to the web, https://www.teamgenerator.online.

Michael
  • 117
  • 5
  • 3
    What have tried, please supply us with some code of what you have tried. – M.B. Sep 26 '19 at 13:10
  • 2
    Along with the problem you should **include the code you have up until this point** (*make some effort to answer your problem/question as [so] is not a code writing service*). After [doing more research](http://meta.stackoverflow.com/questions/261592) if you have a problem you can **post what you've tried** with a **clear explanation of what isn't working** and providing a **[mcve]**. I suggest reading [*How do I ask a Good Question*](/help/how-to-ask) and [*Writing the Perfect Question*](http://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/). Also, be sure to take the [tour]. – Igor Sep 26 '19 at 13:12
  • 1
    I've added my code. Sorry! I'm new to this. Thank you for the pointers. – Michael Sep 26 '19 at 13:14
  • 2
    Do the two lists need to have the same number of entries? If so, what happens in the case where you have an odd number of elements? Randomly decide which list gets the extra element? – Matthew Watson Sep 26 '19 at 13:15
  • 1
    I'll add some sort of validation or code to make sure I only get an even number of entries. I just want to get the lists split first. Taking it a step at a time. – Michael Sep 26 '19 at 13:16

5 Answers5

3
  1. Create an array of bools the same size as the list of strings.
  2. Fill one half of the array of bools with true, the other half with false.
  3. Shuffle the array of bools.
  4. Iterate through the list of strings. For each element, if the corresponding element in the bool array is true, include that element in the first list; otherwise include it in the second list.

This approach keeps the items in the same order as they were in the original array, if that is important. (If not, just shuffle the entire array of strings and take the first half and the second half).

Sample code:

using System;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        public static void Main()
        {
            var strings = Enumerable.Range(1, 20).Select(i => i.ToString()).ToList();

            var rng = new Random();
            int n = strings.Count;

            var include = // Create array of bools where half the elements are true and half are false
                Enumerable.Repeat(true, n/2) // First half is true
                .Concat(Enumerable.Repeat(false, n-n/2)) // Second half is false
                .OrderBy(_ => rng.Next()) // Shuffle
                .ToArray(); 

            var list1 = strings.Where((s, i) =>  include[i]).ToList(); // Take elements where `include[index]` is true
            var list2 = strings.Where((s, i) => !include[i]).ToList(); // Take elements where `include[index]` is false

            Console.WriteLine(string.Join(", ", list1));
            Console.WriteLine(string.Join(", ", list2));
        }
    }
}

Here's a completely different approach that uses a modified version of a standard algorithm for selecting K items from N items (in this case, K = N/2):

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

namespace ConsoleApp1
{
    class Program
    {
        public static void Main()
        {
            var strings = Enumerable.Range(1, 20).Select(n => n.ToString()).ToList();

            var list1 = new List<string>();
            var list2 = new List<string>();

            var rng       = new Random();
            int available = strings.Count;
            int remaining = available / 2;

            foreach (var s in strings)
            {
                if (rng.NextDouble() < remaining / (double) available)
                {
                    list1.Add(s);
                    --remaining;
                }
                else
                {
                    list2.Add(s);
                }

                --available;
            }

            Console.WriteLine(string.Join(", ", list1));
            Console.WriteLine(string.Join(", ", list2));
        }
    }
}

This approach is much more performant than my first solution, but since your list is only about 12 items long, this is hardly important for your problem.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
2

First, try to shuffle the list using the Random function

static class MyExtensions
{
    private static Random rng = new Random();
    public static void Shuffle<T>(this IList<T> list)
    {
        int n = list.Count;
        while (n > 1)
        {
            n--;
            int k = rng.Next(n + 1);
            T value = list[k];
            list[k] = list[n];
            list[n] = value;
        }
    }
}

then split the list into two using Linq

    static void Main(String[] args)
    {
        List<string> examples = new List<string>();
        for(int i=1;i<=12;i++)
        {
            examples.Add($"Example {i}");
        }

        examples.Shuffle();

        var firstlist = examples.Take(examples.ToArray().Length / 2).ToArray();
        Console.WriteLine(String.Join(", ", firstlist));
        var secondlist = examples.Skip(examples.ToArray().Length / 2).ToArray();
        Console.WriteLine(String.Join(", ", secondlist));
        Console.ReadLine();
    }

the output looks like this

Example 6, Example 8, Example 3, Example 9, Example 5, Example 2
Example 10, Example 11, Example 4, Example 7, Example 12, Example 1
Krishna Varma
  • 4,238
  • 2
  • 10
  • 25
2

You're currently only generating one random number and getting one value. What you need to do is put that into a loop that is run half as many times as there are items in the list.

var genRandoms = new Random();
var numberRequired = listOfLines.Count/2;
var output = new List<string>();
for (var i=0; i<numberRequired; i++)
{
    var aRandomTeam = genRandoms.Next(listOfLines.Count);
    output.Add(listOfLines[aRandomTeam]);
    listOfLines.RemoveAt(aRandomTeam);
}

Also, this bit at the beginning:

string[] lines = FormNames.Split(
        new[] { Environment.NewLine },
        StringSplitOptions.None);

    List<string> listOfLines = new List<string>();

    foreach (var i in lines)
    {
        listOfLines.Add(i);
    }
    string[] result1 = listOfLines.Where(item => item != string.Empty).ToArray();

Can be rewritten as:

var listOfLines = FormNames.Split(
        new[] { Environment.NewLine },
        StringSplitOptions.RemoveEmptyEntries).ToList();

Removing the empty items as part of the split, and using a built-in method to convert it to a list.

Robin Bennett
  • 3,192
  • 1
  • 8
  • 18
  • 1
    Thank you, this solution worked. I haven't re-written the bit at the beginning yet. I'll work on that once I've worked through some of the other requirments. – Michael Sep 26 '19 at 18:31
1

So I wrote an example in a console app, but the concept works just the same... See comments in code block below

Sample List

var fullList = new List<string>()
{
    "ITEM 01", "ITEM 02", "ITEM 03", "ITEM 04", "ITEM 05", "ITEM 06",
    "ITEM 07", "ITEM 08", "ITEM 09", "ITEM 10", "ITEM 11", "ITEM 12"
};

Initialize Two Lists to Split Values

var list1 = new List<string>();
var list2 = new List<string>();

Creating Two Random Lists

// Initialize one Random object to use throughout the loop
var random = new Random();

// Note: Start at count and count down because we will alter the count of the list
//       so counting up is going to mess up. Ex: Count = 4, Remove 1 (Count = 3), Loop won't go to 4
for(int i = fullList.Count; i > 0; i--)
{
    // Pull random index
    var randomIndex = random.Next(fullList.Count);

    // Pull item at random index
    var listItem = fullList[randomIndex];

    // If i is even, put it in list 1, else put it in list 2. 
    // You could do whatever you need to choose a list to put it
    if (i % 2 == 0)
        list1.Add(listItem);
    else
        list2.Add(listItem);

    // Remove random item from the full list so it doesn't get chosen again
    fullList.RemoveAt(randomIndex);
}

Results

Console.WriteLine("LIST 1");
Console.WriteLine(string.Join(Environment.NewLine, list1));

Console.WriteLine();
Console.WriteLine("LIST 2");
Console.WriteLine(string.Join(Environment.NewLine, list2));

-----------------------
LIST 1
ITEM 05
ITEM 04
ITEM 12
ITEM 11
ITEM 08
ITEM 01

LIST 2
ITEM 02
ITEM 03
ITEM 09
ITEM 06
ITEM 10
ITEM 07
dvo
  • 2,113
  • 1
  • 8
  • 19
0

Here's a simple solution that falls in line with how you were attempting to solve the problem.

The main logic is as followed:

while there are still items in the master list:
  choose a random number [0,list.count) as the current target index
  choose a random number [0,1] as the current target list to add to
  add the item chosen randomly to the randomly selected list
  remove the item chosen from the master list

Here's the code:

        var random = new Random();

        var list = new List<string> { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"  };

        var newList1 = new List<string>();
        var newList2 = new List<string>();
        while(list.Count > 0)
        {
            //choose the index randomly
            int index = random.Next(list.Count);

            //get the item at the randomly chosen index
            string curItem = list[index];

            //choose the list randomly(1==newList1, 2==newList2)
            int listChoice = random.Next(2);

            //Add the item to the correct list
            if(listChoice == 1)
            {
                newList1.Add(curItem);
            }
            else
            {
                newList2.Add(curItem);
            }
            //finally, remove the element from the string
            list.RemoveAt(index);
        }
ShaneDemskie
  • 389
  • 2
  • 14