Как проверить, полностью ли мой диапазон дат покрыт ПОСЛЕДОВАТЕЛЬНЫМИ строками?

#c#

#c#

Вопрос:

Мне нужно проверить, полностью ли диапазон дат покрыт этой таблицей диапазонов дат, отсортированной в порядке возрастания dFrom, оба типа даты:

 dFrom  dTo
-----  -----
10/01  10/03
10/05  10/08
10/08  10/09
10/09  10/12
10/13  10/18
10/15  10/17
10/19  10/24
  

range A: 10/01-10/14 не полностью покрыто, потому что 10/04 отсутствует в таблице.

range B: 10/10-10/20 полностью покрыто.

Что я могу придумать, так это для заданного диапазона дат, такого как A и B, чтобы проверить, покрыт ли каждый день в таблице:

 var dRangeFrom = rangeFrom.Date; // use "var" as C# has no date type
var dRangeTo = rangeTo.Date;
int DaysCovered = 0;
int HowManyDays = (dRangeTo - dRangeFrom).TotalDays() 1;
int StartFromRow = 0;
while (dRangeFrom <= dRangeTo)
{
  for (int i=StartFromRow; i<table.rows.count; i  )
  {
    if (table.rows[i]["dFrom"] > dRangeFrom)  // optimization 1: no need to continue.
      break;
    if (dRangeFrom >= table.rows[i]["dFrom"] amp;amp; dRangeFrom <= table.rows[i]["dTo"])
    {
      DaysCovered  ;
      StartFromRow = i;   // optimization 2: next day comparison simply starts from here 
      break;
    }
  }
  dRangeFrom.AddDays(1);
} 
if (DaysCovered == HowManyDays)
  Console.Write("Totally covered");
else
  Console.Write("NOT");
  

Комментарии:

1. «C # не имеет типа даты», System. Дата-время?

2. var это просто неявный тип, который разрешается компилятором — это не сам фактический тип. Фактический тип DateTime . Если вы наведете курсор var , он покажет вам тип во всплывающей подсказке в большинстве IDE.

3. table dFrom и dTo не определены в примере кода. Пожалуйста, опубликуйте минимальный образец, иллюстрирующий проблему.

Ответ №1:

Одним из способов решить эту проблему было бы написать вспомогательный метод, который получает все дни в диапазоне:

 public static List<DateTime> GetDaysCovered(DateTime from, DateTime to)
{
    var result = new List<DateTime>();

    for (var i = 0; i < (to.Date - from.Date).TotalDays; i  )
    {
        result.Add(from.Date.AddDays(i));
    }

    return resu<
}
  

А затем мы можем объединить все диапазоны из таблицы вместе и посмотреть, соответствуют ли они дням в диапазоне, который мы пытаемся охватить:

 foreach (DataRow row in table.Rows)
{
    tableDates.AddRange(GetDaysCovered(
        row.Field<DateTime>("dFrom").Date, 
        row.Field<DateTime>("dTo").Date));
}

var rangeDates = GetDaysCovered(dRangeFrom, dRangeTo);

var missingDates = rangeDates
    .Where(rangeDate => !tableDates.Contains(rangeDate))
    .ToList();

if (missingDates.Any())
{
    Console.Write("These dates are not covered: ");
    Console.Write(string.Join(",", 
        missingDates.Select(date => date.ToShortDateString())));
}
else
{
    Console.Write("Totally covered");
}
  

Комментарии:

1. Потрясающий образец. Я, конечно, буду учиться. Я считаю, что с добавлением оптимизации в мой исходный код производительность и удобочитаемость улучшатся. Спасибо.

Ответ №2:

Наивное решение — проверять для каждой даты в диапазоне, покрыта ли она какой-либо строкой.

 var totallyCovered = true;

for (var date = rangeFrom.Date; date <= rangeTo.Date; date = date.AddDays(1)) 
{
    var covered = dates.Any(x => date >= x.dFrom amp;amp; date <= x.dTo);

    if (!covered)
    {
        totallyCovered = false;
        break;
    }
}

if (totallyCovered)
{
    Console.WriteLine("Totally covered.");
}
else
{
    Console.WriteLine("No.");
}
  

Это довольно долго и некрасиво, но, к счастью, вы можете поместить это в один запрос LINQ:

 var dateRange = Enumerable.Range(0, 1   rangeTo.Subtract(rangeFrom).Days)
          .Select(offset => rangeFrom.Date.AddDays(offset));
var totallyCovered = dateRange.All(d => dates.Any(x => d >= x.dFrom amp;amp; d <= x.dTo)); 
  

Примечание: это имеет временную сложность O(|range| * |rows|) , которая может быть слишком большой. Чтобы исправить это, вам пришлось бы использовать более сложную структуру данных, которая позволила бы вам запрашивать диапазоны за логарифмическое время, но поскольку ваш исходный образец также содержал вложенные циклы, я предполагаю, что в этом нет необходимости.

Комментарии:

1. Спасибо за образец. Поскольку LinQ фактически foreach находится за сценой, с добавлением 2 оптимизаций в мой исходный код, производительность в порядке. Также я считаю, что любому, кто читает мой код, легче понять алгоритм.

2. @Jeb50 Я согласен, производительность была бы проблемой, только если бы у вас были сотни тысяч строк.