【问题标题】:In C#, what is the most elegant way to merge items in an array are part of a "single larger item"?在 C# 中,将数组中的项目合并为“单个较大项目”的最优雅方法是什么?
【发布时间】:2016-01-04 23:36:20
【问题描述】:

我有一个 C# 应用程序,我正在从跟踪人们请求的外部跟踪系统中提取数据,并将它们存储在我的数据库中。所以是这样的:

public class Request
{
   public DateTime Start {get;set;}
   public DateTime End {get;set;}
   public int PersonId {get;set;}
} 

IEnumerable<Request> requests = GetExternalRequests();

GetExternalRequests() 的详细信息与问题无关。

问题是服务会分解事情以在每一天向我发送请求(即使请求是多天请求)

例如,如果一个人提出一整周(周一至周五)的请求,我会在数组中获得 5 个不同的项目(每个项目都有一个日期),我想将它们“合并”成一个使用 Start = Monday 和 End = Friday 的请求以避免将 5 条不同的记录保存到我的数据库中。

到目前为止,我现在有一个感觉很不优雅的解决方案,我循环遍历所有请求并将结果放入字典中,然后运行下面的代码

IEnumerable<Request> requests = GetExternalRequests();

IEnumerable<Request> previousRequests = GetAllPreviousRequests();

Dictionary<string, Request> cachedDictionary = CacheAllRequestsByDateandPersonId(requests, previousRequests)

