From 66fef3ae708ebd7536d7a858cfdb422766b4af61 Mon Sep 17 00:00:00 2001 From: Photon-GitHub Date: Tue, 22 Mar 2022 21:09:03 +0100 Subject: [PATCH 1/5] Use EnumMap and ArrayDeque. --- .../danilopianini/util/FlexibleQuadTree.java | 208 ++++++++---------- 1 file changed, 89 insertions(+), 119 deletions(-) diff --git a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java index 67214ab2..d4edefc6 100644 --- a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java +++ b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java @@ -6,16 +6,15 @@ *******************************************************************************/ package org.danilopianini.util; -import com.google.common.base.Optional; import com.google.common.hash.Hashing; import javax.annotation.Nonnull; import java.io.Serializable; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collections; import java.util.Deque; +import java.util.EnumMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -27,22 +26,20 @@ import static java.lang.Math.nextUp; /** - * * @param the type of objects stored in the quadtree */ -public final class FlexibleQuadTree implements SpatialIndex { +public final class FlexibleQuadTree implements SpatialIndex +{ /** * Default maximum number of entries per node. */ public static final int DEFAULT_CAPACITY = 10; private static final long serialVersionUID = 0L; - - private Rectangle2D bounds; - private final List>> children = new ArrayList<>(Collections.nCopies(4, Optional.absent())); - private boolean childrenCreated; + private final EnumMap> children; private final Deque> elements; private final int maxElements; + private Rectangle2D bounds; private FlexibleQuadTree parent; /** @@ -69,15 +66,15 @@ private FlexibleQuadTree(final int elemPerQuad, final FlexibleQuadTree rootNo if (elemPerQuad < 2) { throw new IllegalArgumentException("At least two elements per quadtree are required for this index to work properly"); } - elements = new LinkedList<>(); + elements = new ArrayDeque<>(); + children = new EnumMap<>(Child.class); maxElements = elemPerQuad; parent = parentNode; root = rootNode == null ? this : rootNode; } /** - * @param elemPerQuad - * maximum number of elements per quad + * @param elemPerQuad maximum number of elements per quad */ public FlexibleQuadTree(final int elemPerQuad) { this(elemPerQuad, null, null); @@ -102,9 +99,7 @@ private FlexibleQuadTree create( } private void createChildIfAbsent(final Child c) { - if (!children.get(c.ordinal()).isPresent()) { - setChild(c, create(minX(c), maxX(c), minY(c), maxY(c), this)); - } + children.putIfAbsent(c, create(minX(c), maxX(c), minY(c), maxY(c), this)); } private void createParent(final double x, final double y) { @@ -150,10 +145,6 @@ private void createParent(final double x, final double y) { root.subdivide(); } - private FlexibleQuadTree getChild(final Child c) { - return children.get(c.ordinal()).get(); - } - @Override public int getDimensions() { return 2; @@ -166,13 +157,6 @@ public int getMaxElementsNumber() { return maxElements; } - private boolean hasChildren() { - if (!childrenCreated) { - childrenCreated = children.stream().allMatch(Optional::isPresent); - } - return childrenCreated; - } - private boolean hasSpace() { return elements.size() < maxElements; } @@ -185,13 +169,10 @@ public void insert(final E e, final double... pos) { /** * Same of {@link #insert(Object, double...)}, but with explicit parameters. - * - * @param e - * element - * @param x - * X - * @param y - * Y + * + * @param e element + * @param x X + * @param y Y */ public void insert(final E e, final double x, final double y) { if (bounds == null) { @@ -203,7 +184,7 @@ public void insert(final E e, final double x, final double y) { double miny = Double.POSITIVE_INFINITY; double maxx = Double.NEGATIVE_INFINITY; double maxy = Double.NEGATIVE_INFINITY; - for (final QuadTreeEntry element: elements) { + for (final QuadTreeEntry element : elements) { minx = min(minx, element.x); miny = min(miny, element.y); maxx = max(maxx, element.x); @@ -219,8 +200,9 @@ public void insert(final E e, final double x, final double y) { * I must insert starting from the root. If the root does not contain * the coordinates, then the tree should be expanded upwards */ - for (; !root.contains(x, y); root = root.root) { + while (!root.contains(x, y)) { root.createParent(x, y); + root = root.root; } root.insertHere(e, x, y); } @@ -229,7 +211,7 @@ private void insertHere(final E e, final double x, final double y) { if (hasSpace()) { insertNode(e, x, y); } else { - if (!hasChildren()) { + if (children.isEmpty()) { subdivide(); } selectChild(x, y).insertHere(e, x, y); @@ -247,14 +229,14 @@ private double maxX() { private double maxX(final Child c) { switch (c) { - case TR: - case BR: - return maxX(); - case BL: - case TL: - return centerX(); - default: - throw new IllegalStateException(); + case TR: + case BR: + return maxX(); + case BL: + case TL: + return centerX(); + default: + throw new IllegalStateException(); } } @@ -264,14 +246,14 @@ private double maxY() { private double maxY(final Child c) { switch (c) { - case BL: - case BR: - return centerY(); - case TR: - case TL: - return maxY(); - default: - throw new IllegalStateException(); + case BL: + case BR: + return centerY(); + case TR: + case TL: + return maxY(); + default: + throw new IllegalStateException(); } } @@ -281,14 +263,14 @@ private double minX() { private double minX(final Child c) { switch (c) { - case TR: - case BR: - return centerX(); - case BL: - case TL: - return minX(); - default: - throw new IllegalStateException(); + case TR: + case BR: + return centerX(); + case BL: + case TL: + return minX(); + default: + throw new IllegalStateException(); } } @@ -298,31 +280,27 @@ private double minY() { private double minY(final Child c) { switch (c) { - case BL: - case BR: - return minY(); - case TR: - case TL: - return centerY(); - default: - throw new IllegalStateException(); + case BL: + case BR: + return minY(); + case TR: + case TL: + return centerY(); + default: + throw new IllegalStateException(); } } /** * Same of {@link #move(Object, double[], double[])}, but with explicit * parameters. - * - * @param e - * the element - * @param sx - * the start x - * @param sy - * the start y - * @param fx - * the final x - * @param fy - * the final y + * + * @param e the element + * @param sx the start x + * @param sy the start y + * @param fx the final x + * @param fy the final y + * * @return true if the element is found and no error occurred */ public boolean move(final E e, final double sx, final double sy, final double fx, final double fy) { @@ -353,8 +331,8 @@ private boolean moveFromNode( */ cur.insertNode(e, fx, fy); } else if (cur.parent == null - || !cur.parent.contains(fx, fy) - || !cur.swapMostStatic(e, fx, fy)) { + || !cur.parent.contains(fx, fy) + || !cur.swapMostStatic(e, fx, fy)) { /* * In case: * - we are the root @@ -365,7 +343,7 @@ private boolean moveFromNode( } return true; } - if (!cur.hasChildren()) { + if (cur.children.isEmpty()) { return false; } } @@ -374,15 +352,12 @@ private boolean moveFromNode( /** * Same of querying with arrays, but with explicit parameters. - * - * @param x1 - * Rectangle X coordinate of the first point - * @param y1 - * Rectangle Y coordinate of the first point - * @param x2 - * Rectangle X coordinate of the second point - * @param y2 - * Rectangle Y coordinate of the second point + * + * @param x1 Rectangle X coordinate of the first point + * @param y1 Rectangle Y coordinate of the first point + * @param x2 Rectangle X coordinate of the second point + * @param y2 Rectangle Y coordinate of the second point + * * @return {@link List} of Objects in range. */ public List query(final double x1, final double y1, final double x2, final double y2) { @@ -396,18 +371,18 @@ public List query(final double x1, final double y1, final double x2, final do } private void query(// NOPMD: False positive - final double sx, final double sy, final double fx, final double fy, final List results) { - assert !(bounds == null && hasChildren()); + final double sx, final double sy, final double fx, final double fy, final List results) { + assert !(bounds == null && !children.isEmpty()); if (bounds == null || bounds.intersects(sx, sy, fx, fy)) { for (final QuadTreeEntry entry : elements) { if (entry.isIn(sx, sy, fx, fy)) { results.add(entry.element); } } - if (hasChildren()) { - for (final Optional> childOpt: children) { - childOpt.get().query(sx, sy, fx, fy, results); - } + + // If there are no children, this will skip them. + for (final FlexibleQuadTree childOpt : children.values()) { + childOpt.query(sx, sy, fx, fy, results); } } } @@ -428,13 +403,11 @@ public boolean remove(final E e, final double... pos) { /** * Same of {@link #remove(Object, double...)} with explicit parameters. - * - * @param e - * Element to remove - * @param x - * X position of the element - * @param y - * Y position of the element + * + * @param e Element to remove + * @param x X position of the element + * @param y Y position of the element + * * @return true if the element has been found and removed */ public boolean remove(final E e, final double x, final double y) { @@ -449,29 +422,26 @@ private boolean removeHere(final E e, final double x, final double y) { } private boolean removeInChildren(final E e, final double x, final double y) { - return children.stream() - .filter(Optional::isPresent) - .map(Optional::get) - .anyMatch(c -> c.removeHere(e, x, y)); + return children.values().stream().anyMatch(c -> c.removeHere(e, x, y)); } private FlexibleQuadTree selectChild(final double x, final double y) { - assert hasChildren(); + assert !children.isEmpty(); if (x < centerX()) { if (y < centerY()) { - return getChild(Child.BL); + return children.get(Child.BL); } - return getChild(Child.TL); + return children.get(Child.TL); } else { if (y < centerY()) { - return getChild(Child.BR); + return children.get(Child.BR); } - return getChild(Child.TR); + return children.get(Child.TR); } } private void setChild(final Child c, final FlexibleQuadTree child) { - if (children.set(c.ordinal(), Optional.of(child)).isPresent()) { + if (children.put(c, child) != null) { throw new IllegalStateException(); } child.parent = this; @@ -541,11 +511,11 @@ public boolean equals(final Object obj) { @SuppressWarnings("UnstableApiUsage") public int hashCode() { return Hashing.murmur3_32_fixed().newHasher() - .putDouble(x) - .putDouble(y) - .putInt(element.hashCode()) - .hash() - .asInt(); + .putDouble(x) + .putDouble(y) + .putInt(element.hashCode()) + .hash() + .asInt(); } public boolean isIn(final double sx, final double sy, final double fx, final double fy) { From ce9db2e5bd31e383220858afab8137602d5f2dfe Mon Sep 17 00:00:00 2001 From: Photon-GitHub Date: Tue, 22 Mar 2022 21:17:31 +0100 Subject: [PATCH 2/5] Optimization of ArrayDeque init. --- src/main/java/org/danilopianini/util/FlexibleQuadTree.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java index d4edefc6..5c4d7a98 100644 --- a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java +++ b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java @@ -66,7 +66,7 @@ private FlexibleQuadTree(final int elemPerQuad, final FlexibleQuadTree rootNo if (elemPerQuad < 2) { throw new IllegalArgumentException("At least two elements per quadtree are required for this index to work properly"); } - elements = new ArrayDeque<>(); + elements = new ArrayDeque<>(DEFAULT_CAPACITY); children = new EnumMap<>(Child.class); maxElements = elemPerQuad; parent = parentNode; From fa08fccd4c95b8036bf7cebcb0719cce8cf7a3f1 Mon Sep 17 00:00:00 2001 From: Photon-GitHub Date: Tue, 22 Mar 2022 21:45:02 +0100 Subject: [PATCH 3/5] Use Map interface instead of EnumMap. --- src/main/java/org/danilopianini/util/FlexibleQuadTree.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java index 5c4d7a98..f328d5a0 100644 --- a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java +++ b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java @@ -16,6 +16,7 @@ import java.util.EnumMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import static java.lang.Math.ceil; @@ -36,7 +37,7 @@ public final class FlexibleQuadTree implements SpatialIndex */ public static final int DEFAULT_CAPACITY = 10; private static final long serialVersionUID = 0L; - private final EnumMap> children; + private final Map> children; private final Deque> elements; private final int maxElements; private Rectangle2D bounds; From 5215f8d711dbd750cb3999631aabd6b1dea54804 Mon Sep 17 00:00:00 2001 From: Danilo Pianini Date: Wed, 23 Mar 2022 08:13:43 +0100 Subject: [PATCH 4/5] style: improve indentation and relocate a wrong curly bracket --- .../danilopianini/util/FlexibleQuadTree.java | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java index f328d5a0..9a1b4b8e 100644 --- a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java +++ b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java @@ -29,8 +29,7 @@ /** * @param the type of objects stored in the quadtree */ -public final class FlexibleQuadTree implements SpatialIndex -{ +public final class FlexibleQuadTree implements SpatialIndex { /** * Default maximum number of entries per node. @@ -57,13 +56,23 @@ public FlexibleQuadTree() { } private FlexibleQuadTree( - final double minx, final double maxx, final double miny, final double maxy, - final int elemPerQuad, final FlexibleQuadTree rootNode, final FlexibleQuadTree parentNode) { + final double minx, + final double maxx, + final double miny, + final double maxy, + final int elemPerQuad, + final FlexibleQuadTree rootNode, + final FlexibleQuadTree parentNode + ) { this(elemPerQuad, rootNode, parentNode); bounds = new Rectangle2D(minx, miny, maxx, maxy); } - private FlexibleQuadTree(final int elemPerQuad, final FlexibleQuadTree rootNode, final FlexibleQuadTree parentNode) { + private FlexibleQuadTree( + final int elemPerQuad, + final FlexibleQuadTree rootNode, + final FlexibleQuadTree parentNode + ) { if (elemPerQuad < 2) { throw new IllegalArgumentException("At least two elements per quadtree are required for this index to work properly"); } @@ -94,8 +103,12 @@ private boolean contains(final double x, final double y) { } private FlexibleQuadTree create( - final double minx, final double maxx, final double miny, final double maxy, - final FlexibleQuadTree father) { + final double minx, + final double maxx, + final double miny, + final double maxy, + final FlexibleQuadTree father + ) { return new FlexibleQuadTree<>(minx, maxx, miny, maxy, getMaxElementsNumber(), root, father); } @@ -512,11 +525,11 @@ public boolean equals(final Object obj) { @SuppressWarnings("UnstableApiUsage") public int hashCode() { return Hashing.murmur3_32_fixed().newHasher() - .putDouble(x) - .putDouble(y) - .putInt(element.hashCode()) - .hash() - .asInt(); + .putDouble(x) + .putDouble(y) + .putInt(element.hashCode()) + .hash() + .asInt(); } public boolean isIn(final double sx, final double sy, final double fx, final double fy) { From 15054cf9d951fb13062ff78725ccfd3f86f8d978 Mon Sep 17 00:00:00 2001 From: Danilo Pianini Date: Wed, 23 Mar 2022 08:18:38 +0100 Subject: [PATCH 5/5] style: improve indentation --- .../org/danilopianini/util/FlexibleQuadTree.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java index 9a1b4b8e..1afaed00 100644 --- a/src/main/java/org/danilopianini/util/FlexibleQuadTree.java +++ b/src/main/java/org/danilopianini/util/FlexibleQuadTree.java @@ -344,9 +344,11 @@ private boolean moveFromNode( * Moved within the same quadrant. */ cur.insertNode(e, fx, fy); - } else if (cur.parent == null - || !cur.parent.contains(fx, fy) - || !cur.swapMostStatic(e, fx, fy)) { + } else if ( + cur.parent == null + || !cur.parent.contains(fx, fy) + || !cur.swapMostStatic(e, fx, fy) + ) { /* * In case: * - we are the root @@ -385,7 +387,12 @@ public List query(final double x1, final double y1, final double x2, final do } private void query(// NOPMD: False positive - final double sx, final double sy, final double fx, final double fy, final List results) { + final double sx, + final double sy, + final double fx, + final double fy, + final List results + ) { assert !(bounds == null && !children.isEmpty()); if (bounds == null || bounds.intersects(sx, sy, fx, fy)) { for (final QuadTreeEntry entry : elements) { @@ -393,7 +400,6 @@ private void query(// NOPMD: False positive results.add(entry.element); } } - // If there are no children, this will skip them. for (final FlexibleQuadTree childOpt : children.values()) { childOpt.query(sx, sy, fx, fy, results);