I'm attempting to use a genetic algorithm to arrange a random list of seats for a given section in a classroom so that all the groups in that classroom sit together.
Here is my current attempt, I'm unclear about the best way to improve this algorithm as this is very very early days in my GA learnings.
I'm using the Java library Jenetics, which has been pretty awesome at getting me started, here is the Engine.
final Engine<EnumGene<Seat>, Double> ENGINE = Engine
.builder(SeatingFitness::fitness, encoding)
.populationSize(200)
.selector(new RouletteWheelSelector<>())
.alterers(
new PartiallyMatchedCrossover<>(0.2),
new Mutator<>(0.01)
)
.optimize(Optimize.MINIMUM)
.build();
The encoding looks like this
static final ISeq<Seat> seats = ISeq.of(createSeats());
static final Codec<ISeq<Seat>, EnumGene<Seat>> encoding = Codecs.ofPermutation(seats);
public static List<Seat> createSeats() {
return new ArrayList<>(Arrays.asList(
new Seat("Group C", 1),
new Seat("Group A", 2),
new Seat("Group B", 3)
.....more seats here....)
}
My fitness function can be improved for sure, I'm not using any libraries, so any suggestions here would be great, but it looks like this.
Essentially what I'm doing is just finding the x and y coordinates of each seat in the group and calculating how far each one is from the others in the group, and summing those values up. The lower value the better, hence the Optimize.MINIMUM in the Engine
private static int numberOfSeatsInRow = 8;
public static double fitness(final ISeq<Seat> seats) {
AtomicDouble score = new AtomicDouble();
// Group together all the seats that belong to a particular group.
Map<String, List<Seat>> grouping = seats.stream().collect(groupingBy(Seat::getGroup));
grouping.forEach((group, groupsSeats) -> {
// Find the location in the overall list of the seats for the group.
if (!group.equals(Seat.EMPTY_SEAT)) {
List<Integer> indexOfSeatInOverallList = groupsSeats.stream().map(seats::indexOf).collect(Collectors.toList());
if (indexOfSeatInOverallList.size() > 2) {
// Get the first element positioned correctly on the x and y axis
double totalCalculated = indexOfSeatInOverallList.stream().reduce(0, (subTotal, currentElement) -> {
int xReferenceCoordinate = calculateXCoordinate(currentElement);
int yReferenceCoordinate = calculateYCoordinate(currentElement);
double totalDistance = 0;
int multiplier = groupsSeats.size() <= numberOfSeatsInRow ? 10 : 500;
for (Integer integer : indexOfSeatInOverallList) {
int xSecondary = calculateXCoordinate(integer);
int ySecondary = calculateYCoordinate(integer);
if (ySecondary != yReferenceCoordinate) {
totalDistance += multiplier * Math.abs(yReferenceCoordinate - ySecondary);
}
totalDistance += calculateDistanceBetweenTwoPoints(xReferenceCoordinate, yReferenceCoordinate, xSecondary, ySecondary);
}
return (int) totalDistance;
});
score.getAndAdd(totalCalculated);
}
}
});
return score.get();
}
private static int calculateXCoordinate(int positionInList) {
int xPosition = positionInList % numberOfSeatsInRow;
if (xPosition == 0) {
xPosition = numberOfSeatsInRow;
}
return xPosition;
}
private static int calculateYCoordinate(int positionInList) {
int xPosition = positionInList % numberOfSeatsInRow;
int yPosition = positionInList / numberOfSeatsInRow;
if (xPosition == 0) {
yPosition = yPosition - 1;
}
return yPosition + 1;
}
private static double calculateDistanceBetweenTwoPoints(int x1, int y1, int x2, int y2) {
// https://dqydj.com/2d-distance-calculator/
double xValue = Math.pow((x2 - x1), 2);
double yValue = Math.pow((y2 - y1), 2);
return Math.sqrt(xValue + yValue);
}
See the results image below, as you can see it's pretty good (although it takes about 3 minutes to run to produce a proper result).
I had a look at the fitness function. Some things you are calculating for every fitness function call, can be calculated once.
private static final ISeq<Seat> SEATS = ISeq.of(
new Seat("Group C", 1),
new Seat("Group A", 2),
new Seat("Group B", 3)
);
private static final Map<String, List<Seat>> SEAT_GROUPS = SEATS.stream()
.collect(groupingBy(Seat::getGroup));
The SEAT_GROUPS map is defined by the seats list and will not change. If I'm right, the reduce function in your fitness function is ignoring the previously calculated distance.
double totalCalculated = indexOfSeatInOverallList.stream()
.reduce(0, (subTotal, currentElement) -> {
// subTotal is ignored in your code, but should be added to the result.
return (int) totalDistance + subTotal;
})
Your calculateDistanceBetweenTwoPoints can be implemented as
double distance(final int x1, final int y1, final int x2, final int y2) {
// sqrt(x^2 + y^2)
return Math.hypot(x2 - x1, y2 - y1);
}
My "cleaned" version will look like this.
private static final int SEATS_PER_ROW = 8;
private static final ISeq<Seat> SEATS = ISeq.of(
new Seat("Group C", 1),
new Seat("Group A", 2),
new Seat("Group B", 3)
);
private static final Map<String, List<Seat>> SEAT_GROUPS = SEATS.stream()
.collect(groupingBy(Seat::getGroup));
public static double fitness(final ISeq<Seat> seats) {
double score = 0;
for (var entry : SEAT_GROUPS.entrySet()) {
final var group = entry.getKey();
final var groupsSeats = entry.getValue();
final int multiplier = groupsSeats.size() <= SEATS_PER_ROW ? 10 : 500;
if (!group.equals(Seat.EMPTY_SEAT)) {
final int[] indexes = groupsSeats.stream()
.mapToInt(seats::indexOf)
.toArray();
if (indexes.length > 2) {
final double dist = IntStream.of(indexes)
.reduce(0, (a, b) -> toDistance(multiplier, indexes, a, b));
score += dist;
}
}
}
return score;
}
private static int toDistance(
final int multiplier,
final int[] indexes,
final int sum,
final int index
) {
final int x1 = toX(index);
final int y = toY(index);
int total = 0;
for (int i : indexes) {
final int x2 = toX(i);
final int y2 = toY(i);
if (y2 != y) {
total += multiplier*Math.abs(y - y2);
}
total += distance(x1, y, x2, y2);
}
return sum + total;
}
private static double distance(final int x1, final int y1, final int x2, final int y2) {
// sqrt(x^2 + y^2)
return Math.hypot(x2 - x1, y2 - y1);
}
private static int toX(final int index) {
int x = index%SEATS_PER_ROW;
if (x == 0) {
x = SEATS_PER_ROW;
}
return x;
}
private static int toY(final int index) {
final int x = index%SEATS_PER_ROW;
int y = index/SEATS_PER_ROW;
if (x == 0) {
y = y - 1;
}
return y + 1;
}
One point I can see about your Engine definition is that you are using a Mutator alterer for a combinatorial problem. This causes the poor performance you have (most likely). A Mutator doesn't obey the unique element property in your chromosome. Mutating a chromosome means invalidating it, which causes a constant recreation of new chromosomes. For combinatorial problems, only SwapMutator and PartiallyMatchedCrossover are valid alterers.
final Engine<EnumGene<Seat>, Double> ENGINE = Engine
.builder(SeatingFitness::fitness, encoding)
.populationSize(200)
.selector(new RouletteWheelSelector<>())
.alterers(
new PartiallyMatchedCrossover<>(0.2),
new SwapMutator<>(0.01))
.optimize(Optimize.MINIMUM)
.build();
There might be also improvements in your fitness function, but for commenting on this, the whole fitness function code would be nice.
Related
I'm looking for an algorithm to find the best combination (the highest weight) of a list of sets. For example, let's say we have items 'A', 'B' and 'C' and we got 4 A's, 2 B's and 3 C's. Some possible combinations could be:
{A,B,C},{A,B,C},{A,C},{A}
{A,B},{A,B,C},{A,C},{A,C}
Then the weights are based on the number of items per set, for example:
1 item: 5
2 items: 15
3 items: 20
So in this case the first combination would have a weight of 20+20+15+5=60 and the second one would be 15+20+15+15=65. A greedy algorithm wouldn't work in this case because there are cases where looking for the highest number of items does not return the best combination.
Any ideas?
I solved this using recursion.
These variables define the problem (static variables):
private static boolean[] BEST_SOLUTION_SO_FAR = null;
private static int BEST_WEIGHT_SO_FAR = 0;
private static String[][] POSSIBLE_SETS = {{"A","B","C"},{"A","B","C"},{"A","C"},{"A"},{"A","B"},{"A","B","C"},{"A","C"},{"A","C"}};
private static Map<String, Integer> MAX_OF_EACH_ITEM = new HashMap<>();
static {
MAX_OF_EACH_ITEM.put("A", 4);
MAX_OF_EACH_ITEM.put("B", 2);
MAX_OF_EACH_ITEM.put("C", 3);
}
This is the main method (which sets everything up for the start of the recursion):
public static void main(String[] args) {
BEST_WEIGHT_SO_FAR = 0;
BEST_SOLUTION_SO_FAR = null;
// start the recursion
buildSolution(new boolean[POSSIBLE_SETS.length], new HashMap<>());
// print solution
System.out.println("BEST SOLUTION : ");
for (int i = 0; i < POSSIBLE_SETS.length; i++) {
if(BEST_SOLUTION_SO_FAR[i]){
System.out.println(Arrays.toString(POSSIBLE_SETS[i]));
}
}
}
And this is the recursive method:
private static void buildSolution(boolean[] isSelected, Map<String, Integer> itemsUsed){
boolean canBeExpanded = false;
for (int i = 0; i < isSelected.length; i++) {
// check whether another set can be added
if(!isSelected[i]){
// calculate new numbers of items used
Map<String, Integer> itemsUsedB = new HashMap<>(itemsUsed);
for (int j = 0; j < POSSIBLE_SETS[i].length; j++) {
String key = POSSIBLE_SETS[i][j];
if(itemsUsedB.containsKey(key))
itemsUsedB.put(key, itemsUsedB.get(key) + 1);
else
itemsUsedB.put(key, 1);
}
// check whether this is possible
boolean isPossible = true;
for(String key : MAX_OF_EACH_ITEM.keySet()){
if(itemsUsedB.containsKey(key) && itemsUsedB.get(key) > MAX_OF_EACH_ITEM.get(key)){
isPossible = false;
break;
}
}
// if not possible attempt next
if(!isPossible)
continue;
// mark this solution as expandable
canBeExpanded = true;
// mark as selected
isSelected[i] = true;
// recurse
buildSolution(isSelected, itemsUsedB);
// undo mark
isSelected[i] = false;
}
}
// a solution that can no longer be expanded was found
if(!canBeExpanded){
int w = 0;
int[] setSizeWeight = {0,5,15,20};
for (int i = 0; i < isSelected.length; i++) {
w += isSelected[i] ? setSizeWeight[POSSIBLE_SETS[i].length] : 0;
}
if(w > BEST_WEIGHT_SO_FAR){
BEST_WEIGHT_SO_FAR = w;
BEST_SOLUTION_SO_FAR = Arrays.copyOf(isSelected, isSelected.length);
}
}
}
It outputs:
BEST SOLUTION
[A, B, C]
[A, C]
[A, B]
[A, C]
Which has a weight of 65.
I have a list of 2d points which is a closed loop, 2D, concave polygon.
I want to generate a second polygon, which is fully inside the first polygon and each vertex/edge of the first polygon has a constant distance to each vertex/edge of the second polygon.
Basically, the first polygon would be "outer wall" and the second would be "inner wall", with the distance between two walls constant.
How to do something like that?
For the case that you do not care about self-intersections, the construction is pretty straightforward:
For each vertex of the polygon:
Take the previous and next line segment
Compute the normals of these line segments
Shift the line segments along the normal
Compute the intersection of the shifted line segments
Below is a MCVE implemented in Java/Swing. The actual computation takes place in computeOffsetPolygonPoints, and it should be easy to translate this to other languages and APIs.
For the case that you also have to handle self-intersections, things might become trickier. Then it would be necessary to define the intended result, particularly for the case that the polygon itself self-intersects...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class InnerPolygonShape
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
InnerPolygonShapePanel innerPolygonShapePanel =
new InnerPolygonShapePanel();
JSlider offsetSlider = new JSlider(0, 100, 40);
offsetSlider.addChangeListener(e ->
{
double alpha = offsetSlider.getValue() / 100.0;
double offset = -50.0 + alpha * 100.0;
innerPolygonShapePanel.setOffset(offset);
});
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(innerPolygonShapePanel, BorderLayout.CENTER);
f.getContentPane().add(offsetSlider, BorderLayout.SOUTH);
f.setSize(800,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class InnerPolygonShapePanel extends JPanel
implements MouseListener, MouseMotionListener
{
private final List<Point2D> points;
private Point2D draggedPoint;
private double offset = -10.0;
public InnerPolygonShapePanel()
{
this.points = new ArrayList<Point2D>();
points.add(new Point2D.Double(132,532));
points.add(new Point2D.Double(375,458));
points.add(new Point2D.Double(395,267));
points.add(new Point2D.Double(595,667));
addMouseListener(this);
addMouseMotionListener(this);
}
public void setOffset(double offset)
{
this.offset = offset;
repaint();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLACK);
paint(g, points);
List<Point2D> offsetPolygonPoints =
computeOffsetPolygonPoints(points, offset);
g.setColor(Color.BLUE);
paint(g, offsetPolygonPoints);
}
private static void paint(Graphics2D g, List<Point2D> points)
{
for (int i = 0; i < points.size(); i++)
{
int i0 = i;
int i1 = (i + 1) % points.size();
Point2D p0 = points.get(i0);
Point2D p1 = points.get(i1);
g.draw(new Line2D.Double(p0, p1));
}
g.setColor(Color.RED);
for (Point2D p : points)
{
double r = 5;
g.draw(new Ellipse2D.Double(p.getX()-r, p.getY()-r, r+r, r+r));
}
}
private static List<Point2D> computeOffsetPolygonPoints(
List<Point2D> points, double offset)
{
List<Point2D> result = new ArrayList<Point2D>();
Point2D absoluteLocation = new Point2D.Double();
for (int i = 0; i < points.size(); i++)
{
// Consider three consecutive points (previous, current, next)
int ip = (i - 1 + points.size()) % points.size();
int ic = i;
int in = (i + 1) % points.size();
Point2D pp = points.get(ip);
Point2D pc = points.get(ic);
Point2D pn = points.get(in);
// Compute the line segments between the previous and the current
// point, and the current and the next point, and compute their
// normal
Point2D line0 = difference(pc, pp);
Point2D direction0 = normalize(line0);
Point2D normal0 = rotateCw(direction0);
Point2D line1 = difference(pn, pc);
Point2D direction1 = normalize(line1);
Point2D normal1 = rotateCw(direction1);
// Shift both line segments along the normal
Point2D segment0p0 = add(pp, offset, normal0);
Point2D segment0p1 = add(pc, offset, normal0);
Point2D segment1p0 = add(pc, offset, normal1);
Point2D segment1p1 = add(pn, offset, normal1);
// Compute the intersection between the shifted line segments
intersect(
segment0p0.getX(), segment0p0.getY(),
segment0p1.getX(), segment0p1.getY(),
segment1p0.getX(), segment1p0.getY(),
segment1p1.getX(), segment1p1.getY(),
null, absoluteLocation);
result.add(new Point2D.Double(
absoluteLocation.getX(), absoluteLocation.getY()));
}
return result;
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getX(), e.getY());
repaint();
}
}
#Override
public void mousePressed(MouseEvent e)
{
final double thresholdSquared = 10 * 10;
Point2D p = e.getPoint();
Point2D closestPoint = null;
double minDistanceSquared = Double.MAX_VALUE;
for (Point2D point : points)
{
double dd = point.distanceSq(p);
if (dd < thresholdSquared && dd < minDistanceSquared)
{
minDistanceSquared = dd;
closestPoint = point;
}
}
draggedPoint = closestPoint;
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
#Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
private static Point2D difference(Point2D p0, Point2D p1)
{
double dx = p0.getX() - p1.getX();
double dy = p0.getY() - p1.getY();
return new Point2D.Double(dx, dy);
}
private static Point2D add(Point2D p0, double factor, Point2D p1)
{
double x0 = p0.getX();
double y0 = p0.getY();
double x1 = p1.getX();
double y1 = p1.getY();
return new Point2D.Double(x0 + factor * x1, y0 + factor * y1);
}
private static Point2D rotateCw(Point2D p)
{
return new Point2D.Double(p.getY(), -p.getX());
}
private static Point2D normalize(Point2D p)
{
double x = p.getX();
double y = p.getY();
double length = Math.hypot(x, y);
return new Point2D.Double(x / length, y / length);
}
// From https://github.com/javagl/Geom/blob/master/src/main/java/
// de/javagl/geom/Intersections.java
private static final double DOUBLE_EPSILON = 1e-6;
/**
* Computes the intersection of the specified lines.
*
* Ported from
* http://www.geometrictools.com/LibMathematics/Intersection/
* Wm5IntrSegment2Segment2.cpp
*
* #param s0x0 x-coordinate of point 0 of line segment 0
* #param s0y0 y-coordinate of point 0 of line segment 0
* #param s0x1 x-coordinate of point 1 of line segment 0
* #param s0y1 y-coordinate of point 1 of line segment 0
* #param s1x0 x-coordinate of point 0 of line segment 1
* #param s1y0 y-coordinate of point 0 of line segment 1
* #param s1x1 x-coordinate of point 1 of line segment 1
* #param s1y1 y-coordinate of point 1 of line segment 1
* #param relativeLocation Optional location that stores the
* relative location of the intersection point on
* the given line segments
* #param absoluteLocation Optional location that stores the
* absolute location of the intersection point
* #return Whether the lines intersect
*/
public static boolean intersect(
double s0x0, double s0y0,
double s0x1, double s0y1,
double s1x0, double s1y0,
double s1x1, double s1y1,
Point2D relativeLocation,
Point2D absoluteLocation)
{
double dx0 = s0x1 - s0x0;
double dy0 = s0y1 - s0y0;
double dx1 = s1x1 - s1x0;
double dy1 = s1y1 - s1y0;
double invLen0 = 1.0 / Math.sqrt(dx0*dx0+dy0*dy0);
double invLen1 = 1.0 / Math.sqrt(dx1*dx1+dy1*dy1);
double dir0x = dx0 * invLen0;
double dir0y = dy0 * invLen0;
double dir1x = dx1 * invLen1;
double dir1y = dy1 * invLen1;
double dot = dotPerp(dir0x, dir0y, dir1x, dir1y);
if (Math.abs(dot) > DOUBLE_EPSILON)
{
if (relativeLocation != null || absoluteLocation != null)
{
double c0x = s0x0 + dx0 * 0.5;
double c0y = s0y0 + dy0 * 0.5;
double c1x = s1x0 + dx1 * 0.5;
double c1y = s1y0 + dy1 * 0.5;
double cdx = c1x - c0x;
double cdy = c1y - c0y;
double dot0 = dotPerp(cdx, cdy, dir0x, dir0y);
double dot1 = dotPerp(cdx, cdy, dir1x, dir1y);
double invDot = 1.0/dot;
double s0 = dot1*invDot;
double s1 = dot0*invDot;
if (relativeLocation != null)
{
double n0 = (s0 * invLen0) + 0.5;
double n1 = (s1 * invLen1) + 0.5;
relativeLocation.setLocation(n0, n1);
}
if (absoluteLocation != null)
{
double x = c0x + s0 * dir0x;
double y = c0y + s0 * dir0y;
absoluteLocation.setLocation(x, y);
}
}
return true;
}
return false;
}
/**
* Returns the perpendicular dot product, i.e. the length
* of the vector (x0,y0,0)x(x1,y1,0).
*
* #param x0 Coordinate x0
* #param y0 Coordinate y0
* #param x1 Coordinate x1
* #param y1 Coordinate y1
* #return The length of the cross product vector
*/
private static double dotPerp(double x0, double y0, double x1, double y1)
{
return x0*y1 - y0*x1;
}
}
I need to draw random lines in a program that I have. I have figured out how to draw the Random lines, using the Random class, but I can't figure out how to make them connect. The second part of the class is to draw black lines over the white ones to "make them disappear".
Basically, if line1 has coordinates (0,0,300,300), then the second line should have the coordinates (300,300,random number, random number). I can't figure out how to make this happen using the Random class.
My teacher included a hint: Inserting a number into the parameters of the Random class "seeds" a list that is not random. Once you call the Random class again with that same seed, then the same list will appear. I don't know how to implement this so that the black lines will completely cover the white lines.
Here is my code so far:
public void paint(Graphics g)
{
super.paint(g);
g.setColor(Color.black);
g.fillRect(0, 0, 600, 600);
Point2D.Double one = new Point2D.Double(50,50);
Point2D.Double two = new Point2D.Double(300,300);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(5));
g.setColor(Color.white);
for (int i = 0; i < 10; i++)
{
Random gen = new Random();
/*one.setLocation(gen.nextInt(600), gen.nextInt(600));
two.setLocation(gen.nextInt(600), gen.nextInt(600));
g.drawLine((int)one.getX(), (int)one.getY(), (int)two.getX(), (int)two.getY());*/
int x1 = gen.nextInt(600);
int y1 = gen.nextInt(600);
int x2 = gen.nextInt(600);
int y2 = gen.nextInt(600);
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
try
{
Thread.currentThread().sleep(300);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
/*for (int i = 0; i < 10; i++)
{
g.setColor(Color.BLACK);
Random gen = new Random(1);
one.setLocation(gen.nextInt(600), gen.nextInt(600));
two.setLocation(gen.nextInt(600), gen.nextInt(600));
g.drawLine((int)one.getX(), (int)one.getY(), (int)two.getX(), (int)two.getY());
try
{
Thread.currentThread().sleep(300);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}*/
}
public static void main(String[] args)
{
/*int x = 0;
for (int i = 0; i < 20; i++)
{
x = x + 1;
Random gen = new Random(x);
System.out.println(gen.nextInt(100));
}*/
PointsAndLines application = new PointsAndLines();
application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
The commented stuff is just things that we went over in class. I don't know if this will help me.
Please, no complicated stuff. This is only my second year of programming, and I'm not adept at it yet.
You are doing it correct except that you don't need to generate x1,y1 again but assign it the older value. The initial random x1, y1 will be calculated beforehand, before getting inside the loop to draw lines. Below code may give you an insight.
Random gen = new Random();
int x1 = gen.nextInt(600);
int y1 = gen.nextInt(600);//We get the first x1, y1 random values here itself
for (int i = 0; i < 10; i++)
{
int x2 = gen.nextInt(600);
int y2 = gen.nextInt(600);
g.drawLine(x1, y1, x2, y2);//Once we draw the line we assign x2, y2 to x1, y1 as you did below
x1 = x2;
y1 = y2;
//Now the next time you enter this loop your line will start from where the line had ended and next point will be random
//rest of the code goes below
I can't say how do you plan to make the lines disappear again. Do you intend to draw the lines and erase them?
This should be the working version :)
I think the goal of your teacher is to make you understand the workings of the Random class.
package test;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
public class PointsAndLines extends JFrame
{
/**
*
*/
private static final long serialVersionUID = 1L;
private int seed;
public static void main(String[] args)
{
PointsAndLines application = new PointsAndLines(12); // <- upon changing the seed a different pattern will emerge
application.setVisible(true);
application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public PointsAndLines(int seed)
{
this.seed = seed;
setBounds(100, 100, 600, 600);
}
public void paint(Graphics g)
{
super.paint(g);
g.setColor(Color.black);
g.fillRect(0, 0, 600, 600);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(5));
g.setColor(Color.white);
Random gen = new Random(seed); // apply the seed
int x1 = gen.nextInt(600);
int y1 = gen.nextInt(600);
for (int i = 0; i < 10; i++)
{
int x2 = gen.nextInt(600);
int y2 = gen.nextInt(600);
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
try
{
Thread.sleep(300);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
Random genTwo = new Random(seed); // <- apply the same seed again
x1 = genTwo.nextInt(600);
y1 = genTwo.nextInt(600);
for (int i = 0; i < 10; i++)
{
g.setColor(Color.BLACK);
int x2 = genTwo.nextInt(600);
int y2 = genTwo.nextInt(600);
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
try
{
Thread.sleep(300);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
I have a class called FindSimilar which uses minHash to find similarities between 2 sets (and for this goal, it works great). My problem is that I need to compare more than 2 sets, more specifically, I need to compare a given set1 with an unknown amount of other sets. Here is the class:
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
public class FindSimilar<T>
{
private int hash[];
private int numHash;
public FindSimilar(int numHash)
{
this.numHash = numHash;
hash = new int[numHash];
Random r = new Random(11);
for (int i = 0; i < numHash; i++)
{
int a = (int) r.nextInt();
int b = (int) r.nextInt();
int c = (int) r.nextInt();
int x = hash(a * b * c, a, b, c);
hash[i] = x;
}
}
public double similarity(Set<T> set1, Set<T> set2)
{
int numSets = 4;
Map<T, boolean[]> bitMap = buildBitMap(set1, set2);
int[][] minHashValues = initializeHashBuckets(numSets, numHash);
computeFindSimilarForSet(set1, 0, minHashValues, bitMap);
computeFindSimilarForSet(set2, 1, minHashValues, bitMap);
return computeSimilarityFromSignatures(minHashValues, numHash);
}
private static int[][] initializeHashBuckets(int numSets,
int numHashFunctions)
{
int[][] minHashValues = new int[numSets][numHashFunctions];
for (int i = 0; i < numSets; i++)
{
for (int j = 0; j < numHashFunctions; j++)
{
minHashValues[i][j] = Integer.MAX_VALUE;
}
}
return minHashValues;
}
private static double computeSimilarityFromSignatures(
int[][] minHashValues, int numHashFunctions)
{
int identicalFindSimilares = 0;
for (int i = 0; i < numHashFunctions; i++)
{
if (minHashValues[0][i] == minHashValues[1][i])
{
identicalFindSimilares++;
}
}
return (1.0 * identicalFindSimilares) / numHashFunctions;
}
private static int hash(int x, int a, int b, int c)
{
int hashValue = (int) ((a * (x >> 4) + b * x + c) & 131071);
return Math.abs(hashValue);
}
private void computeFindSimilarForSet(Set<T> set, int setIndex,
int[][] minHashValues, Map<T, boolean[]> bitArray)
{
int index = 0;
for (T element : bitArray.keySet())
{
/*
* for every element in the bit array
*/
for (int i = 0; i < numHash; i++)
{
/*
* for every hash
*/
if (set.contains(element))
{
/*
* if the set contains the element
*/
int hindex = hash[index];
if (hindex < minHashValues[setIndex][index])
{
/*
* if current hash is smaller than the existing hash in
* the slot then replace with the smaller hash value
*/
minHashValues[setIndex][i] = hindex;
}
}
}
index++;
}
}
public Map<T, boolean[]> buildBitMap(Set<T> set1, Set<T> set2)
{
Map<T, boolean[]> bitArray = new HashMap<T, boolean[]>();
for (T t : set1)
{
bitArray.put(t, new boolean[] { true, false });
}
for (T t : set2)
{
if (bitArray.containsKey(t))
{
// item is present in set1
bitArray.put(t, new boolean[] { true, true });
}
else if (!bitArray.containsKey(t))
{
// item is not present in set1
bitArray.put(t, new boolean[] { false, true });
}
}
return bitArray;
}
public static void main(String[] args)
{
Set<String> set1 = new HashSet<String>();
set1.add("FRANCISCO");
set1.add("abc");
set1.add("SAN");
Set<String> set2 = new HashSet<String>();
set2.add("b");
set2.add("a");
set2.add("SAN");
set2.add("USA");
FindSimilar<String> minHash = new FindSimilar<String>(set1.size() + set2.size());
System.out.println("Set1 : " + set1);
System.out.println("Set2 : " + set2);
System.out.println("Similarity between two sets: "
+ minHash.similarity(set1, set2));
}
}
I need to use the similarity method on more than 2 sets. The problem is that I can't find a way to go over all of them. If I create a for, I can't say I want to compare set1 and seti. I am not sure if I am making sense, I must admit I am a bit confused.
The goal of the program is to compare users. A user has a list of contacts (other users) and similar users have similar contacts. Each set is a user and the contents of the sets will be their contacts.
In implementations of set similarity join algorithms, sets are usually converted to an array of integers. Each integer represents a set element, and the conversion is typically done with a hash map. The arrays are sorted, such that the overlap between two sets can be computed in a merge like manner. If you are interested in these algorithms and their pruning techniques, the paper at http://ssjoin.dbresearch.uni-salzburg.at/ could be a good start.
I have found a (not sure if) cheesy solution for my problem by placing all sets inside an ArrayList structure and then converting it to an actual array:
ArrayList<Set<String>> list = new ArrayList<Set<String>>();
for(int i = 0; i < numPeople; i++){
Set<String> set1 = new HashSet<String>();
list.add(set1);
//another for goes here later on
}
Set<String>[] bs = list.toArray(new Set[0]);
.
.
.
public static void main(String[] args)
{
.
.
.
for(int i = 1; i<bs.length; i++){
System.out.format("Set %d: ", i+1);
System.out.println(bs[0]);
System.out.println("Similarity between two sets: "
+ minHash.similarity(bs[0], bs[i]));
}
}
This gives off a The expression of type Set[] needs unchecked conversion to conform to Set<String>[] warning, but runs fine. This does exactly what I wanted it to (I still need a for to put data inside the sets, but that shouldn't be hard. If anyone could tell me if this solution should be used or if there is a better alternative, I'd like to hear it, since I am still learning and any info would be useful.
Here is my problem, I'm modifying code I found for Genetic Algorithms to do numerical optimization of a function. Essentially, given a function F and our Desired Value, the program uses GA to searches for values of x and y which provide the appropriate Desired Value.
I keep tinkering with my fitness function, which I feel is the root of the issue.
The basic code break down is:
Generate a random chromosome population
Use a bubble sort based on each chromosomes fitness
Check if any of them happen to solve the function
If one solves it, then stop and print it
Else,
Generate children based on the parents
Sort, check the best answer, loop
I hope someone can point me in the right direction I'm going to dissect it again some more tonight but I seem to have hit a snag on this. For more complex functions than that I have hard coded, it seems to converge around a random percentage (usually less than 20)... but it should be much closer to 0. The simple coded function keeps returning around 99% difference... so I'm not 100% on whats up.
import java.util.*;
import java.util.Comparator;
import java.util.TreeMap;
/**
* Modified from a file created Jul 9, 2003
* Original #author Fabian Jones
* Modified #author Cutright
* #version 2
*/
public class ScratchGA
{
private static int NUM_CHROMOSOMES = 100; //num of chromosomes in population
private static double MUTATE = .01; //chance of a mutation i.e. 88.8%
private static int desiredValue = 60466176; //desired value of function
private static int cutoff = 1000; // number of iterations before cut off
private static int longPrint = 0; //1 means print out each iteration of the population
private boolean done = false;
private Chromosome[] population;
int iteration = 0;
/**
* Constructor for objects of class ScratchGA
*/
public ScratchGA()
{
generateRandomPopulation(NUM_CHROMOSOMES);
printPopulation();
}
/**
* Generate a random population of chromosomes - WORKS
*
*/
private void generateRandomPopulation(int pop)
{
System.out.println("Generating random population of " + pop + ", now." +"\n");
population = new Chromosome[pop];
for(int i=0; i<pop; i++)
{
int rand = (int)(Math.random()*4095); // Range 0 to 4095
population[i] = (new Chromosome(rand, 12));
}
}
/**
* Codesaver for generating a new line in the output
*/
private void newLine()
{
System.out.println("\n");
}
/**
* Prints the population (the chromosomes)
*/
private void printPopulation()
{
int x=1; // variable to print 10 chromosomes on a line
if (iteration==0)
{
System.out.println("Initial population: " + "\n" );
}
else
{
if (longPrint ==1)
{
System.out.println("Population " + iteration + " :" + "\n");
for(int i=0; i<=(NUM_CHROMOSOMES-1); i++)
{
System.out.print(population[i] + " ");
if(x == 10)
{
newLine();
x=1;
}
else
{
x++;
}
}
newLine();
}
else
{
System.out.println("Best answer for iteration " + iteration + " is: " + population[0] + " with a % difference of " +population[0].getFitness());
newLine();
}
}
}
/**
* Start
* Bubblesort initial population by their fitness, see if the first chromosome
* in the sorted array satisfies our constraint.
* IF done ==true or max num of iterations
* Print best solution and its fitness
* ELSE
* generate new population based on old one, and continue on
*/
public void start()
{
// System.out.println("Starting bubblesort... Please Wait.");
bubbleSort();
//System.out.println("After Bubblesort: " );
//printPopulation();
topFitness();
if(done || iteration==cutoff){
System.out.println("DONE!!");
System.out.println("Best solution: " + population[0] + " % Difference: " + population[0].getFitness());
}
else{
iteration++;
generateNewPopulation();
printPopulation();
start();
}
}
/**
* If the top chromosomes fitness (after being sorted by bubblesort) is 100%
* done == true
*/
private void topFitness()
{
if (population[0].getFitness() == 0)
{
done = true;
}
}
/**
* Called from chromosome,
* Tests the x and y values in the function and returns their output
*/
public static double functionTest(int x, int y)
{
return (3*x)^(2*y); // From our desired value we're looking for x=2, y=5
}
/**
* Returns the desired outcome of the function, with ideal x and y
* Stored above in a private static
*/
public static int getDesired()
{
return desiredValue;
}
/**
* Sort Chromosome array, based on fitness
* utilizes a bubblesort
*/
private void bubbleSort()
{
Chromosome temp;
for(int i=0; i<NUM_CHROMOSOMES; i++){
for(int j=1; j<(NUM_CHROMOSOMES-i); j++){
if(population[j-1].getFitness() > population[j].getFitness())
{
//swap elements
temp = population[j-1];
population[j-1] = population[j];
population[j] = temp;
}
}
}
}
/**
* Top 30: Elitism
* Next 60: Offspring of Elitists
* Next 10: Random
*/
private void generateNewPopulation(){
System.out.println("***Generating New Population");
Chromosome[] temp = new Chromosome[100];
for (int i = 0; i < 30; i++)
{
Chromosome x = population[i];
if (shouldMutate())
mutate(x);
temp[i]=x;
}
for (int i = 0; i < 30; i++)
{
temp[i+30] =cross1(population[i], population[i+1]);
temp[i+60] = cross2(population[i], population[i+1]);
}
for (int i = 90; i<100; i++)
{
int rand = (int)(Math.random()*4095); // Range 0 to 4095
Chromosome x = new Chromosome(rand, 12);
temp[i] = x;
}
population = temp;
}
/**
* First cross type, with two parents
*/
private Chromosome cross1(Chromosome parent1, Chromosome parent2){
String bitS1 = parent1.getBitString();
String bitS2 = parent2.getBitString();
int length = bitS1.length();
int num = (int)(Math.random()*length); // number from 0 to length-1
String newBitString = bitS2.substring(0, num) + bitS1.substring(num, length);
Chromosome offspring = new Chromosome();
offspring.setBitString(newBitString);
if(shouldMutate()){
mutate(offspring);
}
return offspring;
}
/**
* Second cross type, parents given in same order as first, but reverses internal workings
*/
private Chromosome cross2(Chromosome parent1, Chromosome parent2){
String bitS1 = parent1.getBitString();
String bitS2 = parent2.getBitString();
int length = bitS1.length();
int num = (int)(Math.random()*length); // number from 0 to length-1
String newBitString = bitS2.substring(0, num) + bitS1.substring(num, length);
Chromosome offspring = new Chromosome();
offspring.setBitString(newBitString);
if(shouldMutate()){
mutate(offspring);
}
return offspring;
}
/**
* Returns a boolean of whether a character should mutate based on the mutation value at top
*/
private boolean shouldMutate(){
double num = Math.random()*100;
return (num <= MUTATE);
}
/**
* Returns a boolean of whether a character should mutate based on the mutation value at top
*/
private void mutate(Chromosome offspring){
String s = offspring.getBitString();
int num = s.length();
int index = (int) (Math.random()*num);
String newBit = flip(s.substring(index, index+1));
String newBitString = s.substring(0, index) + newBit + s.substring(index+1, s.length());
offspring.setBitString(newBitString);
}
/**
* Flips bits in a string 1 to 0, 0 to 1
*/
private String flip(String s){
return s.equals("0")? "1":"0";
}
}
import java.lang.Comparable;
import java.math.*;
/**
* Modified from a file created on Jul 9, 2003
* Unsure of original author
*
*/
public class Chromosome implements Comparable
{
protected String bitString;
/**
* Constructor for objects of class Chromosome
*/
public Chromosome()
{
}
public Chromosome(int value, int length)
{
bitString = convertIntToBitString(value, length);
}
public void setBitString(String s)
{
bitString = s;
}
public String getBitString()
{
return bitString;
}
public int compareTo(Object o)
{
Chromosome c = (Chromosome) o;
int num = countOnes(this.bitString) - countOnes(c.getBitString());
return num;
}
public double getFitness()
{
String working = bitString;
int x1 = Integer.parseInt(working.substring(0,6),2);
int x2 = Integer.parseInt(working.substring(6),2);
double result = ScratchGA.functionTest(x1,x2);
double percentDiff = ((ScratchGA.getDesired() - result)/ScratchGA.getDesired())*100;
if (percentDiff >= 0)
{
return percentDiff;
}
else
{
return -percentDiff;
}
}
public boolean equals(Object o)
{
if(o instanceof Chromosome)
{
Chromosome c = (Chromosome) o;
return c.getBitString().equals(bitString);
}
return false;
}
public int hashCode()
{
return bitString.hashCode();
}
public String toString()
{
return bitString;
}
public static int countOnes(String bits)
{
int sum = 0;
for(int i = 0; i < bits.length(); ++ i){
String test = bits.substring(i, i+1);
if(test.equals("1")){
sum = sum + 1;
}
}
return sum;
}
public static String convertIntToBitString(int val, int length)
{
int reval = val;
StringBuffer bitString = new StringBuffer(length);
for(int i = length-1; i >=0; --i ){
if( reval - (Math.pow(2, i)) >= 0 ){
bitString.append("1");
reval = (int) (reval - Math.pow(2, i));
}
else{
bitString.append("0");
}
}
return bitString.toString();
}
public static void main(String[] args
){
//System.out.println(convertIntToBitString(2046, 10));
Chromosome c = new Chromosome(1234, 10);
//System.out.println(c.fitness());
}
}
Actually, it was a simple error that eluded me, that I should have caught. The major issue was in using return (3*x)^(2*y); ^ is a bitwise XOR in java, but an exponent. (Whoops) The problem was rectified using Math.pow(3*x,2*y); ...and a little double check of the fitness function had it up and running with some other minor changes :)