View Javadoc
1   package net.bmahe.genetics4j.core;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.List;
6   import java.util.Objects;
7   
8   import org.apache.commons.lang3.Validate;
9   
10  /**
11   * Represents a population of individuals in an evolutionary algorithm.
12   * 
13   * <p>A population is a collection of {@link Individual}s, each consisting of a genotype and its associated fitness.
14   * This class provides methods to manage, access, and iterate over the individuals in the population.
15   * 
16   * <p>Populations are mutable and support adding individuals, either individually or in bulk from other populations. The
17   * class maintains parallel lists of genotypes and fitnesses for efficient access.
18   * 
19   * @param <T> the type of the fitness values, must be comparable for selection operations
20   * @see Individual
21   * @see Genotype
22   */
23  public class Population<T extends Comparable<T>> implements Iterable<Individual<T>> {
24  
25  	private List<Genotype> genotypes;
26  	private List<T> fitnesses;
27  
28  	/**
29  	 * Creates an empty population.
30  	 */
31  	public Population() {
32  		this.genotypes = new ArrayList<>();
33  		this.fitnesses = new ArrayList<>();
34  	}
35  
36  	/**
37  	 * Creates a population with the specified genotypes and fitnesses.
38  	 * 
39  	 * @param _genotype  the list of genotypes for the population
40  	 * @param _fitnesses the list of fitness values corresponding to the genotypes
41  	 * @throws IllegalArgumentException if genotypes or fitnesses are null, or if their sizes don't match
42  	 */
43  	public Population(final List<Genotype> _genotype, final List<T> _fitnesses) {
44  		Objects.requireNonNull(_genotype);
45  		Objects.requireNonNull(_fitnesses);
46  		Validate.isTrue(
47  				_genotype.size() == _fitnesses.size(),
48  				"Size of genotype (%d) does not match size of fitnesses (%d)",
49  				_genotype.size(),
50  				_fitnesses.size());
51  
52  		this.genotypes = new ArrayList<>(_genotype);
53  		this.fitnesses = new ArrayList<>(_fitnesses);
54  	}
55  
56  	/**
57  	 * Adds an individual to the population by specifying its genotype and fitness separately.
58  	 * 
59  	 * @param genotype the genotype of the individual to add
60  	 * @param fitness  the fitness value of the individual to add
61  	 * @throws IllegalArgumentException if genotype or fitness is null
62  	 */
63  	public void add(final Genotype genotype, final T fitness) {
64  		Objects.requireNonNull(genotype);
65  		Objects.requireNonNull(fitness);
66  
67  		genotypes.add(genotype);
68  		fitnesses.add(fitness);
69  	}
70  
71  	/**
72  	 * Adds an individual to the population.
73  	 * 
74  	 * @param individual the individual to add to the population
75  	 * @throws IllegalArgumentException if individual is null
76  	 */
77  	public void add(final Individual<T> individual) {
78  		Objects.requireNonNull(individual);
79  
80  		genotypes.add(individual.genotype());
81  		fitnesses.add(individual.fitness());
82  	}
83  
84  	/**
85  	 * Adds all individuals from another population to this population.
86  	 * 
87  	 * @param population the population whose individuals should be added to this population
88  	 * @throws IllegalArgumentException if population is null
89  	 */
90  	public void addAll(final Population<T> population) {
91  		Objects.requireNonNull(population);
92  
93  		this.genotypes.addAll(population.getAllGenotypes());
94  		this.fitnesses.addAll(population.getAllFitnesses());
95  	}
96  
97  	@Override
98  	public Iterator<Individual<T>> iterator() {
99  		return new PopulationIterator<>(this);
100 	}
101 
102 	/**
103 	 * Returns the genotype at the specified index.
104 	 * 
105 	 * @param index the index of the genotype to retrieve (0-based)
106 	 * @return the genotype at the specified index
107 	 * @throws IllegalArgumentException if index is out of bounds
108 	 */
109 	public Genotype getGenotype(final int index) {
110 		Validate.inclusiveBetween(0, genotypes.size() - 1, index);
111 
112 		return genotypes.get(index);
113 	}
114 
115 	/**
116 	 * Returns the fitness value at the specified index.
117 	 * 
118 	 * @param index the index of the fitness value to retrieve (0-based)
119 	 * @return the fitness value at the specified index
120 	 * @throws IllegalArgumentException if index is out of bounds
121 	 */
122 	public T getFitness(final int index) {
123 		Validate.inclusiveBetween(0, fitnesses.size() - 1, index);
124 
125 		return fitnesses.get(index);
126 	}
127 
128 	/**
129 	 * Returns the individual at the specified index.
130 	 * 
131 	 * @param index the index of the individual to retrieve (0-based)
132 	 * @return the individual at the specified index, combining its genotype and fitness
133 	 * @throws IllegalArgumentException if index is out of bounds
134 	 */
135 	public Individual<T> getIndividual(final int index) {
136 		return Individual.of(getGenotype(index), getFitness(index));
137 	}
138 
139 	/**
140 	 * Returns all genotypes in this population.
141 	 * 
142 	 * @return a list containing all genotypes in this population
143 	 */
144 	public List<Genotype> getAllGenotypes() {
145 		return genotypes;
146 	}
147 
148 	/**
149 	 * Returns all fitness values in this population.
150 	 * 
151 	 * @return a list containing all fitness values in this population
152 	 */
153 	public List<T> getAllFitnesses() {
154 		return fitnesses;
155 	}
156 
157 	/**
158 	 * Returns the number of individuals in this population.
159 	 * 
160 	 * @return the size of the population
161 	 */
162 	public int size() {
163 		return genotypes.size();
164 	}
165 
166 	/**
167 	 * Checks if this population is empty.
168 	 * 
169 	 * @return {@code true} if the population contains no individuals, {@code false} otherwise
170 	 */
171 	public boolean isEmpty() {
172 		return size() == 0;
173 	}
174 
175 	@Override
176 	public int hashCode() {
177 		final int prime = 31;
178 		int result = 1;
179 		result = prime * result + (fitnesses == null ? 0 : fitnesses.hashCode());
180 		return prime * result + (genotypes == null ? 0 : genotypes.hashCode());
181 	}
182 
183 	@Override
184 	public boolean equals(Object obj) {
185 		if (this == obj) {
186 			return true;
187 		}
188 		if (obj == null) {
189 			return false;
190 		}
191 		if (getClass() != obj.getClass()) {
192 			return false;
193 		}
194 
195 		@SuppressWarnings("rawtypes")
196 		Population other = (Population) obj;
197 		if (fitnesses == null) {
198 			if (other.fitnesses != null) {
199 				return false;
200 			}
201 		} else if (!fitnesses.equals(other.fitnesses)) {
202 			return false;
203 		}
204 		if (genotypes == null) {
205 			if (other.genotypes != null) {
206 				return false;
207 			}
208 		} else if (!genotypes.equals(other.genotypes)) {
209 			return false;
210 		}
211 		return true;
212 	}
213 
214 	@Override
215 	public String toString() {
216 		return "Population [genotypes=" + genotypes + ", fitnesses=" + fitnesses + "]";
217 	}
218 
219 	/**
220 	 * Creates a new population with the specified genotypes and fitnesses.
221 	 * 
222 	 * @param <U>        the type of the fitness values
223 	 * @param _genotype  the list of genotypes for the population
224 	 * @param _fitnesses the list of fitness values corresponding to the genotypes
225 	 * @return a new population containing the specified genotypes and fitnesses
226 	 * @throws IllegalArgumentException if genotypes or fitnesses are null, or if their sizes don't match
227 	 */
228 	public static <U extends Comparable<U>> Population<U> of(final List<Genotype> _genotype, final List<U> _fitnesses) {
229 		return new Population<>(_genotype, _fitnesses);
230 	}
231 
232 	/**
233 	 * Creates a new population from a list of individuals.
234 	 * 
235 	 * @param <U>         the type of the fitness values
236 	 * @param individuals the list of individuals to include in the population
237 	 * @return a new population containing the specified individuals
238 	 * @throws IllegalArgumentException if individuals list is null
239 	 */
240 	public static <U extends Comparable<U>> Population<U> of(final List<Individual<U>> individuals) {
241 		Objects.requireNonNull(individuals);
242 
243 		final List<Genotype> genotypes = individuals.stream().map(Individual::genotype).toList();
244 
245 		final List<U> fitnesses = individuals.stream().map(Individual::fitness).toList();
246 
247 		return new Population<>(genotypes, fitnesses);
248 	}
249 
250 	/**
251 	 * Creates an empty population.
252 	 * 
253 	 * @param <U> the type of the fitness values
254 	 * @return a new empty population
255 	 */
256 	public static <U extends Comparable<U>> Population<U> empty() {
257 		return new Population<U>(List.of(), List.of());
258 	}
259 
260 }