[英]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.