Выбор значений из списка

У меня есть следующий список:

public class Products
{
 public string SKU;
 public int WarehouseID;
}

List<Products> products = new List<Products>();

который после заполнения списка я получаю следующие данные:

ProductCode|WarehouseID
SKU001|2
SKU001|3
SKU002|3
SKU003|3
SKU004|1
SKU004|5

У меня есть несколько SKU, так как товар может быть поставлен из более чем одного склада, находящегося на складе. В случае SKU001 на складе ID 2 больше запасов, чем на складе ID 3.

Мне нужно выбрать элементы из наименьшего количества мест склада. То, что я пытаюсь закончить, это что-то вроде

SKU001|3
SKU002|3
SKU003|3
SKU004|1

Это ограничивает выбор продукта только двумя местоположениями, так как SKU001, SKU002 и SKU003 могут быть получены со склада ID 3. Идеальный выбор из местоположения с наибольшим количеством акций, но ограничение количества мест является более важным.

Я использую Linq, чтобы попытаться достичь этого, пытаясь зациклировать каждый элемент списка, но я боюсь, поскольку Linq не является для меня сильным моментом. Я попытался сначала получить наивысший счет повторного идентификатора хранилища, используя

products.GroupBy(i => i).OrderByDescending(grp => grp.Count()).Select(grp => grp.Key).FirstOrDefault();

но я теряюсь на остальных элементах. Любые идеи о том, как я мог бы это сделать?

Ответ 1

Вы можете сделать это в нескольких операциях, сначала получите WarehouseID вместе с отдельными продуктами на каждом складе, а их счетчик:

var query = products.GroupBy(r => r.WarehouseID)
                    .Select(grp => new
                    {

                        WarehouseID = grp.Key,
                        DistinctProducts = grp.Select(r => r.SKU).Distinct(),
                        DistinctCount = grp.Select(r => r.SKU).Distinct().Count(),
                    });

Позже создайте список результатов, например:

List<Products> result = new List<Products>();

foreach (var item in query.OrderByDescending(r => r.DistinctCount)) //warehouse with most products
{
    if (!result.Any(r => item.DistinctProducts.Any(t => t == r.SKU)))
    {
        result.AddRange(item.DistinctProducts.Select(r => new Products { SKU = r, WarehouseID = item.WarehouseID }));
    }
}

Для вывода:

foreach (var item in result)
{
    Console.WriteLine("{0} | {1}", item.SKU, item.WarehouseID);
}

Вывод:

SKU001 | 3
SKU002 | 3
SKU003 | 3
SKU004 | 1

Ответ 2

Edit: Я обновил свой запрос для выбора на основе количества продуктов, хранящихся на каждом складе, а не на складе каждого продукта.

var productQ2 = products.GroupBy(product => product.WarehouseId)
                        .SelectMany(wGroup => wGroup.Select(product => new
                                                                        {
                                                                            Product = product,
                                                                            WarehouseProductCount = wGroup.Select(p => p.SKU)
                                                                                                          .Distinct()
                                                                                                          .Count()
                                                                        }))
                        .GroupBy(product => product.Product.SKU)
                        .Select(pGroup => pGroup.OrderByDescending(product => product.WarehouseProductCount).First().Product);

Оригинальный ответ:

Невозможно получить доступ к количеству акций на каждом складе, поэтому вам нужно добавить это в модель.

public class Product
{
 public string SKU;
 public int WarehouseID;
 public int StockCount;
}

List<Product> products = new List<Product>();

Затем, чтобы получить результаты запроса:

products.GroupBy(product => product.SKU)
        .Select(group => group.OrderByDescending(product => product.StockCount).First())

Ответ 3

Здесь решение, которое создает минимальный набор идентификаторов хранилища:

// Number of product SKUs
int nProducts = products.Select(p => p.SKU).Distinct().Count();
// Warehouses and their products
Dictionary<int, List<Product>> warehouses = products
    .GroupBy(p => p.WarehouseID)
    .ToDictionary(g => g.Key, g => g.ToList());
List<int> minWarehouseSet = warehouses
    // Get list of unique warehouse ids
    .Select(p => p.Key).ToList()
    // Get all combinations of warehouses
    .Combinations()
    // Order sets by the number of items
    .OrderBy(ws => ws.Count())
    // Find set which satisfies requirement (contains all products)
    .First(ws => ws.SelectMany(w => warehouses[w]).Distinct().Count() == nProducts)
    .ToList();
foreach (var product in products.Where(p => minWarehouseSet.Contains(p.WarehouseID)))
    Console.WriteLine("{0}|{1}", product.SKU, product.WarehouseID);

Для этого требуется следующий метод расширения:

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IList<T> allValues)
{
    for (int counter = 0; counter < (1 << allValues.Count); ++counter)
        yield return allValues.Where((_, i) => (counter & 1 << i) == 0).ToList();
}

Вывод:

SKU001|3
SKU002|3
SKU003|3
SKU004|5

Примечания:

  • Это решение выполняет поиск грубой силы для оптимального набора складов. Возможно, там лучшее решение.
  • Количество складов ограничено количеством бит в методе int из Cominations (что равно 32). Вы можете выбрать другой тип переменной или реализовать метод по-другому.
  • Данные, которые вы предоставили, не содержат запаса, и вы не указали, как это влияет на результат. Если вам нужно учитывать запасы, отрегулируйте порядок (для измерения "качества" результата) или фильтрации (для строгих условий).