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

后端 未结 10 2002
执念已碎
执念已碎 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:06

    This seems to works and is easy to understand.

        public static IEnumerable<Range<T>> Collapse<T>(this IEnumerable<Range<T>> me, IComparer<T> comparer)
        {
            List<Range<T>> orderdList = me.OrderBy(r => r.Start).ToList();
            List<Range<T>> newList = new List<Range<T>>();
    
            T max = orderdList[0].End;
            T min = orderdList[0].Start;
    
            foreach (var item in orderdList.Skip(1))
            {
                if (comparer.Compare(item.End, max) > 0 && comparer.Compare(item.Start, max) > 0)
                {
                    newList.Add(new Range<T> { Start = min, End = max });
                    min = item.Start;
                }
                max = comparer.Compare(max, item.End) > 0 ? max : item.End;
            }
            newList.Add(new Range<T>{Start=min,End=max});
    
            return newList;
        }
    

    Here is the variation which I mentioned in the comments. It's basically the same thing, but with some checking and yielding of the results instead of collecting in a list before returning.

        public static IEnumerable<Range<T>> Collapse<T>(this IEnumerable<Range<T>> ranges, IComparer<T> comparer)
        {
            if(ranges == null || !ranges.Any())
                yield break;
    
            if (comparer == null)
                comparer = Comparer<T>.Default;
    
            var orderdList = ranges.OrderBy(r => r.Start);
            var firstRange = orderdList.First();
    
            T min = firstRange.Start;
            T max = firstRange.End;
    
            foreach (var current in orderdList.Skip(1))
            {
                if (comparer.Compare(current.End, max) > 0 && comparer.Compare(current.Start, max) > 0)
                {
                    yield return Create(min, max);
                    min = current.Start;
                }
                max = comparer.Compare(max, current.End) > 0 ? max : current.End;
            }
            yield return Create(min, max);
        }
    
    0 讨论(0)
  • 2020-12-08 01:06

    This could probably be optimized...

    using System.Collections.Generic;
    using System.Linq;
    using System;
    static class Range
    {
        public static Range<T> Create<T>(T start, T end)
        {
            return new Range<T>(start, end);
        }
        public static IEnumerable<Range<T>> Normalize<T>(
            this IEnumerable<Range<T>> ranges)
        {
            return Normalize<T>(ranges, null);
        }
        public static IEnumerable<Range<T>> Normalize<T>(
            this IEnumerable<Range<T>> ranges, IComparer<T> comparer)
        {
            var list = ranges.ToList();
            if (comparer == null) comparer = Comparer<T>.Default;
            for (int i = list.Count - 1; i >= 0; i--)
            {
                var item = list[i];
    
                for (int j = 0; j < i; j++)
                {
                    Range<T>? newValue = TryMerge<T>(comparer, item, list[j]);
    
                    // did we find a useful transformation?
                    if (newValue != null)
                    {
                        list[j] = newValue.GetValueOrDefault();
                        list.RemoveAt(i);
                        break;
                    }
                }
            }
            list.Sort((x, y) =>
            {
                int t = comparer.Compare(x.Start, y.Start);
                if (t == 0) t = comparer.Compare(x.End, y.End);
                return t;
            });
            return list.AsEnumerable();
        }
    
        private static Range<T>? TryMerge<T>(IComparer<T> comparer, Range<T> item, Range<T> other)
        {
            if (comparer.Compare(other.End, item.Start) == 0)
            { // adjacent ranges
                return new Range<T>(other.Start, item.End);
            }
            if (comparer.Compare(item.End, other.Start) == 0)
            { // adjacent ranges
                return new Range<T>(item.Start, other.End);
            }
            if (comparer.Compare(item.Start, other.Start) <= 0
                && comparer.Compare(item.End, other.End) >= 0)
            { // item fully swalls other
                return item;
            }
            if (comparer.Compare(other.Start, item.Start) <= 0
                && comparer.Compare(other.End, item.End) >= 0)
            { // other fully swallows item
                return other;
            }
            if (comparer.Compare(item.Start, other.Start) <= 0
                && comparer.Compare(item.End, other.Start) >= 0
                && comparer.Compare(item.End, other.End) <= 0)
            { // partial overlap
                return new Range<T>(item.Start, other.End);
            }
            if (comparer.Compare(other.Start, item.Start) <= 0
                 && comparer.Compare(other.End, item.Start) >= 0
                && comparer.Compare(other.End, item.End) <= 0)
            { // partial overlap
                return new Range<T>(other.Start, item.End);
            }
            return null;
        }
    }
    public struct Range<T>
    {
        private readonly T start, end;
        public T Start { get { return start; } }
        public T End { get { return end; } }
        public Range(T start, T end)
        {
            this.start = start;
            this.end = end;
        }
        public override string ToString()
        {
            return start + " to " + end;
        }
    }
    
    static class Program
    {
        static void Main()
        {
            var data = new[] 
            {
                Range.Create(1,5), Range.Create(3,9),
                Range.Create(11,15), Range.Create(12,14),
                Range.Create(13,20)
            };
            var result = data.Normalize();
            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-08 01:09

    A Python solution for the non-verbosephile:

    ranges = [
      (11, 15),
      (3, 9),
      (12, 14),
      (13, 20),
      (1, 5)]
    
    result = []
    cur = None
    for start, stop in sorted(ranges): # sorts by start
      if cur is None:
        cur = (start, stop)
        continue
      cStart, cStop = cur
      if start <= cStop:
        cur = (cStart, max(stop, cStop))
      else:
        result.append(cur)
        cur = (start, stop)
    result.append(cur)
    
    print result
    
    0 讨论(0)
  • 2020-12-08 01:13
    static void Main(string[] args) {
        List<Range<int>> ranges = new List<Range<int>>() 
        {               
            new Range<int>(3,9),
            new Range<int>(1,5),
            new Range<int>(11,15),
            new Range<int>(12,14),
            new Range<int>(13,20),
        };
    
        var orderedRanges = ranges.OrderBy(r => r.Start);
        var lastRange = new Range<int>(orderedRanges.First().Start, orderedRanges.First().End);
    
        List<Range<int>> newranges = new List<Range<int>>();            
        newranges.Add(lastRange);
    
        foreach (var range in orderedRanges.Skip(1)) {
            if (range.Start >= lastRange.Start && range.Start <= lastRange.End && range.End > lastRange.End) {
                lastRange.End = range.End;
            }
            else if (range.Start > lastRange.End) {
                lastRange = new Range<int>(range.Start, range.End);
                newranges.Add(lastRange);
            }
        }
    
        foreach (var r in newranges) {
            Console.WriteLine("{0}, {1}", r.Start, r.End);
        }
    }
    

    Something like this. Didn't verify that it works with all inputs.

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

    Tossing another hat into the ring. Very much the same implementation as Gary W's (from which I got the sorted list approach), but done as a test case and with some helpful functions added to the Range class.

    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.Set;
    
    import edu.emory.mathcs.backport.java.util.Collections;
    
    import junit.framework.TestCase;
    
    public class Range2Test extends TestCase {
        public void testCollapse() throws Exception {
            Set<Range<Integer>> set = new HashSet<Range<Integer>>();
            set.add(new Range<Integer>(1, 5));
            set.add(new Range<Integer>(3, 9));
            set.add(new Range<Integer>(11, 15));
            set.add(new Range<Integer>(12, 14));
            set.add(new Range<Integer>(13, 20));
            Set<Range<Integer>> expected = new HashSet<Range<Integer>>();
            expected.add(new Range<Integer>(1, 9));
            expected.add(new Range<Integer>(11, 20));
            assertEquals(expected, collapse(set));
        }
    
        private static <T extends Comparable<T>> Set<Range<T>> collapse(Set<Range<T>> ranges) {
            if (ranges == null)
                return null;
            if (ranges.size() < 2)
                return new HashSet<Range<T>>(ranges);
            ArrayList<Range<T>> list = new ArrayList<Range<T>>(ranges);
            Collections.sort(list);
            Set<Range<T>> result = new HashSet<Range<T>>();
            Range<T> r = list.get(0);
            for (Range<T> range : list) 
                if (r.overlaps(range)) {
                    r = r.union(range);
                } else {
                    result.add(r);
                    r = range;
                }
            result.add(r);
            return result;
        }
    
        private static class Range<T extends Comparable<T>> implements Comparable<Range<T>> {
            public Range(T start, T end) {
                if (start == null || end == null)
                    throw new NullPointerException("Range requires start and end.");
                this.start = start;
                this.end = end;
            }
            public T    start;
            public T    end;
    
            private boolean contains(T t) {
                return start.compareTo(t) <= 0 && t.compareTo(end) <= 0;
            }
    
            public boolean overlaps(Range<T> that) {
                return this.contains(that.start) || that.contains(this.start);
            }
    
            public Range<T> union(Range<T> that) {
                T start = this.start.compareTo(that.start) < 0 ? this.start : that.start;
                T end = this.end.compareTo(that.end) > 0 ? this.end : that.end;
                return new Range<T>(start, end);
            }
    
            public String toString() {
                return String.format("%s - %s", start, end);
            }
    
            public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + end.hashCode();
                result = prime * result + start.hashCode();
                return result;
            }
    
            @SuppressWarnings("unchecked")
            public boolean equals(Object obj) {
            if (this == obj)                    return true;
            if (obj == null)                    return false;
            if (getClass() != obj.getClass())   return false;
            Range<T> that = (Range<T>) obj;
            return end.equals(that.end) && start.equals(that.start);
            }
    
            public int compareTo(Range<T> that) {
                int result = this.start.compareTo(that.start);
                if (result != 0)
                    return result;
                return this.end.compareTo(that.end);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-08 01:22

    This is a slight variation. I didn't need to collapse an unordered list, I wanted to maintain a sorted list instead. This is more efficient in my case. I am posting it here in case it is useful to anyone else reading this thread. Obviously can be made generic very easily.

            private static List<Tuple<int, int>> Insert(List<Tuple<int, int>> ranges, int startIndex, int endIndex)
            {
                if (ranges == null || ranges.Count == 0)
                    return new List<Tuple<int, int>> { new Tuple<int, int>(startIndex, endIndex) };
    
                var newIndex = ranges.Count;
                for (var i = 0; i < ranges.Count; i++)
                {
                    if (ranges[i].Item1 > startIndex)
                    {
                        newIndex = i;
                        break;
                    }
                }
    
                var min = ranges[0].Item1;
                var max = ranges[0].Item2;
    
                var newRanges = new List<Tuple<int, int>>();
                for (var i = 0; i <= ranges.Count; i++)
                {
                    int rangeStart;
                    int rangeEnd;
                    if (i == newIndex)
                    {
                        rangeStart = startIndex;
                        rangeEnd = endIndex;
                    }
                    else
                    {
                        var range = ranges[i > newIndex ? i - 1 : i];
                        rangeStart = range.Item1;
                        rangeEnd = range.Item2;
                    }
    
                    if (rangeStart > max && rangeEnd > max)
                    {
                        newRanges.Add(new Tuple<int, int>(min, max));
                        min = rangeStart;
                    }
                    max = rangeEnd > max ? rangeEnd : max;
                }
                newRanges.Add(new Tuple<int, int>(min, max));
    
                return newRanges;
            }
    
    0 讨论(0)
提交回复
热议问题