<repo_name>Assumeru/CMIBOD023T<file_sep>/src/main/java/nl/hro/cmibod023t/classification/Classifier.java
package nl.hro.cmibod023t.classification;

public interface Classifier<T> extends Trainable<T> {
	Result<T> test(Object... features);

	default Result<T> test(Classifiable object) {
		return test(object.getFeatures());
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/DecisionTree.java
package nl.hro.cmibod023t.classification;

import nl.hro.cmibod023t.classification.tree.Node;

public class DecisionTree<T> implements Classifier<T> {
	private final FeatureTable<T> table;
	private Node<T> tree;

	public DecisionTree(Class<?>... columns) {
		table = new FeatureTable<>(columns);
	}

	@Override
	public DecisionTree<T> train(T result, Object... features) {
		table.train(result, features);
		tree = null;
		return this;
	}

	@Override
	public Result<T> test(Object... features) {
		table.checkFeatureLength(features);
		if(tree == null) {
			tree = Node.build(table);
		}
		return tree.getValue(features);
	}

	@Override
	public String toString() {
		if(tree == null) {
			tree = Node.build(table);
		}
		return tree.toString();
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/Trainable.java
package nl.hro.cmibod023t.classification;

public interface Trainable<T> {
	Trainable<T> train(T result, Object... features);

	default Trainable<T> train(Testable<T> object) {
		return train(object.getTargetClass(), object.getFeatures());
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/exercises/Star.java
package nl.hro.cmibod023t.exercises;

import nl.hro.cmibod023t.cluster.points.EuclidianDoublePoint;

public class Star extends EuclidianDoublePoint {
	private final int id;

	public Star(double x, double y, double z, int id) {
		super(x, y, z);
		this.id = id;
	}

	public int getId() {
		return id;
	}

	public double getX() {
		return dimensions[0];
	}

	public double getY() {
		return dimensions[1];
	}

	public double getZ() {
		return dimensions[2];
	}

	@Override
	public String toString() {
		return "<Star " + id + " (" + getX() + ", " + getY() + ", " + getZ() + ")>";
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/columns/Column.java
package nl.hro.cmibod023t.classification.columns;

import java.util.Collection;
import java.util.Set;

public interface Column<T> {
	void add(Object feature);

	Collection<Integer> getRowsMatching(Object feature);

	Set<Object> getFeatures();

	Column<?> copy(Iterable<Integer> indices);

	int getIndex();

	boolean matches(Object external, T internal);

	static Column<?> create(Class<?> type, int index) {
		if(type == String.class) {
			return new StringColumn(index);
		} else if(type == boolean.class || type == Boolean.class) {
			return new BooleanColumn(index);
		} else if(type == int.class || type == Integer.class) {
			return new IntColumn(index);
		} else if(Enum.class.isAssignableFrom(type)) {
			return new EnumColumn(type, index);
		}
		throw new UnsupportedOperationException("Unsupported type: " + type);
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/tree/ValueNode.java
package nl.hro.cmibod023t.classification.tree;

import nl.hro.cmibod023t.classification.Result;

public class ValueNode<T> implements Node<T> {
	private final Result<T> result;

	public ValueNode(T value, double probability) {
		this.result = new Result<>(value, probability);
	}

	@Override
	public Result<T> getValue(Object... values) {
		return result;
	}

	@Override
	public String toString(int indent) {
		StringBuilder sb = new StringBuilder();
		while(indent > 0) {
			sb.append('\t');
			indent--;
		}
		return sb.append("Value<").append(result).append('>').toString();
	}

	@Override
	public String toString() {
		return toString(0);
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/NaiveClassifier.java
package nl.hro.cmibod023t.classification;

import java.util.Map;
import java.util.Map.Entry;

import nl.hro.cmibod023t.collection.DoubleCountMap;

public class NaiveClassifier<T> implements Classifier<T> {
	private final FeatureTable<T> table;

	public NaiveClassifier(Class<?>... columns) {
		table = new CachingFeatureTable<>(columns);
	}

	@Override
	public NaiveClassifier<T> train(T result, Object... features) {
		table.train(result, features);
		return this;
	}

	@Override
	public Result<T> test(Object... features) {
		table.checkFeatureLength(features);
		Map<T, Integer> results = table.countResults();
		DoubleCountMap<T> probabilities = initMap(results);
		for(int i = 0; i < features.length; i++) {
			Iterable<Integer> indices = table.getColumn(i).getRowsMatching(features[i]);
			Map<T, Integer> featureCount = table.countResults(indices);
			for(Entry<T, Integer> entry : featureCount.entrySet()) {
				double p = entry.getValue();
				probabilities.add(entry.getKey(), Math.log(p / results.get(entry.getKey())));
			}
		}
		return getBest(probabilities);
	}

	private DoubleCountMap<T> initMap(Map<T, Integer> results) {
		DoubleCountMap<T> probabilities = new DoubleCountMap<>();
		for(Entry<T, Integer> entry : results.entrySet()) {
			probabilities.put(entry.getKey(), Math.log(entry.getValue() / (double) table.size()));
		}
		return probabilities;
	}

	private Result<T> getBest(Map<T, Double> probabilities) {
		Entry<T, Double> best = probabilities.entrySet().iterator().next();
		for(Entry<T, Double> entry : probabilities.entrySet()) {
			if(entry.getValue() > best.getValue()) {
				best = entry;
			}
		}
		return new Result<>(best.getKey(), Math.exp(best.getValue()));
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/Testable.java
package nl.hro.cmibod023t.classification;

public interface Testable<T> extends Classifiable {
	T getTargetClass();
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/CachingFeatureTable.java
package nl.hro.cmibod023t.classification;

import java.util.Collections;
import java.util.Map;

import nl.hro.cmibod023t.collection.IntCountMap;

public class CachingFeatureTable<T> extends FeatureTable<T> {
	private final IntCountMap<T> resultCount;
	private final Map<T, Integer> resultCountWrapper;

	public CachingFeatureTable(Class<?>... columns) {
		super(columns);
		resultCount = new IntCountMap<>();
		resultCountWrapper = Collections.unmodifiableMap(resultCount);
	}

	@Override
	public FeatureTable<T> train(T result, Object... features) {
		resultCount.increment(result);
		return super.train(result, features);
	}

	@Override
	public Map<T, Integer> countResults() {
		return resultCountWrapper;
	}

	@Override
	protected IntCountMap<T> buildResultCounter() {
		IntCountMap<T> count = new IntCountMap<>();
		for(T result : resultCount.keySet()) {
			count.add(result, 0);
		}
		return count;
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/tree/Node.java
package nl.hro.cmibod023t.classification.tree;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import nl.hro.cmibod023t.classification.FeatureTable;
import nl.hro.cmibod023t.classification.Result;
import nl.hro.cmibod023t.classification.columns.Column;

public interface Node<T> {
	Result<T> getValue(Object... values);

	String toString(int indent);

	static <T> Node<T> build(FeatureTable<T> table) {
		Map<T, Integer> results = table.countResults();
		if(results.keySet().size() == 1) {
			return new ValueNode<>(results.keySet().iterator().next(), 1);
		}
		Column<?> column;
		if(table.columns().isEmpty()) {
			return getLeaf(results);
		} else if(table.columns().size() == 1) {
			column = table.getColumn(0);
		} else {
			column = table.getColumnWithLargestGain();
		}
		Set<Object> features = column.getFeatures();
		if(features.size() == 1) {
			return getLeaf(results);
		}
		Map<Object, Node<T>> children = new HashMap<>();
		for(Object feature : features) {
			Node<T> child = build(table.subset(column, feature));
			children.put(feature, child);
		}
		return new BranchNode<>(children, column);
	}

	static <T> Node<T> getLeaf(Map<T, Integer> results) {
		int max = Integer.MIN_VALUE;
		T result = null;
		double total = 0;
		for(Entry<T, Integer> entry : results.entrySet()) {
			int value = entry.getValue();
			total += value;
			if(value > max) {
				max = value;
				result = entry.getKey();
			}
		}
		return new ValueNode<>(result, max / total);
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/exercises/Mushroom.java
package nl.hro.cmibod023t.exercises;

import nl.hro.cmibod023t.classification.Classifier;
import nl.hro.cmibod023t.classification.DecisionTree;
import nl.hro.cmibod023t.classification.NaiveClassifier;
import nl.hro.cmibod023t.classification.Testable;

public class Mushroom implements Testable<Mushroom.Class> {
	private final Class type;
	private final CapShape capShape;
	private final Surface capSurface;
	private final Color capColor;
	private final boolean bruised;
	private final Odor odor;
	private final GillAttachment gillAttachment;
	private final GillSpacing gillSpacing;
	private final GillSize gillSize;
	private final Color gillColor;
	private final StalkShape stalkShape;
	private final StalkRoot stalkRoot;
	private final Surface stalkSurfaceAboveRing;
	private final Surface stalkSurfaceBelowRing;
	private final Color stalkColorAboveRing;
	private final Color stalkColorBelowRing;
	private final VeilType veilType;
	private final Color veilColor;
	private final RingNumber ringNumber;
	private final RingType ringType;
	private final Color sporePrintColor;
	private final Population population;
	private final Habitat habitat;

	public Mushroom(Class type, CapShape capShape, Surface capSurface, Color capColor, boolean bruised, Odor odor, GillAttachment gillAttachment, GillSpacing gillSpacing, GillSize gillSize, Color gillColor, StalkShape stalkShape, StalkRoot stalkRoot, Surface stalkSurfaceAboveRing, Surface stalkSurfaceBelowRing, Color stalkColorAboveRing, Color stalkColorBelowRing, VeilType veilType, Color veilColor, RingNumber ringNumber, RingType ringType, Color sporePrintColor, Population population, Habitat habitat) {
		this.type = type;
		this.capShape = capShape;
		this.capSurface = capSurface;
		this.capColor = capColor;
		this.bruised = bruised;
		this.odor = odor;
		this.gillAttachment = gillAttachment;
		this.gillSpacing = gillSpacing;
		this.gillSize = gillSize;
		this.gillColor = gillColor;
		this.stalkShape = stalkShape;
		this.stalkRoot = stalkRoot;
		this.stalkSurfaceAboveRing = stalkSurfaceAboveRing;
		this.stalkSurfaceBelowRing = stalkSurfaceBelowRing;
		this.stalkColorAboveRing = stalkColorAboveRing;
		this.stalkColorBelowRing = stalkColorBelowRing;
		this.veilType = veilType;
		this.veilColor = veilColor;
		this.ringNumber = ringNumber;
		this.ringType = ringType;
		this.sporePrintColor = sporePrintColor;
		this.population = population;
		this.habitat = habitat;
	}

	public Class getType() {
		return type;
	}

	public CapShape getCapShape() {
		return capShape;
	}

	public Surface getCapSurface() {
		return capSurface;
	}

	public Color getCapColor() {
		return capColor;
	}

	public boolean isBruised() {
		return bruised;
	}

	public Odor getOdor() {
		return odor;
	}

	public GillAttachment getGillAttachment() {
		return gillAttachment;
	}

	public GillSpacing getGillSpacing() {
		return gillSpacing;
	}

	public GillSize getGillSize() {
		return gillSize;
	}

	public Color getGillColor() {
		return gillColor;
	}

	public StalkShape getStalkShape() {
		return stalkShape;
	}

	public StalkRoot getStalkRoot() {
		return stalkRoot;
	}

	public Surface getStalkSurfaceAboveRing() {
		return stalkSurfaceAboveRing;
	}

	public Surface getStalkSurfaceBelowRing() {
		return stalkSurfaceBelowRing;
	}

	public Color getStalkColorAboveRing() {
		return stalkColorAboveRing;
	}

	public Color getStalkColorBelowRing() {
		return stalkColorBelowRing;
	}

	public VeilType getVeilType() {
		return veilType;
	}

	public Color getVeilColor() {
		return veilColor;
	}

	public RingNumber getRingNumber() {
		return ringNumber;
	}

	public RingType getRingType() {
		return ringType;
	}

	public Color getSporePrintColor() {
		return sporePrintColor;
	}

	public Population getPopulation() {
		return population;
	}

	public Habitat getHabitat() {
		return habitat;
	}

	public interface CharEnum {
		char getChar();
	}

	public enum Class implements CharEnum {
		POISONOUS('p'), EDIBLE('e');
		private final char character;

		private Class(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum CapShape implements CharEnum {
		BELL('b'), CONICAL('c'), CONVEX('x'), FLAT('f'), KNOBBED('k'), SUNKEN('s');
		private final char character;

		private CapShape(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum Surface implements CharEnum {
		FIBROUS('f'), GROOVES('g'), SCALY('y'), SMOOTH('s'), SILKY('k');
		private final char character;

		private Surface(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum Color implements CharEnum {
		BLACK('k'), BROWN('n'), BUFF('b'), CHOCOLATE('h'), GRAY('g'), GREEN('r'), ORANGE('o'), PINK('p'), PURPLE('u'), RED('e'), WHITE('w'), YELLOW('y'), CINNAMON('c');
		private final char character;

		private Color(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum Odor implements CharEnum {
		ALMOND('a'), ANISE('l'), CREOSOTE('c'), FISHY('y'), FOUL('f'), MUSTY('m'), NONE('n'), PUNGENT('p'), SPICY('s');
		private final char character;

		private Odor(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum GillAttachment implements CharEnum {
		ATTACHED('a'), DESCENDING('l'), FREE('f'), NOTCHED('n');
		private final char character;

		private GillAttachment(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum GillSpacing implements CharEnum {
		CLOSE('c'), CROWDED('w'), DISTANT('d');
		private final char character;

		private GillSpacing(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum GillSize implements CharEnum {
		BROAD('b'), NARROW('n');
		private final char character;

		private GillSize(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum StalkShape implements CharEnum {
		ENLARGING('e'), TAPERING('t');
		private final char character;

		private StalkShape(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum StalkRoot implements CharEnum {
		BULBOUS('b'), CLUB('c'), CUP('u'), EQUAL('e'), RHIZOMORPHS('z'), ROOTED('r'), MISSING('?');
		private final char character;

		private StalkRoot(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum VeilType implements CharEnum {
		PARTIAL('p'), UNIVERSAL('u');
		private final char character;

		private VeilType(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum RingNumber implements CharEnum {
		NONE('n'), ONE('o'), TWO('t');
		private final char character;

		private RingNumber(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum RingType implements CharEnum {
		COBWEBBY('c'), EVANESCENT('e'), FLARING('f'), LARGE('l'), NONE('n'), PENDANT('p'), SHEATHING('s'), ZONE('z');
		private final char character;

		private RingType(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum Population implements CharEnum {
		ABUNDANT('a'), CLUSTERED('c'), NUMEROUS('n'), SCATTERED('s'), SEVERAL('v'), SOLIDARY('y');
		private final char character;

		private Population(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public enum Habitat implements CharEnum {
		GRASSES('g'), LEAVES('l'), MEADOWS('m'), PATHS('p'), URBAN('u'), WASTE('w'), WOODS('d');
		private final char character;

		private Habitat(char character) {
			this.character = character;
		}

		@Override
		public char getChar() {
			return character;
		}
	}

	public static Classifier<Mushroom.Class> createNaiveBayesianClassifier() {
		return new NaiveClassifier<>(CapShape.class, Surface.class, Color.class, boolean.class, Odor.class, GillAttachment.class, GillSpacing.class, GillSize.class, Color.class, StalkShape.class, StalkRoot.class, Surface.class, Surface.class, Color.class, Color.class, VeilType.class, Color.class, RingNumber.class, RingType.class, Color.class, Population.class, Habitat.class);
	}

	public static Classifier<Mushroom.Class> createDecisionTree() {
		return new DecisionTree<>(CapShape.class, Surface.class, Color.class, boolean.class, Odor.class, GillAttachment.class, GillSpacing.class, GillSize.class, Color.class, StalkShape.class, StalkRoot.class, Surface.class, Surface.class, Color.class, Color.class, VeilType.class, Color.class, RingNumber.class, RingType.class, Color.class, Population.class, Habitat.class);
	}

	@Override
	public Object[] getFeatures() {
		return new Object[] {
				getCapShape(), getCapSurface(), getCapColor(),
				isBruised(), getOdor(), getGillAttachment(),
				getGillSpacing(), getGillSize(), getGillColor(),
				getStalkShape(), getStalkRoot(), getStalkSurfaceAboveRing(),
				getStalkSurfaceBelowRing(), getStalkColorAboveRing(), getStalkColorBelowRing(),
				getVeilType(), getVeilColor(), getRingNumber(),
				getRingType(), getSporePrintColor(), getPopulation(), getHabitat()
		};
	}

	@Override
	public Class getTargetClass() {
		return type;
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/continuous/Range.java
package nl.hro.cmibod023t.classification.continuous;

public interface Range<T extends Number> {
	boolean inRange(T value);

	Class<T> getType();

	public static Range<Integer> lessThan(int value) {
		return new IntRange.LessThan(value);
	}

	public static Range<Integer> moreThan(int value) {
		return new IntRange.MoreThan(value);
	}

	public static Range<Integer> create(int min, int max) {
		return new IntRange.InRange(min, max);
	}

	public static Range<Integer> is(int value) {
		return new IntRange.EqualTo(value);
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/cluster/balltree/Node.java
package nl.hro.cmibod023t.cluster.balltree;

import java.util.Arrays;

import nl.hro.cmibod023t.cluster.points.KDPoint;

interface Node<E extends KDPoint> {
	Node<E> getRight();

	Node<E> getLeft();

	E getValue();

	static <E extends KDPoint> Node<E> build(E[] points, int dimension) {
		if(points.length == 1) {
			return new Leaf<>((E) points[0]);
		}
		Arrays.sort(points, KDPoint.getComparator(dimension));
		int median = points.length / 2;
		E point = points[median];
		dimension = (dimension + 1) % point.getDimensions();
		E[] leftP = Arrays.copyOfRange(points, 0, median);
		Node<E> left = leftP.length > 0 ? build(leftP, dimension) : null;
		E[] rightP = Arrays.copyOfRange(points, median + 1, points.length);
		Node<E> right = rightP.length > 0 ? build(rightP, dimension) : null;
		return new Branch<>(point, left, right);
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/cluster/balltree/BallTree.java
package nl.hro.cmibod023t.cluster.balltree;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import nl.hro.cmibod023t.cluster.points.KDPoint;

public class BallTree<E extends KDPoint> {
	private final Node<E> root;

	@SuppressWarnings("unchecked")
	public BallTree(Collection<E> collection) {
		root = (Node<E>) Node.build(collection.toArray(new KDPoint[collection.size()]), 0);
	}

	public Collection<E> getNeighbours(E point, double epsilon, double epsilonSq) {
		return getNeighbours(root, point, epsilon, epsilonSq, 0);
	}

	private Collection<E> getNeighbours(Node<E> node, E point, double epsilon, double epsilonSq, int dimension) {
		List<E> neighbours = new ArrayList<>();
		while(node != null) {
			double dimN = node.getValue().getDimension(dimension);
			double dimP = point.getDimension(dimension);
			dimension = (dimension + 1) % point.getDimensions();
			if(dimN + epsilon >= dimP && dimN - epsilon <= dimP) {
				if(node.getValue().getDistance(point) <= epsilonSq) {
					neighbours.add(node.getValue());
				}
				neighbours.addAll(getNeighbours(node.getLeft(), point, epsilon, epsilonSq, dimension));
				node = node.getRight();
			} else if(dimN < dimP) {
				node = node.getRight();
			} else {
				node = node.getLeft();
			}
		}
		return neighbours;
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/exercises/MushroomParser.java
package nl.hro.cmibod023t.exercises;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class MushroomParser {
	private static final Map<String, CharColumnParser> COLUMNS = new HashMap<>();
	static {
		COLUMNS.put("class", c -> getEnum(c, Mushroom.Class.values()));
		COLUMNS.put("cap-shape", c -> getEnum(c, Mushroom.CapShape.values()));
		COLUMNS.put("cap-surface", c -> getEnum(c, Mushroom.Surface.values()));
		COLUMNS.put("cap-color", c -> getEnum(c, Mushroom.Color.values()));
		COLUMNS.put("bruises", c -> c == 't');
		COLUMNS.put("odor", c -> getEnum(c, Mushroom.Odor.values()));
		COLUMNS.put("gill-attachment", c -> getEnum(c, Mushroom.GillAttachment.values()));
		COLUMNS.put("gill-spacing", c -> getEnum(c, Mushroom.GillSpacing.values()));
		COLUMNS.put("gill-size", c -> getEnum(c, Mushroom.GillSize.values()));
		COLUMNS.put("gill-color", c -> getEnum(c, Mushroom.Color.values()));
		COLUMNS.put("stalk-shape", c -> getEnum(c, Mushroom.StalkShape.values()));
		COLUMNS.put("stalk-root", c -> getEnum(c, Mushroom.StalkRoot.values()));
		COLUMNS.put("stalk-surface-above-ring", c -> getEnum(c, Mushroom.Surface.values()));
		COLUMNS.put("stalk-surface-below-ring", c -> getEnum(c, Mushroom.Surface.values()));
		COLUMNS.put("stalk-color-above-ring", c -> getEnum(c, Mushroom.Color.values()));
		COLUMNS.put("stalk-color-below-ring", c -> getEnum(c, Mushroom.Color.values()));
		COLUMNS.put("veil-type", c -> getEnum(c, Mushroom.VeilType.values()));
		COLUMNS.put("veil-color", c -> getEnum(c, Mushroom.Color.values()));
		COLUMNS.put("ring-number", c -> getEnum(c, Mushroom.RingNumber.values()));
		COLUMNS.put("ring-type", c -> getEnum(c, Mushroom.RingType.values()));
		COLUMNS.put("spore-print-color", c -> getEnum(c, Mushroom.Color.values()));
		COLUMNS.put("population", c -> getEnum(c, Mushroom.Population.values()));
		COLUMNS.put("habitat", c -> getEnum(c, Mushroom.Habitat.values()));
	}
	private final File file;

	public MushroomParser(File file) {
		this.file = file;
	}

	public List<Mushroom> parseMushrooms() throws IOException, InstantiationException, IllegalAccessException, InvocationTargetException {
		List<Mushroom> mushrooms = new ArrayList<>();
		@SuppressWarnings("unchecked")
		Constructor<Mushroom> constructor = (Constructor<Mushroom>) Mushroom.class.getConstructors()[0];
		try(Scanner sc = new Scanner(file)) {
			String[] header = sc.nextLine().split(",");
			if(constructor.getParameterCount() != header.length) {
				throw new IllegalArgumentException("File need " + constructor.getParameterCount() + " headers");
			}
			while(sc.hasNextLine()) {
				String[] chars = sc.nextLine().split(",");
				Object[] params = new Object[header.length];
				for(int i = 0; i < params.length; i++) {
					CharColumnParser column = COLUMNS.get(header[i]);
					if(column == null) {
						throw new IllegalArgumentException("Unexpected header " + header[i]);
					}
					params[i] = column.getValue(chars[i].charAt(0));
				}
				mushrooms.add(constructor.newInstance(params));
			}
		}
		return mushrooms;
	}

	private static <E extends Mushroom.CharEnum> E getEnum(char c, E[] values) {
		for(E value : values) {
			if(value.getChar() == c) {
				return value;
			}
		}
		throw new IllegalArgumentException("Unexpected value " + c + " for " + values[0].getClass());
	}

	@FunctionalInterface
	private interface CharColumnParser {
		Object getValue(char c);
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/exercises/Assignment2.java
package nl.hro.cmibod023t.exercises;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;

import org.jzy3d.analysis.AbstractAnalysis;
import org.jzy3d.analysis.AnalysisLauncher;
import org.jzy3d.chart.factories.AWTChartComponentFactory;
import org.jzy3d.colors.Color;
import org.jzy3d.maths.Coord3d;
import org.jzy3d.plot3d.primitives.Scatter;
import org.jzy3d.plot3d.rendering.canvas.Quality;

import nl.hro.cmibod023t.cluster.Cluster;
import nl.hro.cmibod023t.cluster.KDPointCollection;
import nl.hro.cmibod023t.cluster.dbscan.Dbscan;

public class Assignment2 {
	private static final long SEED = 0;
	private static final double EPSILON = 0.5;
	private static final int MIN_POINTS = 10;

	public static void main(String[] args) throws Exception {
		if(args.length == 0) {
			System.err.println("Missing file path argument");
			System.exit(1);
		}
		File file = new File(args[0]);
		List<Star> stars = parseStars(file);
		Dbscan<Star> dbscan = new Dbscan<>(MIN_POINTS, new KDPointCollection<>(EPSILON));
		dbscan.addPoints(stars);
		Collection<Cluster<Star>> clusters = dbscan.cluster();
		System.out.println("Clusters: " + clusters.size());
		for(Cluster<Star> c : clusters) {
			System.out.println(c.size());
		}
		draw(stars, clusters);
	}

	private static List<Star> parseStars(File file) throws IOException {
		List<Star> stars = new ArrayList<>(64000);
		try(Scanner sc = new Scanner(file)) {
			sc.nextLine();
			while(sc.hasNextLine()) {
				String[] values = sc.nextLine().split(",");
				stars.add(new Star(Double.parseDouble(values[0]), Double.parseDouble(values[1]), Double.parseDouble(values[2]), Integer.parseInt(values[7])));
			}
		}
		return stars;
	}

	private static void draw(List<Star> stars, Collection<Cluster<Star>> clusters) throws Exception {
		Coord3d[] points = new Coord3d[stars.size()];
		Color[] colors = new Color[stars.size()];
		Map<Cluster<Star>, Color> colorMap = getColors(clusters);
		for(int i = 0; i < points.length; i++) {
			Star star = stars.get(i);
			points[i] = new Coord3d(star.getX(), star.getY(), star.getZ());
			colors[i] = colorMap.get(star.getCluster());
		}
		Scatter scatter = new Scatter(points, colors);
		AnalysisLauncher.open(new AbstractAnalysis() {
			@Override
			public void init() throws Exception {
				chart = AWTChartComponentFactory.chart(Quality.Fastest);
				chart.getScene().add(scatter);
			}
		});
	}

	private static Map<Cluster<Star>, Color> getColors(Collection<Cluster<Star>> clusters) {
		Map<Cluster<Star>, Color> colorMap = new HashMap<>();
		colorMap.put(null, new Color(0, 0, 0));
		Random random = new Random(SEED);
		for(Cluster<Star> cluster : clusters) {
			float r = random.nextFloat();
			float g = random.nextFloat();
			float b = random.nextFloat();
			colorMap.put(cluster, new Color(r, g, b));
		}
		return colorMap;
	}

	private Assignment2() {
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/collection/IntCountMap.java
package nl.hro.cmibod023t.collection;

import java.util.HashMap;

public class IntCountMap<T> extends MapWrapper<T, Integer> {
	public IntCountMap() {
		super(new HashMap<>());
	}

	public void add(T key, int amount) {
		Integer value = get(key);
		if(value == null) {
			put(key, amount);
		} else {
			put(key, value + amount);
		}
	}

	public void increment(T key) {
		add(key, 1);
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/classification/columns/BooleanColumn.java
package nl.hro.cmibod023t.classification.columns;

public class BooleanColumn extends AbstractColumn<Boolean> {
	public BooleanColumn(int index) {
		super(Boolean.class, index);
	}
}
<file_sep>/src/main/java/nl/hro/cmibod023t/cluster/DefaultPointCollection.java
package nl.hro.cmibod023t.cluster;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import nl.hro.cmibod023t.cluster.points.Point;
import nl.hro.cmibod023t.collection.CollectionWrapper;

public class DefaultPointCollection<E extends Point> extends CollectionWrapper<E> implements PointCollection<E> {
	private final double epsilon;

	public DefaultPointCollection(double epsilon) {
		super(new ArrayList<>());
		this.epsilon = epsilon * epsilon;
	}

	@Override
	public Collection<E> getNeighbours(E p) {
		List<E> neighbours = new ArrayList<>();
		for(E neighbour : collection) {
			if(p.getDistance(neighbour) <= epsilon) {
				neighbours.add(neighbour);
			}
		}
		return neighbours;
	}
}
