c# - 从每个用户提供的时间列表中找到所有共同时间

标签 c# algorithm linq

我问过this之前的问题。这个想法是一样的,只是我必须在特定的 TimeSpan 中找到所有的公共(public)时间。

背景

假设,

我想见一些人,我说我想见日期时间 X(2014-02-16 09:00:00.000) 到日期时间 Y(2014-02-26 05:00:00.000) 之间的某些人。我说我希望 session 至少持续 N 小时。

然后我想见的人会回复说我将在以下日期有空:

Date1(Certain date from certain start time to certain end time), 

Date2(certain date from certain time to certain time),

...

等等。

目标

然后我必须查找是否存在包含在所有用户响应中的时间范围。

让我们考虑这些是响应

Attendee1(Some GuidId):

Response1: Start Time=2014-02-23 09:00 AM, EndTime = 2014-02-23 11:00 AM,

Response2 : Start Time=2014-02-24 10:00 AM, EndTime = 2014-02-24 12:00 PM,

Response3 : Start Time=2014-02-25 10:00 AM, EndTime = 2014-02-25 11:00 AM,

Response4 : Start Time=2014-02-23 01:00 PM, EndTime = 2014-02-17 5:00 PM

Attendee2(Some GuidId):

Response1: Start Time=2014-02-22 09:00 AM, EndTime = 2014-02-22 05:00 PM,

Response2 : Start Time=2014-02-23 09:00 AM, EndTime = 2014-02-23 05:00 PM,

Response3 : Start Time=2014-02-25 09:00 AM, EndTime = 2014-02-25 12:00 PM,

Attendee3(Some GuidId):

Response1: Start Time=2014-02-22 11:00 AM, EndTime = 2014-02-22 02:00 PM,

Response2 : Start Time=2014-02-23 04:00 PM, EndTime = 2014-02-23 03:00 PM,

Response3 : Start Time=2014-02-23 4:30 PM, EndTime = 2014-02-23 05:30 PM,

Response4 : Start Time=2014-02-24 02:00 AM, EndTime = 2014-02-24 05:00 PM,

Response5 : Start Time=2014-02-25 11:00 AM, EndTime = 2014-02-25 12:00 PM,

所以,如果可能的话,我应该想出一些东西来说明这里找到了匹配项,如果没有找到匹配项,那么只查找所有用户的公共(public)时间是否存在。

时间 X 到时间 Y(X 和 Y 之间的差异应至少为提到的 TimeSpan):本场比赛中的参加者人数 = N(type = int, 1 或 2 个或尽可能多的匹配项)

Time A to Time B : Number of Attendee in this match = N

...

等等

附言:

它是一个 MVC 5.1 应用程序,数据库是使用代码优先方法创建的。 所以在数据库中有一个名为 Appointment 的表,它存储 StartDateTime 和 EndDateTime

这是我的DataModels

public class Appointment
{

    [Key]
    public Guid Id { get; set; }

    public virtual ICollection<Attendee> Attendees { get; set; }

    public DateTime StartDateTime { get; set; }
    public DateTime EndDateTime { get; set; }    
    public TimeSpan MinAppointmentDuration { get; set; }  

}

在这些日期之间,与会者(与会者)将给出他们的回应。每个人(将响应的人),他们的信息存储在名为 Attendees 的数据库表中

public class Attendee
{
    public Guid AttendeeId { get; set; }
    public virtual ICollection<Response> Responses { get; set; } 
}

对于每个用户,他们的响应存储在 Responses 表中,其模型如下所示

public class Response
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public Guid AttendeeId { get; set; }
    public DateTime StartDateTime { get; set; }
    public DateTime EndDateTime { get; set; }

}

这就是我所做的,但它不起作用。

public class CommonTime
{
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public TimeSpan MinAppointmenttime { get; set; }
    public int NumAttendees 
    { 
        get { return Responses.Select(x => x.AttendeeId).Distinct().Count(); } 
    }
    public List<DataModels.Response> Responses { get; set; }

    public CommonTime(DataModels.Response response, TimeSpan time)
    {
        Responses = new List<DataModels.Response>();
        Start = response.StartDateTime;
        End = response.EndDateTime;
        MinAppointmenttime = time;
    }

    public void MergeCommonTime(DataModels.Response response)
    {
        if(Start <= response.StartDateTime && response.EndDateTime<=End)
        {
            Start = response.StartDateTime;
            End = response.EndDateTime;
            if((End-Start)>=MinAppointmenttime)
            {
                Responses.Add(response);
            }

        }
    }

