Added working first version.
This commit is contained in:
parent
1d5d1adee1
commit
f580857c14
|
@ -0,0 +1,116 @@
|
||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Java template
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
|
||||||
|
### Maven template
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
buildNumber.properties
|
||||||
|
.mvn/timing.properties
|
||||||
|
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
distribution.csv
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>nl.andrewlalis</groupId>
|
||||||
|
<artifactId>human-task-distributor</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>12</source>
|
||||||
|
<target>12</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>3.1.1</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>nl.andrewlalis.human_task_distributor.HumanTaskDistributor</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- https://mvnrepository.com/artifact/commons-cli/commons-cli -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-cli</groupId>
|
||||||
|
<artifactId>commons-cli</artifactId>
|
||||||
|
<version>1.4</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-csv -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-csv</artifactId>
|
||||||
|
<version>1.8</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.18</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,85 @@
|
||||||
|
package nl.andrewlalis.human_task_distributor;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class Distributor {
|
||||||
|
|
||||||
|
public Map<Human, Set<Task>> generateDistribution(
|
||||||
|
Map<Human, Float> weightedHumans,
|
||||||
|
Set<Task> tasks,
|
||||||
|
List<Map<Human, Set<Task>>> previousDistributions
|
||||||
|
) {
|
||||||
|
if (weightedHumans.isEmpty() || tasks.isEmpty()) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
float unweightedAverageTasksPerHuman = (float) tasks.size() / weightedHumans.size();
|
||||||
|
|
||||||
|
// Initialize distribution data sets for each human.
|
||||||
|
Map<Human, Set<Task>> taskDistributions = new HashMap<>(weightedHumans.size());
|
||||||
|
weightedHumans.forEach((h, w) -> taskDistributions.put(h, new HashSet<>()));
|
||||||
|
final float totalWeight = weightedHumans.values().stream().reduce(Float::sum).orElse(0.0f);
|
||||||
|
final float averageTasksPerHuman = unweightedAverageTasksPerHuman / (totalWeight / taskDistributions.size());
|
||||||
|
|
||||||
|
Map<Human, Float> maxTasksPerHuman = new HashMap<>();
|
||||||
|
weightedHumans.forEach((h, w) -> maxTasksPerHuman.put(h, w * averageTasksPerHuman));
|
||||||
|
|
||||||
|
// Prepare a stack of tasks we can gradually take from.
|
||||||
|
Stack<Task> taskStack = new Stack<>();
|
||||||
|
taskStack.addAll(tasks);
|
||||||
|
Collections.shuffle(taskStack);
|
||||||
|
|
||||||
|
while (!taskStack.empty()) {
|
||||||
|
Task t = taskStack.pop();
|
||||||
|
Human h = this.chooseHumanForNextTask(
|
||||||
|
taskDistributions,
|
||||||
|
maxTasksPerHuman,
|
||||||
|
t,
|
||||||
|
previousDistributions
|
||||||
|
);
|
||||||
|
taskDistributions.get(h).add(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskDistributions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chooses the next person for a task, using a ranked choice based on two
|
||||||
|
* criteria:
|
||||||
|
* <ol>
|
||||||
|
* <li>Whether the person has been given this exact task before.</li>
|
||||||
|
* <li>The difference between the tasks the person has, and how many they can have at most.</li>
|
||||||
|
* </ol>
|
||||||
|
* @param tasksPerHuman A set of tasks that each person is already assigned to.
|
||||||
|
* @param maxTasksPerHuman For each person, a floating point maximum number
|
||||||
|
* of tasks that they may be assigned to.
|
||||||
|
* @param task The task being assigned.
|
||||||
|
* @param previousDistributions A list of distributions done previously, in
|
||||||
|
* order to avoid assigning people to the same
|
||||||
|
* tasks.
|
||||||
|
* @return The human to use for the task.
|
||||||
|
*/
|
||||||
|
private Human chooseHumanForNextTask(
|
||||||
|
Map<Human, Set<Task>> tasksPerHuman,
|
||||||
|
Map<Human, Float> maxTasksPerHuman,
|
||||||
|
Task task,
|
||||||
|
List<Map<Human, Set<Task>>> previousDistributions
|
||||||
|
) {
|
||||||
|
List<Human> rankedHumans = maxTasksPerHuman.keySet().stream()
|
||||||
|
.sorted((h1, h2) -> {
|
||||||
|
// Sort first so people who haven't had the task are sorted first.
|
||||||
|
boolean h1NeverHadTask = previousDistributions.stream().noneMatch(map -> map.getOrDefault(h1, Set.of()).contains(task));
|
||||||
|
boolean h2NeverHadTask = previousDistributions.stream().noneMatch(map -> map.getOrDefault(h2, Set.of()).contains(task));
|
||||||
|
int previousTaskCompare = Boolean.compare(h1NeverHadTask, h2NeverHadTask);
|
||||||
|
if (previousTaskCompare != 0) {
|
||||||
|
return previousTaskCompare;
|
||||||
|
}
|
||||||
|
final float diff1 = maxTasksPerHuman.get(h1) - tasksPerHuman.get(h1).size();
|
||||||
|
final float diff2 = maxTasksPerHuman.get(h2) - tasksPerHuman.get(h2).size();
|
||||||
|
return Float.compare(diff2, diff1); // Reverse sorting direction so largest diffs appear first.
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return rankedHumans.get(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package nl.andrewlalis.human_task_distributor;
|
||||||
|
|
||||||
|
import org.apache.commons.csv.CSVParser;
|
||||||
|
import org.apache.commons.csv.CSVRecord;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static nl.andrewlalis.human_task_distributor.HumanTaskDistributor.CSV_FORMAT;
|
||||||
|
|
||||||
|
public class FileParser {
|
||||||
|
|
||||||
|
public Map<Human, Float> parseHumanList(String path) {
|
||||||
|
Map<Human, Float> humanNameWeightMap = new HashMap<>();
|
||||||
|
try (CSVParser csvParser = CSVParser.parse(Paths.get(path), StandardCharsets.UTF_8, CSV_FORMAT)) {
|
||||||
|
for (CSVRecord record : csvParser) {
|
||||||
|
if (record.size() > 0) {
|
||||||
|
String name = record.get(0).trim();
|
||||||
|
float weight = 1.0f;
|
||||||
|
if (record.size() > 1) {
|
||||||
|
try {
|
||||||
|
weight = Float.parseFloat(record.get(1));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// Do nothing here, simply skip a custom weight.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
humanNameWeightMap.put(new Human(name), weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return humanNameWeightMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Task> parseTaskList(String path) {
|
||||||
|
Set<Task> taskSet = new HashSet<>();
|
||||||
|
try (CSVParser csvParser = CSVParser.parse(Paths.get(path), StandardCharsets.UTF_8, CSV_FORMAT)) {
|
||||||
|
for (CSVRecord record : csvParser) {
|
||||||
|
if (record.size() > 0) {
|
||||||
|
String taskName = record.get(0);
|
||||||
|
if (!taskName.isBlank()) {
|
||||||
|
taskSet.add(new Task(taskName.trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return taskSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<Human, Set<Task>>> parsePreviousTaskDistributions(String[] paths) {
|
||||||
|
List<Map<Human, Set<Task>>> previousDistributions = new ArrayList<>();
|
||||||
|
for (String path : paths) {
|
||||||
|
Map<Human, Set<Task>> distribution = new HashMap<>();
|
||||||
|
try (CSVParser csvParser = CSVParser.parse(Paths.get(path), StandardCharsets.UTF_8, CSV_FORMAT)) {
|
||||||
|
for (CSVRecord record : csvParser) {
|
||||||
|
if (record.size() > 1) {
|
||||||
|
Human h = new Human(record.get(0).trim());
|
||||||
|
Task t = new Task(record.get(1).trim());
|
||||||
|
if (!distribution.containsKey(h)) {
|
||||||
|
distribution.put(h, new HashSet<>());
|
||||||
|
}
|
||||||
|
distribution.get(h).add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
previousDistributions.add(distribution);
|
||||||
|
}
|
||||||
|
return previousDistributions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package nl.andrewlalis.human_task_distributor;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class Human {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public Human(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Human human = (Human) o;
|
||||||
|
return getName().equals(human.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.getName();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package nl.andrewlalis.human_task_distributor;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.*;
|
||||||
|
import org.apache.commons.csv.CSVFormat;
|
||||||
|
import org.apache.commons.csv.CSVPrinter;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class HumanTaskDistributor {
|
||||||
|
public static final CSVFormat CSV_FORMAT = CSVFormat.RFC4180;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
final Options options = getOptions();
|
||||||
|
CommandLineParser cmdParser = new DefaultParser();
|
||||||
|
try {
|
||||||
|
CommandLine cmd = cmdParser.parse(options, args);
|
||||||
|
FileParser fileParser = new FileParser();
|
||||||
|
Map<Human, Float> nameWeightMap = fileParser.parseHumanList(cmd.getOptionValue("hl"));
|
||||||
|
Set<Task> tasks = fileParser.parseTaskList(cmd.getOptionValue("tl"));
|
||||||
|
List<Map<Human, Set<Task>>> previousDistributions = fileParser.parsePreviousTaskDistributions(cmd.getOptionValues("prev"));
|
||||||
|
Map<Human, Set<Task>> taskDistributions = new Distributor().generateDistribution(nameWeightMap, tasks, previousDistributions);
|
||||||
|
taskDistributions.forEach((key, value) -> {
|
||||||
|
System.out.println("Task distribution for " + key + ", " + value.size() + " tasks:");
|
||||||
|
value.forEach(t -> System.out.println("\t" + t.getName()));
|
||||||
|
});
|
||||||
|
String filePath = cmd.hasOption("o") ? cmd.getOptionValue("o") : "distribution.csv";
|
||||||
|
CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(Paths.get(filePath), StandardCharsets.UTF_8), CSV_FORMAT);
|
||||||
|
for (Map.Entry<Human, Set<Task>> entry : taskDistributions.entrySet()) {
|
||||||
|
Human human = entry.getKey();
|
||||||
|
Set<Task> assignedTasks = entry.getValue();
|
||||||
|
List<Task> sortedTasks = assignedTasks.stream().sorted(Comparator.comparing(Task::getName)).collect(Collectors.toList());
|
||||||
|
for (Task task : sortedTasks) {
|
||||||
|
printer.printRecord(human.getName(), task.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printer.close(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error: " + e.getMessage());
|
||||||
|
HelpFormatter hf = new HelpFormatter();
|
||||||
|
hf.printHelp("HumanTaskDistributor", options);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Options getOptions() {
|
||||||
|
Options options = new Options();
|
||||||
|
options.addOption(Option.builder("hl")
|
||||||
|
.longOpt("humans-list")
|
||||||
|
.hasArg(true)
|
||||||
|
.desc("Path to a CSV file containing list of humans to distribute tasks to. First column should be the name of the person, and second column can be empty, or contain a floating-point weight.")
|
||||||
|
.required(true)
|
||||||
|
.numberOfArgs(1)
|
||||||
|
.type(String.class)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
options.addOption(Option.builder("tl")
|
||||||
|
.longOpt("tasks-list")
|
||||||
|
.hasArg(true)
|
||||||
|
.desc("Path to a CSV file containing list of tasks that can be distributed to humans. First column should be unique task name.")
|
||||||
|
.required(true)
|
||||||
|
.numberOfArgs(1)
|
||||||
|
.type(String.class)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
options.addOption(Option.builder("prev")
|
||||||
|
.longOpt("previous-distributions")
|
||||||
|
.desc("One or more CSV files containing previous task distribution results, to aid in balancing distribution over multiple iterations. Each should be of the form: person name, task name")
|
||||||
|
.numberOfArgs(Option.UNLIMITED_VALUES)
|
||||||
|
.hasArg(true)
|
||||||
|
.required(false)
|
||||||
|
.valueSeparator(',')
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
options.addOption(Option.builder("o")
|
||||||
|
.longOpt("output")
|
||||||
|
.desc("Output file to write CSV distribution data to.")
|
||||||
|
.hasArg(true)
|
||||||
|
.numberOfArgs(1)
|
||||||
|
.required(false)
|
||||||
|
.type(String.class)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package nl.andrewlalis.human_task_distributor;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class Task {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public Task(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Task task = (Task) o;
|
||||||
|
return getName().equals(task.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(getName());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package nl.andrewlalis.human_task_distributor;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class TaskDistribution {
|
||||||
|
private final Human human;
|
||||||
|
private final float weight;
|
||||||
|
private final Set<Task> tasks;
|
||||||
|
|
||||||
|
public TaskDistribution(Human human, float weight, Set<Task> tasks) {
|
||||||
|
this.human = human;
|
||||||
|
this.weight = weight;
|
||||||
|
this.tasks = tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskDistribution(Human human, float weight) {
|
||||||
|
this(human, weight, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TaskDistribution{" +
|
||||||
|
"human=" + human +
|
||||||
|
", weight=" + weight +
|
||||||
|
", tasks=" + tasks +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue