Finding minimum sum of non consecutive elements - python-3.x

I'm trying to find the minimum sum of non consecutive elements in list,
where every time I can jump one cell or two cells.
The first cell is not counted in sum.
The implementation has to include recursion and memoization. I tried to implement such method, but unfortunately, it doesn't actually use memoization and it doesn't work for some examples.
My code looks like this:
def optimal_cruise_seasick(toilet_prices):
prices_dict = {}
return optimal_cruise_seasick_mem(toilet_prices, 0, len(toilet_prices), prices_dict)
def optimal_cruise_seasick_mem(prices_lst, start, end, dic):
if (start,end) in dic:
return dic[(start,end)]
if(start + 1 == end or start + 2 == end):
return prices_lst[end-1]
min_cost = prices_lst[end-1]
i = start + 1
while(i < end - 1):
one = optimal_cruise_seasick_mem(prices_lst, i, i+1, dic)
two = optimal_cruise_seasick_mem(prices_lst, i, i+2, dic)
if (one <= two):
min_cost += one
i += 1
else:
min_cost += two
i += 2
dic[(start,end)] = min_cost
return min_cost
I tried to run it for toilet_prices = [20,40,60,80,100,70,50,30,10,30].
It returns 320 instead of 250 (40,80,70,30,30). How should I fix my code so it will work as required?
Thanks!

Your implementation is, frankly, confusing. I have written an alternate implementation that works correctly (per your clarifications in the comments), though I don't know if it's memoizing in the way you want it to.
What we do is start at the first "actual" thing in the prices list (stripping off the first element; in your example, 20), and then successively see whether chopping off the left one or left two elements gives us a lower sum.
def f(prices, memo):
if prices not in memo:
if len(prices) == 1:
memo[prices] = prices[0]
elif len(prices) == 2:
# We must include the final cell in the list, but can choose to get there
# either by two 1-moves or one 2-move. Obviously the one 2-move is going
# to be non-pessimal if all prices are non-negative, but we'll put the min
# call in here for elucidation/explicitness anyway.
memo[prices] = min(prices[1], prices[0] + prices[1])
else:
memo[prices] = prices[0] + min(f(prices[1:], memo), f(prices[2:], memo))
return memo[prices]
def optimal_cruise_seasick(prices):
return f(prices[1:], dict())
Adding some print statements to indicate when we read from memo:
>>> optimal_cruise_seasick((20,40,60,80,100,70,50,30,10,30))
Read from memo: (10, 30) -> 30
Read from memo: (30, 10, 30) -> 60
Read from memo: (50, 30, 10, 30) -> 80
Read from memo: (70, 50, 30, 10, 30) -> 130
Read from memo: (100, 70, 50, 30, 10, 30) -> 180
Read from memo: (80, 100, 70, 50, 30, 10, 30) -> 210
250
As you can see, it's memoizing over suffixes of the prices list.
I should add that you can achieve the same effect in a cleaner way using functools.lru_cache. The f2 here is equivalent to the f above (if your price lists are short enough that everything fits into the cache), except that you don't pass in a dict() for memo, obviously.
from functools import lru_cache
#lru_cache()
def f2(prices):
if len(prices) == 1:
return prices[0]
elif len(prices) == 2:
return min(prices[1], prices[0] + prices[1])
else:
return prices[0] + min(f2(prices[1:]), f2(prices[2:]))

Related

Function that fills in missing numbers to create complete sequence

