Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
86 changes: 72 additions & 14 deletions src/main/java/org/cicirello/permutations/Permutation.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,27 @@
public final class Permutation
implements Serializable, Iterable<Permutation>, Copyable<Permutation> {

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,
* permutation.length).
*/
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.
Expand Down Expand Up @@ -159,6 +172,8 @@ private Permutation(int[] p, boolean validate) {
*/
public Permutation(Permutation p) {
permutation = p.permutation.clone();
hashCodeIsCached = p.hashCodeIsCached;
hashCode = p.hashCode;
}

/**
Expand Down Expand Up @@ -197,6 +212,7 @@ public Permutation(Permutation p, int length) {
*/
public void apply(PermutationUnaryOperator operator) {
operator.apply(permutation);
hashCodeIsCached = false;
}

/**
Expand All @@ -206,6 +222,7 @@ public void apply(PermutationUnaryOperator operator) {
*/
public void apply(PermutationFullUnaryOperator operator) {
operator.apply(permutation, this);
hashCodeIsCached = false;
}

/**
Expand All @@ -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;
}

/**
Expand All @@ -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;
}

/**
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -413,6 +440,7 @@ public Permutation getInversePermutation() {
*/
public void invert() {
System.arraycopy(getInverse(), 0, permutation, 0, permutation.length);
hashCodeIsCached = false;
}

/**
Expand Down Expand Up @@ -444,6 +472,7 @@ public void scramble(RandomGenerator r) {
permutation[j] = i;
}
}
hashCodeIsCached = false;
}
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -667,6 +699,7 @@ public void swap(int i, int j) {
int temp = permutation[i];
permutation[i] = permutation[j];
permutation[j] = temp;
hashCodeIsCached = false;
}

/**
Expand All @@ -688,6 +721,7 @@ public void cycle(int[] indexes) {
permutation[indexes[i - 1]] = permutation[indexes[i]];
}
permutation[indexes[indexes.length - 1]] = temp;
hashCodeIsCached = false;
}
}

Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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;
}

/**
Expand All @@ -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;
}
}

Expand All @@ -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;
}
}

Expand All @@ -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;
}
}

Expand All @@ -831,6 +873,7 @@ public void set(int[] p) {
}
validate(p);
System.arraycopy(p, 0, permutation, 0, p.length);
hashCodeIsCached = false;
}

/**
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
*/
public class PermutationIterator implements Iterator<Permutation> {

private Permutation p;
private int[] lastSwap;
private final Permutation p;
private final int[] lastSwap;
private boolean done;

/**
Expand Down Expand Up @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* JavaPermutationTools: A Java library for computation on permutations and sequences
* Copyright 2005-2022 Vincent A. Cicirello, <https://www.cicirello.org/>.
* Copyright 2005-2023 Vincent A. Cicirello, <https://www.cicirello.org/>.
*
* This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
*
Expand Down Expand Up @@ -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());
}
}
}
Expand All @@ -168,6 +169,7 @@ public void testPermutationCopyMethod() {
assertEquals(p, copy);
assertTrue(p != copy);
assertEquals(p.hashCode(), copy.hashCode());
assertEquals(p.hashCode(), copy.hashCode());
}
}
}
Expand Down
Loading