间隔集合的数据结构

时间:2015-10-12 06:32:53

标签: java data-structures

我正在寻找基于Java的数据结构,该结构管理基于时间/日期的收集间隔(最好是Joda时间),这样对于添加到集合的每个间隔,数据结构返回所添加的子间隔(s)间隔,尚未在数据结构中并巩固间隔。

现在就集合理论而言,这将是容易的,即返回值将是“待添加”\“现有”,并且所得到的结构将是“待加”的“现有”联合。当然,我当然可以使用一组离散点来模拟日期/时间间隔,但这似乎并不真正有效。

所以我正在寻找现有的数据结构,它已经开箱即用使用间隔提供了这些设置操作。

只是为了澄清,这里是我正在寻找的例证。

  • 现有:时间间隔的集合
  • 待添加:应添加到集合中的间隔
  • 返回值:应添加的区间的子区间(尚未包含在数据结构中)
  • 结果:间隔的集合,包括刚刚添加的间隔
//       case 0
//       existing         *********************************************
//       to be added                      ********
//       return value                     --empty--  
//       result           *********************************************
//       
//       case 1
//       existing             *****************************************
//       to be added      ************
//       return value     ****
//       result           *********************************************
//       
//       case 2
//       existing               ***************************************
//       to be added      ****
//       return value     ****
//       result           ****  ***************************************
//       
//       case 3
//       existing         *****************************************
//       to be added                                       ************
//       return value                                              ****
//       result           *********************************************
//       
//       case 4
//       existing         ***************************************
//       to be added                                               ****
//       return value                                              ****
//       result           ***************************************  ****
//       
//       case 5
//       existing         *****************             ***************
//       to be added                           ****
//       return value                          ****
//       result           *****************    ****     ***************
//       
//       case 6
//       existing         *****************             ***************
//       to be added                    ********
//       return value                      *****
//       result           **********************        ***************
//       
//       case 7
//       existing         *****************             ***************
//       to be added                               ********
//       return value                              *****
//       result           *****************        ********************
//       
//       case 8
//       existing         *****************    ****     ***************
//       to be added                     *****************
//       return value                      ****    *****
//       result           *********************************************
//       
//       ......
//

2 个答案:

答案 0 :(得分:3)

没有现成的结构,至少在stdlib或commons中没有,但你可以使用NavigableMapTreeMap)相对容易地实现某些功能。特别是floorEntry() / ceilingEntry()subMap()等方法应该对此有用。

在我们的应用程序中,我们有一个范围聚合器类,用于计算范围的分配总和。即基本输入是一组(可能重叠)日期范围,其中附有数字。输出是相应的非重叠范围的集合,输入的数字在输入范围重叠的位置相加。它少于100行代码并使用TreeMap

答案 1 :(得分:1)

Guava Ranges可能会派上用场。他们已经描述了范围/间隔,其中一些实用方法有助于创建数据结构以维持这些间隔,如问题中所述。

事实上,它们比这里所需的间隔要强大得多,假设 all 的间隔是closed intervals,其中包含端点。但我猜这里没有理由重新发明轮子。

所需数据结构所需的操作是

  • 计算间隔的并集。这已由Range#span方法
  • 提供
  • 检测区间是否重叠或至少是连接在一起,还是相互包围。这也是Range#isConnectedRange#encloses方法提供的。
  • 计算间隔之间的差异

最后一个是Ranges类提供的 - 原因很简单:两个范围之间的差异可能不是单个范围,但一个 两个 范围。但它可以很容易地使用效用函数构建,它返回List范围,包含0 ... 2个元素,具体取决于重叠配置。

以下是所需数​​据结构的可能实现:

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import com.google.common.collect.Range;

class Ranges
{
    // Computes the result of subtracting all the given ranges from
    // the given range.
    static <T extends Comparable<? super T>> List<Range<T>> subtractAll(
        Range<T> r, List<Range<T>> ranges)
    {
        List<Range<T>> currentList = Collections.singletonList(r);
        for (Range<T> other : ranges)
        {
            List<Range<T>> nextList = new ArrayList<Range<T>>();
            for (int i=0; i<currentList.size(); i++)
            {
                Range<T> current = currentList.get(i);
                List<Range<T>> differences = subtract(current, other);
                nextList.addAll(differences);
            }
            currentList = nextList;
        }
        return currentList;
    }