    public List<CommonTime> FindCommonMatches(Guid appointmentId)
    {
        var appointment = _db.Appointments.Find(appointmentId);
        var attendees = appointment.Attendees.ToList();
        var matches = new List<CommonTime>();
        bool isFirstAttendee = true;
        foreach (var attendee in attendees)
        {
            if (isFirstAttendee)
            {
                foreach (var response in attendee.Responses)
                {
                    matches.Add(new CommonTime(response, appointment.MinAppointmentDuration));

                }
                isFirstAttendee = false;
            }
            else
            {
                foreach (var response in attendee.Responses)
                {
                    matches.ForEach(x => x.MergeCommonTime(response));
                }
            }
        }

        return matches;
    }

}

所以在这种情况下,如果 Attendee X(带有一些 Guid Id)提供他/她的可用性作为 enter image description here

与会者 Y 提供他/她的可用性

enter image description here

这是我得到的常见匹配

enter image description here enter image description here enter image description here enter image description here

正如我所解释的那样,这不是我想要的。

那么,我应该怎么做才能得到我想要的。


编辑:

基于 Zache 的回答

    public class Meeting
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }

        public List<DataModels.Attendee> Attendees { get; set; }

    }

    public class Requirement
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }

        public TimeSpan MinHours { get; set; }

        public int MinAttendees { get; set; }

        public IEnumerable<Meeting> Meetings()
        {
            var possibleMeetings = new List<Meeting>();
            var availableHours = (End - Start).TotalHours;

            for (var i = 0; i < availableHours - MinHours.Hours; i++)
                yield return new Meeting
                {
                    Start = Start.AddHours(i),
                    End = Start.AddHours(i+MinHours.Hours)
                };
        }
    }

    public class Scheduler
    {
        public IEnumerable<Meeting> Schedule(Requirement req, List<DataModels.Attendee> attendees)
        {
            var fullMatches = new List<Meeting>();
            var partialMatches = new List<Meeting>();

            foreach (var m in req.Meetings())
            {
                foreach (var a in attendees)
                {
                    if (fullMatches.Any())
                    {
                        if (a.Responses.Any(r => r.StartDateTime <= m.Start && r.EndDateTime >= m.End))
                        {
                            if (m.Attendees == null)
                            {
                                m.Attendees = new List<DataModels.Attendee> { a };
                            }
                            else
                            {
                                m.Attendees.Add(a);
                            }
                        }
                        else
                        {
                            break; // If we found one full match we aren't interested in the partials anymore.
                        }
                    }
                    else
                    {
                        if (a.Responses.Any(r => r.StartDateTime <= m.Start && r.EndDateTime >= m.End))
                        {
                            if (m.Attendees == null)
                            {
                                m.Attendees = new List<DataModels.Attendee> { a };
                            }
                            else
                            {
                                m.Attendees.Add(a);
                            }
                        }
                    }
                }

                if (m.Attendees != null)
                {
                    if (m.Attendees.Count == attendees.Count)
                        fullMatches.Add(m);
                    else if (m.Attendees.Count >= req.MinAttendees)
                        partialMatches.Add(m);
                }
            }

            return fullMatches.Any() ? fullMatches : partialMatches;
        }
    }
}

存储库

public IEnumerable<Meeting> FindCommonMatches(Guid appointmentId)
{
    var appointment = _db.Appointments.Find(appointmentId);
    var attendees = appointment.Attendees.Where(a => a.HasResponded == true).ToList();
    var req = new Requirement
    {
        Start = appointment.StartDateTime,
        End = appointment.EndDateTime,
        MinHours = appointment.MinAppointmentDuration,
        MinAttendees = 1
    };
    var schedule = new Scheduler();
    var schedules = schedule.Schedule(req, attendees);
    return schedules;
 }

开始日期:2/24/2014 11:30:00 AM//我在创建约会时设置的

结束日期:2/25/2014 11:30:00 AM

当第一个用户响应以下数据时:

enter image description here

匹配结果:

enter image description here

enter image description here

当第二个与会者回复以下数据时

enter image description here

匹配结果

enter image description here

enter image description here

这里常见的匹配应该是:

Match1: 2/24/2014 10:00:00 AM   to   2/24/2014 11:00:00 AM
 Match2: 2/25/2014 9:00:00 AM   to   2/25/2014 11:00:00 AM

最佳答案

以下 GetMeetingWindows 函数将返回所有匹配窗口和可用与会者的列表。然后可以根据结果的需要应用全部或最少参加者规定,例如

GetMeetingWindows(attendees, TimeSpan.FromMinutes(60)).Where(x => x.AvailableAttendees.Count() == attendees.Count());

我假设提供的与会者响应已经考虑到 session 可用的总体时间范围,但如果不是这种情况,则可以将此限制添加为额外的与会者,然后在结果中对其进行过滤,例如

GetMeetingWindows(...).Where(x => x.AvailableAttendees.Contains(meetingRoom));

代码:

public class Attendee
{
    public ICollection<Response> Responses { get; set; } 
}

