У меня есть этот класс, представляющий период времени:
public class Period
{
public Period(DateTime dateFrom)
{
DateFrom = dateFrom;
}
public Period(DateTime dateFrom, DateTime? dateTo)
{
DateFrom = dateFrom;
DateTo = dateTo;
}
public DateTime DateFrom { get; set; }
public DateTime? DateTo { get; set; }
public bool IsOverlapping(Period other)
{
if (!DateTo.HasValue)
{
return DateFrom <= other.DateTo.Value;
}
if (!other.DateTo.HasValue)
{
return other.DateFrom <= DateTo.Value;
}
return DateFrom <= other.DateTo.Value && other.DateFrom <= DateTo.Value;
}
public bool IsFinite => DateTo.HasValue;
public bool IsInfinite => !IsFinite;
protected bool Equals(Period other)
{
return DateFrom.Equals(other.DateFrom) && Nullable.Equals(DateTo, other.DateTo);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Period) obj);
}
public override int GetHashCode()
{
return HashCode.Combine(DateFrom, DateTo);
}
}
Теперь у меня есть список периодов, и для каждого из них я должен выполнить сетевой вызов, поэтому, чтобы минимизировать их, я решил объединить все перекрывающиеся периоды.
Вот такой список:
- 2020-01-01 -> 2020-01-10
- 2020-02-05 -> 2020-02-10
- 2020-02-07 -> 2020-02-15
- 2020-02-13 -> 2020-02-20
- 2020-03-01 -> 2020-03-10
- 2020-03-25 -> 2020-03-31
- 2020-03-30 ->
Должно стать:
- 2020-01-01 -> 2020-01-10
- 2020-02-05 -> 2020-02-20
- 2020-03-01 -> 2020-03-10
- 2020-03-25 ->
Я пробовал этот код
periods.OrderBy(p => p.DateFrom.Value)
.Aggregate(new List<Period>(), (ps, p) =>
{
if (!ps.Any())
{
ps.Add(p);
return ps;
}
var last = ps.Last();
if (last.IsOverlapping(p))
{
if (last.IsInfinite || p.IsInfinite)
{
ps[ps.Count() - 1] = new Period(DateTimeHelpers.Min(last.DateFrom.Value, p.DateFrom.Value), null);
}
else
{
ps[ps.Count() - 1] = new Period(DateTimeHelpers.Min(last.DateFrom.Value, p.DateFrom.Value), DateTimeHelpers.Max(last.DateTo.Value, p.DateTo.Value));
}
return ps;
}
ps.Add(p);
return ps;
});
Он работает правильно, но меня это не устраивает, поэтому мне интересно, есть ли более производительный / элегантный / читаемый способ сделать это?
Я говорю не просто о рефакторинге для извлечения некоторых методов, а о принципиально другом решении, возможно, я пропустил полезный оператор LINQ.
Вот мой тест, если вы хотите его воспроизвести (MsTest + FluentAssertions). Период не в правильном порядке, чтобы гарантировать, что он будет обработан самим методом:
// Arrange
var periods = new List<Period>()
{
new Period(new DateTime(2020, 2, 13), new DateTime(2020, 2, 20)),
new Period(new DateTime(2020, 3, 1), new DateTime(2020, 3, 10)),
new Period(new DateTime(2020, 3, 25), new DateTime(2020, 3, 31)),
new Period(new DateTime(2020, 3, 30)),
new Period(new DateTime(2020, 1, 1), new DateTime(2020, 1, 10)),
new Period(new DateTime(2020, 2, 5), new DateTime(2020, 2, 10)),
new Period(new DateTime(2020, 2, 7), new DateTime(2020, 2, 15))
};
// Act
var mergedPeriods = Implementation(periods);
// Assert
mergedPeriods.Should().HaveCount(4);
mergedPeriods[0].Should().Be(new Period(new DateTime(2020, 1, 1), new DateTime(2020, 1, 10)));
mergedPeriods[1].Should().Be(new Period(new DateTime(2020, 2, 5), new DateTime(2020, 2, 20)));
mergedPeriods[2].Should().Be(new Period(new DateTime(2020, 3, 1), new DateTime(2020, 3, 10)));
mergedPeriods[3].Should().Be(new Period(new DateTime(2020, 3, 25)));