    // Computes the result of subtracting range r1 from
    // the given range r0. The result will be 
    // - an empty list if r1 encloses r0 
    // - an two-element list if r0 encloses r1
    // - a list containing r0 if the ranges are disjoint
    // - a single-element list otherwise 
    static <T extends Comparable<? super T>> List<Range<T>> subtract(
        Range<T> r0, Range<T> r1)
    {
        T min0 = r0.lowerEndpoint();
        T max0 = r0.upperEndpoint();
        T min1 = r1.lowerEndpoint();
        T max1 = r1.upperEndpoint();
        if (r0.encloses(r1))
        {
            List<Range<T>> result = 
                new ArrayList<Range<T>>();
            result.add(Range.closed(min0, min1));
            result.add(Range.closed(max1, max0));
            return result;
        }
        else if (r1.encloses(r0))
        {
            return Collections.emptyList();
        }
        else if (r0.isConnected(r1))
        {
            T min = null;
            T max = null;
            if (min0.compareTo(min1) < 0)
            {
                min = min0;
                max = min1;
            }
            else
            {
                min = max1;
                max = max0;
            }
            return Collections.singletonList(Range.closed(min, max));
        }
        return Collections.singletonList(r0);
    }

    // "Normalize" the given list of ranges: As long as two of
    // them are connected, they are replaced by their "span" (union)
    static <T extends Comparable<? super T>> List<Range<T>> normalize(
        List<Range<T>> ranges)
    {
        List<Range<T>> currentList = new ArrayList<Range<T>>(ranges);
        while (true)
        {
            List<Range<T>> toRemove = new ArrayList<Range<T>>();
            List<Range<T>> toAdd = new ArrayList<Range<T>>();
            for (int i=0; i<currentList.size(); i++)
            {
                for (int j=i+1; j<currentList.size(); j++)
                {
                    Range<T> r0 = currentList.get(i);
                    Range<T> r1 = currentList.get(j);
                    if (r0.isConnected(r1))
                    {
                        toRemove.add(r0);
                        toRemove.add(r1);
                        toAdd.add(r0.span(r1));
                    }
                }
            }
            if (toAdd.isEmpty())
            {
                break;
            }
            Set<Range<T>> set = new LinkedHashSet<Range<T>>();
            set.addAll(currentList);
            set.removeAll(toRemove);
            set.addAll(toAdd);
            currentList = new ArrayList<Range<T>>(set);
        }
        return currentList;
    }

}


class RangesContainer<T extends Comparable<? super T>>
{
    private List<Range<T>> ranges;

    RangesContainer()
    {
        ranges = new ArrayList<Range<T>>();
    }


    public List<Range<T>> add(Range<T> newRange)
    {
        if (ranges.stream().anyMatch(r -> r.encloses(newRange)))
        {
            return Collections.emptyList();
        }
        if (ranges.stream().noneMatch(r -> r.isConnected(newRange)))
        {
            ranges.add(newRange);
            ranges = Ranges.normalize(ranges);
            return Collections.singletonList(newRange);
        }
        List<Range<T>> differences = Ranges.subtractAll(newRange, ranges);
        ranges.add(newRange);
        ranges = Ranges.normalize(ranges);
        return differences;
    }


    List<Range<T>> getRanges()
    {
        return Collections.unmodifiableList(ranges);
    }

}

public class RangesTest
{
    static final LocalDateTime ZERO = 
        LocalDateTime.of(2015, 1, 1, 0, 0);

    public static void main(String[] args)
    {
        testCase0();
        testCase1();
        testCase2();
        testCase3();
        testCase4();
        testCase5();
        testCase6();
        testCase7();
        testCase8();
    }


    private static void testCase0()
    {
        System.out.println("Test case 0");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(45));

