/* Copyright 2009-2016 David Hadka
*
* This file is part of the MOEA Framework.
*
* The MOEA Framework is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* The MOEA Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the MOEA Framework. If not, see <http://www.gnu.org/licenses/>.
*/
package org.moeaframework.core.variable;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.moeaframework.core.FrameworkException;
import org.moeaframework.core.PRNG;
import org.moeaframework.core.Variable;
/**
* Decision variable for fixed-size subsets. Use a {@code BinaryVariable} for
* variable-size subsets.
*/
public class Subset implements Variable {
private static final long serialVersionUID = -4491760813656852414L;
/**
* Optimization factor. As long as {@code k} is marginally smaller than
* {@code n}, it is more computationally efficient to randomly generate
* candidate members in the subset. An {@code OPT_FACTOR} of {@code 1.1}
* enables random generation as long as {@code k} is at least {@code 10%}
* smaller than {@code n}.
*/
private static final double OPT_FACTOR = 1.1;
/**
* Enables validation checks to ensure the subset is valid. Due to the
* time complexity of validating subsets, this is disabled by default.
*/
private static final boolean VALIDATE = false;
/**
* The number of candidate members.
*/
private int n;
/**
* The ordered members in this subset.
*/
private int[] members;
/**
* Set of members in this subset for faster querying.
*/
private Set<Integer> memberSet;
/**
* Constructs a new decision variable for representing subsets of size
* {@code k} from a set of size {@code n}.
*
* @param k the fixed size of the subset
* @param n the size of the original set (i.e., the number of candidate
* members)
*/
public Subset(int k, int n) {
super();
this.n = n;
if (k > n) {
throw new IllegalArgumentException("k must be <= n");
}
members = new int[k];
memberSet = new HashSet<Integer>();
for (int i = 0; i < k; i++) {
members[i] = i;
memberSet.add(i);
}
}
/**
* Returns the fixed size of this subset.
*
* @return the fixed size of this subset
*/
public int getK() {
return members.length;
}
/**
* The size of the original set.
*
* @return the size of the original set
*/
public int getN() {
return n;
}
/**
* Gets the member of this subset at the given index.
*
* @param index the index
* @return the member of this subset at the given index
*/
public int get(int index) {
return members[index];
}
/**
* Checks if this subset is valid, throwing an exception if not.
*
* @throws FrameworkException if this subset is not valid
*/
public void validate() {
if (VALIDATE) {
Set<Integer> values = new HashSet<Integer>();
for (int i = 0; i < members.length; i++) {
values.add(members[i]);
}
if (values.size() != members.length) {
throw new FrameworkException("not a valid subset");
}
}
}
/**
* Assigns the member of this subset at the given index.
*
* @param index the index
* @param value the new member
*/
public void set(int index, int value) {
memberSet.remove(members[index]);
members[index] = value;
memberSet.add(value);
validate();
}
/**
* Returns the membership in this subset as an unmodifiable set.
*
* @return the membership in this subset.
*/
public Set<Integer> getSet() {
return Collections.unmodifiableSet(memberSet);
}
/**
* Returns the membership in this subset as an array.
*
* @return the membership in this subset
*/
public int[] toArray() {
return members.clone();
}
/**
* Populates this subset from an array.
*
* @param members the array containing the subset members
*/
public void fromArray(int[] members) {
if (this.members.length != members.length) {
throw new IllegalArgumentException("invalid subset length");
}
memberSet.clear();
for (int i = 0; i < members.length; i++) {
this.members[i] = members[i];
memberSet.add(members[i]);
}
validate();
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(n)
.append(members)
.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if ((obj == null) || (obj.getClass() != getClass())) {
return false;
} else {
Subset rhs = (Subset)obj;
return new EqualsBuilder()
.append(n, rhs.n)
.append(members, rhs.members)
.isEquals();
}
}
@Override
public Subset copy() {
Subset copy = new Subset(members.length, n);
copy.fromArray(members);
return copy;
}
@Override
public void randomize() {
if (members.length < n / OPT_FACTOR) {
Set<Integer> generated = new HashSet<Integer>();
for (int i = 0; i < members.length; i++) {
while (true) {
int value = PRNG.nextInt(n);
if (!generated.contains(value)) {
members[i] = value;
generated.add(value);
break;
}
}
}
} else {
List<Integer> pool = new LinkedList<Integer>();
for (int i = 0; i < n; i++) {
pool.add(i);
}
for (int i = 0; i < members.length; i++) {
members[i] = pool.remove(PRNG.nextInt(pool.size()));
}
}
}
/**
* Randomly pick a value that is not contained in this subset.
*/
public int randomNonmember() {
if (members.length == n) {
throw new FrameworkException("no non-member exists (k == n)");
} else if (members.length < n / OPT_FACTOR) {
while (true) {
int value = PRNG.nextInt(n);
if (!memberSet.contains(value)) {
return value;
}
}
} else {
int result = -1;
int count = 0;
for (int i = 0; i < n; i++) {
if (!memberSet.contains(i)) {
count++;
if (PRNG.nextInt(count) == 0) {
result = i;
}
}
}
return result;
}
}
}