Michael
Michael

Reputation: 117

Create two random lists from one list

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.

Upvotes: 5

Views: 1131

Answers (5)

Matthew Watson
Matthew Watson

Reputation: 109547

  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.

Upvotes: 3

ShaneDemskie
ShaneDemskie

Reputation: 388

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);
        }

Upvotes: 0

dvo
dvo

Reputation: 2153

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

Upvotes: 1

Robin Bennett
Robin Bennett

Reputation: 3231

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.

Upvotes: 2

Krishna Varma
Krishna Varma

Reputation: 4250

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

Upvotes: 2

Related Questions