var groupedByPerson = requests.GroupBy(r=>r.PersonId);
foreach (var group in groupedByPerson)
{
    foreach (Request request in group.OrderBy(r=>r.StartDate) 
    {
         var offSet = 1;
         if (request.StartDate.DayOfWeek == DayOfWeek.Friday)
         {
             offSet = 3;
         }
         if (cachedDictionary.ContainsKey(request.PersonId + request.StartDate.AddDays(offset))
         {
              //delete the request from the list and change the start date of the next request to the start date of this request.
         }
    }
}

所以我想获得一些建议,看看是否有更优雅的方式来“合并”这些结果。

为了增加一些清晰度(基于下面的一些 cmets)

  • 请求不能重叠(想想休假请求)
  • 如果我在星期一已经有一个先前的请求,而在星期二又收到了一个新请求,那么我也想合并这些请求

【问题讨论】:

  • 什么是MergeAlignedRequests? - 似乎是与问题最相关的方法?此外,这是合并所有请求在插入之前,还是您还想合并现有请求? (例如,他们在星期一提出请求,然后在星期二也提出请求?)
  • cacheRequest 是什么类型?分配在哪里?
  • 只有Request.StartRequest.End中的Day相关吗?
  • 一般来说,您能对Request.StartRequest.End 做出任何假设吗?会不会有重叠的请求?
  • @Rob - 这是一个错字。我为问题移动了内联函数

标签: c# collections merge


【解决方案1】:

假设您的 GetExternalRequests 返回一些类似的种子数据

private static IEnumerable<Request> GetExternalRequests()
{
    yield return new Request(new DateTime(2015, 1, 4), new DateTime(2015, 1, 4), 1);
    yield return new Request(new DateTime(2015, 1, 5), new DateTime(2015, 1, 5), 1);
    yield return new Request(new DateTime(2015, 1, 6), new DateTime(2015, 1, 6), 1);
    yield return new Request(new DateTime(2015, 1, 7), new DateTime(2015, 1, 7), 1);
    yield return new Request(new DateTime(2015, 1, 8), new DateTime(2015, 1, 8), 1);

    yield return new Request(new DateTime(2015, 1, 11), new DateTime(2015, 1, 11), 1);
    yield return new Request(new DateTime(2015, 1, 15), new DateTime(2015, 1, 15), 1);

    yield return new Request(new DateTime(2015, 1, 19), new DateTime(2015, 1, 19), 1);

    yield return new Request(new DateTime(2015, 1, 26), new DateTime(2015, 1, 26), 1);

    yield return new Request(new DateTime(2015, 1, 4), new DateTime(2015, 1, 4), 2);
    yield return new Request(new DateTime(2015, 1, 7), new DateTime(2015, 1, 7), 2);
}

然后您可以使用 GroupBy 合并您的数据,然后 Aggregate 以合并连续的天数

请看下面的代码:

private static IList<Request> MergeRequests(IEnumerable<Request> requests)
{
    return requests.GroupBy(r => r.PersonId)
                    .Aggregate(new Stack<Request>(), (list, grouping) =>
                    {
                        foreach (var request in grouping.OrderBy(r => r.StartDate))
                        {
                            var peek = list.Any() ? list.Peek() : null;
                            if (peek?.EndDate.Date.Day + 1 == request.StartDate.Date.Day)
                                peek.EndDate = request.EndDate;
                            else
                                list.Push(request);
                        }
                        return list;
                    })
                    .OrderBy(x => x.PersonId).ThenBy(x => x.StartDate)
                    .ToList();
}

让我们测试一下这个解决方案

public static void Main(string[] args)
{
    Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
    IEnumerable<Request> requests = GetExternalRequests();

    var requestsMerge = MergeRequests(requests);


    foreach (var request in requestsMerge)
        Console.WriteLine($"Person Id: {request.PersonId} - StartDate: {request.StartDate} - EndDate: {request.EndDate}");
}

输出数据为:

人员 ID:1 - 开始日期:2015 年 1 月 4 日上午 12:00:00 - 结束日期:2015 年 1 月 8 日上午 12:00:00

人员 ID:1 - 开始日期:2015 年 1 月 11 日上午 12:00:00 - 结束日期:2015 年 1 月 12 日上午 12:00:00

人员 ID:1 - 开始日期:2015 年 1 月 19 日上午 12:00:00 - 结束日期:2015 年 1 月 19 日上午 12:00:00

人员 ID:1 - 开始日期:2015 年 1 月 26 日上午 12:00:00 - 结束日期:2015 年 1 月 26 日上午 12:00:00

人员 ID:2 - 开始日期:2015 年 1 月 4 日上午 12:00:00 - 结束日期:2015 年 1 月 4 日上午 12:00:00

人员 ID:2 - 开始日期:2015 年 1 月 7 日上午 12:00:00 - 结束日期:2015 年 1 月 7 日上午 12:00:00

【讨论】:

  • 这将对不连续的请求进行分组-例如,他们要求MondayFriday,它将选择Monday -&gt; Friday
  • @Rob 他说If I already have a previous request on Monday and a new request comes in on Tuesday then I also want to merge those
  • 是的,但是合并周一和周二不同于合并周一和周五。如果在周一和周五给出此代码,将创建周一、周二、周三周四周五的请求。
  • @Rob 你看到我添加的种子数据了吗?它没有在星期一和星期四有一个请求的示例,我的代码只返回一个请求。
  • @Rob 为什么是两个?它应该只有一个请求,开始日期在星期一,结束日期在星期四
【解决方案2】:

你可以试试这样的:

public class Request
{
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public int PersonId { get; set; }
    public Request(DateTime start)
    {
        while (!IsWorkingDay(start))
            start = start.AddDays(1);
        Start = start;

        End = start.AddDays(1);
        while (!IsWorkingDay(End))
            End = End.AddDays(1);
    }

    private bool IsWorkingDay(DateTime date)
    {
        return date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday;
    }

    public bool Intersects(Request otherRequest)
    {
        if (otherRequest == this)
            return true;

        return !(otherRequest.End < Start || otherRequest.Start > End);
    }

    public void Merge(Request otherRequest)
    {
        if (otherRequest.Start < Start)
            Start = otherRequest.Start;
        if (otherRequest.End > End)
            End = otherRequest.End;
    }
}

并按如下方式使用:

var requests = GetExternalRequests().ToList();

var result = requests.GroupBy(g => g.PersonId)
    .Select(g => g.OrderBy (r => r.Start)
    .Aggregate(new List<Request>(), 
        (acc, right) => {
        if (acc.Count > 0)
        {
            var lastItem = acc[acc.Count - 1];
            if (lastItem.Intersects(right))
            {
                lastItem.Merge(right);
                return acc;
            }
        }
        acc.Add(right);
        return acc;
    }));

Request 类本身应该负责诸如周末扩展之类的事情。这样可以确保您的请求不会中断。

【讨论】:

    【解决方案3】:

    Dictionary&lt;T, T&gt; 上的cachedDictionary.Contains 是一种 linq 方法,它使用蛮力搜索。这绝对是算法中最慢的部分。

    我建议更改内部循环的内容。不要尝试就地修改请求。相反,构建一个新的请求列表,仅在内部循环中遇到非连续一天时创建一个新请求。当下一个请求开始时,或者循环完成时设置请求的结束。

    【讨论】:

      猜你喜欢
      • 2010-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多