From ff8f49b7f66c32c3e61f6389caaefc19d95a71fe Mon Sep 17 00:00:00 2001 From: "Vincent A. Cicirello" Date: Thu, 13 Apr 2023 17:24:50 -0400 Subject: [PATCH 1/2] cache hash on first call to Permutation.hashCode() --- .../cicirello/permutations/Permutation.java | 86 ++++++++++++++++--- .../permutations/PermutationIterator.java | 8 +- .../PermutationConstructorRelatedTests.java | 4 +- .../permutations/PermutationCycleTests.java | 10 ++- .../permutations/PermutationInverseTests.java | 7 +- .../PermutationIteratorTests.java | 2 +- ...PermutationNoncontiguousScrambleTests.java | 6 +- .../PermutationOperatorsTests.java | 63 +++++++++++--- .../PermutationRemoveInsertTests.java | 12 ++- .../permutations/PermutationReverseTests.java | 16 +++- .../permutations/PermutationRotateTests.java | 13 ++- .../PermutationScrambleTests.java | 46 +++++++--- .../permutations/PermutationSwapTests.java | 11 ++- .../PermutationToFromArraysTests.java | 2 +- .../SharedTestHelpersPermutation.java | 2 +- 15 files changed, 231 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/cicirello/permutations/Permutation.java b/src/main/java/org/cicirello/permutations/Permutation.java index 71a51da5..826176c1 100644 --- a/src/main/java/org/cicirello/permutations/Permutation.java +++ b/src/main/java/org/cicirello/permutations/Permutation.java @@ -42,7 +42,7 @@ public final class Permutation implements Serializable, Iterable, Copyable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** * Raw permutation, which should consist of a permutation of the integers in [0, @@ -50,6 +50,19 @@ public final class Permutation */ private final int[] permutation; + /* + * Class caches the hashCode the first time hashCode() is called + * to avoid cost of recomputing in applications that rely on HashSets or HashMaps + * of Permutations, etc with heavy use of the hashCode. + */ + private transient int hashCode; + + /* + * Flag for validating/invalidating cache of hashCode. All methods + * that change state of Permutation must invalidate the cache. + */ + private transient boolean hashCodeIsCached; + /** * Initializes a random permutation of n integers. Uses {@link ThreadLocalRandom} as the source of * efficient random number generation. @@ -159,6 +172,8 @@ private Permutation(int[] p, boolean validate) { */ public Permutation(Permutation p) { permutation = p.permutation.clone(); + hashCodeIsCached = p.hashCodeIsCached; + hashCode = p.hashCode; } /** @@ -197,6 +212,7 @@ public Permutation(Permutation p, int length) { */ public void apply(PermutationUnaryOperator operator) { operator.apply(permutation); + hashCodeIsCached = false; } /** @@ -206,6 +222,7 @@ public void apply(PermutationUnaryOperator operator) { */ public void apply(PermutationFullUnaryOperator operator) { operator.apply(permutation, this); + hashCodeIsCached = false; } /** @@ -218,6 +235,8 @@ public void apply(PermutationFullUnaryOperator operator) { */ public void apply(PermutationBinaryOperator operator, Permutation other) { operator.apply(permutation, other.permutation); + hashCodeIsCached = false; + other.hashCodeIsCached = false; } /** @@ -230,6 +249,8 @@ public void apply(PermutationBinaryOperator operator, Permutation other) { */ public void apply(PermutationFullBinaryOperator operator, Permutation other) { operator.apply(permutation, other.permutation, this, other); + hashCodeIsCached = false; + other.hashCodeIsCached = false; } /** @@ -243,6 +264,7 @@ public void apply(PermutationFullBinaryOperator operator, Permutation other) { public void applyThenValidate(PermutationUnaryOperator operator) { try { operator.apply(permutation); + hashCodeIsCached = false; validate(permutation); } catch (IllegalArgumentException exception) { throw new IllegalPermutationStateException( @@ -261,6 +283,7 @@ public void applyThenValidate(PermutationUnaryOperator operator) { public void applyThenValidate(PermutationFullUnaryOperator operator) { try { operator.apply(permutation, this); + hashCodeIsCached = false; validate(permutation); } catch (IllegalArgumentException exception) { throw new IllegalPermutationStateException( @@ -282,6 +305,8 @@ public void applyThenValidate(PermutationFullUnaryOperator operator) { public void applyThenValidate(PermutationBinaryOperator operator, Permutation other) { try { operator.apply(permutation, other.permutation); + hashCodeIsCached = false; + other.hashCodeIsCached = false; validate(permutation); validate(other.permutation); } catch (IllegalArgumentException exception) { @@ -305,6 +330,8 @@ public void applyThenValidate(PermutationBinaryOperator operator, Permutation ot public void applyThenValidate(PermutationFullBinaryOperator operator, Permutation other) { try { operator.apply(permutation, other.permutation, this, other); + hashCodeIsCached = false; + other.hashCodeIsCached = false; validate(permutation); validate(other.permutation); } catch (IllegalArgumentException exception) { @@ -413,6 +440,7 @@ public Permutation getInversePermutation() { */ public void invert() { System.arraycopy(getInverse(), 0, permutation, 0, permutation.length); + hashCodeIsCached = false; } /** @@ -444,6 +472,7 @@ public void scramble(RandomGenerator r) { permutation[j] = i; } } + hashCodeIsCached = false; } } @@ -471,13 +500,14 @@ public void scramble(RandomGenerator r, boolean guaranteeDifferent) { for (int i = permutation.length - 1; i > 1; i--) { int j = RandomIndexer.nextInt(i + 1, r); if (i != j) { - swap(i, j); + internalSwap(i, j); changed = true; } } if (permutation.length > 1 && (!changed || r.nextBoolean())) { - swap(0, 1); + internalSwap(0, 1); } + hashCodeIsCached = false; } else { scramble(r); } @@ -509,22 +539,23 @@ public void scramble(int i, int j, RandomGenerator r) { if (i == j) { return; } + int k = j; if (i > j) { - int temp = i; + k = i; i = j; - j = temp; } boolean changed = false; - for (int k = j; k > i + 1; k--) { + for (; k > i + 1; k--) { int l = i + RandomIndexer.nextInt(k - i + 1, r); if (l != k) { - swap(l, k); + internalSwap(l, k); changed = true; } } if (!changed || r.nextBoolean()) { - swap(i, i + 1); + internalSwap(i, i + 1); } + hashCodeIsCached = false; } /** @@ -544,13 +575,14 @@ public void scramble(int[] indexes, RandomGenerator r) { for (int j = indexes.length - 1; j > 1; j--) { int i = RandomIndexer.nextInt(j + 1, r); if (i != j) { - swap(indexes[i], indexes[j]); + internalSwap(indexes[i], indexes[j]); changed = true; } } if (!changed || r.nextBoolean()) { - swap(indexes[0], indexes[1]); + internalSwap(indexes[0], indexes[1]); } + hashCodeIsCached = false; } } @@ -667,6 +699,7 @@ public void swap(int i, int j) { int temp = permutation[i]; permutation[i] = permutation[j]; permutation[j] = temp; + hashCodeIsCached = false; } /** @@ -688,6 +721,7 @@ public void cycle(int[] indexes) { permutation[indexes[i - 1]] = permutation[indexes[i]]; } permutation[indexes[indexes.length - 1]] = temp; + hashCodeIsCached = false; } } @@ -718,14 +752,16 @@ public void swapBlocks(int a, int b, int i, int j) { System.arraycopy(permutation, b + 1, temp, k, m); System.arraycopy(permutation, a, temp, k + m, b - a + 1); System.arraycopy(temp, 0, permutation, a, temp.length); + hashCodeIsCached = false; } } /** Reverses the order of the elements in the permutation. */ public void reverse() { for (int i = 0, j = permutation.length - 1; i < j; i++, j--) { - swap(i, j); + internalSwap(i, j); } + hashCodeIsCached = false; } /** @@ -739,13 +775,14 @@ public void reverse() { public void reverse(int i, int j) { if (i > j) { for (; i > j; i--, j++) { - swap(i, j); + internalSwap(i, j); } } else { for (; i < j; i++, j--) { - swap(i, j); + internalSwap(i, j); } } + hashCodeIsCached = false; } /** @@ -762,10 +799,12 @@ public void removeAndInsert(int i, int j) { int n = permutation[i]; System.arraycopy(permutation, i + 1, permutation, i, j - i); permutation[j] = n; + hashCodeIsCached = false; } else if (i > j) { int n = permutation[i]; System.arraycopy(permutation, j, permutation, j + 1, i - j); permutation[j] = n; + hashCodeIsCached = false; } } @@ -784,6 +823,7 @@ public void rotate(int numPositions) { System.arraycopy( permutation, numPositions, permutation, 0, permutation.length - numPositions); System.arraycopy(temp, 0, permutation, permutation.length - numPositions, numPositions); + hashCodeIsCached = false; } } @@ -809,11 +849,13 @@ public void removeAndInsert(int i, int size, int j) { System.arraycopy(permutation, j, temp, 0, i - j); System.arraycopy(permutation, i, permutation, j, size); System.arraycopy(temp, 0, permutation, j + size, i - j); + hashCodeIsCached = false; } else { // Condition is implied by above: if (i < j) int[] temp = new int[size]; System.arraycopy(permutation, i, temp, 0, size); System.arraycopy(permutation, i + size, permutation, i, j - i); System.arraycopy(temp, 0, permutation, j, size); + hashCodeIsCached = false; } } @@ -831,6 +873,7 @@ public void set(int[] p) { } validate(p); System.arraycopy(p, 0, permutation, 0, p.length); + hashCodeIsCached = false; } /** @@ -892,7 +935,11 @@ public boolean equals(Object other) { */ @Override public int hashCode() { - return Arrays.hashCode(permutation); + if (hashCodeIsCached) { + return hashCode; + } + hashCodeIsCached = true; + return hashCode = Arrays.hashCode(permutation); } private boolean validate(int[] p) { @@ -909,4 +956,15 @@ private boolean validate(int[] p) { } return true; } + + /* + * Use internally, such as from reverse, etc to avoid + * repeatedly invalidating hashCode cache (as well as from + * the PermutationIterator). + */ + final void internalSwap(int i, int j) { + int temp = permutation[i]; + permutation[i] = permutation[j]; + permutation[j] = temp; + } } diff --git a/src/main/java/org/cicirello/permutations/PermutationIterator.java b/src/main/java/org/cicirello/permutations/PermutationIterator.java index 0d9f29af..0d941ffa 100644 --- a/src/main/java/org/cicirello/permutations/PermutationIterator.java +++ b/src/main/java/org/cicirello/permutations/PermutationIterator.java @@ -39,8 +39,8 @@ */ public class PermutationIterator implements Iterator { - private Permutation p; - private int[] lastSwap; + private final Permutation p; + private final int[] lastSwap; private boolean done; /** @@ -92,14 +92,14 @@ public Permutation next() { done = true; } else { for (int i = lastSwap.length - 2; i >= 0; i--) { - if (lastSwap[i] != i) p.swap(i, lastSwap[i]); + if (lastSwap[i] != i) p.internalSwap(i, lastSwap[i]); if (lastSwap[i] == lastSwap.length - 1) { lastSwap[i] = i; if (i == 0) done = true; continue; } lastSwap[i]++; - p.swap(i, lastSwap[i]); + p.internalSwap(i, lastSwap[i]); break; } } diff --git a/src/test/java/org/cicirello/permutations/PermutationConstructorRelatedTests.java b/src/test/java/org/cicirello/permutations/PermutationConstructorRelatedTests.java index 300ae4f6..c07b9ec3 100644 --- a/src/test/java/org/cicirello/permutations/PermutationConstructorRelatedTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationConstructorRelatedTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -155,6 +155,7 @@ public void testPermutationCopyConstructor() { Permutation copy = new Permutation(p); assertEquals(p, copy); assertEquals(p.hashCode(), copy.hashCode()); + assertEquals(p.hashCode(), copy.hashCode()); } } } @@ -168,6 +169,7 @@ public void testPermutationCopyMethod() { assertEquals(p, copy); assertTrue(p != copy); assertEquals(p.hashCode(), copy.hashCode()); + assertEquals(p.hashCode(), copy.hashCode()); } } } diff --git a/src/test/java/org/cicirello/permutations/PermutationCycleTests.java b/src/test/java/org/cicirello/permutations/PermutationCycleTests.java index d8d35b21..2d3c4b98 100644 --- a/src/test/java/org/cicirello/permutations/PermutationCycleTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationCycleTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -38,8 +38,10 @@ public void testOneZeroCycles() { Permutation p2 = new Permutation(p); p2.cycle(indexes1); assertEquals(p, p2); + assertEquals(p.hashCode(), p2.hashCode()); p2.cycle(indexes0); assertEquals(p, p2); + assertEquals(p.hashCode(), p2.hashCode()); } } @@ -52,7 +54,9 @@ public void testTwoCycles() { Permutation p2 = new Permutation(p); indexes[0] = 0; indexes[1] = i - 1; + assertEquals(p.hashCode(), p2.hashCode()); p2.cycle(indexes); + if (i > 1) assertNotEquals(p.hashCode(), p2.hashCode()); assertEquals(p.get(0), p2.get(i - 1)); assertEquals(p.get(i - 1), p2.get(0)); for (int j = 1; j < i - 1; j++) { @@ -64,7 +68,9 @@ public void testTwoCycles() { Permutation p2 = new Permutation(p); indexes[1] = 0; indexes[0] = i - 1; + assertEquals(p.hashCode(), p2.hashCode()); p2.cycle(indexes); + if (i > 1) assertNotEquals(p.hashCode(), p2.hashCode()); assertEquals(p.get(0), p2.get(i - 1)); assertEquals(p.get(i - 1), p2.get(0)); for (int j = 1; j < i - 1; j++) { @@ -76,7 +82,9 @@ public void testTwoCycles() { Permutation p2 = new Permutation(p); indexes[0] = (i - 1) / 2; indexes[1] = indexes[0] + 1; + assertEquals(p.hashCode(), p2.hashCode()); p2.cycle(indexes); + assertNotEquals(p.hashCode(), p2.hashCode()); assertEquals(p.get(indexes[0]), p2.get(indexes[1])); assertEquals(p.get(indexes[1]), p2.get(indexes[0])); for (int j = 0; j < indexes[0]; j++) { diff --git a/src/test/java/org/cicirello/permutations/PermutationInverseTests.java b/src/test/java/org/cicirello/permutations/PermutationInverseTests.java index f4a8f1cc..29c169c5 100644 --- a/src/test/java/org/cicirello/permutations/PermutationInverseTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationInverseTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -68,14 +68,19 @@ public void testGetInversePermutation() { public void testInvert() { int[] before = {4, 2, 5, 0, 3, 1}; Permutation p = new Permutation(before); + int oldHash = p.hashCode(); int[] expected = {3, 5, 1, 4, 0, 2}; Permutation pExpected = new Permutation(expected); p.invert(); assertEquals(pExpected, p); + assertEquals(pExpected.hashCode(), p.hashCode()); + assertNotEquals(oldHash, p.hashCode()); int[] array = {0, 1, 2, 3, 4, 5}; p = new Permutation(array); pExpected = new Permutation(array); + assertEquals(pExpected.hashCode(), p.hashCode()); p.invert(); assertEquals(pExpected, p); + assertEquals(pExpected.hashCode(), p.hashCode()); } } diff --git a/src/test/java/org/cicirello/permutations/PermutationIteratorTests.java b/src/test/java/org/cicirello/permutations/PermutationIteratorTests.java index 4c9c7860..3e122e20 100644 --- a/src/test/java/org/cicirello/permutations/PermutationIteratorTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationIteratorTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * diff --git a/src/test/java/org/cicirello/permutations/PermutationNoncontiguousScrambleTests.java b/src/test/java/org/cicirello/permutations/PermutationNoncontiguousScrambleTests.java index 83fced5b..90e90658 100644 --- a/src/test/java/org/cicirello/permutations/PermutationNoncontiguousScrambleTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationNoncontiguousScrambleTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -34,10 +34,12 @@ public void testNoncontiguousScrambleLengthOne() { SplittableRandom r2 = new SplittableRandom(42); // Verify does nothing if permutation length < 2. Permutation p = new Permutation(1); + int oldHash = p.hashCode(); int[] indexes = {0}; for (int i = 0; i < 5; i++) { p.scramble(indexes, r2); assertEquals(0, p.get(0)); + assertEquals(oldHash, p.hashCode()); p.scramble(indexes); assertEquals(0, p.get(0)); } @@ -55,9 +57,11 @@ public void testNoncontiguousScrambleTwoIndexes() { shouldChange[indexes[0]] = shouldChange[indexes[1]] = true; Permutation p = new Permutation(n, 0); Permutation p0 = new Permutation(p); + assertEquals(p0.hashCode(), p.hashCode()); p.scramble(indexes, r2); assertEquals(0, p.get(n - 1)); assertEquals(n - 1, p.get(0)); + assertNotEquals(p0.hashCode(), p.hashCode()); for (int i = 0; i < n; i++) { if (!shouldChange[i]) { assertEquals(p0.get(i), p.get(i)); diff --git a/src/test/java/org/cicirello/permutations/PermutationOperatorsTests.java b/src/test/java/org/cicirello/permutations/PermutationOperatorsTests.java index c2c41c40..e4e25f80 100644 --- a/src/test/java/org/cicirello/permutations/PermutationOperatorsTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationOperatorsTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.SplittableRandom; import org.junit.jupiter.api.*; /** JUnit tests for the applyThenValidate and apply methods of the Permutation class. */ @@ -45,7 +46,8 @@ public void testIllegalPermutationStateExceptionConstructors() { @Test public void testUnaryOperator() { - Permutation p = new Permutation(10); + Permutation p = new Permutation(10, new SplittableRandom(42)); + int oldHash = p.hashCode(); p.apply( perm -> { for (int i = 0; i < perm.length; i++) { @@ -55,6 +57,7 @@ public void testUnaryOperator() { for (int i = 0; i < 10; i++) { assertEquals(i, p.get(i)); } + assertNotEquals(oldHash, p.hashCode()); p.apply( perm -> { for (int i = 0; i < perm.length; i++) { @@ -64,11 +67,13 @@ public void testUnaryOperator() { for (int i = 0; i < 10; i++) { assertEquals(9 - i, p.get(i)); } + assertNotEquals(oldHash, p.hashCode()); } @Test public void testValidatedUnaryOperator() { - final Permutation p = new Permutation(10); + final Permutation p = new Permutation(10, new SplittableRandom(42)); + int oldHash = p.hashCode(); p.applyThenValidate( perm -> { for (int i = 0; i < perm.length; i++) { @@ -78,6 +83,7 @@ public void testValidatedUnaryOperator() { for (int i = 0; i < 10; i++) { assertEquals(i, p.get(i)); } + assertNotEquals(oldHash, p.hashCode()); p.applyThenValidate( perm -> { for (int i = 0; i < perm.length; i++) { @@ -87,6 +93,7 @@ public void testValidatedUnaryOperator() { for (int i = 0; i < 10; i++) { assertEquals(9 - i, p.get(i)); } + assertNotEquals(oldHash, p.hashCode()); IllegalPermutationStateException thrown = assertThrows( IllegalPermutationStateException.class, @@ -102,8 +109,10 @@ public void testValidatedUnaryOperator() { @Test public void testBinaryOperator() { - Permutation p1 = new Permutation(10); - Permutation p2 = new Permutation(10); + Permutation p1 = new Permutation(10, new SplittableRandom(42)); + int oldHash1 = p1.hashCode(); + Permutation p2 = new Permutation(10, new SplittableRandom(73)); + int oldHash2 = p2.hashCode(); p1.apply( (perm1, perm2) -> { for (int i = 0; i < perm1.length; i++) { @@ -116,6 +125,8 @@ public void testBinaryOperator() { assertEquals(i, p1.get(i)); assertEquals(9 - i, p2.get(i)); } + assertNotEquals(oldHash1, p1.hashCode()); + assertNotEquals(oldHash2, p2.hashCode()); p1.apply( (perm1, perm2) -> { for (int i = 0; i < perm1.length; i++) { @@ -128,12 +139,16 @@ public void testBinaryOperator() { assertEquals(i, p2.get(i)); assertEquals(9 - i, p1.get(i)); } + assertNotEquals(oldHash1, p1.hashCode()); + assertNotEquals(oldHash2, p2.hashCode()); } @Test public void testValidatedBinaryOperator() { - final Permutation p1 = new Permutation(10); - final Permutation p2 = new Permutation(10); + final Permutation p1 = new Permutation(10, new SplittableRandom(42)); + int oldHash1 = p1.hashCode(); + final Permutation p2 = new Permutation(10, new SplittableRandom(73)); + int oldHash2 = p2.hashCode(); p1.applyThenValidate( (perm1, perm2) -> { for (int i = 0; i < perm1.length; i++) { @@ -146,6 +161,8 @@ public void testValidatedBinaryOperator() { assertEquals(i, p1.get(i)); assertEquals(9 - i, p2.get(i)); } + assertNotEquals(oldHash1, p1.hashCode()); + assertNotEquals(oldHash2, p2.hashCode()); p1.applyThenValidate( (perm1, perm2) -> { for (int i = 0; i < perm1.length; i++) { @@ -158,6 +175,8 @@ public void testValidatedBinaryOperator() { assertEquals(i, p2.get(i)); assertEquals(9 - i, p1.get(i)); } + assertNotEquals(oldHash1, p1.hashCode()); + assertNotEquals(oldHash2, p2.hashCode()); IllegalPermutationStateException thrown = assertThrows( IllegalPermutationStateException.class, @@ -175,7 +194,8 @@ public void testValidatedBinaryOperator() { @Test public void testFullUnaryOperator() { - Permutation p = new Permutation(10); + Permutation p = new Permutation(10, new SplittableRandom(42)); + int oldHash = p.hashCode(); p.apply( (perm, original) -> { for (int i = 0; i < perm.length; i++) { @@ -186,6 +206,7 @@ public void testFullUnaryOperator() { for (int i = 0; i < 10; i++) { assertEquals(i, p.get(i)); } + assertNotEquals(oldHash, p.hashCode()); p.apply( (perm, original) -> { for (int i = 0; i < perm.length; i++) { @@ -196,11 +217,13 @@ public void testFullUnaryOperator() { for (int i = 0; i < 10; i++) { assertEquals(9 - i, p.get(i)); } + assertNotEquals(oldHash, p.hashCode()); } @Test public void testValidatedFullUnaryOperator() { - final Permutation p = new Permutation(10); + final Permutation p = new Permutation(10, new SplittableRandom(42)); + int oldHash = p.hashCode(); p.applyThenValidate( (perm, original) -> { for (int i = 0; i < perm.length; i++) { @@ -211,6 +234,7 @@ public void testValidatedFullUnaryOperator() { for (int i = 0; i < 10; i++) { assertEquals(i, p.get(i)); } + assertNotEquals(oldHash, p.hashCode()); p.applyThenValidate( (perm, original) -> { for (int i = 0; i < perm.length; i++) { @@ -221,6 +245,7 @@ public void testValidatedFullUnaryOperator() { for (int i = 0; i < 10; i++) { assertEquals(9 - i, p.get(i)); } + assertNotEquals(oldHash, p.hashCode()); IllegalPermutationStateException thrown = assertThrows( IllegalPermutationStateException.class, @@ -236,8 +261,10 @@ public void testValidatedFullUnaryOperator() { @Test public void testFullBinaryOperator() { - Permutation p1 = new Permutation(10); - Permutation p2 = new Permutation(10); + Permutation p1 = new Permutation(10, new SplittableRandom(42)); + int oldHash1 = p1.hashCode(); + Permutation p2 = new Permutation(10, new SplittableRandom(73)); + int oldHash2 = p2.hashCode(); p1.apply( (perm1, perm2, o1, o2) -> { for (int i = 0; i < perm1.length; i++) { @@ -252,6 +279,8 @@ public void testFullBinaryOperator() { assertEquals(i, p1.get(i)); assertEquals(9 - i, p2.get(i)); } + assertNotEquals(oldHash1, p1.hashCode()); + assertNotEquals(oldHash2, p2.hashCode()); p1.apply( (perm1, perm2, o1, o2) -> { for (int i = 0; i < perm1.length; i++) { @@ -266,12 +295,16 @@ public void testFullBinaryOperator() { assertEquals(i, p2.get(i)); assertEquals(9 - i, p1.get(i)); } + assertNotEquals(oldHash1, p1.hashCode()); + assertNotEquals(oldHash2, p2.hashCode()); } @Test public void testValidatedFullBinaryOperator() { - final Permutation p1 = new Permutation(10); - final Permutation p2 = new Permutation(10); + final Permutation p1 = new Permutation(10, new SplittableRandom(42)); + int oldHash1 = p1.hashCode(); + final Permutation p2 = new Permutation(10, new SplittableRandom(73)); + int oldHash2 = p2.hashCode(); p1.applyThenValidate( (perm1, perm2, o1, o2) -> { for (int i = 0; i < perm1.length; i++) { @@ -286,6 +319,8 @@ public void testValidatedFullBinaryOperator() { assertEquals(i, p1.get(i)); assertEquals(9 - i, p2.get(i)); } + assertNotEquals(oldHash1, p1.hashCode()); + assertNotEquals(oldHash2, p2.hashCode()); p1.applyThenValidate( (perm1, perm2, o1, o2) -> { for (int i = 0; i < perm1.length; i++) { @@ -300,6 +335,8 @@ public void testValidatedFullBinaryOperator() { assertEquals(i, p2.get(i)); assertEquals(9 - i, p1.get(i)); } + assertNotEquals(oldHash1, p1.hashCode()); + assertNotEquals(oldHash2, p2.hashCode()); IllegalPermutationStateException thrown = assertThrows( IllegalPermutationStateException.class, diff --git a/src/test/java/org/cicirello/permutations/PermutationRemoveInsertTests.java b/src/test/java/org/cicirello/permutations/PermutationRemoveInsertTests.java index cf604f35..827898c2 100644 --- a/src/test/java/org/cicirello/permutations/PermutationRemoveInsertTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationRemoveInsertTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.SplittableRandom; import org.junit.jupiter.api.*; /** JUnit tests for removing and reinserting. */ @@ -30,10 +31,11 @@ public class PermutationRemoveInsertTests { @Test public void testPermutationRemoveInsert() { - Permutation p = new Permutation(5); + Permutation p = new Permutation(5, new SplittableRandom(42)); for (int i = 0; i < p.length(); i++) { for (int j = 0; j < p.length(); j++) { Permutation copy = new Permutation(p); + assertEquals(p.hashCode(), copy.hashCode()); copy.removeAndInsert(i, j); if (i < j) { for (int k = 0; k < i; k++) { @@ -46,6 +48,7 @@ public void testPermutationRemoveInsert() { for (int k = j + 1; k < p.length(); k++) { assertEquals(p.get(k), copy.get(k)); } + assertNotEquals(p.hashCode(), copy.hashCode()); } else if (i > j) { for (int k = 0; k < j; k++) { assertEquals(p.get(k), copy.get(k)); @@ -57,6 +60,7 @@ public void testPermutationRemoveInsert() { for (int k = i + 1; k < p.length(); k++) { assertEquals(p.get(k), copy.get(k)); } + assertNotEquals(p.hashCode(), copy.hashCode()); } else { assertEquals(p, copy); } @@ -76,16 +80,20 @@ public void testPermutationBlockRemoveInsert() { Permutation p = new Permutation(a); Permutation p1 = new Permutation(a1); Permutation mutant = new Permutation(p); + assertEquals(p.hashCode(), mutant.hashCode()); mutant.removeAndInsert(7, 1, 0); assertEquals(p1, mutant); + assertNotEquals(p.hashCode(), mutant.hashCode()); Permutation p2 = new Permutation(a2); mutant = new Permutation(p); mutant.removeAndInsert(2, 1, 10); assertEquals(p2, mutant); Permutation p3 = new Permutation(a3); mutant = new Permutation(p); + assertEquals(p.hashCode(), mutant.hashCode()); mutant.removeAndInsert(7, 2, 0); assertEquals(p3, mutant); + assertNotEquals(p.hashCode(), mutant.hashCode()); Permutation p4 = new Permutation(a4); mutant = new Permutation(p); mutant.removeAndInsert(1, 2, 9); diff --git a/src/test/java/org/cicirello/permutations/PermutationReverseTests.java b/src/test/java/org/cicirello/permutations/PermutationReverseTests.java index 131fb3ea..019b4b99 100644 --- a/src/test/java/org/cicirello/permutations/PermutationReverseTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationReverseTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.SplittableRandom; import org.junit.jupiter.api.*; /** JUnit tests for reversing a Permutation. */ @@ -38,26 +39,35 @@ public void testReverse0() { @Test public void testReverseComplete() { for (int n = 1; n <= 8; n *= 2) { - Permutation p = new Permutation(n); + Permutation p = new Permutation(n, new SplittableRandom(42)); Permutation copy = new Permutation(p); + assertEquals(p.hashCode(), copy.hashCode()); copy.reverse(); for (int i = 0; i < n; i++) { assertEquals(p.get(i), copy.get(n - 1 - i)); } + if (n >= 2) { + assertNotEquals(p.hashCode(), copy.hashCode()); + } } } @Test public void testReverseSub() { - Permutation p = new Permutation(8); + Permutation p = new Permutation(8, new SplittableRandom(42)); for (int j = 0; j < p.length(); j++) { for (int k = j + 1; k < p.length(); k++) { Permutation copy = new Permutation(p); + assertEquals(p.hashCode(), copy.hashCode()); copy.reverse(j, k); validateReversal(p, copy, j, k); + assertNotEquals(p.hashCode(), copy.hashCode()); + copy = new Permutation(p); + assertEquals(p.hashCode(), copy.hashCode()); copy.reverse(k, j); validateReversal(p, copy, j, k); + assertNotEquals(p.hashCode(), copy.hashCode()); } } } diff --git a/src/test/java/org/cicirello/permutations/PermutationRotateTests.java b/src/test/java/org/cicirello/permutations/PermutationRotateTests.java index b8b51cad..70415fb3 100644 --- a/src/test/java/org/cicirello/permutations/PermutationRotateTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationRotateTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.SplittableRandom; import org.junit.jupiter.api.*; /** JUnit tests for rotating a Permutation. */ @@ -30,20 +31,28 @@ public class PermutationRotateTests { @Test public void testPermutationRotate() { - Permutation p = new Permutation(10); + Permutation p = new Permutation(10, new SplittableRandom(42)); for (int r = 0; r < p.length(); r++) { Permutation copy = new Permutation(p); + assertEquals(p.hashCode(), copy.hashCode()); copy.rotate(r); for (int i = 0; i < p.length(); i++) { int j = (i + r) % p.length(); assertEquals(p.get(j), copy.get(i), "elements should be left rotated " + r + " places"); } + if (r > 0) { + assertNotEquals(p.hashCode(), copy.hashCode()); + } } Permutation copy = new Permutation(p); + assertEquals(p.hashCode(), copy.hashCode()); copy.rotate(p.length()); assertEquals(p, copy); + assertEquals(p.hashCode(), copy.hashCode()); copy = new Permutation(p); + assertEquals(p.hashCode(), copy.hashCode()); copy.rotate(-1); + assertNotEquals(p.hashCode(), copy.hashCode()); for (int i = 1; i < p.length(); i++) { assertEquals(p.get(i - 1), copy.get(i), "elements should be RIGHT rotated 1 place"); } diff --git a/src/test/java/org/cicirello/permutations/PermutationScrambleTests.java b/src/test/java/org/cicirello/permutations/PermutationScrambleTests.java index 2b667e89..f0aa2948 100644 --- a/src/test/java/org/cicirello/permutations/PermutationScrambleTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationScrambleTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -50,7 +50,7 @@ public void testScrambleZero() { @Test public void testScramble() { - SplittableRandom r2 = new SplittableRandom(); + SplittableRandom r2 = new SplittableRandom(42); for (int i = 0; i < 8; i++) { Permutation p = new Permutation(i); for (int j = 0; j < 10; j++) { @@ -58,20 +58,34 @@ public void testScramble() { p.scramble(); validatePermutation(p, i); original = new Permutation(p); + assertEquals(original.hashCode(), p.hashCode()); p.scramble(r2); + if (i >= 7) assertNotEquals(original.hashCode(), p.hashCode()); validatePermutation(p, i); + p.scramble(false); validatePermutation(p, i); + p.scramble(r2, false); validatePermutation(p, i); + original = new Permutation(p); + assertEquals(original.hashCode(), p.hashCode()); p.scramble(true); validatePermutation(p, i); - if (i > 1) assertNotEquals(original, p); + if (i > 1) { + assertNotEquals(original, p); + assertNotEquals(original.hashCode(), p.hashCode()); + } + original = new Permutation(p); + assertEquals(original.hashCode(), p.hashCode()); p.scramble(r2, true); validatePermutation(p, i); - if (i > 1) assertNotEquals(original, p); + if (i > 1) { + assertNotEquals(original, p); + assertNotEquals(original.hashCode(), p.hashCode()); + } } } } @@ -80,30 +94,39 @@ public void testScramble() { public void testScrambleFromItoJ() { SplittableRandom r2 = new SplittableRandom(); for (int n = 4; n < 8; n++) { - Permutation p = new Permutation(n); + Permutation p = new Permutation(n, r2); Permutation original = new Permutation(p); + assertEquals(original.hashCode(), p.hashCode()); p.scramble(1, n - 2); validatePermutation(p, n); - if (n > 1) assertNotEquals(original, p); + assertNotEquals(original, p); + assertNotEquals(original.hashCode(), p.hashCode()); assertEquals(original.get(0), p.get(0)); assertEquals(original.get(n - 1), p.get(n - 1)); + original = new Permutation(p); + assertEquals(original.hashCode(), p.hashCode()); p.scramble(1, n - 2, r2); validatePermutation(p, n); - if (n > 1) assertNotEquals(original, p); + assertNotEquals(original, p); + assertNotEquals(original.hashCode(), p.hashCode()); assertEquals(original.get(0), p.get(0)); assertEquals(original.get(n - 1), p.get(n - 1)); + original = new Permutation(p); p.scramble(0, n - 1); validatePermutation(p, n); - if (n > 1) assertNotEquals(original, p); + assertNotEquals(original, p); + original = new Permutation(p); p.scramble(0, n - 1, r2); validatePermutation(p, n); - if (n > 1) assertNotEquals(original, p); + assertNotEquals(original, p); + original = new Permutation(p); p.scramble(1, 1, r2); assertEquals(original, p); + original = new Permutation(p); p.scramble(1, 1); assertEquals(original, p); @@ -111,13 +134,14 @@ public void testScrambleFromItoJ() { original = new Permutation(p); p.scramble(n - 2, 1); validatePermutation(p, n); - if (n > 1) assertNotEquals(original, p); + assertNotEquals(original, p); assertEquals(original.get(0), p.get(0)); assertEquals(original.get(n - 1), p.get(n - 1)); + original = new Permutation(p); p.scramble(n - 2, 1, r2); validatePermutation(p, n); - if (n > 1) assertNotEquals(original, p); + assertNotEquals(original, p); assertEquals(original.get(0), p.get(0)); assertEquals(original.get(n - 1), p.get(n - 1)); } diff --git a/src/test/java/org/cicirello/permutations/PermutationSwapTests.java b/src/test/java/org/cicirello/permutations/PermutationSwapTests.java index c4bd495d..fb09d668 100644 --- a/src/test/java/org/cicirello/permutations/PermutationSwapTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationSwapTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * @@ -28,6 +28,15 @@ /** JUnit tests for swap and swapBlocks methods. */ public class PermutationSwapTests { + @Test + public void testHashCodeInvalidation() { + Permutation p1 = new Permutation(4, 0); + Permutation p2 = new Permutation(4, 0); + assertEquals(p1.hashCode(), p2.hashCode()); + p2.swap(1, 2); + assertNotEquals(p1.hashCode(), p2.hashCode()); + } + @Test public void testSwap() { for (int i = 1; i <= 5; i++) { diff --git a/src/test/java/org/cicirello/permutations/PermutationToFromArraysTests.java b/src/test/java/org/cicirello/permutations/PermutationToFromArraysTests.java index ad81bbd4..254a34be 100644 --- a/src/test/java/org/cicirello/permutations/PermutationToFromArraysTests.java +++ b/src/test/java/org/cicirello/permutations/PermutationToFromArraysTests.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * diff --git a/src/test/java/org/cicirello/permutations/SharedTestHelpersPermutation.java b/src/test/java/org/cicirello/permutations/SharedTestHelpersPermutation.java index c27695db..ed7ceff6 100644 --- a/src/test/java/org/cicirello/permutations/SharedTestHelpersPermutation.java +++ b/src/test/java/org/cicirello/permutations/SharedTestHelpersPermutation.java @@ -1,6 +1,6 @@ /* * JavaPermutationTools: A Java library for computation on permutations and sequences - * Copyright 2005-2022 Vincent A. Cicirello, . + * Copyright 2005-2023 Vincent A. Cicirello, . * * This file is part of JavaPermutationTools (https://jpt.cicirello.org/). * From 0c2d3c2d60640443784f3b4bcd833310d717ea15 Mon Sep 17 00:00:00 2001 From: "Vincent A. Cicirello" Date: Thu, 13 Apr 2023 17:30:57 -0400 Subject: [PATCH 2/2] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c6c0499..75668646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - 2023-04-07 +## [Unreleased] - 2023-04-13 ### Added ### Changed +* Permutation now caches hash on first call to hashCode() to optimize applications that rely heavily on hashing. ### Deprecated