I'm trying to create a function that will fill in any missing numbers in between two numbers in a list. The original list must be altered and cannot be new.
For example: [13,15,20] would return [13,14,15,16,17,18,19,20].
Note that I am not allowed to use a range function.
Here's my code:
def complete(list1):
i= 0
if len(list1) > 1:
for number in list1:
if number - list1[i+1] != -1:
number += 1
list1.insert(i + 1, number)
i += 1
return list1
else:
return list1
I got a "list index out of range" error.
Here is the source of your error:
...
for number in list1:
if number - list1[i+1] != -1:
...
i += 1
Basically, there comes a point (that point being the last number in list1) when i+1 gets you out of bounds and you are not doing anything to prevent that from happening. Indexing is tricky like that, so I would like to offer an indexing-free (well, almost) approach. By the way, from your comment to Bonfire's answer, I see that the task is to change original lists in-place. While mutating arguments is considered a very poor coding practice these days, here is a relatively efficient way of doing that:
import typing as t
def complete_sequence(partial: t.List[int]) -> t.List[int]:
# edge case
if len(partial) < 2:
return partial
# a lookup table for numbers we already have
observed = set(partial)
# append numbers we don't have
start = partial[0]
stop = partial[-1]
num = start + 1
while num < stop:
if not num in observed:
partial.append(num)
num += 1
# in-place sort
partial.sort()
return partial
As you see, instead of inserting values between existing numbers (paying O(n) time for each insertion), we can simply append everything (O(1) per insertion) and sort. This not only simplifies the logic (we no longer have to track those pesky indices), but also reduces computational time-complexity from O(n^2) to O(n*log(n)).
To achieve what you want to do I have made some changes to the logic:
def complete(list1):
if len(list1) < 2 : return list1
num = list1[0]
i = -1
while num < list1[-1]:
num += 1
i += 1
if num in list1: continue
if i < len(list1) - 1:
list1.insert(i + 1, num)
else:
list1.append(num)
return list1
print(complete([13, 14, 20]))
# [13, 14, 15, 16, 17, 18, 19, 20]
print(complete([13, 14, 15]))
# [13, 14, 15]

Is there a pythonic way to count the pairs that total up to whole minutes

I'm trying to find pairs of songs with durations that add up to whole minutes. Example given song lengths [10, 50, 90, 30]. Calculate the total number of different pairs. I'm expecting a return of 2 since the first and second pair to 60 seconds and the third and fourth songs pair to 120. But I'm instead getting 1 pair.
def pair_with_target_sum(songs, k):
n = len(songs)
count = 0
for i in range(0, n):
for j in range(i + 1, n):
if songs[i] + songs[j] == k:
count += 1
return count
def main():
print(pair_with_target_sum([10, 50, 90, 30], 60))
print(pair_with_target_sum([30, 20, 150, 100, 40], 60))
main()
There is different, and much simpler algorithm:
Create array with 60 buckets.
For each value in list run counts[value % k] += 1
Sum min(counts[n], counts[(n + k) % k]) (the weird calculation instead of just using k - n is to handle special case 0)
I'd use the itertools.combinations in conjunction with the modulo operator:
from itertools import combinations
def f(songs):
count = 0
for pair in combinations(songs, 2):
if sum(pair) % 60 == 0:
count += 1
return count
You can make your code work correctly with only changing a line and deleting the k parameter from your function definiton like this:
def pair_with_target_sum(songs):
n = len(songs)
count = 0
for i in range(0, n):
for j in range(i + 1, n):
if (songs[i] + songs[j]) % 60 == 0:
count += 1
return count
def main():
print(pair_with_target_sum([10, 50, 90, 30]))
print(pair_with_target_sum([30, 20, 150, 100, 40]))
print(pair_with_target_sum([60, 60, 60]))
main()
This works correctly for me when I run the code with different inputs.

How does a value from a previous level in recursion go back up?

I'm trying to make a recursive function to get minimum number of coins for change, but I think my understanding of what each layer's return value in the stack is wrong. What I want is for the coin amount to be passed back up when the recursion reaches it's base case, but looking at the debugger, the coin case decreases on the way back up.
I've already tried to look at solutions for this problem, but they all seem to use dynamic programming, and I know that it's more efficient in terms of complexity, but I want to figure out how to do the recursion before adding the dynamic programming portion
def min_coin(coin_list, value, counter = 0):
if value == 0:
return 0
else:
for coin in coin_list:
if coin <= value:
sub_result = value - coin
min_coin(coin_list, sub_result, counter)
counter +=1
return counter
#counter += 1 #should add returning out from,
#return counter
coin_list = [5, 2, 1]
value = 8
print(min_coin(coin_list,value))
I want an output of 3, but the actual output is 1 no matter the value
You need to increment the counter before calling min_coin().
def min_coin(coin_list, value, counter = 0):
if value == 0:
return counter
else:
for coin in coin_list:
if coin <= value:
sub_result = value - coin
return min_coin(coin_list, sub_result, counter+1)
You can solve your task without recursion, answer from geekforcoders
# Python 3 program to find minimum
# number of denominations
def findMin(V):
# All denominations of Indian Currency
deno = [1, 2, 5, 10, 20, 50,
100, 500, 1000]
n = len(deno)
# Initialize Result
ans = []
# Traverse through all denomination
i = n - 1
while(i >= 0):
# Find denominations
while (V >= deno[i]):
V -= deno[i]
ans.append(deno[i])
i -= 1
# Print result
for i in range(len(ans)):
print(ans[i], end = " ")
# Driver Code
if __name__ == '__main__':
n = 93
print("Following is minimal number",
"of change for", n, ": ", end = "")
findMin(n)

