平々毎々(アーカイブ)

はてなダイアリーのアーカイブです。

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だけど、今回はそうしていない。)

IdeOneで表示する

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;
            }
        }
    }
}