001/* 002 * Java Genetic Algorithm Library (jenetics-8.1.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 */ 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 * {@snippet lang="java": 126 * final CharacterChromosome chromosome = null; // @replace substring='null' replacement="..." 127 * final CharacterChromosome uppercase = chromosome.map(Main::uppercase); 128 * 129 * static int[] uppercase(final int[] values) { 130 * for (int i = 0; i < values.length; ++i) { 131 * values[i] = Character.toUpperCase(values[i]); 132 * } 133 * return values; 134 * } 135 * } 136 * 137 * @since 6.1 138 * 139 * @param f the mapper function 140 * @return a newly created chromosome with the mapped gene values 141 * @throws NullPointerException if the mapper function is {@code null}. 142 * @throws IllegalArgumentException if the length of the mapped 143 * {@code char[]} array is empty or doesn't match with the allowed 144 * length range 145 */ 146 public CharacterChromosome map(final Function<? super char[], char[]> f) { 147 requireNonNull(f); 148 149 final char[] chars = f.apply(toArray()); 150 final var genes = IntStream.range(0, chars.length) 151 .mapToObj(i -> CharacterGene.of(chars[i], _validCharacters)) 152 .collect(ISeq.toISeq()); 153 154 return newInstance(genes); 155 } 156 157 @Override 158 public int hashCode() { 159 return hash(super.hashCode(), hash(_validCharacters)); 160 } 161 162 @Override 163 public boolean equals(final Object obj) { 164 return obj == this || 165 obj != null && 166 getClass() == obj.getClass() && 167 Objects.equals(_validCharacters, ((CharacterChromosome)obj)._validCharacters) && 168 super.equals(obj); 169 } 170 171 @Override 172 public String toString() { 173 return new String(toArray()); 174 } 175 176 /** 177 * Returns a char array containing all the elements in this chromosome 178 * in a proper sequence. If the chromosome fits in the specified array, it is 179 * returned therein. Otherwise, a new array is allocated with the length of 180 * this chromosome. 181 * 182 * @since 3.0 183 * 184 * @param array the array into which the elements of this chromosome are to 185 * be stored, if it is big enough; otherwise, a new array is 186 * allocated for this purpose. 187 * @return an array containing the elements of this chromosome 188 * @throws NullPointerException if the given {@code array} is {@code null} 189 */ 190 public char[] toArray(final char[] array) { 191 final char[] a = array.length >= length() 192 ? array 193 : new char[length()]; 194 195 for (int i = length(); --i >= 0;) { 196 a[i] = charAt(i); 197 } 198 199 return a; 200 } 201 202 /** 203 * Returns a char array containing all the elements in this chromosome 204 * in a proper sequence. 205 * 206 * @since 3.0 207 * 208 * @return an array containing the elements of this chromosome 209 */ 210 public char[] toArray() { 211 return toArray(new char[length()]); 212 } 213 214 215 /* ************************************************************************* 216 * Static factory methods. 217 * ************************************************************************/ 218 219 /** 220 * Create a new chromosome with the {@code validCharacters} char set as 221 * valid characters. 222 * 223 * @since 4.3 224 * 225 * @param validCharacters the valid characters for this chromosome. 226 * @param lengthRange the allowed length range of the chromosome. 227 * @return a new {@code CharacterChromosome} with the given parameter 228 * @throws NullPointerException if the {@code validCharacters} is 229 * {@code null}. 230 * @throws IllegalArgumentException if the length of the gene sequence is 231 * empty, doesn't match with the allowed length range, the minimum 232 * or maximum of the range is smaller or equal zero, or the given 233 * range size is zero. 234 */ 235 public static CharacterChromosome of( 236 final CharSeq validCharacters, 237 final IntRange lengthRange 238 ) { 239 return new CharacterChromosome( 240 CharacterGene.seq(validCharacters, lengthRange), 241 lengthRange 242 ); 243 } 244 245 /** 246 * Create a new chromosome with the {@link CharacterGene#DEFAULT_CHARACTERS} 247 * char set as valid characters. 248 * 249 * @param lengthRange the allowed length range of the chromosome. 250 * @return a new {@code CharacterChromosome} with the given parameter 251 * @throws IllegalArgumentException if the {@code length} is smaller than 252 * one. 253 */ 254 public static CharacterChromosome of(final IntRange lengthRange) { 255 return of(DEFAULT_CHARACTERS, lengthRange); 256 } 257 258 /** 259 * Create a new chromosome with the {@code validCharacters} char set as 260 * valid characters. 261 * 262 * @since 4.3 263 * 264 * @param validCharacters the valid characters for this chromosome. 265 * @param length the {@code length} of the new chromosome. 266 * @return a new {@code CharacterChromosome} with the given parameter 267 * @throws NullPointerException if the {@code validCharacters} is 268 * {@code null}. 269 * @throws IllegalArgumentException if the length of the gene sequence is 270 * empty, doesn't match with the allowed length range, the minimum 271 * or maximum of the range is smaller or equal zero, or the given 272 * range size is zero. 273 */ 274 public static CharacterChromosome of( 275 final CharSeq validCharacters, 276 final int length 277 ) { 278 return of(validCharacters, IntRange.of(length)); 279 } 280 281 /** 282 * Create a new chromosome with the {@link CharacterGene#DEFAULT_CHARACTERS} 283 * char set as valid characters. 284 * 285 * @param length the {@code length} of the new chromosome. 286 * @return a new {@code CharacterChromosome} with the given parameter 287 * @throws IllegalArgumentException if the {@code length} is smaller than 288 * one. 289 */ 290 public static CharacterChromosome of(final int length) { 291 return of(DEFAULT_CHARACTERS, length); 292 } 293 294 /** 295 * Create a new chromosome from the given genes (given as string). 296 * 297 * @param alleles the character genes. 298 * @param validChars the valid characters. 299 * @return a new {@code CharacterChromosome} with the given parameter 300 * @throws IllegalArgumentException if the genes string is empty. 301 */ 302 public static CharacterChromosome of( 303 final String alleles, 304 final CharSeq validChars 305 ) { 306 final MSeq<CharacterGene> genes = MSeq.ofLength(alleles.length()); 307 for (int i = 0; i < alleles.length(); ++i) { 308 genes.set(i, CharacterGene.of(alleles.charAt(i), validChars)); 309 } 310 311 return new CharacterChromosome(genes.toISeq(), IntRange.of(alleles.length())); 312 } 313 314 /** 315 * Create a new chromosome from the given genes (given as string). 316 * 317 * @param alleles the character genes. 318 * @return a new {@code CharacterChromosome} with the given parameter 319 * @throws IllegalArgumentException if the genes string is empty. 320 */ 321 public static CharacterChromosome of(final String alleles) { 322 return of(alleles, DEFAULT_CHARACTERS); 323 } 324 325 326 /* ************************************************************************* 327 * Java object serialization 328 * ************************************************************************/ 329 330 @Serial 331 private Object writeReplace() { 332 return new SerialProxy(SerialProxy.CHARACTER_CHROMOSOME, this); 333 } 334 335 @Serial 336 private void readObject(final ObjectInputStream stream) 337 throws InvalidObjectException 338 { 339 throw new InvalidObjectException("Serialization proxy required."); 340 } 341 342 void write(final DataOutput out) throws IOException { 343 writeInt(lengthRange().min(), out); 344 writeInt(lengthRange().max(), out); 345 writeString(_validCharacters.toString(), out); 346 writeString(toString(), out); 347 } 348 349 static CharacterChromosome read(final DataInput in) throws IOException { 350 final var lengthRange = IntRange.of(readInt(in), readInt(in)); 351 final var validCharacters = new CharSeq(readString(in)); 352 final var chars = readString(in); 353 354 final MSeq<CharacterGene> values = MSeq.ofLength(chars.length()); 355 for (int i = 0, n = chars.length(); i < n; ++i) { 356 values.set(i, CharacterGene.of(chars.charAt(i), validCharacters)); 357 } 358 359 return new CharacterChromosome(values.toISeq(), lengthRange); 360 } 361 362}