简体   繁体   English

具有图和内部类的Java程序中的无限循环

[英]Infinite loop in Java program with graph and inner classes

Ok so my task is that given a list of Donations and a Set of Projects (Classes given below) I must see if all the projects can be funded by the donations. 好的,我的任务是给定一个捐赠清单和一组项目(下面给出的类),我必须查看所有项目是否都可以由捐赠来资助。 If they cannot, it must de-allocate all money allocated so far and return false. 如果不能,则必须取消分配到目前为止分配的所有资金,并返回false。 Donations have a list of projects that they can donate to and have a given amount. 捐赠列出了可以捐赠的项目清单,并且有一定数量。

I chose to implement this with a graph. 我选择使用图形来实现。 Vertices represent a project and the connections are the donations they share with other projects. 顶点表示一个项目,联系是它们与其他项目共享的捐赠。 It is set up so that it cycles through the donations and donates 1 dollar at a time to each project it can fund. 它的设置使得它可以循环进行捐赠,并向可以资助的每个项目一次捐赠1美元。 If a project cannot be fully funded but can transfer money from another project it is connected to by donations, transfer money from that project. 如果一个项目不能完全获得资金,但可以通过捐赠从与之相关的另一个项目中转移资金,请从该项目转移资金。 If there is no possible way of funding all of the projects then de-allocate everything and return false. 如果没有办法为所有项目提供资金,则取消所有分配并返回false。

My two problems are: 1) My test suite is entering an infinite loop when transfers are required. 我的两个问题是:1)当需要传输时,我的测试套件进入无限循环。 At the moment i'd be happy if someone could just locate my infinite loop problem and propose a fix to make it exit before i really work on getting the transfers part to work. 目前,如果有人能找到我的无限循环问题并提出解决方案,使其在我真正致力于使转移部分开始工作之前退出,我将很高兴。

The two given classes don't need to be fixed, its just the first chunk of code that has the problem. 给定的两个类无需修复,只需修复有问题的第一部分代码即可。 I have no doubt as well that my graph, node and connector classes are not very good at all but they work at the moment. 我也毫无疑问,我的图形,节点和连接器类根本不是很好,但是目前它们可以工作。 I'm just looking for a fix in the canAllocateHelper method. 我只是在寻找canAllocateHelper方法中的修复程序。

Note: Sorry for posting so much code but i posted a similar question about 12 hours ago and i was told that i didn't give enough code for it to be debugged so i thought i'd just post everything. 注意:很抱歉发布了这么多代码,但是我在12个小时前发布了一个类似的问题,并被告知我没有提供足够的代码来对其进行调试,因此我认为我只是发布了所有内容。

My code: 我的代码:

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);
        }
    }



}

The test Suite: 测试套件:

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());
        }
    }
}

Donation Class: 捐赠类别:

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);
    }
}

Project Class: 项目类别:

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;
            }
        }
    }

}

I'm not really into your code but there is one thing in it that's a common reason for infinite loops. 我不是很喜欢您的代码,但是其中有一件事是无限循环的普遍原因。 At some parts in your canAllocateHelper(donations, projects, graph, index) function you use recursion. canAllocateHelper(donations, projects, graph, index)功能的某些部分,您可以使用递归。 First in to if-conditions. 首先要了解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);
}

That's okay because you call the function again with an incremented index. 没关系,因为您使用递增的索引再次调用了该函数。 But later in the for-loop you call the function again without an incremented index. 但是稍后在for循环中,您将在不增加索引的情况下再次调用该函数。 Using recursion evertytime with the same function parameters leads to infinite loops in a lot of cases. 在很多情况下,经常使用具有相同功能参数的递归会导致无限循环。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM