Skip to content
This repository was archived by the owner on Jul 18, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 114 additions & 9 deletions eating_cookies/eating_cookies.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,16 +1,121 @@
#!/usr/bin/python
#!/usr/bin/env python


import sys
import json

from typing import Hashable, Any, Iterable


class Memoizer:
'''
Class to facilitate memoization of function returns.

Attributes:
functions (
Dict[
callable: Dict[
Tuple[frozenset, frozenset]: Object
]
]
)
A dictionary of functions: dictionary of args: results

Methods:
get_result: Gets the result of a function call, either
by returning the stored result or by running the
function if no stored results are found.
'''

def __init__(self):
'''
Inits a new Memoizer.
'''

self.functions = {}

def get_result(self, function: callable, *args, assume_hashable_args=True, **kwargs) -> Any:
'''
Gets the result of a function call with specific arguments.
If the function has been called through get_result before with these
parameters in this Memoizer, this will return the memoized result.
Otherwise, it will run the function and memoize the new result.

Args:
function (callable): The function to run.
This should *always* be idempotent or nullipotent.
*args: Variable length argument list. Passed to function.
**kwargs: Arbitrary keyword arguments. Passed to function.

Returns:
Object: The return value of function.
'''

if function not in self.functions:
self.functions[function] = {}

if assume_hashable_args:
params = (tuple(args), self.make_hashable(kwargs))
else:
params = (self.make_hashable(args), self.make_hashable(kwargs))

if params in self.functions[function]:
return self.functions[function][params]
else:
self.functions[function][params] = function(*args, **kwargs)
return self.functions[function][params]

@staticmethod
def make_hashable(obj) -> Hashable:
try:
hash(obj) # Isinstance Hashable fails on nested objects
return obj
except TypeError:
if isinstance(obj, dict):
return tuple(sorted((Memoizer.make_hashable((key, value)) for key, value in obj.items())))
elif isinstance(obj, Iterable):
return tuple((Memoizer.make_hashable(value) for value in obj))
return json.dumps(obj)


# The cache parameter is here for if you want to implement
# a solution that is more efficient than the naive
# a solution that is more efficient than the naive
# recursive solution
def eating_cookies(n, cache=None):
pass
def eating_cookies_recursive(n, cache=None):
if n < 0:
return 0
elif n == 0:
return 1

if cache is None:
cache = Memoizer()
for i in range(n):
cache.get_result(
eating_cookies_recursive,
i,
cache=cache
)

permutations = 0
can_eat = [1, 2, 3]
for cookie_count in can_eat:
permutations += cache.get_result(
eating_cookies_recursive,
n - cookie_count,
cache=cache
)

return permutations


eating_cookies = eating_cookies_recursive


if __name__ == "__main__":
if len(sys.argv) > 1:
num_cookies = int(sys.argv[1])
print("There are {ways} ways for Cookie Monster to eat {n} cookies.".format(ways=eating_cookies(num_cookies), n=num_cookies))
else:
print('Usage: eating_cookies.py [num_cookies]')
if len(sys.argv) > 1:
num_cookies = int(sys.argv[1])
print("There are {ways} ways for Cookie Monster to eat {n} cookies.".format(ways=eating_cookies(num_cookies), n=num_cookies))
else:
print('Usage: eating_cookies.py [num_cookies]')


26 changes: 15 additions & 11 deletions eating_cookies/test_eating_cookies.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
#!/usr/bin/env python


import unittest
from eating_cookies import eating_cookies


class Test(unittest.TestCase):

def test_eating_cookies_small_n(self):
self.assertEqual(eating_cookies(0), 1)
self.assertEqual(eating_cookies(1), 1)
self.assertEqual(eating_cookies(2), 2)
self.assertEqual(eating_cookies(5), 13)
self.assertEqual(eating_cookies(10), 274)
def test_eating_cookies_small_n(self):
self.assertEqual(eating_cookies(0), 1)
self.assertEqual(eating_cookies(1), 1)
self.assertEqual(eating_cookies(2), 2)
self.assertEqual(eating_cookies(5), 13)
self.assertEqual(eating_cookies(10), 274)

def test_eating_cookies_large_n(self):
self.assertEqual(eating_cookies(50, [0 for i in range(51)]), 10562230626642)
self.assertEqual(eating_cookies(100, [0 for i in range(101)]), 180396380815100901214157639)
self.assertEqual(eating_cookies(500, [0 for i in range(501)]), 1306186569702186634983475450062372018715120191391192207156664343051610913971927959744519676992404852130396504615663042713312314219527)
def test_eating_cookies_large_n(self):
self.assertEqual(eating_cookies(50), 10562230626642)
self.assertEqual(eating_cookies(100), 180396380815100901214157639)
self.assertEqual(eating_cookies(500), 1306186569702186634983475450062372018715120191391192207156664343051610913971927959744519676992404852130396504615663042713312314219527)


if __name__ == '__main__':
unittest.main()
unittest.main()
8 changes: 4 additions & 4 deletions knapsack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ You can run all of the tests with `python test_knapsack.py`, or run the tests in

