You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by lu...@apache.org on 2014/01/20 16:34:41 UTC

svn commit: r1559746 - in /commons/proper/math/trunk/src: changes/changes.xml main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java

Author: luc
Date: Mon Jan 20 15:34:41 2014
New Revision: 1559746

URL: http://svn.apache.org/r1559746
Log:
IntervalsSet now implements Iterable<double[]>.

Fixes MATH-1090

Modified:
    commons/proper/math/trunk/src/changes/changes.xml
    commons/proper/math/trunk/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java

Modified: commons/proper/math/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/changes/changes.xml?rev=1559746&r1=1559745&r2=1559746&view=diff
==============================================================================
--- commons/proper/math/trunk/src/changes/changes.xml (original)
+++ commons/proper/math/trunk/src/changes/changes.xml Mon Jan 20 15:34:41 2014
@@ -51,6 +51,11 @@ If the output is not quite correct, chec
   </properties>
   <body>
     <release version="3.3" date="TBD" description="TBD">
+      <action dev="luc" type="add" issue="MATH-1090">
+       IntervalsSet now implements Iterable&lt;double[]&gt;, so one can iterate
+       over the sub-intervals without building a full list containing
+       a copy of everything beforehand.
+      </action>
       <action dev="tn" type="fix" issue="MATH-1089">
         "Precision#round(double, ...)" will now return negative zero for negative
         values rounded to zero, similar to the float variant.

Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java?rev=1559746&r1=1559745&r2=1559746&view=diff
==============================================================================
--- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java (original)
+++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java Mon Jan 20 15:34:41 2014
@@ -18,7 +18,9 @@ package org.apache.commons.math3.geometr
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
+import java.util.NoSuchElementException;
 
 import org.apache.commons.math3.geometry.Point;
 import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
@@ -30,7 +32,7 @@ import org.apache.commons.math3.util.Pre
  * @version $Id$
  * @since 3.0
  */
