[英]Infinite loop in Java program with graph and inner classes
好的,我的任务是给定一个捐赠清单和一组项目(下面给出的类),我必须查看所有项目是否都可以由捐赠来资助。 如果不能,则必须取消分配到目前为止分配的所有资金,并返回false。 捐赠列出了可以捐赠的项目清单,并且有一定数量。
我选择使用图形来实现。 顶点表示一个项目,联系是它们与其他项目共享的捐赠。 它的设置使得它可以循环进行捐赠,并向可以资助的每个项目一次捐赠1美元。 如果一个项目不能完全获得资金,但可以通过捐赠从与之相关的另一个项目中转移资金,请从该项目转移资金。 如果没有办法为所有项目提供资金,则取消所有分配并返回false。
我的两个问题是:1)当需要传输时,我的测试套件进入无限循环。 目前,如果有人能找到我的无限循环问题并提出解决方案,使其在我真正致力于使转移部分开始工作之前退出,我将很高兴。
给定的两个类无需修复,只需修复有问题的第一部分代码即可。 我也毫无疑问,我的图形,节点和连接器类根本不是很好,但是目前它们可以工作。 我只是在寻找canAllocateHelper方法中的修复程序。
注意:很抱歉发布了这么多代码,但是我在12个小时前发布了一个类似的问题,并被告知我没有提供足够的代码来对其进行调试,因此我认为我只是发布了所有内容。
我的代码:
package a2;
import java.util.*;
import a2.IterativeAllocator.Connector;
import a2.IterativeAllocator.Node;
public class IterativeAllocator {
/**
* @precondition: Neither of the inputs are null or contain null elements.
* The parameter donations is a list of distinct donations
* such that for each d in donations, d.getTotal() equals
* d.getUnspent(); and for each p in projects
* p.allocatedFunding() equals 0.
* @postcondition: returns false if there no way to completely fund all of
* the given projects using the donations, leaving both the
* input list of donations and set of projects unmodified;
* otherwise returns true and allocates to each project
* funding from the donations. The allocation to each
* project must be complete and may not violate the
* conditions of the donations.
*/
public static boolean canAllocate(List<Donation> donations,
Set<Project> projects) {
ArrayList<Node<Project>> nodes = getNodes(donations, projects);
Graph<?> uniGraph = createGraph(nodes);
if (donations.isEmpty()) {
return false;
}
return canAllocateHelper(donations, projects, uniGraph, 0);
}
// Helper Methods
private static boolean canAllocateHelper(List<Donation> donations,
Set<Project> projects, Graph graph, int index ) {
Donation donation = donations.get(index);
Set<Project> p = donation.getProjects();
int count = countFullyFunded(projects);
if (count == projects.size()) { return true; }
if (donation.spent()) {
if (index == donations.size() - 1) { return false; }
return canAllocateHelper(donations, projects, graph, index + 1);
}
int pCount = countFullyFunded(p);
if (pCount == p.size() && (index + 1) < donations.size() ) {
return canAllocateHelper(donations, projects, graph, index + 1);
}
for (int i=0; i < graph.size(); i++) {
Node<Project> tempNode = graph.getNode(i);
Project tempProj = tempNode.getElement();
if (donation.canBeUsedFor(tempProj)) {
if (!tempProj.fullyFunded()) {
if (donation.spent()) {
LinkedList<Connector<Project>> conns = tempNode.getConnections();
for (int k=0; k<conns.size(); k++) {
tempProj.transfer(1, conns.get(k).getSecond().getElement());
canAllocateHelper(donations, projects, graph, index);
}
}
tempProj.allocate(donation, 1);
if (canAllocateHelper(donations, projects, graph, index)) {
return true;
}
tempProj.deallocate(donation, 1);
}
}
}
return false;
}
private static ArrayList<Node<Project>> getNodes(List<Donation> donations,
Set<Project> projects) {
ArrayList<Node<Project>> nodes = new ArrayList<Node<Project>>();
Iterator<Project> pIterator = projects.iterator();
while (pIterator.hasNext()) {
Node<Project> node = new Node<Project>(pIterator.next());
nodes.add(node);
}
// Iterate through donations to get an Array of connections
for (int i=0; i < donations.size(); i++) {
Set<Project> connections = donations.get(i).getProjects();
ArrayList<Node<Project>> cArray = new ArrayList<Node<Project>>();
Iterator<Project> cIterator = connections.iterator();
while (cIterator.hasNext()) {
Node<Project> temp = new Node<Project>(cIterator.next());
cArray.add(temp);
}
// for each node, see if in cArray. If so, connect it
for (int j=0; j < nodes.size(); j++) {
if ( cArray.contains(nodes.get(j))) {
for ( int k = 0; k < cArray.size(); k++) {
nodes.get(j).connect(cArray.get(k),
donations.get(i).getTotal());
}
}
}
}
return nodes;
}
private static Graph<?> createGraph(ArrayList<Node<Project>> nodes) {
Graph<?> graph = new Graph<Object>();
for (int i=0; i < nodes.size(); i++) {
graph.addNode(nodes.get(i));
}
return graph;
}
private static int countFullyFunded(Set<Project> projects) {
Iterator<Project> projectsIterator = projects.iterator();
int count = 0;
while(projectsIterator.hasNext()) {
Project proj = projectsIterator.next();
if (proj.fullyFunded()) {
count++;
}
}
return count;
}
// Private classes
public static class Node<E> {
private int count = 0;
private E element;
private int id;
private LinkedList<Connector<E>> connections;
public Node() {
this(null);
}
public Node(E element) {
this.element = element;
id = count++;
connections = new LinkedList<Connector<E>>();
}
public int getID() {
return id;
}
public E getElement() {
return element;
}
public void setElement(E elem) {
this.element = elem;
}
public void connect(Node<E> newPoint, int cost) {
Connector<E> a = new Connector<E>(this, newPoint, cost);
if(!connections.contains(a)) {
connections.add(a);
}
}
public LinkedList<Connector<E>> getConnections() {
return connections;
}
public void sortConnections() {
Collections.sort(connections);
}
public Iterator<Connector<E>> iterator() {
return connections.iterator();
}
public boolean equals(Node<E> other) {
if(other.connections.size() != connections.size()) {
return false;
}
LinkedList<Connector<E>> temp = new
LinkedList<Connector<E>>(other.getConnections());
return !(temp.retainAll(this.connections));
}
public String toString() {
return this.element.toString();
}
}
public static class Connector<E> implements Comparable<Connector<E>>{
private Node<E> first, second;
private int dist;
public Connector(Node<E> first, Node<E> second){
this(first, second, 0);
}
public Connector(Node<E> first, Node<E> second, int dist){
this.first = first;
this.second = second;
this.dist = dist;
}
public Node<E> getFirst(){
return first;
}
public Node<E> getSecond(){
return second;
}
public int getDistance(){
return dist;
}
public void setDistance(int distance){
this.dist = distance;
}
public boolean equals(Connector<E> other){
return first.equals(other.getFirst()) &&
second.equals(other.getSecond()) &&
dist == other.getDistance();
}
public String toString(){ return "(" + first.getElement() +
", " + second.getElement() + "): " + dist; }
public int compareTo(Connector<E> other){
return this.dist - other.dist;
}
}
private static class Graph<E> {
private ArrayList<Node<E>> nodes;
public Graph() {
nodes = new ArrayList<Node<E>>();
}
public boolean addNode(Node<Project> node) {
if (nodes.contains(node)) {
return false;
}
else {
nodes.add((Node<E>) node);
return true;
}
}
public Node<E> getNode(int index) {
return nodes.get(index);
}
public int size() {
return nodes.size();
}
public boolean equals(Graph<E> other) {
if (other.size() != nodes.size()) {
return false;
}
ArrayList<Node<E>> temp = new ArrayList<Node<E>>(other.nodes);
return temp.retainAll(nodes);
}
}
}
测试套件:
package a2.test;
import org.junit.*;
import java.util.*;
import a2.*;
/**
* Some tests for the part2.IterativeAllocator.canAllocate method. A much more
* extensive test suite will be used to mark your code, but this should get you
* started writing your own tests to help you to debug your implementation.
*/
public class IterativeAllocatorTest {
@Test
public void basicTestTrue() {
List<Project> projects = new ArrayList<Project>();
ArrayList<Donation> donations = new ArrayList<Donation>();
projects.add(new Project("P0", 100));
projects.add(new Project("P1", 100));
projects.add(new Project("P2", 100));
donations.add(new Donation("D0", 100, new HashSet<Project>(Arrays
.asList(projects.get(0), projects.get(1)))));
donations.add(new Donation("D1", 100, new HashSet<Project>(Arrays
.asList(projects.get(1), projects.get(2)))));
donations.add(new Donation("D2", 50, new HashSet<Project>(Arrays
.asList(projects.get(0)))));
donations.add(new Donation("D3", 50, new HashSet<Project>(Arrays
.asList(projects.get(2)))));
List<Donation> actualDonations = new ArrayList<>(donations);
Set<Project> actualProjects = new HashSet<>(projects);
Assert.assertTrue(IterativeAllocator.canAllocate(actualDonations,
actualProjects));
// no donations should be added or removed from the list of donations
Assert.assertEquals(donations, actualDonations);
// no projects should be added or removed from the set of projects
Assert.assertEquals(new HashSet<>(projects), actualProjects);
// allocation should be complete and valid
checkCompleteAllocation(actualDonations, actualProjects);
}
@Test
public void basicTestFalse() {
List<Project> projects = new ArrayList<Project>();
ArrayList<Donation> donations = new ArrayList<Donation>();
projects.add(new Project("P0", 100));
projects.add(new Project("P1", 100));
projects.add(new Project("P2", 100));
donations.add(new Donation("D0", 100, new HashSet<Project>(Arrays
.asList(projects.get(0), projects.get(1), projects.get(2)))));
donations.add(new Donation("D1", 200, new HashSet<Project>(Arrays
.asList(projects.get(0)))));
List<Donation> actualDonations = new ArrayList<>(donations);
Set<Project> actualProjects = new HashSet<>(projects);
Assert.assertFalse(IterativeAllocator.canAllocate(actualDonations,
actualProjects));
// no donations should be added or removed from the list of donations
Assert.assertEquals(donations, actualDonations);
// no projects should be added or removed from the set of projects
Assert.assertEquals(new HashSet<>(projects), actualProjects);
// no allocations should have been made
checkEmptyAllocation(actualDonations, actualProjects);
}
// basic no transfer test
@Test
public void noTransferTrue() {
List<Project> projects = new ArrayList<Project>();
ArrayList<Donation> donations = new ArrayList<Donation>();
projects.add(new Project("P0", 10));
projects.add(new Project("P1", 10));
projects.add(new Project("P2", 10));
donations.add(new Donation("D0", 10, new HashSet<Project>(Arrays
.asList(projects.get(0), projects.get(1)))));
donations.add(new Donation("D1", 10, new HashSet<Project>(Arrays
.asList(projects.get(1), projects.get(2)))));
donations.add(new Donation("D2", 5, new HashSet<Project>(Arrays
.asList(projects.get(0)))));
donations.add(new Donation("D3", 5, new HashSet<Project>(Arrays
.asList(projects.get(2)))));
List<Donation> actualDonations = new ArrayList<>(donations);
Set<Project> actualProjects = new HashSet<>(projects);
Assert.assertTrue(IterativeAllocator.canAllocate(actualDonations,
actualProjects));
// no donations should be added or removed from the list of donations
Assert.assertEquals(donations, actualDonations);
// no projects should be added or removed from the set of projects
Assert.assertEquals(new HashSet<>(projects), actualProjects);
// allocation should be complete and valid
checkCompleteAllocation(actualDonations, actualProjects);
}
// helper methods
/**
* Helper method to check that each project has been completely allocated by
* the given donations, and that the total spent on each donation is equal
* to that spent on the given projects.
**/
private void checkCompleteAllocation(List<Donation> donations,
Set<Project> projects) {
// the amount spent from each donation by all of the combined projects
Map<Donation, Integer> totalSpent = new HashMap<>();
// check that each project has been completely (and properly) allocated
// and calculate totalSpent
for (Project p : projects) {
Assert.assertTrue(p.fullyFunded());
for (Map.Entry<Donation, Integer> allocation : p.getAllocations()
.entrySet()) {
Donation d = allocation.getKey();
int amount = allocation.getValue();
Assert.assertTrue(amount > 0);
Assert.assertTrue(d.canBeUsedFor(p));
Assert.assertTrue(donations.contains(d));
if (totalSpent.containsKey(d)) {
totalSpent.put(d, totalSpent.get(d) + amount);
} else {
totalSpent.put(d, amount);
}
}
}
// check that the remaining funds in each donation are correct, assuming
// that no funds were spent from each donation to begin with.
for (Donation d : donations) {
if (totalSpent.containsKey(d)) {
Assert.assertTrue(d.getUnspent() >= 0);
Assert.assertEquals(d.getUnspent(),
d.getTotal() - totalSpent.get(d));
} else {
Assert.assertEquals(d.getUnspent(), d.getTotal());
}
}
}
/**
* Helper method to check that no allocations have been made for any project
* in projects and that all donations have not been spent at all.
**/
private void checkEmptyAllocation(List<Donation> donations,
Set<Project> projects) {
for (Project p : projects) {
Assert.assertEquals(p.getCost(), p.neededFunds());
}
for (Donation d : donations) {
Assert.assertEquals(d.getUnspent(), d.getTotal());
}
}
}
捐赠类别:
package a2;
import java.util.*;
/**
* A class representing a donation. A donation has a name, the total amount of
* the donation, the unspent portion of the donation and a set of projects that
* the donation may be spent on.
*
* DO NOT MODIFY THIS FILE IN ANY WAY.
*/
public class Donation {
// name of donation
private String name;
// total donation amount
private int total;
// amount of donation that has not yet been spent
private int unspent;
// projects that the funds from this donation could be spent on
private Set<Project> projects;
/*
* invariant: name != null && total > 0 && 0 <= unspent <= total &&
* projects!=null
*/
/**
* @precondition: name!= null && total > 0 && projects != null
* @postcondition: creates a new donation with given name, total donation
* amount and projects that this donation could be spent on.
* No funds from the donation have initially been spent.
*/
public Donation(String name, int total, Set<Project> projects) {
assert name != null && projects != null && total > 0;
this.name = name;
this.total = total;
this.projects = projects;
this.unspent = total;
}
/**
* @postcondition: returns the total amount of this donation.
*/
public int getTotal() {
return total;
}
/**
* @postcondition: returns the amount of this donation that hasn't been
* spent yet.
*/
public int getUnspent() {
return unspent;
}
/**
* @postcondition: returns true iff this donation has been totally spent.
*/
public boolean spent() {
return (unspent == 0);
}
/**
* @precondition: 0 <= cost <= getUnspent()
* @postcondition: removes cost from the amount of available funds for this
* donation. The only method that should call this one
* directly is the allocate method from the Project class.
* (That is, it should only be executed as part of an
* allocation of these funds to a particular project.)
*/
public void spend(int cost) {
assert 0 <= cost && cost <= unspent;
unspent = unspent - cost;
}
/**
* @precondition: 0 <= cost <= total - getUnspent()
* @postcondition: adds cost back to the available funds for this donation.
* The only method that should call this one directly is the
* deallocate method from the Project class. (That is, it
* should only be executed as part of the deallocation of
* these funds from a particular project.)
*/
public void unspend(int cost) {
assert 0 <= cost && cost <= total - unspent;
unspent = unspent + cost;
}
/**
* @postcondition: returns true iff this donation is allowed to be spent on
* the given project.
*/
public boolean canBeUsedFor(Project project) {
return projects.contains(project);
}
/**
* @postcondition: returns a (shallow copy of) the set of the projects for
* this donation.
*/
public Set<Project> getProjects() {
return new HashSet<>(projects);
}
}
项目类别:
package a2;
import java.util.*;
/**
* A class representing a project and its current allocation of funds.
*
* DO NOT MODIFY THIS FILE IN ANY WAY.
*/
public class Project {
private String name; // name of project
private int cost; // total cost of the project
private int allocatedFunding; // sum of the funds currently allocated
private Map<Donation, Integer> allocations; // funds currently allocated
/*
* invariant:
*
* cost > 0 && allocatedFunding >= 0 && allocatedFunding <= cost &&
*
* allocations != null && name != null &&
*
* for each entry (d, x) in allocations,
* d!=null && x>0 && d.canBeUsedFor(this) &&
*
* allocatedFunding is the sum of values in the allocations map
*/
/**
* @precondition: name!= null && cost > 0
* @postcondition: creates a new project with given name and cost and an
* initially empty allocation of funds.
*/
public Project(String name, int cost) {
assert (name != null && cost > 0);
this.name = name;
this.cost = cost;
allocations = new HashMap<Donation, Integer>();
allocatedFunding = 0;
}
/**
* @postcondition: returns the total cost of the project.
*/
public int getCost() {
return cost;
}
/**
* @postcondition: returns true if and only if the allocated funds are equal
* to the total cost of the project.
*/
public boolean fullyFunded() {
return (cost == allocatedFunding);
}
/**
* @postcondition: returns the amount of money that is needed to completely
* fund the project.
*/
public int neededFunds() {
return (cost - allocatedFunding);
}
/**
* @postcondition: returns the amount of money currently allocated to the
* project.
*/
public int allocatedFunding() {
return allocatedFunding;
}
/**
* @postcondition: returns (a shallow copy of) the current allocations to
* the project. (Changing the returned map won't change the
* allocations of the project. To do that use the
* allocation, deallocation or transfer methods of this
* class.)
*/
public Map<Donation, Integer> getAllocations() {
return new HashMap<>(allocations);
}
/**
* @precondition: donation!=null && 0 < amount <= donation.getUnspent() &&
* amount <= neededFunds() donation.canBeUsedFor(this)
* @postcondition: spends the given amount of money from the donation by
* allocating it to this project.
*/
public void allocate(Donation donation, int amount) {
assert donation != null;
assert 0 < amount && amount <= donation.getUnspent()
&& amount <= neededFunds() && donation.canBeUsedFor(this);
addToAllocations(donation, amount);
donation.spend(amount);
}
private void addToAllocations(Donation donation, int amount) {
Integer existingAmount = allocations.get(donation);
if (existingAmount == null) {
existingAmount = 0;
}
allocations.put(donation, amount + existingAmount);
allocatedFunding += amount;
}
/**
* @precondition: donation!=null && allocations.containsKey(donation) &&
* allocations.get(donation) >= amount
* @postcondition: puts the given amount of money back into the unspent
* funds for the donation and removes it from the allocation
* to this project.
*/
public void deallocate(Donation donation, int amount) {
assert donation != null;
assert allocations.containsKey(donation);
assert allocations.get(donation) >= amount;
removeFromAllocations(donation, amount);
donation.unspend(amount);
}
/**
* @postcondition: deallocates all allocations to this project.
*/
public void deallocateAll() {
for (Map.Entry<Donation, Integer> entry : allocations.entrySet()) {
Donation d = entry.getKey();
int amount = entry.getValue();
d.unspend(amount);
allocatedFunding -= amount;
}
allocations.clear();
}
private void removeFromAllocations(Donation donation, int amount) {
int existingAmount = allocations.get(donation);
if (existingAmount > amount) {
allocations.put(donation, existingAmount - amount);
} else {
allocations.remove(donation);
}
allocatedFunding -= amount;
}
/**
* @precondition: amount <= neededFunds() && the given amount of money may
* be transferred from the source project to this project
* @postcondition: transfers $amount from source project to this project.
*/
public void transfer(int amount, Project source) {
assert amount <= neededFunds();
Iterator<Map.Entry<Donation, Integer>> it = source.allocations
.entrySet().iterator();
Map.Entry<Donation, Integer> entry;
while (it.hasNext() && amount > 0) {
entry = it.next();
if (entry.getKey().canBeUsedFor(this)) {
int transferAmount = Math.min(amount, entry.getValue());
// deallocate transferAmount from source project
entry.setValue(entry.getValue() - transferAmount);
source.allocatedFunding -= transferAmount;
if (entry.getValue() == 0) {
it.remove();
}
// allocate transfer amount to this project
this.addToAllocations(entry.getKey(), transferAmount);
// update the amount that we have left to transfer
amount = amount - transferAmount;
}
}
}
}
我不是很喜欢您的代码,但是其中有一件事是无限循环的普遍原因。 在canAllocateHelper(donations, projects, graph, index)
功能的某些部分,您可以使用递归。 首先要了解if条件。
if (donation.spent()) {
if (index == donations.size() - 1) { return false; }
return canAllocateHelper(donations, projects, graph, index + 1);
}
int pCount = countFullyFunded(p);
if (pCount == p.size() && (index + 1) < donations.size() ) {
return canAllocateHelper(donations, projects, graph, index + 1);
}
没关系,因为您使用递增的索引再次调用了该函数。 但是稍后在for循环中,您将在不增加索引的情况下再次调用该函数。 在很多情况下,经常使用具有相同功能参数的递归会导致无限循环。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.