C#で時間帯重複チェック(応用編)
FromToを少し書きなおした。FromToの集合に対する集合演算はFromToExクラスに定義した。少し長くなったが気にしない。
(追記)トラックバック記事を見て、コードを整理した。
一応説明しておくと、FromToを開始が早い順に並べて、隣と比較していき、重複時間を調べる。
ただし、単に隣と比較するだけだと、下の状況の時に問題が起きる。
つまり、 { (f0, t0)∩(f1, t1) } ∪ { (f1, t1)∩(f2, t2) } = (f1, t1) ∪ (f2, t1) = (f1, t1) で、これは正しくない。
このような場合は(f1, t1)と(f2, t2)じゃなくて、終了時間が遅いほう、つまり(f0, t0)と(f2, t2)を比較する。(引き算して、(t1, t0)と(f2, t2)を比較するロジックでもOKだけど、今回はそうしていない。)
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main(string[] args) { Console.WriteLine("Q1: " + new[] { new FromTo(12, 0, 13, 0), new FromTo(10, 0, 12, 15) }.Intersection().Normalize().Join(", ")); Console.WriteLine("Q2: " + new[] { new FromTo(16, 0, 23, 0), new FromTo(9, 0, 17, 0), new FromTo(5, 0, 10, 30) }.Intersection().Normalize().Join(", ")); Console.WriteLine("Q3: " + new[] { new FromTo(12, 0, 23, 0), new FromTo(13, 0, 14, 0), new FromTo(15, 0, 16, 0), new FromTo(17, 0, 18, 0), new FromTo(19, 0, 20, 0), new FromTo(21, 0, 22, 0) }.Intersection().Normalize().Join(", ")); Console.WriteLine("Q4: " + new[] { new FromTo(10, 0, 12, 0), new FromTo(11, 0, 11, 30), new FromTo(10, 30, 11, 15) }.Intersection().Normalize().Join(", ")); Console.WriteLine("Q5: " + new[] { new FromTo(9, 0, 17, 0), new FromTo(19, 0, 21, 0) }.Intersection().Normalize().Join(", ")); } } struct FromTo { public readonly TimeSpan From; public readonly TimeSpan To; public FromTo(TimeSpan from, TimeSpan to) { if (!Validate(from, to)) throw new ArgumentException(); this.From = from; this.To = to; } public FromTo(int fromHours, int fromMinutes, int toHours, int toMinutes) : this(Convert(fromHours, fromMinutes), Convert(toHours, toMinutes)) { } private static TimeSpan Convert(int hours, int minutes) { TimeSpan result = new TimeSpan(hours, minutes, 0); if (result.Hours != hours || result.Minutes != minutes) throw new ArgumentException(); return result; } private static bool Validate(TimeSpan from, TimeSpan to) { return (TimeSpan.Zero <= from && from <= to && to <= new TimeSpan(24, 0, 0)); } public bool IsEmpty { get { return this.From == this.To; } } public override string ToString() { return string.Format( "{0:00}:{1:00}-{2:00}:{3:00}", this.From.Hours, this.From.Minutes, this.To.Hours, this.To.Minutes); } } static class FromToEx { public static string Join(this IEnumerable<FromTo> source, string separator) { return string.Join(separator, source.Select(x => x.ToString()).ToArray()); } public static IEnumerable<FromTo> Normalize(this IEnumerable<FromTo> source) { var ordered = source.OrderBy(x => x.From); var prev = default(FromTo); foreach (var current in ordered) { if (prev.To <= current.From) { // prevとcurrentが重なっていない if (!prev.IsEmpty) yield return prev; prev = current; } else { // prevとcurrentが重なっているので、結合する var from = prev.From; var to = (prev.To < current.To) ? current.To : prev.To; prev = new FromTo(from, to); } } if (!prev.IsEmpty) { yield return prev; } } public static IEnumerable<FromTo> Intersection(this IEnumerable<FromTo> source) { var ordered = source.OrderBy(x => x.From); var prev = default(FromTo); foreach (var current in ordered) { if (prev.To <= current.From) { // prevとcurrentが重なっていない prev = current; } else { // prevとcurrentが重なっているので、重なっている部分を取り出す var from = current.From; var to = (prev.To < current.To) ? prev.To : current.To; yield return new FromTo(from, to); prev = (prev.To < current.To) ? current : prev; } } } }