【问题标题】:Perform calculations on a set using only LINQ仅使用 LINQ 对集合执行计算
【发布时间】:2018-02-20 19:32:35
【问题描述】:

我想仅使用 LINQ 执行以下操作。

我有一个时间表条目列表,其中包含用户的进出时间。该类如下所示:

public class TimeSheetLog
{
   public Guid EmployeeId { get; set; }
   public DateTime ClockInTimeStamp { get; set; }
   public DateTime ClockOutTimeStamp { get; set; }
}

我正在传递一个List<TimeSheetLog>(),其中包含从年初至今的所有日志。

我正在尝试计算 1 月份的总工作时间(不考虑员工)。另请注意,我有一个名为 GetTimeDifferenceInMinutes() 的函数,它计算两个日期/时间值之间的分钟数。

这是我目前拥有的,但我觉得整个事情都可以只使用 LINQ 来完成。

public static int GetTotalTimeWorked(List<TimeSheetLog> logs, DateTime startDate, DateTime endDate)
{
   // I'm passing 1/1/2018 for startDate and 1/31/2018 for endDate to this function
   var totalTimeWorkedInMinutes = 0;

   var januaryLogs = logs.Where(x => x.ClockInTimeStamp >= startDate && 
   x.ClockOutTimeStamp <= endDate);

   foreach(var item in januaryLogs)
   {
      totalTimeWorkedInMinutes += GetTimeDifferenceInMinutes(item.ClockInTimeStamp, itemClockOutTimeStamp);
   }

   return totalTimeWorkedInMinutes;
}

【问题讨论】:

    标签: c# linq


    【解决方案1】:
    var logsFilteredByDate = logs.Where(x => x.ClockInTimeStamp >= startDate &&
        x.ClockOutTimeStamp <= endDate);
    var totalTimeWorkedInMinutes = logsFilteredByDate.Sum(x => 
        GetTimeDifferenceInMinutes(x.ClockInTimeStamp, x.ClockOutTimeStamp));
    

    或者,将它们全部组合成一个查询,这是不必要且难以阅读的,

    var totalTimeWorkedInMinutes = logs.Where(x => x.ClockInTimeStamp >= startDate &&
        x.ClockOutTimeStamp <= endDate)
        .Sum(x => 
            GetTimeDifferenceInMinutes(x.ClockInTimeStamp, x.ClockOutTimeStamp));
    

    【讨论】:

    • 这种方法不包括与开始或结束日期部分重叠的任何时间表,因此一些员工显然不会获得全额报酬。如果你在 1 月 31 日晚上 10 点开始在墓地工作,你应该在 1 月获得两个小时的报酬,其余时间在 2 月获得。
    • @JohnWu 我只是在回答如何使用已经提供的查询来执行 LINQ。您指出的是有效的,但这不是问题所在。问题是如何获取第一个查询的结果并使用 LINQ 对其求和。这对 OP 来说是一个很好的评论,但它不在问题的范围内。
    【解决方案2】:

    你需要总和

    var tot = januaryLogs.Sum(item=>GetTimeDifferenceInMinutes(item.ClockInTimeStamp, itemClockOutTimeStamp));
    

    【讨论】:

      【解决方案3】:

      你不能用 Sum 做 Where 并在 Sum 中做 DateTime Subtract,所以

      decimal total = logs.Where(x => x.ClockInTimeStamp >= startDate && x.ClockOutTimeStamp <= endDate).Sum(x.ClockOutTimeStamp.Subtract(x.ClockInTimeStamp).TotalMinutes);
      

      【讨论】:

        【解决方案4】:

        在您意识到时间表可以跨越数月之前,问题似乎很容易。因此,如果有人在 1 月 31 日打卡并在 2 月 1 日打卡,您必须计算部分时间表,才能做到这一点。

        这是我的解决方案:

        public static class ExtensionMethods
        {
            static public double TotalMinutes(this IEnumerable<TimeSheetLog> input, DateTime startPeriod, DateTime endPeriod)
            {
                return TimeSpan.FromTicks
                (
                    input
                    .Where( a=>
                        a.ClockOutTimeStamp >= startPeriod &&
                        a.ClockInTimeStamp <= endPeriod
                    )
                    .Select( a=>
                        Math.Min(a.ClockOutTimeStamp.Ticks, endPeriod.Ticks) - 
                        Math.Max(a.ClockInTimeStamp.Ticks, startPeriod.Ticks)
                    )
                    .Sum()
                )
                .TotalMinutes;
            }
        }
        

        逻辑:

        1. 查找至少部分与关注期间重叠的所有时间表。
        2. 将开始时间计算为时钟时间或周期开始时间,以较晚者为准。
        3. 将结束时间计算为下班时间或周期结束时间,以较早者为准。
        4. 将开始时间和结束时间的差作为刻度。 Sum()这些。
        5. 为了完成所有这些数学运算,我们将所有时间戳转换为Ticks,因为您不能使用两个日期时间的Max()。我们可以很好地添加刻度,然后在返回之前将总数转换回分钟。

        测试计划(注意第三个时间表跨越 1 月和 2 月):

        public class Program
        {
            static public List<TimeSheetLog> testData = new List<TimeSheetLog>
            {
                new TimeSheetLog
                {
                    ClockInTimeStamp = DateTime.Parse("1/1/2018 9:00 am"),
                    ClockOutTimeStamp = DateTime.Parse("1/1/2018 5:00 pm")
        
                },
                new TimeSheetLog
                {
                    ClockInTimeStamp = DateTime.Parse("1/2/2018 9:00 am"),
                    ClockOutTimeStamp = DateTime.Parse("1/2/2018 5:00 pm")
        
                },
                new TimeSheetLog
                {
                    ClockInTimeStamp = DateTime.Parse("1/31/2018 6:00 pm"),
                    ClockOutTimeStamp = DateTime.Parse("2/1/2018 9:00 am")
        
                },
                new TimeSheetLog
                {
                    ClockInTimeStamp = DateTime.Parse("2/3/2018 9:00 am"),
                    ClockOutTimeStamp = DateTime.Parse("2/3/2018 5:00 pm")
        
                }
            };
        
        
            public static void Main()
            {
                var startPeriod = new DateTime(2018, 1, 1);
                var endPeriod = new DateTime(2018, 1, 31, 23, 59, 59, 9999); 
                Console.WriteLine( testData.TotalMinutes(startPeriod, endPeriod).ToString("0.00") );
            }
        }
        

        输出:

        1320.00
        

        ...这是正确的。

        See my code on DotNetFiddle

        【讨论】:

          【解决方案5】:

          另一种选择是使用.Aggregate 函数。

          public static int GetTotalTimeWorked(List<TimeSheetLog> logs, DateTime startDate, DateTime endDate)
          {
              var totalTimeWorkedInMinutes = 0;
          
              return logs.Where(x => x.ClockInTimeStamp >= startDate && x.ClockOutTimeStamp <= endDate)
                  .Aggregate(totalTimeWorkedInMinutes, (total, item) => total + GetTimeDifferenceInMinutes(item.ClockInTimeStamp, item.ClockOutTimeStamp));
          
          }
          

          【讨论】:

          • 虽然 Aggregate 通常很有用,但 Sum 方法是专门用于计算总和的聚合器。
          猜你喜欢
          • 1970-01-01
          • 2011-03-02
          • 1970-01-01
          • 1970-01-01
          • 2010-09-05
          • 2022-01-11
          • 1970-01-01
          • 2010-11-03
          • 2018-11-16
          相关资源
          最近更新 更多