-public class IntervalsSet extends AbstractRegion<Euclidean1D, Euclidean1D> {
+public class IntervalsSet extends AbstractRegion<Euclidean1D, Euclidean1D> implements Iterable<double[]> {
 
     /** Default value for tolerance. */
     private static final double DEFAULT_TOLERANCE = 1.0e-10;
@@ -281,47 +283,356 @@ public class IntervalsSet extends Abstra
      */
     public List<Interval> asList() {
         final List<Interval> list = new ArrayList<Interval>();
-        recurseList(getTree(false), list,
-                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+        for (final double[] a : this) {
+            list.add(new Interval(a[0], a[1]));
+        }
         return list;
     }
 
-    /** Update an intervals list.
-     * @param node current node
-     * @param list list to update
-     * @param lower lower bound of the current convex cell
-     * @param upper upper bound of the current convex cell
-     */
-    private void recurseList(final BSPTree<Euclidean1D> node,
-                             final List<Interval> list,
-                             final double lower, final double upper) {
+    /** Get the first leaf node of a tree.
+     * @param root tree root
+     * @return first leaf node
+     */
+    private BSPTree<Euclidean1D> getFirstLeaf(final BSPTree<Euclidean1D> root) {
+
+        if (root.getCut() == null) {
+            return root;
+        }
+
+        // find the smallest internal node
+        BSPTree<Euclidean1D> smallest = null;
+        for (BSPTree<Euclidean1D> n = root; n != null; n = previousInternalNode(n)) {
+            smallest = n;
+        }
+
+        return leafBefore(smallest);
+
+    }
+
+    /** Get the node corresponding to the first interval boundary.
+     * @return smallest internal node,
+     * or null if there are no internal nodes (i.e. the set is either empty or covers the real line)
+     */
+    private BSPTree<Euclidean1D> getFirstIntervalBoundary() {
 
+        // start search at the tree root
+        BSPTree<Euclidean1D> node = getTree(false);
         if (node.getCut() == null) {
-            if ((Boolean) node.getAttribute()) {
-                // this leaf cell is an inside cell: an interval
-                list.add(new Interval(lower, upper));
-            }
+            return null;
+        }
+
+        // walk tree until we find the smallest internal node
+        node = getFirstLeaf(node).getParent();
+
+        // walk tree until we find an interval boundary
+        while (node != null && !(isIntervalStart(node) || isIntervalEnd(node))) {
+            node = nextInternalNode(node);
+        }
+
+        return node;
+
+    }
+
+    /** Check if an internal node corresponds to the start abscissa of an interval.
+     * @param node internal node to check
+     * @return true if the node corresponds to the start abscissa of an interval
+     */
+    private boolean isIntervalStart(final BSPTree<Euclidean1D> node) {
+
+        if ((Boolean) leafBefore(node).getAttribute()) {
+            // it has an inside cell before it, it may end an interval but not start it
+            return false;
+        }
+
+        if (!(Boolean) leafAfter(node).getAttribute()) {
+            // it has an outside cell after it, it is a dummy cut away from real intervals
+            return false;
+        }
+
+        // the cell has an outside before and an inside after it
+        // it is the start of an interval
+        return true;
+
+    }
+
+    /** Check if an internal node corresponds to the end abscissa of an interval.
+     * @param node internal node to check
+     * @return true if the node corresponds to the end abscissa of an interval
+     */
+    private boolean isIntervalEnd(final BSPTree<Euclidean1D> node) {
+
+        if (!(Boolean) leafBefore(node).getAttribute()) {
+            // it has an outside cell before it, it may start an interval but not end it
+            return false;
+        }
+
+        if ((Boolean) leafAfter(node).getAttribute()) {
+            // it has an inside cell after it, it is a dummy cut in the middle of an interval
+            return false;
+        }
+
+        // the cell has an inside before and an outside after it
+        // it is the end of an interval
+        return true;
+
+    }
+
+    /** Get the next internal node.
+     * @param node current internal node
+     * @return next internal node in ascending order, or null
+     * if this is the last internal node
+     */
+    private BSPTree<Euclidean1D> nextInternalNode(BSPTree<Euclidean1D> node) {
+
+        if (childAfter(node).getCut() != null) {
+            // the next node is in the sub-tree
+            return leafAfter(node).getParent();
+        }
+
+        // there is nothing left deeper in the tree, we backtrack
+        while (isAfterParent(node)) {
+            node = node.getParent();
+        }
+        return node.getParent();
+
+    }
+
+    /** Get the previous internal node.
+     * @param node current internal node
+     * @return previous internal node in ascending order, or null
+     * if this is the first internal node
+     */
+    private BSPTree<Euclidean1D> previousInternalNode(BSPTree<Euclidean1D> node) {
+
+        if (childBefore(node).getCut() != null) {
+            // the next node is in the sub-tree
+            return leafBefore(node).getParent();
+        }
+
+        // there is nothing left deeper in the tree, we backtrack
+        while (isBeforeParent(node)) {
+            node = node.getParent();
+        }
+        return node.getParent();
+
+    }
+
+    /** Find the leaf node just before an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return leaf node just before the internal node
+     */
+    private BSPTree<Euclidean1D> leafBefore(BSPTree<Euclidean1D> node) {
+
+        node = childBefore(node);
+        while (node.getCut() != null) {
+            node = childAfter(node);
+        }
+
+        return node;
+
+    }
+
+    /** Find the leaf node just after an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return leaf node just after the internal node
+     */
+    private BSPTree<Euclidean1D> leafAfter(BSPTree<Euclidean1D> node) {
+
+        node = childAfter(node);
+        while (node.getCut() != null) {
+            node = childBefore(node);
+        }
+
+        return node;
+
+    }
+
+    /** Check if a node is the child before its parent in ascending order.
+     * @param node child node considered
+     * @return true is the node has a parent end is before it in ascending order
+     */
+    private boolean isBeforeParent(final BSPTree<Euclidean1D> node) {
+        final BSPTree<Euclidean1D> parent = node.getParent();
+        if (parent == null) {
+            return false;
+        } else {
+            return node == childBefore(parent);
+        }
+    }
+
+    /** Check if a node is the child after its parent in ascending order.
+     * @param node child node considered
+     * @return true is the node has a parent end is after it in ascending order
+     */
+    private boolean isAfterParent(final BSPTree<Euclidean1D> node) {
+        final BSPTree<Euclidean1D> parent = node.getParent();
+        if (parent == null) {
+            return false;
         } else {
-            final OrientedPoint op  = (OrientedPoint) node.getCut().getHyperplane();
-            final Vector1D       loc = op.getLocation();
-            double              x   = loc.getX();
-
-            // make sure we explore the tree in increasing order
-            final BSPTree<Euclidean1D> low  =
-                op.isDirect() ? node.getMinus() : node.getPlus();
-            final BSPTree<Euclidean1D> high =
-                op.isDirect() ? node.getPlus()  : node.getMinus();
-
-            recurseList(low, list, lower, x);
-            if ((checkPoint(low,  (Point<Euclidean1D>) loc) == Location.INSIDE) &&
-                (checkPoint(high, (Point<Euclidean1D>) loc) == Location.INSIDE)) {
-                // merge the last interval added and the first one of the high sub-tree
-                x = list.remove(list.size() - 1).getInf();
+            return node == childAfter(parent);
+        }
+    }
+
+    /** Find the child node just before an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return child node just before the internal node
+     */
+    private BSPTree<Euclidean1D> childBefore(BSPTree<Euclidean1D> node) {
+        if (isDirect(node)) {
+            // smaller abscissas are on minus side, larger abscissas are on plus side
+            return node.getMinus();
+        } else {
+            // smaller abscissas are on plus side, larger abscissas are on minus side
+            return node.getPlus();
+        }
+    }
+
+    /** Find the child node just after an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return child node just after the internal node
+     */
+    private BSPTree<Euclidean1D> childAfter(BSPTree<Euclidean1D> node) {
+        if (isDirect(node)) {
+            // smaller abscissas are on minus side, larger abscissas are on plus side
+            return node.getPlus();
+        } else {
+            // smaller abscissas are on plus side, larger abscissas are on minus side
+            return node.getMinus();
+        }
+    }
+
+    /** Check if an internal node has a direct oriented point.
+     * @param node internal node to check
+     * @return true if the oriented point is direct
+     */
+    private boolean isDirect(final BSPTree<Euclidean1D> node) {
+        return ((OrientedPoint) node.getCut().getHyperplane()).isDirect();
+    }
+
+    /** Get the abscissa of an internal node.
+     * @param node internal node to check
+     * @return abscissa
+     */
+    private double getAngle(final BSPTree<Euclidean1D> node) {
+        return ((OrientedPoint) node.getCut().getHyperplane()).getLocation().getX();
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * The iterator returns the limit values of sub-intervals in ascending order.
+     * </p>
+     * <p>
+     * The iterator does <em>not</em> support the optional {@code remove} operation.
+     * </p>
+     * @since 3.3
+     */
+    public Iterator<double[]> iterator() {
+        return new SubIntervalsIterator();
+    }
+
+    /** Local iterator for sub-intervals. */
+    private class SubIntervalsIterator implements Iterator<double[]> {
+
+        /** Current node. */
+        private BSPTree<Euclidean1D> current;
+
+        /** Sub-interval no yet returned. */
+        private double[] pending;
+
+        /** Simple constructor.
+         */
+        public SubIntervalsIterator() {
+
+            current = getFirstIntervalBoundary();
+
+            if (current == null) {
+                // all the leaf tree nodes share the same inside/outside status
+                if ((Boolean) getFirstLeaf(getTree(false)).getAttribute()) {
+                    // it is an inside node, it represents the full real line
+                    pending = new double[] {
+                        Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY
+                    };
+                } else {
+                    pending = null;
+                }
+            } else if (isIntervalEnd(current)) {
+                // the first boundary is an interval end,
+                // so the first interval starts at infinity
+                pending = new double[] {
+                    Double.NEGATIVE_INFINITY, getAngle(current)
+                };
+            } else {
+                selectPending();
+            }
+        }
+
+        /** Walk the tree to select the pending sub-interval.
+         */
+        private void selectPending() {
+
+            // look for the start of the interval
+            BSPTree<Euclidean1D> start = current;
+            while (start != null && !isIntervalStart(start)) {
+                start = nextInternalNode(start);
+            }
+
+            if (start == null) {
+                // we have exhausted the iterator
+                current = null;
+                pending = null;
+                return;
+            }
+
+            // look for the end of the interval
+            BSPTree<Euclidean1D> end = start;
+            while (end != null && !isIntervalEnd(end)) {
+                end = nextInternalNode(end);
+            }
+
+            if (end != null) {
+
+                // we have identified the interval
+                pending = new double[] {
+                    getAngle(start), getAngle(end)
+                };
+
+                // prepare search for next interval
+                current = end;
+
+            } else {
+
+                // the final interval is open toward infinity
+                pending = new double[] {
+                    getAngle(start), Double.POSITIVE_INFINITY
+                };
+
+                // there won't be any other intervals
+                current = null;
+
             }
-            recurseList(high, list, x, upper);
 
         }
 
+        /** {@inheritDoc} */
+        public boolean hasNext() {
+            return pending != null;
+        }
+
+        /** {@inheritDoc} */
+        public double[] next() {
+            if (pending == null) {
+                throw new NoSuchElementException();
+            }
+            final double[] next = pending;
+            selectPending();
+            return next;
+        }
+
+        /** {@inheritDoc} */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
     }
 
 }