        rc.add(existing0);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(16),
            ZERO.plusMinutes(24));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }

    private static void testCase1()
    {
        System.out.println("Test case 1");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(4),
            ZERO.plusMinutes(45));

        rc.add(existing0);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(12));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }

    private static void testCase2()
    {
        System.out.println("Test case 2");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(6),
            ZERO.plusMinutes(45));

        rc.add(existing0);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(4));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }

    private static void testCase3()
    {
        System.out.println("Test case 3");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(41));

        rc.add(existing0);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(33),
            ZERO.plusMinutes(45));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }


    private static void testCase4()
    {
        System.out.println("Test case 4");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(39));

        rc.add(existing0);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(41),
            ZERO.plusMinutes(45));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }

    private static void testCase5()
    {
        System.out.println("Test case 5");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(17));
        Range<LocalDateTime> existing1 = Range.closed(
            ZERO.plusMinutes(30),
            ZERO.plusMinutes(45));

        rc.add(existing0);
        rc.add(existing1);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(21),
            ZERO.plusMinutes(25));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }


    private static void testCase6()
    {
        System.out.println("Test case 6");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(17));
        Range<LocalDateTime> existing1 = Range.closed(
            ZERO.plusMinutes(30),
            ZERO.plusMinutes(45));

        rc.add(existing0);
        rc.add(existing1);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(14),
            ZERO.plusMinutes(22));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }


    private static void testCase7()
    {
        System.out.println("Test case 7");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(17));
        Range<LocalDateTime> existing1 = Range.closed(
            ZERO.plusMinutes(30),
            ZERO.plusMinutes(45));

        rc.add(existing0);
        rc.add(existing1);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(25),
            ZERO.plusMinutes(33));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }

    private static void testCase8()
    {
        System.out.println("Test case 8");

        RangesContainer<LocalDateTime> rc = 
            new RangesContainer<LocalDateTime>();

        Range<LocalDateTime> existing0 = Range.closed(
            ZERO.plusMinutes(0),
            ZERO.plusMinutes(17));
        Range<LocalDateTime> existing1 = Range.closed(
            ZERO.plusMinutes(21),
            ZERO.plusMinutes(25));
        Range<LocalDateTime> existing2 = Range.closed(
            ZERO.plusMinutes(30),
            ZERO.plusMinutes(45));

        rc.add(existing0);
        rc.add(existing1);
        rc.add(existing2);

        Range<LocalDateTime> added = Range.closed(
            ZERO.plusMinutes(15),
            ZERO.plusMinutes(31));

        System.out.println("Existing:");
        print(rc.getRanges());

        System.out.println("Adding:");
        print("", added);

        List<Range<LocalDateTime>> returnValue = rc.add(added);

        System.out.println("Returned:");
        print(returnValue);

        System.out.println("Result");
        print(rc.getRanges());

        System.out.println("\n");
    }


    static void print(List<Range<LocalDateTime>> list)
    {
        for (int i=0; i<list.size(); i++)
        {
            Range<LocalDateTime> range = list.get(i);
            String message = String.format("%3d", i);
            print(message, range);
        }
    }

    static void print(String message, LocalDateTime t0, LocalDateTime t1)
    {
        long minutes0 = ZERO.until(t0, ChronoUnit.MINUTES);
        long minutes1 = ZERO.until(t1, ChronoUnit.MINUTES);
        System.out.printf("%10s:", message);
        for (long i=0; i<minutes0; i++)
        {
            System.out.print(" ");
        }
        for (long i=minutes0; i<minutes1; i++)
        {
            System.out.print("*");
        }
        System.out.println();
    }

    static void print(String message, Range<LocalDateTime> r)
    {
        print(message, r.lowerEndpoint(), r.upperEndpoint());
    }

}

输出如下:

Test case 0
Existing:
     0:*********************************************
Adding:
      :                ********
Returned:
Result
     0:*********************************************


Test case 1
Existing:
     0:    *****************************************
Adding:
      :************
Returned:
     0:****
Result
     0:*********************************************


Test case 2
Existing:
     0:      ***************************************
Adding:
      :****
Returned:
     0:****
Result
     0:      ***************************************
     1:****


Test case 3
Existing:
     0:*****************************************
Adding:
      :                                 ************
Returned:
     0:                                         ****
Result
     0:*********************************************


Test case 4
Existing:
     0:***************************************
Adding:
      :                                         ****
Returned:
     0:                                         ****
Result
     0:***************************************
     1:                                         ****


Test case 5
Existing:
     0:*****************
     1:                              ***************
Adding:
      :                     ****
Returned:
     0:                     ****
Result
     0:*****************
     1:                              ***************
     2:                     ****


Test case 6
Existing:
     0:*****************
     1:                              ***************
Adding:
      :              ********
Returned:
     0:                 *****
Result
     0:                              ***************
     1:**********************


Test case 7
Existing:
     0:*****************
     1:                              ***************
Adding:
      :                         ********
Returned:
     0:                         *****
Result
     0:*****************
     1:                         ********************


Test case 8
Existing:
     0:*****************
     1:                     ****
     2:                              ***************
Adding:
      :               ****************
Returned:
     0:                 ****
     1:                         *****
Result
     0:*********************************************

我为间隔边框选择LocalDateTime,但任何Comparable对象都可以。对于测试,在那里打印的星数只是自任意ZERO LocalDateTime以来的分钟数。

说明:

尽管此处介绍了您所描述的测试用例,并提供了所需的结果,但可以/应该引入其他测试。例如,添加空间隔,或添加准确位于其他两个间隔边界的间隔...(这就是为什么这些被称为边界情况; - ))

我还应该提到,实施不一定是最有效或最优雅的。我在那里介绍的normalize方法可能可以通过范围边界的勤勉簿记来避免,可能使用另一个答案中建议的NavigableSetNavigableMap。如果性能至关重要,那么像Interval Trees这样的复杂数据结构也值得一看。但我认为给定的实施是合理的,相对容易理解。