【问题标题】:In C#, what is the best way to calculate the latest in a collection?在 C# 中,计算集合中最新的最好方法是什么?
【发布时间】:2013-12-06 10:50:15
【问题描述】:

我有一组 PersonTimeSheet 对象,看起来像这样(简化)

 public class PersonTimeSheet
{
        public int Year;
        public string Name;
        public int Jan;
        public int Feb;
        public int March;
        public int April;
        public int May;
}

每个月字段代表该月的工作小时数。

我收集了这些我

  Enumerable<PersonTimeSheet> timeSheets = GetTimeSheets();
  var latestPerson = CalculateLatestPerson(timesheets);

我想找到一种优雅的方式来实现上面的 CalculateLatestPerson() 函数。 (计算最近一个月(不一定是 12 月)谁工作得最多)

例如,假设我的收藏中有 3 件物品(为了简化,只需要到 5 月)

| Name  | Jan | Feb | Mar | Apr |  May |  
| Joe   | 1   |  3  |  4  |  0  |  0   |  
| Bill  | 4   |  3  |  4  |  7  |  0   |  
| Scott | 1   |  3  |  4  |  2  |  0   |   

我希望函数返回 Bill,因为他在人们工作的“最近”月份工作得最多。

我显然需要从 12 ==> 开始的某种类型的循环,但由于我的字段是月份名称而不是整数,我想看看是否有一种优雅的方式来编写这个函数。

【问题讨论】:

  • 你能修改你的类吗?使用Dictionary&lt;int,int&gt; 或类似的值来存储每个月的整数表示形式的值(即使是int[] 也会这样做,因为一年中的月数不会经常变化:))。然后,您可以使用现有属性返回适当的值。会更容易查询
  • 您没有在示例中指定“最近一个月”。无论如何它应该成为一个参数。 5 月每个人都有 0 小时。
  • @RGraham - 请注意,有些日历的月数是可变的。这甚至更需要移动到几个月的字典。
  • @ErnodeWeerd 也许吧。但是,如果他将年份表示为强类型变量,我们可以放心地假设他不会

标签: c# linq collections


【解决方案1】:

我会采用这种方法:

public class PersonTimeSheet
{
        public int Year;
        public string Name;
        public int[] months = new int[12]; //months are a fixed entity so array should be the best option here
}

要检查特定月份的值,请参考以下值:

pts.months[indexOfMonth];

【讨论】:

  • 不是答案,因为它没有找到时间表。
【解决方案2】:

您的数据结构 PersonTimeSheet 不太适合您尝试编写的查询。如果你可以改变这个结构,那么我会选择更好地模拟日期时间的东西。如果您必须使用 PersonTimeSheets 列表,那么我将首先编写一个新类型来更好地表示数据,例如:

public class MonthWorked
{
    public DateTime Date { get; set; }
    public int HoursWorked { get; set; }
    public string Name { get; set; }
}

然后您可以编写一个扩展方法来从 PersonTimeSheet 转换为新类型,如下所示:

public static class PersonTimeSheetExtensions
{
    public static IEnumerable<MonthWorked> ToMonthWorked(this PersonTimeSheet personTimeSheet)
    {
        yield return
            new MonthWorked
                {
                    Date = new DateTime(personTimeSheet.Year, 1, 0),
                    Name = personTimeSheet.Name,
                    HoursWorked = personTimeSheet.Jan
                };

        yield return
            new MonthWorked
            {
                Date = new DateTime(personTimeSheet.Year, 2, 0),
                Name = personTimeSheet.Name,
                HoursWorked = personTimeSheet.Feb
             };

            yield return
                new MonthWorked
                {
                    Date = new DateTime(personTimeSheet.Year, 3, 0),
                    Name = personTimeSheet.Name,
                    HoursWorked = personTimeSheet.March
                };

            yield return
                new MonthWorked
                {
                    Date = new DateTime(personTimeSheet.Year, 4, 0),
                    Name = personTimeSheet.Name,
                    HoursWorked = personTimeSheet.April
                };

            yield return
                new MonthWorked
                {
                    Date = new DateTime(personTimeSheet.Year, 5, 0),
                    Name = personTimeSheet.Name,
                    HoursWorked = personTimeSheet.May
                };

            //...
        }
    }

一旦所有这些都到位,查询就变得很容易编写:

var person = personTimeSheets.SelectMany(p => p.ToMonthWorked())
                                         .Where(p => p.HoursWorked > 0)
                                         .OrderByDescending(p => p.Date)
                                         .ThenByDescending(p => p.HoursWorked)
                                         .First();

变量 person 现在将保存一个 MonthWorked 实例,其中包含最近一个月工作最多的人。这表明了设计类型的重要性,以便它们很好地适合您需要编写的查询。