Select a number randomly with probability proportional to its magnitude from the given array of n elements

Ex 1: A = [0 5 27 6 13 28 100 45 10 79]
let f(x) denote the number of times x getting selected in 100 experiments.
f(100) > f(79) > f(45) > f(28) > f(27) > f(13) > f(10) > f(6) > f(5) > f(0)
My code:
def pick_a_number_from_list(A,l):
Sum = 0
#l = len(A)
for i in range(l):
Sum+=A[i]
A_dash = []
for i in range(l):
b=A[i]/Sum
A_dash.append(b)
#print(A_dash)
series = pd.Series(A_dash)
cumsum = series.cumsum(skipna=False)
#print(cumsum[9])
sample_value = uniform(0.0,1.0)
r = sample_value
print(r)
#for i in range(l):
if r<cumsum[1]:
return 1
elif r>cumsum[1] and r <cumsum[2]:
return 2
elif r<cumsum[3]:
return 3
elif r<cumsum[4]:
return 4
elif r<cumsum[5]:
return 5
elif r<cumsum[6]:
return 6
elif r<cumsum[7]:
return 7
elif r<cumsum[8]:
return 8
elif r<cumsum[9]:
return 9
def sampling_based_on_magnitued():
A = [0,5,27,6,13,28,100,45,10,79]
n = len(A)
#for i in range(1,10):
num = pick_a_number_from_list(A,n)
print(A[num])
sampling_based_on_magnitued()
In mu code i am using multiple if else statement and because it is hardcoded
i can make by o/p right till 10 element in the list.
I want to make my code dynamic for any value in the list.
Here in my code i have restricted it to n=10
Pls tell me how can i right generic code which can replace all if - elseif statement with for loop
sum1=0;
for i in A:
sum1+=i;
x=0
list1=[]
for i in A:
list1.append(x+i/sum1)
x=x+i/sum1;
#list1 contsins cumulative sum
bit=uniform(0,1)
for i in range (0,len(list1)):
if bit<list1[i]:
return A[i]
you may use this
you can use random.choices
A = [0,5, 27, 6, 13, 28, 100, 45, 10, 79]
let no of random values want to pick it be 100 s0 k=100
w = [0.0, 0.01597444089456869, 0.08626198083067092, 0.019169329073482427, 0.04153354632587859, 0.08945686900958466, 0.3194888178913738, 0.14376996805111822, 0.03194888178913738, 0.2523961661341853]
weights is calculsted by using A[i]/(total sum of all the values of A)
x = random.choices(A,w,k=100)
print(x)
it displays the values from list A according to there weights
Some changes in Bitan Guha Roy's code to return just one value
import numpy as np
sum1=0;
for i in A:
sum1+=i;
x=0
list1=[]
for i in A:
list1.append(x+i/sum1)
x=x+i/sum1;
# list1 contains cumulative sum
bit=np.random.uniform(0.0,1.0,1)
for i in range (0,len(list1)):
if bit>=list1[i] and bit<list1[i+1]:
print(A[i+1]) # or return if under a function
import random
lst=[0, 5 ,27, 6, 13, 28, 100, 45, 10,79]
def pick_a_number_from_list(A):
weights1=[]
for i in A:
weights1.append(i/sum(lst))
selected_random_number = random.choices(A,weights=weights1,k=1)
return selected_random_number
def sampling_based_on_magnitued():
for i in range(1,100):
number=pick_a_number_from_list(lst)
print(number)
sampling_based_on_magnitued()
# used random.choices which gives option to choose random number according respective weights. Please suggest any modification if you've any

Extending current code to include both median and mode