public class Response
{
    public DateTime StartDateTime { get; set; }
    public DateTime EndDateTime { get; set; }
}

public class Window
{
    public DateTime StartDateTime { get; set; }
    public DateTime EndDateTime { get; set; }
    public IEnumerable<Attendee> AvailableAttendees { get; set; }   
}

public IEnumerable<Window> GetMeetingWindows(IEnumerable<Attendee> attendees, TimeSpan meetingDuration)
{
    var windows = new List<Window>();
    var responses = attendees.SelectMany(x => x.Responses).Where(x => x.EndDateTime - x.StartDateTime >= meetingDuration);

    foreach(var time in (responses.Select(x => x.StartDateTime)).Distinct())
    {
        var matches = attendees.Select(x => new { 
            Attendee = x, 
            MatchingAvailabilities = x.Responses.Where(y => y.StartDateTime <= time && y.EndDateTime >= time.Add(meetingDuration)) 
        });

        windows.Add(new Window { 
            StartDateTime = time, 
            EndDateTime = matches.SelectMany(x => x.MatchingAvailabilities).Min(x => x.EndDateTime), 
            AvailableAttendees = matches.Where(y => y.MatchingAvailabilities.Any()).Select(x => x.Attendee) 
        });
    }

    foreach(var time in (responses.Select(x => x.EndDateTime)).Distinct())
    {
        var matches = attendees.Select(x => new { 
            Attendee = x, 
            MatchingAvailabilities = x.Responses.Where(y => y.EndDateTime >= time && y.StartDateTime <= time.Add(-meetingDuration)) 
        });

        windows.Add(new Window { 
            EndDateTime = time, 
            StartDateTime = matches.SelectMany(x => x.MatchingAvailabilities).Max(x => x.StartDateTime), 
            AvailableAttendees = matches.Where(y => y.MatchingAvailabilities.Any()).Select(x => x.Attendee) 
        });
    }

    return windows.GroupBy(x => new { x.StartDateTime, x.EndDateTime }).Select(x => x.First()).OrderBy(x => x.StartDateTime).ThenBy(x => x.EndDateTime);
}

public void Test() 
{
    var attendees = new List<Attendee>();
    attendees.Add(new Attendee { Responses = new[] { 
        new Response { StartDateTime = DateTime.Parse("2014-02-24 9:00:00 AM"), EndDateTime = DateTime.Parse("2014-02-24 11:00:00 AM") },
        new Response { StartDateTime = DateTime.Parse("2014-02-24 2:00:00 PM"), EndDateTime = DateTime.Parse("2014-02-24 4:00:00 PM") },
        new Response { StartDateTime = DateTime.Parse("2014-02-25 9:00:00 AM"), EndDateTime = DateTime.Parse("2014-02-25 11:00:00 AM") },
        new Response { StartDateTime = DateTime.Parse("2014-02-25 3:00:00 PM"), EndDateTime = DateTime.Parse("2014-02-25 5:00:00 PM") }
    }});
    attendees.Add(new Attendee { Responses = new[] { 
        new Response { StartDateTime = DateTime.Parse("2014-02-24 10:00:00 AM"), EndDateTime = DateTime.Parse("2014-02-24 11:00:00 AM") },
        new Response { StartDateTime = DateTime.Parse("2014-02-24 4:00:00 PM"), EndDateTime = DateTime.Parse("2014-02-24 5:00:00 PM") },
        new Response { StartDateTime = DateTime.Parse("2014-02-25 9:00:00 AM"), EndDateTime = DateTime.Parse("2014-02-25 11:00:00 AM") }
    }});

    var windows = GetMeetingWindows(attendees, TimeSpan.FromMinutes(60));
    foreach(var window in windows)
    {
        Console.WriteLine(String.Format("Start: {0:yyyy-MM-dd HH:mm}, End: {1:yyyy-MM-dd HH:mm}, AttendeeCount: {2}", window.StartDateTime, window.EndDateTime, window.AvailableAttendees.Count()));
    }
}

关于c# - 从每个用户提供的时间列表中找到所有共同时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21931540/

相关文章:

c# - 正则表达式 - 转义转义字符

c# - Asp.net MVC 在不使用 html 助手的情况下重新填充表单

c++ - 没有 if-else 语句的递归二分查找 C++

java 列表类型变量

c# - 如何将字符串从 C# 传递到 javascript 变量

javascript - 在类内部定义的函数上获取引用错误 "ReferenceError: insertLevelOrder is not defined"

xml - LINQ to XML 和 Distinct 自定义类

ASP.NET - 通过 SiteMapNode.ChildNodes 枚举

c# - LINQ 查询查询具有数组成员的列表

c# - 如何使用 EventReceiver 从 C# 中的 SharePoint 列表中提取 DateTime 值?