【问题标题】:Using Dictionary<T, Func<List, bool>> to Find Most Common Occurrences In List in C#在 C# 中使用 Dictionary<T, Func<List, bool>> 查找 List 中最常见的出现
【发布时间】:2018-06-07 03:49:41
【问题描述】:

假设我的 C# 代码定义如下,我想向控制台返回一个输出,显示列表或汽车中最常见的燃料类型。

// set delegates defining rules as a function that takes list of cars and returns an int
Func<List<Car>, int> CountElectric = (cars) => cars
    .GroupBy(c => c.IsElectric == true).Count(); // count electric cars

Func<List<Car>, int> CountGassoline = (cars) => cars
    .GroupBy(c => c.FuelType == FuelTypes.Gassoline).Count(); // count cars using gassoline

Func<List<Car>, int> CountDiesel = (cars) => cars
    .GroupBy(c => c.FuelType == FuelTypes.Diesel).Count(); // Count cars using diesel

// Add rules to dictionary of (Key=CarType, Value=Func<List<Car>, bool>
CarsRules = new Dictionary<CarType, Func<List<Car>, int>>()
{
    {FueldType.Electric, CountElectric},
    {FueldType.Diesel,CountDiesel},
    {FueldType.Gassoline, CountGassoline}
};

// Create List of Cars and add some cars
List<Car> cars = new List<Car>();

