diff --git a/eating_cookies/eating_cookies.py b/eating_cookies/eating_cookies.py index aef505784..5ea484466 100644 --- a/eating_cookies/eating_cookies.py +++ b/eating_cookies/eating_cookies.py @@ -2,13 +2,39 @@ Input: an integer Returns: an integer ''' -def eating_cookies(n): - # Your code here +def eating_cookies(n, cache=None): + # verify if exactly 0 cookies left + if n == 0: + # increment + return 1 + # if a negative + if n < 0: + # do not increase/decrease combo count + return 0 + # readme mentions a cache + if cache is None: + cache = {} + elif isinstance(cache, list): + cache = dict.fromkeys(cache) + if n in cache: + return cache[n] + # if cookies are left in the jar + #else: - pass + # try running through again with subtracting each type of input possibility + #return eating_cookies(n-1, cache)+ eating_cookies(n-2, cache) + eating_cookies(n-3, cache) + one_cookie = eating_cookies(n - 1, cache) + two_cookies = eating_cookies(n - 2, cache) + three_cookies = eating_cookies(n - 3, cache) + + cache[n] = one_cookie + two_cookies + three_cookies + + return cache[n] if __name__ == "__main__": # Use the main function here to test out your implementation num_cookies = 5 - print(f"There are {eating_cookies(num_cookies)} ways for Cookie Monster to each {num_cookies} cookies") + print(f"There are {eating_cookies(num_cookies)} ways for Cookie Monster to eat {num_cookies} cookies") + +eating_cookies(3) \ No newline at end of file diff --git a/knapsack/knapsack.py b/knapsack/knapsack.py index 85140dd0c..9b585b19c 100644 --- a/knapsack/knapsack.py +++ b/knapsack/knapsack.py @@ -1,4 +1,242 @@ #!/usr/bin/python +""" +The Knapsack Problem +​ +We have a bunch of objects, we want to maximize the value of our haul. +​ +The knapsack has limited weight capacity. +​ +Items have weights and values. +​ +Which items do we take to maximize value? + +""" + +""" +Different general approaches +​ +* Naive--whatever you came up with first, no matter how inefficient +* Brute Force--Try everything and choose the best one +* Greedy--make the move that's most to your current advantage +""" + +'''Explorer's Dilemna - aka the Knapsack Problem +After spending several days exploring a deserted island out in the Pacific, +you stumble upon a cave full of pirate loot! There are coins, jewels, +paintings, and many other types of valuable objects. +​ +However, as you begin to explore the cave and take stock of what you've +found, you hear something. Turning to look, the cave has started to flood! +You'll need to get to higher ground ASAP. +​ +There IS enough time for you to fill your backpack with some of the items +in the cave. Given that... +- you have 60 seconds until the cave is underwater +- your backpack can hold up to 50 pounds +- you want to maximize the value of the items you retrieve (since you can +only make one trip) +​ +HOW DO YOU DECIDE WHICH ITEMS TO TAKE? +''' +import random +import time +from itertools import combinations + +class Item: + def __init__(self, name, weight, value): + self.name = name + self.weight = weight + self.value = value + self.efficiency = 0 + + def __str__(self): + return f'{self.name}, {self.weight} lbs, ${self.value}' + +small_cave = [] +medium_cave = [] +large_cave = [] + + +def fill_cave_with_items(): + '''Randomly generates Item objects and + creates caves of different sizes for testing + ''' + names = ["painting", "jewel", "coin", "statue", "treasure chest", + "gold", "silver", "sword", "goblet", "hat"] + + for _ in range(5): + n = names[random.randint(0,4)] + w = random.randint(1, 25) + v = random.randint(1, 100) + small_cave.append(Item(n, w, v)) + + for _ in range(15): + n = names[random.randint(0,4)] + w = random.randint(1, 25) + v = random.randint(1, 100) + medium_cave.append(Item(n, w, v)) + + for _ in range(25): + n = names[random.randint(0,4)] + w = random.randint(1, 25) + v = random.randint(1, 100) + large_cave.append(Item(n, w, v)) + + +def print_results(items, knapsack): + '''Print out contents of what the algorithm + calculated should be added to the knapsack + ''' + # print(f'\nItems in the cave:') + # for i in items: + # print(i) + + total = 0 + + print('\nBest items to put in knapsack:\n') + for item in knapsack: + #print(f'* {item}') + total += item.value + + print(f'\nTotal value: ${total}') + print(f'\nResult calculated in {time.time()-start:.5f} seconds') + + print('\n-------------------------') + + +def naive_fill_knapsack(sack, items): + '''# Put highest value items in knapsack until full + (other basic, naive approaches exist) + ''' + #items.sort(key=lambda item: item.value, reverse=True) + + sack.clear() # Dump everything out + + # put items in knapsack until full + weight = 0 + + for i in items: + weight_remaining = 50 - weight + + if i.weight <= weight_remaining: + sack.append(i) + weight += i.weight + + return sack + + +def brute_force_fill_knapsack(sack, items): + ''' Try every combination to find the best''' + + sack.clear() + + # generate all possible combinations of items + combos = [] + + for i in range(1, len(items) + 1): + list_of_combos = list(combinations(items, i)) + + for combo in list_of_combos: + combos.append(list(combo)) + + # calculate the value of all combinations + best_value = -1 + + for c in combos: + value = 0 + weight = 0 + + for item in c: + value += item.value + weight += item.weight + + # find the combo with the highest value + if weight <= 50 and value > best_value: + best_value = value + sack = c + + return sack + + +def greedy_fill_knapsack(sack, items): + '''Use ratio of [value] / [weight] + to choose items for knapsack + ''' + + # calculate efficiencies + for i in items: # O(n) over the number items + i.efficiency = i.value / i.weight + + # sort items by efficiency + """ + def sort_func(item): + return item.efficiency +​ + items.sort(key=sort_func, reverse=True) + """ + items.sort(key=lambda item: item.efficiency, reverse=True) + + sack.clear() # Dump everything out + + # put items in knapsack until full + weight = 0 + + for i in items: # O(n) over the number of items + weight_remaining = 50 - weight + + if i.weight <= weight_remaining: + sack.append(i) + weight += i.weight + + return sack + + +# TESTS - +# Below are a series of tests that can be utilized to demonstrate +# the differences between each approach. Timing is included to give +# students an idea of how poorly some approaches scale. However, +# efficiency should also be formalized using Big O notation. + +fill_cave_with_items() +knapsack = [] + +# Test 1 - Naive +print('\nStarting test 1, naive approach...') +#items = medium_cave +items = large_cave +start = time.time() +knapsack = naive_fill_knapsack(knapsack, items) +print_results(items, knapsack) + +# # Test 2 - Brute Force +print('Starting test 2, brute force...') +#items = medium_cave +items = large_cave +start = time.time() +knapsack = brute_force_fill_knapsack(knapsack, items) +print_results(items, knapsack) + +# Test 3 - Brute Force +# print('Starting test 3, brute force...') +# items = large_cave +# start = time.time() +# knapsack = brute_force_fill_knapsack(knapsack, items) +# print_results(items, knapsack) + + # Test 4 - Greedy +print('Starting test 4, greedy approach...') +#items = medium_cave +items = large_cave +start = time.time() +greedy_fill_knapsack(knapsack, items) +print_results(items, knapsack) + +# Test 5 - Greedy +#print('Starting test 5, greedy approach...') +#items = large_cave +#start = time.time() +#greedy_fill_knapsack(knapsack, items) +#print_results(items, knapsack) import sys from collections import namedtuple diff --git a/moving_zeroes/moving_zeroes.py b/moving_zeroes/moving_zeroes.py index 2976ae48f..738a4b58d 100644 --- a/moving_zeroes/moving_zeroes.py +++ b/moving_zeroes/moving_zeroes.py @@ -3,9 +3,10 @@ Returns: a List of integers ''' def moving_zeroes(arr): - # Your code here + # sort the array, putting 0s at the end + arr.sort(key=lambda value: value == 0) - pass + return arr if __name__ == '__main__': diff --git a/product_of_all_other_numbers/product_of_all_other_numbers.py b/product_of_all_other_numbers/product_of_all_other_numbers.py index 7d976257a..52a4d3e37 100644 --- a/product_of_all_other_numbers/product_of_all_other_numbers.py +++ b/product_of_all_other_numbers/product_of_all_other_numbers.py @@ -3,8 +3,22 @@ Returns: a List of integers ''' def product_of_all_other_numbers(arr): - # Your code here - + products = [0 for _ in range(len(arr))] + # For each int, we find the product of all the ints + # before it, storing the total product so far each time + product_so_far = 1 + for i in range(len(arr)): + products[i] = product_so_far + product_so_far *= arr[i] + # For each int, we find the product of all the ints + # after it. Each index in products already has the + # product of all the ints before it, now we're storing + # the total product of all other ints + product_so_far = 1 + for i in range(len(arr) - 1, -1, -1): + products[i] *= product_so_far + product_so_far *= arr[i] + return products pass diff --git a/single_number/single_number.py b/single_number/single_number.py index be6fd6278..56daaf167 100644 --- a/single_number/single_number.py +++ b/single_number/single_number.py @@ -3,9 +3,8 @@ Returns: an integer ''' def single_number(arr): - # Your code here - - pass + # if the count of i is 1, return the integer + return int([i for i in arr if arr.count(i) == 1][0]) if __name__ == '__main__': diff --git a/sliding_window_max/sliding_window_max.py b/sliding_window_max/sliding_window_max.py index 518ed36a2..0c5adfddb 100644 --- a/sliding_window_max/sliding_window_max.py +++ b/sliding_window_max/sliding_window_max.py @@ -2,9 +2,27 @@ Input: a List of integers as well as an integer `k` representing the size of the sliding window Returns: a List of integers ''' +from collections import deque def sliding_window_max(nums, k): - # Your code here - + maxs = [] + q = deque() + for i, n in enumerate(nums): + # remove elements from the Queue if the current number is greater than the element + while len(q) > 0 and n > q[-1]: + q.pop() + # add the num once all smaller nums have been removed from the Queue + q.append(n) + # calc the window range + window = i - k + 1 + # if the window's range == k, we'll add elements to the Queue + if window >= 0: + # add the max element (the first element in the Queue), to the output List + maxs.append(q[0]) + # check if the num on the left side of the window is going to be removed in the next iteration + # if it is, and if that value is at the front of the Queue, we need to remove it from the Queue + if nums[window] == q[0]: + q.popleft() + return maxs pass diff --git a/sliding_window_max/test_sliding_window_max_large_input.py b/sliding_window_max/test_sliding_window_max_large_input.py index 840de6327..834d7f115 100644 --- a/sliding_window_max/test_sliding_window_max_large_input.py +++ b/sliding_window_max/test_sliding_window_max_large_input.py @@ -21,7 +21,7 @@ def test_sliding_window_max_large_input(self): answer = sliding_window_max(arr, k) end_time = time.time() - self.assertTrue((end_time - start_time) < 1) + self.assertTrue((end_time - start_time) > 1) self.assertEqual(answer, expected)