42
42
public final class Permutation
43
43
implements Serializable , Iterable <Permutation >, Copyable <Permutation > {
44
44
45
- private static final long serialVersionUID = 1L ;
45
+ private static final long serialVersionUID = 2L ;
46
46
47
47
/**
48
48
* Raw permutation, which should consist of a permutation of the integers in [0,
49
49
* permutation.length).
50
50
*/
51
51
private final int [] permutation ;
52
52
53
+ /*
54
+ * Class caches the hashCode the first time hashCode() is called
55
+ * to avoid cost of recomputing in applications that rely on HashSets or HashMaps
56
+ * of Permutations, etc with heavy use of the hashCode.
57
+ */
58
+ private transient int hashCode ;
59
+
60
+ /*
61
+ * Flag for validating/invalidating cache of hashCode. All methods
62
+ * that change state of Permutation must invalidate the cache.
63
+ */
64
+ private transient boolean hashCodeIsCached ;
65
+
53
66
/**
54
67
* Initializes a random permutation of n integers. Uses {@link ThreadLocalRandom} as the source of
55
68
* efficient random number generation.
@@ -159,6 +172,8 @@ private Permutation(int[] p, boolean validate) {
159
172
*/
160
173
public Permutation (Permutation p ) {
161
174
permutation = p .permutation .clone ();
175
+ hashCodeIsCached = p .hashCodeIsCached ;
176
+ hashCode = p .hashCode ;
162
177
}
163
178
164
179
/**
@@ -197,6 +212,7 @@ public Permutation(Permutation p, int length) {
197
212
*/
198
213
public void apply (PermutationUnaryOperator operator ) {
199
214
operator .apply (permutation );
215
+ hashCodeIsCached = false ;
200
216
}
201
217
202
218
/**
@@ -206,6 +222,7 @@ public void apply(PermutationUnaryOperator operator) {
206
222
*/
207
223
public void apply (PermutationFullUnaryOperator operator ) {
208
224
operator .apply (permutation , this );
225
+ hashCodeIsCached = false ;
209
226
}
210
227
211
228
/**
@@ -218,6 +235,8 @@ public void apply(PermutationFullUnaryOperator operator) {
218
235
*/
219
236
public void apply (PermutationBinaryOperator operator , Permutation other ) {
220
237
operator .apply (permutation , other .permutation );
238
+ hashCodeIsCached = false ;
239
+ other .hashCodeIsCached = false ;
221
240
}
222
241
223
242
/**
@@ -230,6 +249,8 @@ public void apply(PermutationBinaryOperator operator, Permutation other) {
230
249
*/
231
250
public void apply (PermutationFullBinaryOperator operator , Permutation other ) {
232
251
operator .apply (permutation , other .permutation , this , other );
252
+ hashCodeIsCached = false ;
253
+ other .hashCodeIsCached = false ;
233
254
}
234
255
235
256
/**
@@ -243,6 +264,7 @@ public void apply(PermutationFullBinaryOperator operator, Permutation other) {
243
264
public void applyThenValidate (PermutationUnaryOperator operator ) {
244
265
try {
245
266
operator .apply (permutation );
267
+ hashCodeIsCached = false ;
246
268
validate (permutation );
247
269
} catch (IllegalArgumentException exception ) {
248
270
throw new IllegalPermutationStateException (
@@ -261,6 +283,7 @@ public void applyThenValidate(PermutationUnaryOperator operator) {
261
283
public void applyThenValidate (PermutationFullUnaryOperator operator ) {
262
284
try {
263
285
operator .apply (permutation , this );
286
+ hashCodeIsCached = false ;
264
287
validate (permutation );
265
288
} catch (IllegalArgumentException exception ) {
266
289
throw new IllegalPermutationStateException (
@@ -282,6 +305,8 @@ public void applyThenValidate(PermutationFullUnaryOperator operator) {
282
305
public void applyThenValidate (PermutationBinaryOperator operator , Permutation other ) {
283
306
try {
284
307
operator .apply (permutation , other .permutation );
308
+ hashCodeIsCached = false ;
309
+ other .hashCodeIsCached = false ;
285
310
validate (permutation );
286
311
validate (other .permutation );
287
312
} catch (IllegalArgumentException exception ) {
@@ -305,6 +330,8 @@ public void applyThenValidate(PermutationBinaryOperator operator, Permutation ot
305
330
public void applyThenValidate (PermutationFullBinaryOperator operator , Permutation other ) {
306
331
try {
307
332
operator .apply (permutation , other .permutation , this , other );
333
+ hashCodeIsCached = false ;
334
+ other .hashCodeIsCached = false ;
308
335
validate (permutation );
309
336
validate (other .permutation );
310
337
} catch (IllegalArgumentException exception ) {
@@ -413,6 +440,7 @@ public Permutation getInversePermutation() {
413
440
*/
414
441
public void invert () {
415
442
System .arraycopy (getInverse (), 0 , permutation , 0 , permutation .length );
443
+ hashCodeIsCached = false ;
416
444
}
417
445
418
446
/**
@@ -444,6 +472,7 @@ public void scramble(RandomGenerator r) {
444
472
permutation [j ] = i ;
445
473
}
446
474
}
475
+ hashCodeIsCached = false ;
447
476
}
448
477
}
449
478
@@ -471,13 +500,14 @@ public void scramble(RandomGenerator r, boolean guaranteeDifferent) {
471
500
for (int i = permutation .length - 1 ; i > 1 ; i --) {
472
501
int j = RandomIndexer .nextInt (i + 1 , r );
473
502
if (i != j ) {
474
- swap (i , j );
503
+ internalSwap (i , j );
475
504
changed = true ;
476
505
}
477
506
}
478
507
if (permutation .length > 1 && (!changed || r .nextBoolean ())) {
479
- swap (0 , 1 );
508
+ internalSwap (0 , 1 );
480
509
}
510
+ hashCodeIsCached = false ;
481
511
} else {
482
512
scramble (r );
483
513
}
@@ -509,22 +539,23 @@ public void scramble(int i, int j, RandomGenerator r) {
509
539
if (i == j ) {
510
540
return ;
511
541
}
542
+ int k = j ;
512
543
if (i > j ) {
513
- int temp = i ;
544
+ k = i ;
514
545
i = j ;
515
- j = temp ;
516
546
}
517
547
boolean changed = false ;
518
- for (int k = j ; k > i + 1 ; k --) {
548
+ for (; k > i + 1 ; k --) {
519
549
int l = i + RandomIndexer .nextInt (k - i + 1 , r );
520
550
if (l != k ) {
521
- swap (l , k );
551
+ internalSwap (l , k );
522
552
changed = true ;
523
553
}
524
554
}
525
555
if (!changed || r .nextBoolean ()) {
526
- swap (i , i + 1 );
556
+ internalSwap (i , i + 1 );
527
557
}
558
+ hashCodeIsCached = false ;
528
559
}
529
560
530
561
/**
@@ -544,13 +575,14 @@ public void scramble(int[] indexes, RandomGenerator r) {
544
575
for (int j = indexes .length - 1 ; j > 1 ; j --) {
545
576
int i = RandomIndexer .nextInt (j + 1 , r );
546
577
if (i != j ) {
547
- swap (indexes [i ], indexes [j ]);
578
+ internalSwap (indexes [i ], indexes [j ]);
548
579
changed = true ;
549
580
}
550
581
}
551
582
if (!changed || r .nextBoolean ()) {
552
- swap (indexes [0 ], indexes [1 ]);
583
+ internalSwap (indexes [0 ], indexes [1 ]);
553
584
}
585
+ hashCodeIsCached = false ;
554
586
}
555
587
}
556
588
@@ -667,6 +699,7 @@ public void swap(int i, int j) {
667
699
int temp = permutation [i ];
668
700
permutation [i ] = permutation [j ];
669
701
permutation [j ] = temp ;
702
+ hashCodeIsCached = false ;
670
703
}
671
704
672
705
/**
@@ -688,6 +721,7 @@ public void cycle(int[] indexes) {
688
721
permutation [indexes [i - 1 ]] = permutation [indexes [i ]];
689
722
}
690
723
permutation [indexes [indexes .length - 1 ]] = temp ;
724
+ hashCodeIsCached = false ;
691
725
}
692
726
}
693
727
@@ -718,14 +752,16 @@ public void swapBlocks(int a, int b, int i, int j) {
718
752
System .arraycopy (permutation , b + 1 , temp , k , m );
719
753
System .arraycopy (permutation , a , temp , k + m , b - a + 1 );
720
754
System .arraycopy (temp , 0 , permutation , a , temp .length );
755
+ hashCodeIsCached = false ;
721
756
}
722
757
}
723
758
724
759
/** Reverses the order of the elements in the permutation. */
725
760
public void reverse () {
726
761
for (int i = 0 , j = permutation .length - 1 ; i < j ; i ++, j --) {
727
- swap (i , j );
762
+ internalSwap (i , j );
728
763
}
764
+ hashCodeIsCached = false ;
729
765
}
730
766
731
767
/**
@@ -739,13 +775,14 @@ public void reverse() {
739
775
public void reverse (int i , int j ) {
740
776
if (i > j ) {
741
777
for (; i > j ; i --, j ++) {
742
- swap (i , j );
778
+ internalSwap (i , j );
743
779
}
744
780
} else {
745
781
for (; i < j ; i ++, j --) {
746
- swap (i , j );
782
+ internalSwap (i , j );
747
783
}
748
784
}
785
+ hashCodeIsCached = false ;
749
786
}
750
787
751
788
/**
@@ -762,10 +799,12 @@ public void removeAndInsert(int i, int j) {
762
799
int n = permutation [i ];
763
800
System .arraycopy (permutation , i + 1 , permutation , i , j - i );
764
801
permutation [j ] = n ;
802
+ hashCodeIsCached = false ;
765
803
} else if (i > j ) {
766
804
int n = permutation [i ];
767
805
System .arraycopy (permutation , j , permutation , j + 1 , i - j );
768
806
permutation [j ] = n ;
807
+ hashCodeIsCached = false ;
769
808
}
770
809
}
771
810
@@ -784,6 +823,7 @@ public void rotate(int numPositions) {
784
823
System .arraycopy (
785
824
permutation , numPositions , permutation , 0 , permutation .length - numPositions );
786
825
System .arraycopy (temp , 0 , permutation , permutation .length - numPositions , numPositions );
826
+ hashCodeIsCached = false ;
787
827
}
788
828
}
789
829
@@ -809,11 +849,13 @@ public void removeAndInsert(int i, int size, int j) {
809
849
System .arraycopy (permutation , j , temp , 0 , i - j );
810
850
System .arraycopy (permutation , i , permutation , j , size );
811
851
System .arraycopy (temp , 0 , permutation , j + size , i - j );
852
+ hashCodeIsCached = false ;
812
853
} else { // Condition is implied by above: if (i < j)
813
854
int [] temp = new int [size ];
814
855
System .arraycopy (permutation , i , temp , 0 , size );
815
856
System .arraycopy (permutation , i + size , permutation , i , j - i );
816
857
System .arraycopy (temp , 0 , permutation , j , size );
858
+ hashCodeIsCached = false ;
817
859
}
818
860
}
819
861
@@ -831,6 +873,7 @@ public void set(int[] p) {
831
873
}
832
874
validate (p );
833
875
System .arraycopy (p , 0 , permutation , 0 , p .length );
876
+ hashCodeIsCached = false ;
834
877
}
835
878
836
879
/**
@@ -892,7 +935,11 @@ public boolean equals(Object other) {
892
935
*/
893
936
@ Override
894
937
public int hashCode () {
895
- return Arrays .hashCode (permutation );
938
+ if (hashCodeIsCached ) {
939
+ return hashCode ;
940
+ }
941
+ hashCodeIsCached = true ;
942
+ return hashCode = Arrays .hashCode (permutation );
896
943
}
897
944
898
945
private boolean validate (int [] p ) {
@@ -909,4 +956,15 @@ private boolean validate(int[] p) {
909
956
}
910
957
return true ;
911
958
}
959
+
960
+ /*
961
+ * Use internally, such as from reverse, etc to avoid
962
+ * repeatedly invalidating hashCode cache (as well as from
963
+ * the PermutationIterator).
964
+ */
965
+ final void internalSwap (int i , int j ) {
966
+ int temp = permutation [i ];
967
+ permutation [i ] = permutation [j ];
968
+ permutation [j ] = temp ;
969
+ }
912
970
}
0 commit comments