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