Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create two random lists from one list

Tags:

c#

asp.net-mvc

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.

like image 417
Michael Avatar asked Sep 26 '19 13:09

Michael


4 Answers

  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.

like image 151
Matthew Watson Avatar answered Oct 12 '22 11:10

Matthew Watson


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.

like image 31
Robin Bennett Avatar answered Oct 12 '22 11:10

Robin Bennett


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
like image 32
Krishna Varma Avatar answered Oct 12 '22 11:10

Krishna Varma


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
like image 29
dvo Avatar answered Oct 12 '22 11:10

dvo