2

I'm studying Optimization Problems and I got stuck at a homework problem. I have to write a Brute Force algorithm to minimizes the number of spaceship trips. The problem is: aliens created a new type of cow and now they want to transport it back with the minimum number of trips. Each trip has the maximum value of 10 tons.

The exercise provided some things, like this algorithm to get all possible partitions for a list:


# From codereview.stackexchange.com                    
def partitions(set_):
    if not set_:
        yield []
        return
    for i in range(2**len(set_)//2):
        parts = [set(), set()]
        for item in set_:
            parts[i&1].add(item)
            i >>= 1
        for b in partitions(parts[1]):
            yield [parts[0]]+b

def get_partitions(set_):
    for partition in partitions(set_):
        yield [list(elt) for elt in partition]

The input is a dict of cows, like this one: cows = {'Jesse': 6,'Maybel': 3, 'Callie': 2, 'Maggie': 5}, with the key being the name of the cow and the value being the cow's weight in tons.

The output must be a list of lists, where each inner list represents a trip, like this one: [['Jesse', 'Callie'], ['Maybel', 'Maggie']]

My question is: How can I implement this algorithm using get_partitions()? Does DFS is a good way to solve this?

I tried many ways already, but two of them I found at stackoverflow, that seemed to be closer to the answer was:

This was the closest I get from the correct answer. First I used get_partitions to generate all possible partitions, then I filtered the partitions to a list named possible with only trips with limit <= 10 and if the trips had all cows inside (to exclude those partitions with only one or two cow names).

def brute_force_cow_transport(cows,limit=10):
    """
    Finds the allocation of cows that minimizes the number of spaceship trips
    via brute force.  The brute force algorithm should follow the following method:

    1. Enumerate all possible ways that the cows can be divided into separate trips
        Use the given get_partitions function in ps1_partition.py to help you!
    2. Select the allocation that minimizes the number of trips without making any trip
        that does not obey the weight limitation

    Does not mutate the given dictionary of cows.

    Parameters:
    cows - a dictionary of name (string), weight (int) pairs
    limit - weight limit of the spaceship (an int)

    Returns:
    A list of lists, with each inner list containing the names of cows
    transported on a particular trip and the overall list containing all the
    trips
    """
    possible_combinations = []
    for partition in get_partitions(cows.keys()):
        possible_combinations.append(partition)
    possible_combinations.sort(key=len)

    def _is_valid_trip(cows, trip):
        valid = False
        for cow_name in cows:
            if cow_name in trip:
                valid = True
            else:
                valid = False
        return valid

    possibles = []
    for partition in possible_combinations:
        trips = []
        for trip in partition:
            total = sum([cows.get(cow) for cow in trip])
            if total <= limit and _is_valid_trip(cows.keys(), trip):
                trips.append(trip)
            possibles.append(trips)

    all_possibilities = [possibility for possibility in possibles if possibility != []]
    return min(all_possibilities)

My TestCase for this still gives:

AssertionError: Lists differ: [['Callie', 'Maggie']] != [['Jesse', 'Callie'], ['Maybel', 'Maggie']]

First differing element 0:
['Callie', 'Maggie']
['Jesse', 'Callie']


Second list contains 1 additional elements.
First extra element 1:
['Maybel', 'Maggie']

- [['Callie', 'Maggie']]
+ [['Jesse', 'Callie'], ['Maybel', 'Maggie']]

----------------------------------------------------------------------
Ran 5 tests in 0.009s

FAILED (failures=1)
Andressa Cabistani
  • 463
  • 1
  • 5
  • 14

1 Answers1

3

This was the closest I get from the correct answer. First I used get_partitions to generate all possible partitions, then I filtered the partitions to a list named possible with only trips with limit <= 10 and if the trips had all cows inside (to exclude those partitions with only one or two cow names).

This is the right idea except for the last statement, a partition of a set by definition will include all elements of the set exactly once. The issue is that you are building the list from trips and not partitions, there is no need for this since you are already generating the full set of partitions in possible_combinations, all you need to do is remove those partitions which contain trips that exceed the weight limit which would leave you with something like this:

def brute_force_cow_transport(cows, limit):
    ## Generate set of partitions
    possible_combinations = []
    for partition in get_partitions(cows.keys()):
        possible_combinations.append(partition)
    possible_combinations.sort(key=len)

    valid_combinations = possible_combinations[:] ## or list.copy() if using python 3.3+

    ## Remove invalid partitions
    for partition in possible_combinations:
        for trip in partition:
            total = sum([cows.get(cow) for cow in trip])
            if total > limit:
                valid_combinations.remove(partition)
                break

    ## Return valid partition of minimum length
    return min(valid_combinations, key=len)

Here, since we are iterating over the partitions we first make a copy of the partitions list so that we can remove partitions which contain trips over the limit and then return the list of minimum length as the solution. There are some simple ways to improve the performance of this but they are left as an exercise to the reader.

Victor C
  • 46
  • 2