I have a method that gets a number of objects of this class
class Range
{
public T Start;
public T End;
}
In my case
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
}
Here is a simple looping impelmentation, but at least is clear.
-- 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;
}
}
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 :)
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