/*
* Copyright © 2010 by Ondrej Skalicka. All Rights Reserved
*/
package cz.cvut.felk.cig.jcop.problem.knapsack;
import cz.cvut.felk.cig.jcop.problem.*;
import cz.cvut.felk.cig.jcop.util.JcopRandom;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
/**
* Knapsack problem definition consists of having a knapsack of given capacity and a list of items, each having weight
* and price. The goal is to fit in the knapsack items worth the most possible amount while not exceeding the knapsack
* capacity.
*
* @author Ondrej Skalicka
* @see <a href="http://service.felk.cvut.cz/courses/X36PAA/knapsack.html"> Knapsack problem on felk.cvut.fel</a>
*/
public class Knapsack extends BaseProblem implements StartingConfigurationProblem, RandomConfigurationProblem, GlobalSearchProblem {
/**
* List of all possible AddOperations. Index is {@link KnapsackItem#index} of {@link KnapsackItem}.
*/
protected List<AddOperation> addOperations;
/**
* List of all possible RemoveOperations. Index is {@link KnapsackItem#index} of {@link KnapsackItem}.
*/
protected List<RemoveOperation> removeOperations;
/**
* Id of problem instance if supplied in {@link #Knapsack(java.io.File, String)}.
*/
protected String id;
/**
* Capacity of knapsack. Only configurations with weight lower or equal to capacity are considered solutions.
*/
protected int capacity;
/**
* List of all items for this knapsack.
*/
protected List<KnapsackItem> knapsackItems;
/**
* Starting configuration, containing no items.
*/
protected Configuration startingConfiguration;
/**
* Creates knapsack from file. Takes first line and then uses it for {@link #init(String)}. Expects same format as
* {@link #init(String)}.
*
* @param configFile input file to load data from
* @throws IOException if problem loading file occurs
* @throws ProblemFormatException if line format is invalid
*/
public Knapsack(File configFile) throws IOException, ProblemFormatException {
this.setLabel("file=" + configFile.getName());
BufferedReader br = new BufferedReader(new FileReader(configFile));
String line;
// fetch just one first line
if ((line = br.readLine()) == null) {
throw new IOException("Cannot fetch first line from file " + configFile);
}
br.close();
this.init(line);
}
/**
* Creates knapsack from file. Tries to find a line with id equal to specified parameter. Then initializes knapsack
* using {@link #init(String)}.
* <p/>
* Line format is identical to {@link #init(String)}.
*
* @param configFile input file to load data from
* @param id id of row to be fetched
* @throws IOException if problem loading file occurs
* @throws ProblemNotFoundException if problem with such id is not found
* @throws ProblemFormatException if line format is invalid
*/
public Knapsack(File configFile, String id) throws IOException, ProblemNotFoundException, ProblemFormatException {
this.setLabel("file=" + configFile.getName() + ",id=" + id);
BufferedReader br = new BufferedReader(new FileReader(configFile));
String line;
while ((line = br.readLine()) != null) {
StringTokenizer st = new StringTokenizer(line);
if (st.nextToken().equals(id)) {
this.init(line);
br.close();
return;
}
}
br.close();
throw new ProblemNotFoundException("Cannot fetch line with id " + id + " from file " + configFile);
}
/**
* Create knapsack from a single string. Works just as {@link #init(String)} (actually {@link #init(String)} is
* called in constructor) and format of line is expected the same.
*
* @param line string containing data to init this knapsack
* @throws ProblemFormatException if line format is invalid
*/
public Knapsack(String line) throws ProblemFormatException {
this.setLabel("line=" + line);
this.init(line);
}
/**
* Init knapsack from one line of string. Expects format
* <p/>
* id dimension capacity weight1 price1 weight2 price2 ... weightN priceN (all numeric)
*
* @param line string containing data to init this knapsack
* @throws ProblemFormatException if line format is invalid
*/
protected void init(String line) throws ProblemFormatException {
StringTokenizer st = new StringTokenizer(line);
try {
this.id = st.nextToken();
this.dimension = Integer.valueOf(st.nextToken());
this.capacity = Integer.valueOf(st.nextToken());
this.knapsackItems = new ArrayList<KnapsackItem>(this.dimension);
for (int i = 0; i < dimension; i++) {
int weight = Integer.valueOf(st.nextToken());
int price = Integer.valueOf(st.nextToken());
this.knapsackItems.add(new KnapsackItem(i, weight, price));
}
if (st.hasMoreTokens())
throw new ProblemFormatException("Too many elements in line");
} catch (NoSuchElementException e) {
throw new ProblemFormatException("Insufficient number of elements in line");
} catch (NumberFormatException e) {
throw new ProblemFormatException("Non numeric elements found in line");
}
this.addOperations = new ArrayList<AddOperation>(this.dimension);
this.removeOperations = new ArrayList<RemoveOperation>(this.dimension);
AddOperation addTmp;
RemoveOperation removeTmp;
for (int i = 0; i < this.dimension; i++) {
addTmp = new AddOperation(this.knapsackItems.get(i));
removeTmp = new RemoveOperation(this.knapsackItems.get(i));
// set reverses
addTmp.setReverse(removeTmp);
removeTmp.setReverse(addTmp);
// add operations to list
this.addOperations.add(addTmp);
this.removeOperations.add(removeTmp);
}
List<Integer> tmp = new ArrayList<Integer>(this.dimension);
for (int i = 0; i < this.dimension; ++i) tmp.add(0);
this.startingConfiguration = new Configuration(tmp, "Empty knapsack created");
}
public boolean isSolution(Configuration configuration) {
int totalWeight = 0;
for (KnapsackItem it : this.knapsackItems) {
if (configuration.valueAt(it.getIndex()) == 1) {
totalWeight += it.getWeight();
if (totalWeight > this.capacity) return false;
}
}
return true;
}
public KnapsackIterator getOperationIterator(Configuration configuration) {
return new KnapsackIterator(configuration, this);
}
public Fitness getDefaultFitness() {
return new KnapsackFitness(this);
}
/**
* Returns price of items in given configuration.
*
* Does not check for capacity.
*
* @param configuration configuration to calculate price of
* @return price of configuration
*/
public long getPrice(Configuration configuration) {
long total = 0;
for (KnapsackItem knapsackItem : getKnapsackItems()) {
if (configuration.valueAt(knapsackItem.getIndex()) == 1) {
total += knapsackItem.getPrice();
}
}
return total;
}
/* required for fitness calculations */
public int getCapacity() {
return capacity;
}
public List<KnapsackItem> getKnapsackItems() {
return knapsackItems;
}
/* StartingConfigurationProblem interface */
public Configuration getStartingConfiguration() {
return this.startingConfiguration;
}
/* RandomConfigurationProblem interface */
public Configuration getRandomConfiguration() {
List<Integer> tmp = new ArrayList<Integer>(this.dimension);
for (int i = 0; i < this.dimension; ++i)
tmp.add(JcopRandom.nextBoolean() ? 1 : 0);
return new Configuration(tmp, "Empty knapsack created (random)");
}
/* GlobalSearchProblem interface */
public Integer getMaximum(int index) {
return 1;
}
}