Получать случайные элементы с фиксированной длиной разных типов

У меня есть List<Fruit>,

public class Fruit
{
    public string Name { get; set; }
    public string Type { get; set; }
}

а в приведенном выше списке содержится 30 объектов фруктов двух типов: Apple и Orange. 20 яблок и 10 апельсинов.

List<Fruit> fruits = new List<Fruit>();
fruits.Add(new Fruit(){ Name = "Red Delicious", Type = "Apple" });
fruits.Add(new Fruit(){ Name = "Granny Smith", Type = "Apple" });
fruits.Add(new Fruit(){ Name = "Sour Granny", Type = "Orange" });
fruits.Add(new Fruit(){ Name = "Delicious Yummy", Type = "Orange" });
.....

Как я могу получить список из 10 случайных фруктов (из корзины из 30 фруктов), но там должно быть 3 апельсина и 7 яблок?

Ответ 1

Вы можете использовать LINQ и Guid или Random для произвольного выбора:

var apples = fruits.
     Where( f => f.Type == "Apple" ). //choose only from apples
     OrderBy( f => Guid.NewGuid() ). //order randomly
     Take( 7 ). //take 7 apples
     ToList();

То же самое относится к oranges, только с "Orange" вместо "Apple" и с 3 вместо 7.

Как это работает?

OrderBy сортирует перечислимое по заданному условию. Поскольку Guid.NewGuid() возвращает случайный уникальный идентификатор, результатом является то, что элементы упорядочены случайным образом. Когда мы применим Take( n ), в этом случайном порядке возьмем n первые элементы.

Примечание, вместо Guid вы можете создать экземпляр Random и использовать f => random.NextDouble() как выражение OrderBy. Это потенциально безопасное решение, потому что Guid.NewGuid() не гарантируется как случайный, только для того, чтобы быть уникальным.

Ответ 2

Сначала разделите между яблоками и апельсинами, чтобы вы имели

var apples = basket.Where(f => f.Type == "Apple");
var oranges = basket.Where(f => f.Type == "Orange");

Теперь, чтобы случайное число элементов без повторения из обоих списков, вы можете использовать что-то вроде этого:

var random = new Random();
var numOfElements = 7; // Replace this with the number you want
apples.OrderBy(x => random.Next()).Take(numOfElements).ToList();

Наконец, объедините два списка, которые вы получаете.

Ответ 3

Вы можете перетасовать один раз, а затем очистить яблоки и апельсины
Это будет немного более эффективным, чем случайным для каждого фрукта, но в кратком списке не будет иметь большого значения.

public static void TestFruit()
{
    List<Fruit> fruits = new List<Fruit>();
    fruits.Add(new Fruit("01", "orange")); fruits.Add(new Fruit("02", "orange"));
    fruits.Add(new Fruit("03", "orange")); fruits.Add(new Fruit("04", "orange"));
    fruits.Add(new Fruit("05", "orange")); fruits.Add(new Fruit("06", "orange"));
    fruits.Add(new Fruit("07", "orange")); fruits.Add(new Fruit("08", "orange"));
    fruits.Add(new Fruit("09", "orange")); fruits.Add(new Fruit("10", "orange"));
    fruits.Add(new Fruit("01", "apple"));  fruits.Add(new Fruit("02", "apple"));
    fruits.Add(new Fruit("03", "apple"));  fruits.Add(new Fruit("04", "apple"));
    fruits.Add(new Fruit("05", "apple"));  fruits.Add(new Fruit("06", "apple"));
    fruits.Add(new Fruit("07", "apple"));  fruits.Add(new Fruit("08", "apple"));
    fruits.Add(new Fruit("09", "apple"));  fruits.Add(new Fruit("10", "apple"));
    fruits.Add(new Fruit("11", "apple"));  fruits.Add(new Fruit("12", "apple"));
    fruits.Add(new Fruit("13", "apple"));  fruits.Add(new Fruit("14", "apple"));
    fruits.Add(new Fruit("15", "apple"));  fruits.Add(new Fruit("16", "apple"));
    fruits.Add(new Fruit("17", "apple"));  fruits.Add(new Fruit("18", "apple"));
    fruits.Add(new Fruit("19", "apple"));  fruits.Add(new Fruit("20", "apple"));
    Shuffle<Fruit>(fruits);
    List<Fruit> randomFruits = fruits.Where(x => x.Type == "apple").Take(7).ToList();
    randomFruits.AddRange(fruits.Where(x => x.Type == "orange").Take(3));
    foreach (Fruit f in randomFruits)
    {
        Debug.WriteLine("Name {0}  Type {1}", f.Name, f.Type);
    }
    Debug.WriteLine("");
}
public static void Shuffle<T>(List<T> list)
{   // FisherYates 
    for (int i = list.Count - 1; i >= 1; i--)
    {
        int j = rand.Next(i + 1);
        if (j != i)
        {   // exchange values
            T curVal = list[i];
            list[i] = list[j];
            list[j] = curVal;
        }
    }
}
public class Fruit
{
    public string Name { get; set; }
    public string Type { get; set; }
    public Fruit(string name, string type)
    {
        Name = name;
        Type = type;
    }
}