2222import java .util .Collection ;
2323import java .util .Iterator ;
2424import java .util .LinkedHashMap ;
25+ import java .util .LinkedHashSet ;
2526import java .util .List ;
2627import java .util .Map ;
2728import java .util .Map .Entry ;
29+ import java .util .Set ;
2830
2931final class ChunkCrossOvers {
3032 private ChunkCrossOvers () {}
@@ -98,6 +100,25 @@ static <K, V> void insertChunk(
98100 }
99101 }
100102
103+ static <K > void insertChunk (
104+ Set <K > set , Set <K > otherSet , int maxSize , PseudoRandom prng , boolean hasFixedSize ) {
105+ int originalSize = set .size ();
106+ int maxChunkSize = Math .min (maxSize - originalSize , otherSet .size ());
107+ int chunkSize = prng .sizeInClosedRange (1 , maxChunkSize , hasFixedSize );
108+ int fromChunkOffset = prng .closedRange (0 , otherSet .size () - chunkSize );
109+ Iterator <K > fromIterator = otherSet .iterator ();
110+ for (int i = 0 ; i < fromChunkOffset ; i ++) {
111+ fromIterator .next ();
112+ }
113+ // insertChunk only inserts new entries and does not overwrite existing
114+ // ones. As skipping those entries would lead to fewer insertions than
115+ // requested, loop over the rest of the map to fill the chunk if possible.
116+ while (set .size () < originalSize + chunkSize && fromIterator .hasNext ()) {
117+ K key = fromIterator .next ();
118+ set .add (key );
119+ }
120+ }
121+
101122 static <K , V > void overwriteChunk (
102123 Map <K , V > map , Map <K , V > otherMap , PseudoRandom prng , boolean hasFixedSize ) {
103124 onCorrespondingChunks (
@@ -117,6 +138,59 @@ static <K, V> void overwriteChunk(
117138 hasFixedSize );
118139 }
119140
141+ static <K > void overwriteChunk (
142+ Set <K > set , Set <K > otherSet , PseudoRandom prng , boolean hasFixedSize ) {
143+ onCorrespondingChunks (
144+ set ,
145+ otherSet ,
146+ prng ,
147+ (fromIterator , toIterator , chunkSize ) -> {
148+ // As keys can not be overwritten, only removed and new ones added, this
149+ // cross over overwrites the values. Removal of keys is handled by the
150+ // removeChunk mutation. Value equality is not checked here.
151+ for (int i = 0 ; i < chunkSize ; i ++) {
152+ K from = fromIterator .next ();
153+ K to = toIterator .next ();
154+ }
155+ },
156+ hasFixedSize );
157+ }
158+
159+ static <K > void crossOverChunk (
160+ Set <K > set , Set <K > otherSet , SerializingMutator <K > keyMutator , PseudoRandom prng ) {
161+ onCorrespondingChunks (
162+ set ,
163+ otherSet ,
164+ prng ,
165+ (fromIterator , toIterator , chunkSize ) -> {
166+ Set <K > entriesToAdd = new LinkedHashSet <>(chunkSize );
167+ for (int i = 0 ; i < chunkSize ; i ++) {
168+ K to = toIterator .next ();
169+ K from = fromIterator .next ();
170+
171+ // The entry has to be removed from the map before the cross-over, as
172+ // mutating its key could cause problems in subsequent lookups.
173+ // Furthermore, no new entries may be added while using the iterator,
174+ // so crossed-over keys are collected for later addition.
175+ toIterator .remove ();
176+
177+ // As cross-overs do not guarantee to mutate the given object, no
178+ // checks if the crossed over key already exists in the map are
179+ // performed. This potentially overwrites existing entries or
180+ // generates equal keys.
181+ // In case of cross over this behavior is acceptable.
182+ K newKey = keyMutator .crossOver (to , from , prng );
183+
184+ // Prevent null keys, as those are not allowed in some map implementations.
185+ if (newKey != null ) {
186+ entriesToAdd .add (newKey );
187+ }
188+ }
189+ set .addAll (entriesToAdd );
190+ },
191+ keyMutator .hasFixedSize ());
192+ }
193+
120194 static <K , V > void crossOverChunk (
121195 Map <K , V > map ,
122196 Map <K , V > otherMap ,
@@ -198,6 +272,11 @@ private interface ChunkMapOperation<K, V> {
198272 void apply (Iterator <Entry <K , V >> fromIterator , Iterator <Entry <K , V >> toIterator , int chunkSize );
199273 }
200274
275+ @ FunctionalInterface
276+ private interface ChunkSetOperation <K > {
277+ void apply (Iterator <K > fromIterator , Iterator <K > toIterator , int chunkSize );
278+ }
279+
201280 static <K , V > void onCorrespondingChunks (
202281 Map <K , V > map ,
203282 Map <K , V > otherMap ,
@@ -219,6 +298,27 @@ static <K, V> void onCorrespondingChunks(
219298 operation .apply (fromIterator , toIterator , chunkSize );
220299 }
221300
301+ static <K > void onCorrespondingChunks (
302+ Set <K > set ,
303+ Set <K > otherSet ,
304+ PseudoRandom prng ,
305+ ChunkSetOperation <K > operation ,
306+ boolean hasFixedSize ) {
307+ int maxChunkSize = Math .min (set .size (), otherSet .size ());
308+ int chunkSize = prng .sizeInClosedRange (1 , maxChunkSize , hasFixedSize );
309+ int fromChunkOffset = prng .closedRange (0 , otherSet .size () - chunkSize );
310+ int toChunkOffset = prng .closedRange (0 , set .size () - chunkSize );
311+ Iterator <K > fromIterator = otherSet .iterator ();
312+ for (int i = 0 ; i < fromChunkOffset ; i ++) {
313+ fromIterator .next ();
314+ }
315+ Iterator <K > toIterator = set .iterator ();
316+ for (int i = 0 ; i < toChunkOffset ; i ++) {
317+ toIterator .next ();
318+ }
319+ operation .apply (fromIterator , toIterator , chunkSize );
320+ }
321+
222322 public enum CrossOverAction {
223323 INSERT_CHUNK ,
224324 OVERWRITE_CHUNK ,
0 commit comments