cars.Add(new Car() 
{Id = Guid, Name = "Tesla", IsElectric = true, FuelType = FuelTypes.Electric};

cars.Add(new Car() 
{Id = Guid, Name = "VW", IsElectric = false, FuelType = FuelTypes.Gassoline};

cars.Add(new Car() 
{Id = Guid, Name = "Toyota", IsElectric = false, FuelType = FuelTypes.Gassoline};

cars.Add(new Car() 
{Id = Guid, Name = "Volvo", IsElectric = false, FuelType = FuelTypes.Diesel};

在上面的汽车列表中,汽油是最常见的。我怎么知道呢? IE 我想在控制台上只显示“汽油”

如果我在上面的列表中打成平手,例如 2 辆使用汽油的汽车和 2 辆使用柴油的汽车,我将如何找到并退回两者? IE 我想在控制台上同时显示“汽油”和“柴油”

我很难理解这种结构,我想像下面这样用老式的方式来做,但我很清楚应该有更优雅的方式来使用 Func 代表

public List<string> GetMostUsedFuelTypes(List<Car> cars)
{
    List<string> winners = new List<string>();

    int CountElectric = 0;
    int CountGassoline = 0;
    int CountDiesel = 0;

    foreach(var car in cars)
    {
        // Count and return fuel type that occurs most, if a tye, return the highest counts
    }

    return winners;
}

【问题讨论】:

  • 与问题无关的改进建议:Func&lt;List&lt;Car&gt;, bool&gt; CountElectric = (cars) =&gt; cars.GroupBy(c =&gt; c.IsElectric == true).Count(); 可能比Func&lt;List&lt;Car&gt;, bool&gt; CountElectric = (cars) =&gt; cars.Count(c =&gt; c.IsElectric == true); 更好如果IsElectric 的类型是bool(而不是bool?),那么您也可以删除==true.
  • Func&lt;List&lt;Car&gt;, int&gt; CountElectric = (cars) =&gt; cars.GroupBy(c =&gt; c.IsElectric == true).Count() 不计算有多少电动汽车。它计算c.IsElectric == true 有多少个结果——最多两个。你想要的是Func&lt;List&lt;Car&gt;, int&gt; CountElectric = (cars) =&gt; cars.Where(c =&gt; c.IsElectric == true).Count()
  • @Enigmativity ...然后可以进一步改进,因为执行“Where”后跟“Count”是浪费:它创建一个对象列表然后计算它们。您可以使用 lambda 表达式将“Where”更改为“Count”,该表达式只保留计数。尽管您的解释比我的要清楚得多,但这使您获得了我之前评论中的价值。 :)
  • @Richardissimo - 执行 Where/Count 与带有 lambda 的 Count 的运行时性能几乎没有区别。
  • @Enigmativity 是的,你是对的,因为我说它创建一个列表是错误的,它只是在处理一个可枚举的。 (cars) =&gt; cars.Count(car =&gt; car.IsElectric) 仍然更容易阅读。

标签: c# dictionary lambda delegates func


【解决方案1】:

你的结构Dictionary&lt;CarType, Func&lt;List&lt;Car&gt;, int&gt;&gt; 是做这种事情的错误类型。如果您尝试组合具有重叠子集的类型,则可以组合两个 int 值以获得准确的计数。

我会这样做:

var CarsRules = new Dictionary<FuelTypes, Func<List<Car>, IEnumerable<Car>>>()
{
    { FuelTypes.Electric, cs => cs.Where(c => c.IsElectric) },
    { FuelTypes.Diesel, cs => cs.Where(c => c.FuelType == FuelTypes.Diesel) },
    { FuelTypes.Gassoline, cs => cs.Where(c => c.FuelType == FuelTypes.Gassoline) },
}

List<Car> cars = new List<Car>()
{
    new Car(Guid.NewGuid(), "Tesla", true, FuelTypes.Electric),
    new Car(Guid.NewGuid(), "VW", false, FuelTypes.Gassoline),
    new Car(Guid.NewGuid(), "Toyota", false, FuelTypes.Gassoline),
    new Car(Guid.NewGuid(), "Volvo", false, FuelTypes.Diesel),
};

Console.WriteLine($"Electric: {CarsRules[FuelTypes.Electric](cars).Count()}");
Console.WriteLine($"Gassoline: {CarsRules[FuelTypes.Gassoline](cars).Count()}");
Console.WriteLine($"Diesel: {CarsRules[FuelTypes.Diesel](cars).Count()}");
Console.WriteLine($"Gassoline & Diesel: {CarsRules[FuelTypes.Gassoline](cars).Union(CarsRules[FuelTypes.Diesel](cars)).Count()}");

这需要定义这些类型:

public enum FuelTypes
{
    Electric, Gassoline, Diesel
}

public sealed class Car : IEquatable<Car>
{
    private readonly Guid _Id;
    private readonly string _Name;
    private readonly bool _IsElectric;
    private readonly FuelTypes _FuelType;

    public Guid Id { get { return _Id; } }
    public string Name { get { return _Name; } }
    public bool IsElectric { get { return _IsElectric; } }
    public FuelTypes FuelType { get { return _FuelType; } }

    public Car(Guid Id, string Name, bool IsElectric, FuelTypes FuelType)
    {
        _Id = Id;
        _Name = Name;
        _IsElectric = IsElectric;
        _FuelType = FuelType;
    }

    public override bool Equals(object obj)
    {
        if (obj is Car)
            return Equals((Car)obj);
        return false;
    }

    public bool Equals(Car obj)
    {
        if (obj == null) return false;
        if (!EqualityComparer<Guid>.Default.Equals(_Id, obj._Id)) return false;
        if (!EqualityComparer<string>.Default.Equals(_Name, obj._Name)) return false;
        if (!EqualityComparer<bool>.Default.Equals(_IsElectric, obj._IsElectric)) return false;
        if (!EqualityComparer<FuelTypes>.Default.Equals(_FuelType, obj._FuelType)) return false;
        return true;
    }

    public override int GetHashCode()
    {
        int hash = 0;
        hash ^= EqualityComparer<Guid>.Default.GetHashCode(_Id);
        hash ^= EqualityComparer<string>.Default.GetHashCode(_Name);
        hash ^= EqualityComparer<bool>.Default.GetHashCode(_IsElectric);
        hash ^= EqualityComparer<FuelTypes>.Default.GetHashCode(_FuelType);
        return hash;
    }

    public override string ToString()
    {
        return String.Format("{{ Id = {0}, Name = {1}, IsElectric = {2}, FuelType = {3} }}", _Id, _Name, _IsElectric, _FuelType);
    }

    public static bool operator ==(Car left, Car right)
    {
        if (object.ReferenceEquals(left, null))
        {
            return object.ReferenceEquals(right, null);
        }

        return left.Equals(right);
    }

    public static bool operator !=(Car left, Car right)
    {
        return !(left == right);
    }
}

【讨论】:

  • 实际上,我只是想到了一种更像您当前的方法。有时间我会在稍后发布。
【解决方案2】:
var mostCommonFuelType =
    cars
        .GroupBy(car => car.FuelType)
        .OrderBy(carsByFuelType => carsByFuelType.Count())
        .Select(carsByFuelType => carsByFuelType.Key
        .FirstOrDefault();

如果您需要自定义规则,只需让规则选择要在其分组中使用的键。除非它们不是互斥的,在这种情况下,您还需要使用 SelectMany 并返回键值对。

【讨论】:

  • 不,这不是我要问的。我说的是上面的 CarRules 字典,其中 CarType 作为键,Func 委托作为值。 Func 用于测试并返回布尔值
【解决方案3】:

您可以创建一个字典,记录每种燃料类型中有多少辆汽车,然后列出哪些燃料类型的数量最多,如下所示...

var fuelTypeCountDictionary = cars
    .GroupBy(car => car.FuelType)
    .ToDictionary(x => x.Key, x => x.Count());
var max = fuelTypeCountDictionary.Values.Max();
var maxFuelTypes = fuelTypeCountDictionary.Where(x => x.Value == max).Select(x => x.Key);
string mostUsedFuelTypes = string.Join(", ", maxFuelTypes);
Console.WriteLine(mostUsedFuelTypes);

对于给出的示例数据,这将在控制台上仅显示“汽油”。

如果出现平局,例如 2 辆使用汽油的汽车和 2 辆使用柴油的汽车,则会在控制台上显示“汽油,柴油”。

附: “gassoline”应该只有一个“s”。

【讨论】:

  • 不,这不是我要问的。我说的是上面的 CarRules 字典,其中 CarType 作为键,Func 委托作为值。 Func 用于测试并返回布尔值
  • @pixel 发布代码中的Funcs 不返回布尔值。他们返回int。这个问题的标题同样有问题。但是,如果标题和您的评论是正确的,而只是发布的代码有问题,我可以提供基于谓词字典的解决方案...
【解决方案4】:

我想出了如何在我的字典 CarRules 中调用 Func 委托并将其传递给 Car 对象列表。如果其他人需要这样的 smtg,我就是这样做的,例如查找汽车是否为电动汽车。

int countElectric = CarsRules[FueldType.Electric].Invoke(cars);

例如,上面将调用 Func 委托 CountElectric,它接受汽车列表并返回一个 int。

使用这个,应该很容易找出列表中最常出现的汽车类型

【讨论】:

  • 不,我不是你要问的。每个试图帮助回答您的人都对您的问题有完全相同的理解:“在上面的汽车列表中,汽油是最常见的。我如何才能找到它?”这回答了一个不同的问题:“如何调用存储在字典中的函数?”这与您发布的代码很少有关。
  • 不是问题的答案
【解决方案5】:

由于您特别想使用“CarsRules”字典作为驱动程序,因此您可以调整我的其他答案以适应;但首先你需要修复Count*Funcs 中的错误。目前他们返回的答案是 0、1 或 2,无论您在列表中放了多少辆车;它并没有返回你的想法。 例如,如果您将一辆汽油车放入列表中,那么 CountElectric 将返回 1。要解决这个问题...

Func<List<Car>, bool> CountElectric = (cars) => cars.GroupBy(c => c.IsElectric == true).Count();

需要

Func<List<Car>, bool> CountElectric = (cars) => cars.Count(c => c.IsElectric == true);

如果 IsElectric 的类型是 bool(而不是 bool?),那么您还可以删除 ==true 以使其更简单。

需要对所有 3 个 Funcs 进行更改。

既然字典中的 `Func's 完成了回答问题所需的功能,您可以将我之前的答案修改如下:

var fuelTypeCountDictionary = CarsRules
    .ToDictionary(x => x.Key, x => x.Value.Invoke(cars));
var max = fuelTypeCountDictionary.Values.Max();
var maxFuelTypes = fuelTypeCountDictionary.Where(x => x.Value == max).Select(x => x.Key);
Console.WriteLine(string.Join(", ", maxFuelTypes));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-26
    • 2010-10-09
    • 2012-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-07
    • 1970-01-01
    相关资源
    最近更新 更多