Population.java
package net.bmahe.genetics4j.core;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.Validate;
/**
* Represents a population of individuals in an evolutionary algorithm.
*
* <p>A population is a collection of {@link Individual}s, each consisting of a genotype and its associated fitness.
* This class provides methods to manage, access, and iterate over the individuals in the population.
*
* <p>Populations are mutable and support adding individuals, either individually or in bulk from other populations.
* The class maintains parallel lists of genotypes and fitnesses for efficient access.
*
* @param <T> the type of the fitness values, must be comparable for selection operations
* @see Individual
* @see Genotype
*/
public class Population<T extends Comparable<T>> implements Iterable<Individual<T>> {
private List<Genotype> genotypes;
private List<T> fitnesses;
/**
* Creates an empty population.
*/
public Population() {
this.genotypes = new ArrayList<>();
this.fitnesses = new ArrayList<>();
}
/**
* Creates a population with the specified genotypes and fitnesses.
*
* @param _genotype the list of genotypes for the population
* @param _fitnesses the list of fitness values corresponding to the genotypes
* @throws IllegalArgumentException if genotypes or fitnesses are null, or if their sizes don't match
*/
public Population(final List<Genotype> _genotype, final List<T> _fitnesses) {
Validate.notNull(_genotype);
Validate.notNull(_fitnesses);
Validate.isTrue(_genotype.size() == _fitnesses.size(),
"Size of genotype (%d) does not match size of fitnesses (%d)",
_genotype.size(),
_fitnesses.size());
this.genotypes = new ArrayList<Genotype>(_genotype);
this.fitnesses = new ArrayList<>(_fitnesses);
}
/**
* Adds an individual to the population by specifying its genotype and fitness separately.
*
* @param genotype the genotype of the individual to add
* @param fitness the fitness value of the individual to add
* @throws IllegalArgumentException if genotype or fitness is null
*/
public void add(final Genotype genotype, final T fitness) {
Validate.notNull(genotype);
Validate.notNull(fitness);
genotypes.add(genotype);
fitnesses.add(fitness);
}
/**
* Adds an individual to the population.
*
* @param individual the individual to add to the population
* @throws IllegalArgumentException if individual is null
*/
public void add(final Individual<T> individual) {
Validate.notNull(individual);
genotypes.add(individual.genotype());
fitnesses.add(individual.fitness());
}
/**
* Adds all individuals from another population to this population.
*
* @param population the population whose individuals should be added to this population
* @throws IllegalArgumentException if population is null
*/
public void addAll(final Population<T> population) {
Validate.notNull(population);
this.genotypes.addAll(population.getAllGenotypes());
this.fitnesses.addAll(population.getAllFitnesses());
}
@Override
public Iterator<Individual<T>> iterator() {
return new PopulationIterator<>(this);
}
/**
* Returns the genotype at the specified index.
*
* @param index the index of the genotype to retrieve (0-based)
* @return the genotype at the specified index
* @throws IllegalArgumentException if index is out of bounds
*/
public Genotype getGenotype(final int index) {
Validate.inclusiveBetween(0, genotypes.size() - 1, index);
return genotypes.get(index);
}
/**
* Returns the fitness value at the specified index.
*
* @param index the index of the fitness value to retrieve (0-based)
* @return the fitness value at the specified index
* @throws IllegalArgumentException if index is out of bounds
*/
public T getFitness(final int index) {
Validate.inclusiveBetween(0, fitnesses.size() - 1, index);
return fitnesses.get(index);
}
/**
* Returns the individual at the specified index.
*
* @param index the index of the individual to retrieve (0-based)
* @return the individual at the specified index, combining its genotype and fitness
* @throws IllegalArgumentException if index is out of bounds
*/
public Individual<T> getIndividual(final int index) {
return Individual.of(getGenotype(index), getFitness(index));
}
/**
* Returns all genotypes in this population.
*
* @return a list containing all genotypes in this population
*/
public List<Genotype> getAllGenotypes() {
return genotypes;
}
/**
* Returns all fitness values in this population.
*
* @return a list containing all fitness values in this population
*/
public List<T> getAllFitnesses() {
return fitnesses;
}
/**
* Returns the number of individuals in this population.
*
* @return the size of the population
*/
public int size() {
return genotypes.size();
}
/**
* Checks if this population is empty.
*
* @return {@code true} if the population contains no individuals, {@code false} otherwise
*/
public boolean isEmpty() {
return size() == 0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fitnesses == null) ? 0 : fitnesses.hashCode());
result = prime * result + ((genotypes == null) ? 0 : genotypes.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
@SuppressWarnings("rawtypes")
Population other = (Population) obj;
if (fitnesses == null) {
if (other.fitnesses != null)
return false;
} else if (!fitnesses.equals(other.fitnesses))
return false;
if (genotypes == null) {
if (other.genotypes != null)
return false;
} else if (!genotypes.equals(other.genotypes))
return false;
return true;
}
@Override
public String toString() {
return "Population [genotypes=" + genotypes + ", fitnesses=" + fitnesses + "]";
}
/**
* Creates a new population with the specified genotypes and fitnesses.
*
* @param <U> the type of the fitness values
* @param _genotype the list of genotypes for the population
* @param _fitnesses the list of fitness values corresponding to the genotypes
* @return a new population containing the specified genotypes and fitnesses
* @throws IllegalArgumentException if genotypes or fitnesses are null, or if their sizes don't match
*/
public static <U extends Comparable<U>> Population<U> of(final List<Genotype> _genotype, final List<U> _fitnesses) {
return new Population<U>(_genotype, _fitnesses);
}
/**
* Creates a new population from a list of individuals.
*
* @param <U> the type of the fitness values
* @param individuals the list of individuals to include in the population
* @return a new population containing the specified individuals
* @throws IllegalArgumentException if individuals list is null
*/
public static <U extends Comparable<U>> Population<U> of(final List<Individual<U>> individuals) {
Validate.notNull(individuals);
final List<Genotype> genotypes = individuals.stream()
.map(Individual::genotype)
.toList();
final List<U> fitnesses = individuals.stream()
.map(Individual::fitness)
.toList();
return new Population<U>(genotypes, fitnesses);
}
/**
* Creates an empty population.
*
* @param <U> the type of the fitness values
* @return a new empty population
*/
public static <U extends Comparable<U>> Population<U> empty() {
return new Population<U>(List.of(), List.of());
}
}