【讨论】:

    【解决方案3】:

    试试这个:

    var output = input.Where(x=>x.Year == DateTime.Now.Year)
                      .Select(x=> new {x,
                                       LatestWeight = new[]{
                                            x.Jan, x.Feb, x.March, x.April, x.May
                                       }.Select((a,i)=>a + i * 31).Max()
                                      })
                      .OrderByDescending(a=>a.LatestWeight)
                      .Select(a=>a.x).FirstOrDefault();
    //the output is a PersonTimeSheet, you can select what you want from it.
    

    我们也可以使用一些Reflection 来按照声明的顺序选择字段,但是我认为使用反射会非常不可靠。因此,只需编写所有字段,那是因为您自己的方法。

    【讨论】:

      【解决方案4】:

      如果您可以标准化月份名称字段,使其全部为完整的月份名称或全部为三个字母的月份名称,那么使用反射来获取正确的值并使用简单的查询来获得最多的工作将非常简单

              // Get the three letter month name of the current month
              string monthName = DateTime.Now.ToString("MMM");
      
              // Get the correct field info
              System.Reflection.FieldInfo fld = new PersonTimeSheet().GetType().GetField(monthName);
      
              // Order the list of data by the selected field then get the first one
              var mostWorked = data.OrderByDescending(t => fld.GetValue(t)).FirstOrDefault();
      

      【讨论】:

        【解决方案5】:

        我有时会感到无聊,并喜欢处理困难的班级设计。不要误解,其他答案的建议是“正确的”。您应该重新设计您的PersonTimeSheet 以维护月份排序、年份排序等信息。但是,您可能受其他程序员类设计的摆布,所以这里是直接解决您提出的问题。

        您的整体解决方案需要以下签名、过滤和排序:

        Func<IEnumerable<PersonTimeSheet>, IEnumerable<PersonTimeSheet>>
        

        首先,只过滤到最近的一年:

        int latestYear = timesheets.Max(x => x.Year);
        IEnumerable<PersonTimeSheet> latestYearSheets = timesheets.Where((x, i) => x.Year == latestYear);
        

        接下来,我们将过滤到最近一个月。您的班级规范将月份视为班级的不同(无序)成员。编译器无法“知道”这些月份的时间顺序。然而,我们需要知道它。因此,我将创建一种将月份顺序传达给任何排序函数等的方法。首先,我将创建一个 enum 来说明顺序:

        enum MonthOrder = { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
        

        然后是一个将您的PersonTimeSheet 链接到该订单的方法:

        static MonthOrder getLatestMonth(PersonTimeSheet x)
        {
            x.Dec != 0 ? return MonthOrder.DEC;
            x.Nov != 0 ? return MonthOrder.NOV;
            x.Oct != 0 ? return MonthOrder.OCT;
            // Hopefully you get the picture.
        }
        

        最后,我需要一种方法,可以从PersonTimeSheet 的集合中找到最新的MonthOrder。所以我的整体签名是这样的:

        Func<IEnumerable<PersonTimeSheet>, MonthOrder>
        

        查看整体类型签名,我需要一种减少查找最新月份的方法。因此,我们对序列中的每个PersonTimeSheet 进行滚动比较,最后返回该比较的最高 成员:

        MonthOrder latestMonth = latestYearSheets.Max(x => getLatestMonth(x));
        

        现在我们将PersonTimeSheet 过滤到最近一个月:

        IEnumerable<PersonTimeSheet> latestMonthSheets = latestYearSheets.Where((x, i) => getMonth(x) == latestMonth);
        

        最后,我们筛选并订购了最近一个月的工作表。我们现在要按该月的最高工作小时数排序。但是,您的课程设计意味着我们不能只获得最近一个月的小时数(我们没有访问器)。所以我们需要一个带有签名的函数:

        Func<MonthOrder, PersonTimeSheet, int>
        

        一个实现

        static int HoursOfMonth(MonthOrder month, PersonTimeSheet person)
        {
            switch(month)
            {
                case MonthOrder.JAN:
                    return person.Jan;
                case MonthOrder.FEB:
                    return person.Feb;
                // Hopefully you get the point.
            }
        }
        

        我们现在可以获取最近一个月的小时数并进行排序:

        IEnumerable<PersonTimeSheet> finalResult = latestMonthSheets.OrderBy(x => HoursOfMonth(latestMonth, x));
        

        这更适合我的练习。但希望它说明一个好的核心类设计总是能胜过。围绕一个问题进行编码。稍微改进的类设计将在显着减少行数中产生您的答案。

        【讨论】:

          猜你喜欢
          • 2011-11-15
          • 2012-02-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多