001/*
002 * Java Genetic Algorithm Library (jenetics-7.2.0).
003 * Copyright (c) 2007-2023 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 */
020package io.jenetics;
021
022import static java.util.Objects.requireNonNull;
023import static io.jenetics.CharacterGene.DEFAULT_CHARACTERS;
024import static io.jenetics.internal.util.Hashes.hash;
025import static io.jenetics.internal.util.SerialIO.readInt;
026import static io.jenetics.internal.util.SerialIO.readString;
027import static io.jenetics.internal.util.SerialIO.writeInt;
028import static io.jenetics.internal.util.SerialIO.writeString;
029
030import java.io.DataInput;
031import java.io.DataOutput;
032import java.io.IOException;
033import java.io.InvalidObjectException;
034import java.io.ObjectInputStream;
035import java.io.Serial;
036import java.io.Serializable;
037import java.util.Objects;
038import java.util.function.Function;
039import java.util.stream.IntStream;
040
041import io.jenetics.util.CharSeq;
042import io.jenetics.util.ISeq;
043import io.jenetics.util.IntRange;
044import 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 */
058public 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         * <pre>{@code
127         * final CharacterChromosome chromosome = ...;
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         * }</pre>
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}