001 /*
002 * Java Genetic Algorithm Library (jenetics-8.0.0).
003 * Copyright (c) 2007-2024 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 * Author:
018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020 package io.jenetics;
021
022 import static java.util.Objects.requireNonNull;
023 import static io.jenetics.CharacterGene.DEFAULT_CHARACTERS;
024 import static io.jenetics.internal.util.Hashes.hash;
025 import static io.jenetics.internal.util.SerialIO.readInt;
026 import static io.jenetics.internal.util.SerialIO.readString;
027 import static io.jenetics.internal.util.SerialIO.writeInt;
028 import static io.jenetics.internal.util.SerialIO.writeString;
029
030 import java.io.DataInput;
031 import java.io.DataOutput;
032 import java.io.IOException;
033 import java.io.InvalidObjectException;
034 import java.io.ObjectInputStream;
035 import java.io.Serial;
036 import java.io.Serializable;
037 import java.util.Objects;
038 import java.util.function.Function;
039 import java.util.stream.IntStream;
040
041 import io.jenetics.util.CharSeq;
042 import io.jenetics.util.ISeq;
043 import io.jenetics.util.IntRange;
044 import io.jenetics.util.MSeq;
045
046 /**
047 * Character chromosome which represents character sequences.
048 *
049 * @see CharacterGene
050 *
051 * @implNote
052 * This class is immutable and thread-safe.
053 *
054 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
055 * @since 1.0
056 * @version 6.1
057 */
058 public class CharacterChromosome
059 extends VariableChromosome<CharacterGene>
060 implements
061 CharSequence,
062 Serializable
063 {
064 @Serial
065 private static final long serialVersionUID = 3L;
066
067 private transient final CharSeq _validCharacters;
068
069 /**
070 * Create a new chromosome from the given {@code genes} array. The genes
071 * array is copied, so changes to the given genes array don't affect the
072 * genes of this chromosome.
073 *
074 * @since 4.0
075 *
076 * @param genes the genes that form the chromosome.
077 * @param lengthRange the allowed length range of the chromosome.
078 * @throws NullPointerException if the given gene array is {@code null}.
079 * @throws IllegalArgumentException if the length of the gene array is
080 * smaller than one.
081 */
082 protected CharacterChromosome(
083 final ISeq<CharacterGene> genes,
084 final IntRange lengthRange
085 ) {
086 super(genes, lengthRange);
087 _validCharacters = genes.get(0).validChars();
088 }
089
090 @Override
091 public char charAt(final int index) {
092 return get(index).charValue();
093 }
094
095 @Override
096 public boolean isEmpty() {
097 return super.isEmpty();
098 }
099
100 @Override
101 public CharacterChromosome subSequence(final int start, final int end) {
102 return new CharacterChromosome(_genes.subSeq(start, end), lengthRange());
103 }
104
105 /**
106 * @throws NullPointerException if the given gene array is {@code null}.
107 */
108 @Override
109 public CharacterChromosome newInstance(final ISeq<CharacterGene> genes) {
110 return new CharacterChromosome(genes, lengthRange());
111 }
112
113 /**
114 * Create a new, <em>random</em> chromosome.
115 */
116 @Override
117 public CharacterChromosome newInstance() {
118 return of(_validCharacters, lengthRange());
119 }
120
121 /**
122 * Maps the gene alleles of this chromosome, given as {@code char[]} array,
123 * by applying the given mapper function {@code f}. The mapped gene values
124 * are then wrapped into a newly created chromosome.
125 *
126 * {@snippet lang="java":
127 * final CharacterChromosome chromosome = null; // @replace substring='null' replacement="..."
128 * final CharacterChromosome uppercase = chromosome.map(Main::uppercase);
129 *
130 * static int[] uppercase(final int[] values) {
131 * for (int i = 0; i < values.length; ++i) {
132 * values[i] = Character.toUpperCase(values[i]);
133 * }
134 * return values;
135 * }
136 * }
137 *
138 * @since 6.1
139 *
140 * @param f the mapper function
141 * @return a newly created chromosome with the mapped gene values
142 * @throws NullPointerException if the mapper function is {@code null}.
143 * @throws IllegalArgumentException if the length of the mapped
144 * {@code char[]} array is empty or doesn't match with the allowed
145 * length range
146 */
147 public CharacterChromosome map(final Function<? super char[], char[]> f) {
148 requireNonNull(f);
149
150 final char[] chars = f.apply(toArray());
151 final var genes = IntStream.range(0, chars.length)
152 .mapToObj(i -> CharacterGene.of(chars[i], _validCharacters))
153 .collect(ISeq.toISeq());
154
155 return newInstance(genes);
156 }
157
158 @Override
159 public int hashCode() {
160 return hash(super.hashCode(), hash(_validCharacters));
161 }
162
163 @Override
164 public boolean equals(final Object obj) {
165 return obj == this ||
166 obj != null &&
167 getClass() == obj.getClass() &&
168 Objects.equals(_validCharacters, ((CharacterChromosome)obj)._validCharacters) &&
169 super.equals(obj);
170 }
171
172 @Override
173 public String toString() {
174 return new String(toArray());
175 }
176
177 /**
178 * Returns a char array containing all the elements in this chromosome
179 * in a proper sequence. If the chromosome fits in the specified array, it is
180 * returned therein. Otherwise, a new array is allocated with the length of
181 * this chromosome.
182 *
183 * @since 3.0
184 *
185 * @param array the array into which the elements of this chromosome are to
186 * be stored, if it is big enough; otherwise, a new array is
187 * allocated for this purpose.
188 * @return an array containing the elements of this chromosome
189 * @throws NullPointerException if the given {@code array} is {@code null}
190 */
191 public char[] toArray(final char[] array) {
192 final char[] a = array.length >= length()
193 ? array
194 : new char[length()];
195
196 for (int i = length(); --i >= 0;) {
197 a[i] = charAt(i);
198 }
199
200 return a;
201 }
202
203 /**
204 * Returns a char array containing all the elements in this chromosome
205 * in a proper sequence.
206 *
207 * @since 3.0
208 *
209 * @return an array containing the elements of this chromosome
210 */
211 public char[] toArray() {
212 return toArray(new char[length()]);
213 }
214
215
216 /* *************************************************************************
217 * Static factory methods.
218 * ************************************************************************/
219
220 /**
221 * Create a new chromosome with the {@code validCharacters} char set as
222 * valid characters.
223 *
224 * @since 4.3
225 *
226 * @param validCharacters the valid characters for this chromosome.
227 * @param lengthRange the allowed length range of the chromosome.
228 * @return a new {@code CharacterChromosome} with the given parameter
229 * @throws NullPointerException if the {@code validCharacters} is
230 * {@code null}.
231 * @throws IllegalArgumentException if the length of the gene sequence is
232 * empty, doesn't match with the allowed length range, the minimum
233 * or maximum of the range is smaller or equal zero, or the given
234 * range size is zero.
235 */
236 public static CharacterChromosome of(
237 final CharSeq validCharacters,
238 final IntRange lengthRange
239 ) {
240 return new CharacterChromosome(
241 CharacterGene.seq(validCharacters, lengthRange),
242 lengthRange
243 );
244 }
245
246 /**
247 * Create a new chromosome with the {@link CharacterGene#DEFAULT_CHARACTERS}
248 * char set as valid characters.
249 *
250 * @param lengthRange the allowed length range of the chromosome.
251 * @return a new {@code CharacterChromosome} with the given parameter
252 * @throws IllegalArgumentException if the {@code length} is smaller than
253 * one.
254 */
255 public static CharacterChromosome of(final IntRange lengthRange) {
256 return of(DEFAULT_CHARACTERS, lengthRange);
257 }
258
259 /**
260 * Create a new chromosome with the {@code validCharacters} char set as
261 * valid characters.
262 *
263 * @since 4.3
264 *
265 * @param validCharacters the valid characters for this chromosome.
266 * @param length the {@code length} of the new chromosome.
267 * @return a new {@code CharacterChromosome} with the given parameter
268 * @throws NullPointerException if the {@code validCharacters} is
269 * {@code null}.
270 * @throws IllegalArgumentException if the length of the gene sequence is
271 * empty, doesn't match with the allowed length range, the minimum
272 * or maximum of the range is smaller or equal zero, or the given
273 * range size is zero.
274 */
275 public static CharacterChromosome of(
276 final CharSeq validCharacters,
277 final int length
278 ) {
279 return of(validCharacters, IntRange.of(length));
280 }
281
282 /**
283 * Create a new chromosome with the {@link CharacterGene#DEFAULT_CHARACTERS}
284 * char set as valid characters.
285 *
286 * @param length the {@code length} of the new chromosome.
287 * @return a new {@code CharacterChromosome} with the given parameter
288 * @throws IllegalArgumentException if the {@code length} is smaller than
289 * one.
290 */
291 public static CharacterChromosome of(final int length) {
292 return of(DEFAULT_CHARACTERS, length);
293 }
294
295 /**
296 * Create a new chromosome from the given genes (given as string).
297 *
298 * @param alleles the character genes.
299 * @param validChars the valid characters.
300 * @return a new {@code CharacterChromosome} with the given parameter
301 * @throws IllegalArgumentException if the genes string is empty.
302 */
303 public static CharacterChromosome of(
304 final String alleles,
305 final CharSeq validChars
306 ) {
307 final MSeq<CharacterGene> genes = MSeq.ofLength(alleles.length());
308 for (int i = 0; i < alleles.length(); ++i) {
309 genes.set(i, CharacterGene.of(alleles.charAt(i), validChars));
310 }
311
312 return new CharacterChromosome(genes.toISeq(), IntRange.of(alleles.length()));
313 }
314
315 /**
316 * Create a new chromosome from the given genes (given as string).
317 *
318 * @param alleles the character genes.
319 * @return a new {@code CharacterChromosome} with the given parameter
320 * @throws IllegalArgumentException if the genes string is empty.
321 */
322 public static CharacterChromosome of(final String alleles) {
323 return of(alleles, DEFAULT_CHARACTERS);
324 }
325
326
327 /* *************************************************************************
328 * Java object serialization
329 * ************************************************************************/
330
331 @Serial
332 private Object writeReplace() {
333 return new SerialProxy(SerialProxy.CHARACTER_CHROMOSOME, this);
334 }
335
336 @Serial
337 private void readObject(final ObjectInputStream stream)
338 throws InvalidObjectException
339 {
340 throw new InvalidObjectException("Serialization proxy required.");
341 }
342
343 void write(final DataOutput out) throws IOException {
344 writeInt(lengthRange().min(), out);
345 writeInt(lengthRange().max(), out);
346 writeString(_validCharacters.toString(), out);
347 writeString(toString(), out);
348 }
349
350 static CharacterChromosome read(final DataInput in) throws IOException {
351 final var lengthRange = IntRange.of(readInt(in), readInt(in));
352 final var validCharacters = new CharSeq(readString(in));
353 final var chars = readString(in);
354
355 final MSeq<CharacterGene> values = MSeq.ofLength(chars.length());
356 for (int i = 0, n = chars.length(); i < n; ++i) {
357 values.set(i, CharacterGene.of(chars.charAt(i), validCharacters));
358 }
359
360 return new CharacterChromosome(values.toISeq(), lengthRange);
361 }
362
363 }
|