What's a good, generic algorithm for collapsing a set of potentially-overlapping ranges?

后端 未结 10 2003
执念已碎
执念已碎 2020-12-08 00:28

I have a method that gets a number of objects of this class

class Range
{
    public T Start;
    public T End;
}

In my case

相关标签:
10条回答
  • 2020-12-08 01:27

    Algorithm in Go based on the Python answer:

    package main
    
    import "sort"
    import "fmt"
    
    type TupleList [][]int
    
    // Methods required by sort.Interface.
    func (s TupleList) Len() int {
        return len(s)
    }
    func (s TupleList) Less(i, j int) bool {
        return s[i][1] < s[j][1]
    }
    func (s TupleList) Swap(i, j int) {
        s[i], s[j] = s[j], s[i]
    }
    
    func main() {
    
        ranges :=
            TupleList{
                {11, 15},
                {3, 9},
                {12, 14},
                {13, 20},
                {1, 5}}
    
        fmt.Print(ranges)
        sort.Sort(ranges)
        fmt.Print("\n")
        fmt.Print(ranges)
        fmt.Print("\n")
        result := TupleList{}
    
        var cur []int
        for _, t := range ranges {
            if cur == nil {
                cur = t
                continue
            }
            cStart, cStop := cur[0], cur[1]
            if t[0] <= cStop {
                cur = []int{cStart, max(t[1], cStop)}
            } else {
                result = append(result, cur)
                cur = t
            }
        }
        result = append(result, cur)
        fmt.Print(result)
    }
    
    func max(v1, v2 int) int {
        if v1 <= v2 {
            return v2
        }
        return v1
    }
    
    0 讨论(0)
  • 2020-12-08 01:29

    Here is a simple looping impelmentation, but at least is clear.

    • It works for DateTime as well as Int, in my simple tests
    • Most of the complexity is in the Overlap/Combine methods on the range
    • The algorithm is actually easily understandable, no floating vars
    • Adds some ability to the Range class which is probably useful in general

    -- this line intentionally meaningless, to fix markdown problem --

    public static class CollapseRange
    {
        public static IEnumerable<Range<T>> Collapse<T>(this IEnumerable<Range<T>> me)
            where T:struct
        {
            var result = new List<Range<T>>();
            var sorted = me.OrderBy(x => x.Start).ToList();
            do {
                var first = sorted.FirstOrDefault();
                sorted.Remove(first);
                while (sorted.Any(x => x.Overlap(first))) {
                    var other = sorted.FirstOrDefault(x => x.Overlap(first));
                    first = first.Combine(other);
                    sorted.Remove(other);
                }
                result.Add(first);
            } while (sorted.Count > 0);
            return result;
        }
    }
    
    [DebuggerDisplay("Range {Start} - {End}")]
    public class Range<T> where T : struct
    {
        public T Start { set; get; }
        public T End { set; get; }
        public bool Overlap(Range<T> other)
        {
            return (Within(other.Start) || Within(other.End) || other.Within(this.Start) || other.Within(this.End));
        }
        public bool Within(T point)
        {
            var Comp = Comparer<T>.Default;
            var st = Comp.Compare(point, this.Start);
            var ed = Comp.Compare(this.End, point);
            return (st >= 0 && ed >= 0);
        }
        /// <summary>Combines to ranges, updating the current range</summary>
        public void Merge(Range<T> other)
        {
            var Comp = Comparer<T>.Default;
            if (Comp.Compare(this.Start, other.Start) > 0) this.Start = other.Start;
            if (Comp.Compare(other.End, this.End) > 0) this.End = other.End;
        }
        /// <summary>Combines to ranges, returning a new range in their place</summary>
        public Range<T> Combine(Range<T> other)
        {
            var Comp = Comparer<T>.Default;
            var newRange = new Range<T>() { Start = this.Start, End = this.End };
            newRange.Start = (Comp.Compare(this.Start, other.Start) > 0) ? other.Start : this.Start;
            newRange.End = (Comp.Compare(other.End, this.End) > 0) ? other.End : this.End;
            return newRange;
        }
    }
    
    0 讨论(0)
  • 2020-12-08 01:30

    The idea of collapsing a list just screamed out "reduce" to me. It didn't end up quite as elegant as I had hoped though.

    def collapse(output,next_range):
        last_start,last_end = output[-1]
        next_start, next_end = next_range
        if (next_start <= last_end):
            output[-1] = (last_start, max(next_end, last_end))
        else:
            output.append(next_range)
        return output
    
    ranges = [
      (11, 15),
      (3, 9),
      (12, 14),
      (13, 20),
      (1, 5)]
    
    ranges.sort()
    result = [ranges.pop(0)]
    reduce(collapse, ranges,result)
    
    print result
    

    thanks to yairchu for typing in the data so I could cut and paste it :)

    0 讨论(0)
  • 2020-12-08 01:30

    A ruby version. Sort the ranges before merge seems to be a good idea.

    def merge a , b
        return b if a.nil?
        if b.begin <= a.end
            (a.begin..b.end)
        el
            [a , b ]     #no overlap
        end
    end
    
    ranges = [(1..5),(11..15),(3..9),(12..14),(13..20)]
    sorted_ranges = ranges.sort_by {|r| r.begin}   #sorted by the start of the range
    
    merged_ranges = sorted_ranges.inject([]) do |m , r|
           last = m.pop
           m << merge(last , r)   
           m.flatten
    end
    
    puts merged_ranges
    
    0 讨论(0)
提交回复
热议问题