I have this line of code that i used for one assignment, but i can't figure out how to add the median and mode into the code to let it run without error.
def main():
filename = input('File name: ')
num=0
try:
infile = open(filename, 'r')
count = 0
total = 0.0
average = 0.0
maximum = 0
minimum = 0
range1 = 0
for line in infile:
num = int(line)
count = count + 1
total = total + num
if count == 1:
maximum = num
minimum = num
else:
if num > maximum:
maximum = num
if num < minimum:
minimum = num
if count > 0:
average = total / count
range1 = maximum - minimum
I'll jump right in and show you the code. It's a very simple and quite pythonic solution.
Solution
import statistics
def open_file(filename):
try:
return open(filename, 'r')
except OSError as e:
print(e)
return None
def main():
# Read file. Note that we are trusting the user input here without sanitizing.
fd = open_file(input('File name: '))
if fd is None: # Ensure we have a file descriptor
return
data = fd.read() # Read whole file
if data == '':
print("No data in file")
return
lines = data.split('\n') # Split the data into a list of strings
# We need to convert the list of strings to a list of integers
# I don't know a pythonic way of doing this.
for number, item in enumerate(lines):
lines[number] = int(item)
total_lines = len(lines)
total_sum = sum(lines)
maximum = max(lines)
minimum = min(lines)
# Here is the python magic, no need to reinvent the wheel!
mean = statistics.mean(lines) # mean == average
median = statistics.median(lines)
mode = "No mode!"
try:
mode = statistics.mode(lines)
except statistics.StatisticsError as ec:
pass # No mode, due to having the same quantity of 2 or more different values
print("Total lines: " + str(total_lines))
print("Sum: " + str(total_sum))
print("Max: " + str(maximum))
print("Min: " + str(minimum))
print("Mean: " + str(mean))
print("Median: " + str(median))
print("Mode: " + str(mode))
if __name__ == '__main__':
main()
Explanation
Generally, in python, it's safe to assume that if you want to calculate any mundane value using a well known algorithm, there will already be a function written for you to do just that. No need to reinvent the wheel!
These functions aren't usually hard to find online either. For instance, you can find suggestions regarding the statistics library by googling python calculate the median
Although you have the solution, I strongly advise looking through the source code of the statistics library (posted below), and working out how these functions work for yourself. It will help your grow as a developer and mathematician.
statistics.py
mean
def mean(data):
"""Return the sample arithmetic mean of data.
>>> mean([1, 2, 3, 4, 4])
2.8
>>> from fractions import Fraction as F
>>> mean([F(3, 7), F(1, 21), F(5, 3), F(1, 3)])
Fraction(13, 21)
>>> from decimal import Decimal as D
>>> mean([D("0.5"), D("0.75"), D("0.625"), D("0.375")])
Decimal('0.5625')
If ``data`` is empty, StatisticsError will be raised.
"""
if iter(data) is data:
data = list(data)
n = len(data)
if n < 1:
raise StatisticsError('mean requires at least one data point')
T, total, count = _sum(data)
assert count == n
return _convert(total/n, T)
median
def median(data):
"""Return the median (middle value) of numeric data.
When the number of data points is odd, return the middle data point.
When the number of data points is even, the median is interpolated by
taking the average of the two middle values:
>>> median([1, 3, 5])
3
>>> median([1, 3, 5, 7])
4.0
"""
data = sorted(data)
n = len(data)
if n == 0:
raise StatisticsError("no median for empty data")
if n%2 == 1:
return data[n//2]
else:
i = n//2
return (data[i - 1] + data[i])/2
mode
def mode(data):
"""Return the most common data point from discrete or nominal data.
``mode`` assumes discrete data, and returns a single value. This is the
standard treatment of the mode as commonly taught in schools:
>>> mode([1, 1, 2, 3, 3, 3, 3, 4])
3
This also works with nominal (non-numeric) data:
>>> mode(["red", "blue", "blue", "red", "green", "red", "red"])
'red'
If there is not exactly one most common value, ``mode`` will raise
StatisticsError.
"""
# Generate a table of sorted (value, frequency) pairs.
table = _counts(data)
if len(table) == 1:
return table[0][0]
elif table:
raise StatisticsError(
'no unique mode; found %d equally common values' % len(table)
)
else:
raise StatisticsError('no mode for empty data')

Resources