## Hints
1. Base cases you might want to consider for a naive recursive implementation of this problem are:
* What if there are no items left to consider?
* What if the item I'm considering is too large to fit in my bag's remaining capacity?
2. In order to move towards one of our base cases, we'll pick up an item from the pile to consider, and add it to a copy of our bag. Now we have two versions of our bag, one with the item we're considering, and one without. All we have to do now is pick the bag that yields us a higher value.
3. As far as caching for this problem is concerned, a simple hash table or array is not going to suffice, because each solution now depends upon two parameters: the number of items in our pile to consider, as well as the capacity of our bag. So we'll need a 2x2 matrix in order to cache our answers adequately.
* What if there are no items left to consider?
* What if the item I'm considering is too large to fit in my bag's remaining capacity?
2. In order to move towards one of our base cases, we'll pick up an item from the pile to consider, and add it to a copy of our bag. Now we have two versions of our bag, one with the item we're considering, and one without. All we have to do now is pick the bag that yields us a higher value.
3. As far as caching for this problem is concerned, a simple hash table or array is not going to suffice, because each solution now depends upon two parameters: the number of items in our pile to consider, as well as the capacity of our bag. So we'll need a 2x2 matrix in order to cache our answers adequately.
4. Here's another way we might consider tackling this problem: what if we iterated through every single element in our pile and assign each one a value given by its value/weight ratio. Then we can sort all of the items based on this assigned value such that those items with a higher value/weight ratio are at the top of our sorted list of items. From there, we can just grab off the items at the top of our list until our bag is full. What would be the runtime complexity of this scheme? Would it work in every single scenario for any pile of items?
165 changes: 148 additions & 17 deletions knapsack/knapsack.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,26 +1,157 @@
#!/usr/bin/python
#!/usr/bin/env python

import sys

from collections import namedtuple
from pprint import pprint
from typing import Iterable, Hashable, Any

Item = namedtuple('Item', ['index', 'size', 'value'])


class Memoizer:
'''
Class to facilitate memoization of function returns.

Attributes:
functions (
Dict[
callable: Dict[
Tuple[frozenset, frozenset]: Object
]
]
)
A dictionary of functions: dictionary of args: results

Methods:
get_result: Gets the result of a function call, either
by returning the stored result or by running the
function if no stored results are found.
'''

def __init__(self):
'''
Inits a new Memoizer.
'''

self.functions = {}

def get_result(self, function: callable, *args, assume_hashable_args=True, **kwargs) -> Any:
'''
Gets the result of a function call with specific arguments.
If the function has been called through get_result before with these
parameters in this Memoizer, this will return the memoized result.
Otherwise, it will run the function and memoize the new result.

Args:
function (callable): The function to run.
This should *always* be idempotent or nullipotent.
*args: Variable length argument list. Passed to function.
**kwargs: Arbitrary keyword arguments. Passed to function.

Returns:
Object: The return value of function.
'''

if function not in self.functions:
self.functions[function] = {}

if assume_hashable_args:
params = (tuple(args), self.make_hashable(kwargs))
else:
params = (self.make_hashable(args), self.make_hashable(kwargs))

if params in self.functions[function]:
return self.functions[function][params]
else:
self.functions[function][params] = function(*args, **kwargs)
return self.functions[function][params]

@staticmethod
def make_hashable(obj) -> Hashable:
try:
hash(obj) # Isinstance Hashable fails on nested objects
return obj
except TypeError:
if isinstance(obj, dict):
return tuple(sorted((Memoizer.make_hashable((key, value)) for key, value in obj.items())))
elif isinstance(obj, Iterable):
return tuple((Memoizer.make_hashable(value) for value in obj))
return json.dumps(obj)


def knapsack_inner(items, capacity, cache=None):
if cache is None:
cache = Memoizer()
for lower_capacity in range(0, capacity, 10):
print(lower_capacity)
cache.get_result(
knapsack_inner, items, lower_capacity, cache=cache, assume_hashable_args=False
)

options = {}
for item in items:
if item.size > capacity:
continue

new_items = items[:]
new_items.remove(item)
new_capacity = capacity - item.size

result = cache.get_result(
knapsack_inner, new_items, new_capacity, cache=cache, assume_hashable_args=False
)

options[item] = {
'value': item.value + result['value'],
'weight': item.size + result['weight'],
'best_option': result['best_option'],
}

best_option = {'value': 0, 'weight': 0}
best_item = None
for item, option in options.items():
if option['value'] > best_option['value']:
best_option = option
best_item = item

retval = {
'value': best_option['value'],
'weight': best_option['weight'],
'best_option': {best_item: best_option}
}

return retval


def knapsack_solver(items, capacity):
pass

results = knapsack_inner(items, capacity)
items = []
current_item = results
while None not in current_item['best_option']:
item = list(current_item['best_option'].keys())[0]
items.append(item)
current_item = current_item['best_option'][item]

retval = {
'Value': results['value'],
'Chosen': [item.index for item in items]
}
return retval


if __name__ == '__main__':
if len(sys.argv) > 1:
capacity = int(sys.argv[2])
file_location = sys.argv[1].strip()
file_contents = open(file_location, 'r')
items = []

for line in file_contents.readlines():
data = line.rstrip().split()
items.append(Item(int(data[0]), int(data[1]), int(data[2])))
file_contents.close()
print(knapsack_solver(items, capacity))
else:
print('Usage: knapsack.py [filename] [capacity]')
if len(sys.argv) > 1:
capacity = int(sys.argv[2])
file_location = sys.argv[1].strip()
file_contents = open(file_location, 'r')
items = []

for line in file_contents.readlines():
data = line.rstrip().split()
items.append(Item(int(data[0]), int(data[1]), int(data[2])))

file_contents.close()
pprint(knapsack_solver(items, capacity))
else:
print('Usage: knapsack.py [filename] [capacity]')
Loading