pair of item price under budget - python

I was trying to practice the binary search.
The aim is to find the pair of item prices under budget.
def binary_search(arr, lo, hi, x):
while lo < hi:
count = 0
sum = arr[lo] + arr[hi]
if sum <= x:
result = [arr[lo], arr[hi]]
print(result)
count += 1
return binary_search(arr, lo, hi-1, x)
else:
return binary_search(arr, lo, hi-1, x)
A = [1, 2, 3, 4, 6, 7, 8]
print(binary_search(A, 0, len(A)-1, 10))
As you can see, I can only find the first item with the others:
(1,8)
(1,7)
(1,6)
(1,5)
(1,4)
(1,3)
(1,2)
of course, I can do it again using
return binary_search(arr, lo+1, hi, x)
in the function to find the other pairs, but it's not ideal.
or I can use the itertools in a very easy way.
from itertools import combinations
A = [1, 2, 3, 4, 6, 7, 8]
budget = 10
comb = combinations(A, 2)
answer = []
for i in list(comb):
if sum(i) <= 10:
print(I)
answer.append(i)
print(len(answer))
print(answer)
Is there any better ways to deal with this using binary search. Any helps are highly appreciated!

Related

How would I fix this function?

Hey this is my first question so I hope I'm doing it right.
I'm trying to write a function that given a list of integers and N as the maximum occurrence, would then return a list with any integer above the maximum occurrence deleted. For example if I input:
[20,37,20,21] #list of integers and 1 #maximum occurrence.
Then as output I would get:
[20,37,21] because the number 20 appears twice and the maximum occurrence is 1, so it is deleted from the list. Here's another example:
Input: [1,1,3,3,7,2,2,2,2], 3
Output: [1,1,3,3,7,2,2,2]
Here's what I wrote so far, how would I be able to optimize it? I keep on getting a timeout error. Thank you very much in advance.
def delete_nth(order,n):
order = Counter(order)
for i in order:
if order[i] > n:
while order[i] > n:
order[i] - 1
return order
print(delete_nth([20,37,20,21], 1))
You can remove building the Counter at the beginning - and just have temporary dictionary as counter:
def delete_nth(order,n):
out, counter = [], {}
for v in order:
counter.setdefault(v, 0)
if counter[v] < n:
out.append(v)
counter[v] += 1
return out
print(delete_nth([20,37,20,21], 1))
Prints:
[20, 37, 21]
You wrote:
while order[i] > n:
order[i] - 1
That second line should presumably be order[i] -= 1, or any code that enters the loop will never leave it.
You could use a predicate with a default argument collections.defaultdict to retain state as your list of numbers is being filtered.
def delete_nth(numbers, n):
from collections import defaultdict
def predicate(number, seen=defaultdict(int)):
seen[number] += 1
return seen[number] <= n
return list(filter(predicate, numbers))
print(delete_nth([1, 1, 3, 3, 7, 2, 2, 2, 2], 3))
Output:
[1, 1, 3, 3, 7, 2, 2, 2]
>>>
I've renamed variables to something that had more meaning for me:
This version, though very short and fairly efficient, will output identical values adjacently:
from collections import Counter
def delete_nth(order, n):
counters = Counter(order)
output = []
for value in counters:
cnt = min(counters[value], n)
output.extend([value] * cnt)
return output
print(delete_nth([1,1,2,3,3,2,7,2,2,2,2], 3))
print(delete_nth([20,37,20,21], 1))
Prints:
[1, 1, 2, 2, 2, 3, 3, 7]
[20, 37, 21]
This version will maintain original order, but run a bit more slowly:
from collections import Counter
def delete_nth(order, n):
counters = Counter(order)
for value in counters:
counters[value] = min(counters[value], n)
output = []
for value in order:
if counters[value]:
output.append(value)
counters[value] -= 1
return output
print(delete_nth([1,1,2,3,3,2,7,2,2,2,2], 3))
print(delete_nth([20,37,20,21], 1))
Prints:
[1, 1, 2, 3, 3, 2, 7, 2]
[20, 37, 21]

Algorithm for fast combinations

Input:Given items=[1,2,3] and values=[100,300,800] OR it can be in dictionary={1:100,2:300,3:800}.
Find all combinations items such that sum values is less than 500
Solution:
[1]
[2]
[1,2]
This has to be done for millions of inputs.
WHat is the best and fastest algorithm to implement this??
import copy
dictionary = {
100: 1,
200: 2,
800: 3,
}
value = sorted([100, 200, 800])
threshold = 500
res = []
def dfs(l, s, cur):
if s < threshold:
if len(l) > 0:
res.append(l)
else:
return
for i in range(cur + 1, len(value)):
tmp = copy.copy(l)
tmp.append(dictionary.get(value[i]))
dfs(tmp, s+value[i], i)
dfs([], 0, -1)
print res
Time complexity is O(K). K is number of correct result.
A much more efficient method is to use breadth-first-search and avoid enqueueing any further if the current item value plus the current sum already reaches the limit, so that in a value list of [1, 2, 3, 4, 5] and a limit of 5, if the current combination of values is [1, 2] and the current item value is 3, then since we find that 1 + 2 + 3 is already no less than 5, we will not enqueue [1, 2, 3] for further search. This drastically cuts down on the number of combinations we need to test:
from collections import deque
def sums_less_than(items, values, limit):
seeds = [(index, 0, [], item_value) for index, item_value in enumerate(zip(items, values))]
queue = deque(seeds)
while queue:
index, _sum, combination, (item, value) = queue.popleft()
new_sum = _sum + value
if new_sum < limit:
new_combination = combination + [item]
yield new_combination
for i in range(index + 1, len(seeds)):
queue.append((i, new_sum, new_combination, seeds[i][-1]))
so that:
items=[1,2,3]
values=[100,300,800]
print(list(sums_less_than(items, values, 500)))
will output:
[[1], [2], [1, 2]]
You can use itertools.combinations on a zipped sequence of items and values after filtering out values that are greater than the limit first:
from itertools import combinations
items=[1,2,3]
values=[100,300,800]
def sums_less_than(items, values, limit):
filtered = [(item, value) for item, value in zip(items, values) if value < limit]
return [[item for item, _ in c] for n in range(1, len(filtered) + 1) for c in combinations(filtered, n) if sum(value for _, value in c) < limit]
print(sums_less_than(items, values, 500))
This outputs:
[[1], [2], [1, 2]]
With modificaton:
import copy
dictionary = {
100: 1,
200: 2,
800: 3,
50 : 4,
}
value = sorted(dictionary.keys())
threshold = 500
res = []
thres_val=[]
def dfs(l, s, cur):
if s < threshold:
if len(l) > 0:
res.append(l)
thres_val.append(s)
else:
return
for i in range(cur + 1, len(value)):
tmp = copy.copy(l)
tmp.append(dictionary.get(value[i]))
dfs(tmp, s+value[i], i)
dfs([], 0, -1)
print(res)
print(thres_val)
print("\tItem list-->Value")
j=0
for i in res:
print("\t",i,"-->",thres_val[j])`

Partition a number into a given set of numbers

Here is what I am trying to do. Given a number and a set of numbers, I want to partition that number into the numbers given in the set (with repetitions).
For example :
take the number 9, and the set of numbers = {1, 4, 9}.
It will yield the following partitions :
{ (1, 1, 1, 1, 1, 1, 1, 1, 1), (1, 1, 1, 1, 1, 4), (1, 4, 4), (9,)}
No other possible partitions using the set {1, 4, 9} cannot be formed to sum the number 9.
I wrote a function in Python which do the task :
S = [ 1, 4, 9, 16 ]
def partition_nr_into_given_set_of_nrs(nr , S):
lst = set()
# Build the base case :
M = [1]*(nr%S[0]) + [S[0]] * (nr //S[0])
if set(M).difference(S) == 0 :
lst.add(M)
else :
for x in S :
for j in range(1, len(M)+1):
for k in range(1, nr//x +1 ) :
if k*x == sum(M[:j]) :
lst.add( tuple(sorted([x]*k + M[j:])) )
return lst
It works correctly but I want to see some opinions about it. I'm not satisfied about the fact that it uses 3 loops and I guess that it can be improved in a more elegant way. Maybe recursion is more suited in this case. Any suggestions or corrections would be appreciated. Thanks in advance.
I would solve this using a recursive function, starting with the largest number and recursively finding solutions for the remaining value, using smaller and smaller numbers.
def partition_nr_into_given_set_of_nrs(nr, S):
nrs = sorted(S, reverse=True)
def inner(n, i):
if n == 0:
yield []
for k in range(i, len(nrs)):
if nrs[k] <= n:
for rest in inner(n - nrs[k], k):
yield [nrs[k]] + rest
return list(inner(nr, 0))
S = [ 1, 4, 9, 16 ]
print(partition_nr_into_given_set_of_nrs(9, S))
# [[9], [4, 4, 1], [4, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1]]
Of course you could also do without the inner function by changing the parameters of the function and assuming that the list is already sorted in reverse order.
If you want to limit the number of parts for large numbers, you can add an aditional parameter indicating the remaining allowed number of elements and only yield result if that number is still greater than zero.
def partition_nr_into_given_set_of_nrs(nr, S, m=10):
nrs = sorted(S, reverse=True)
def inner(n, i, m):
if m > 0:
if n == 0:
yield []
for k in range(i, len(nrs)):
if nrs[k] <= n:
for rest in inner(n - nrs[k], k, m - 1):
yield [nrs[k]] + rest
return list(inner(nr, 0, m))
Here is a solution using itertools and has two for loops so time complexity is about O(n*n) (roughly)
A little memoization applied to reshape list by removing any element that is greater than max sum needed.
Assuming you are taking sum to be max of your set (9 in this case).
sourceCode
import itertools
x = [ 1, 4, 9, 16 ]
s = []
n = 9
#Remove elements >9
x = [ i for i in x if i <= n]
for i in xrange(1,n + 1):
for j in itertools.product(x,repeat = i):
if sum(j) == n:
s.append(list(j))
#Sort each combo
s =[sorted(i) for i in s]
#group by unique combo
print list(k for k,_ in itertools.groupby(s))
Result
>>>
>>>
[[9], [1, 4, 4], [1, 1, 1, 1, 1, 4], [1, 1, 1, 1, 1, 1, 1, 1, 1]]
EDIT
You can further optimize speed (if needed) by stopping finding combo's after sum of product is > 9
e.g.
if sum(j) > n + 2:
break

take a number and sum its digits

I'm working through this Kata and although I've looked through the solutions none are quite similar enough to mine to answer my question.
Problem Text: The number 89 is the first integer with more than one digit that fulfills the property partially introduced in the title of this kata. What's the use of saying "Eureka"? Because this sum gives the same number.
In effect: 89 = 8^1 + 9^2
The next number in having this property is 135.
See this property again: 135 = 1^1 + 3^2 + 5^3
We need a function to collect these numbers, that may receive two integers a, b that defines the range [a, b] (inclusive) and outputs a list of the sorted numbers in the range that fulfills the property described above.
def sum_dig_pow(a, b): # range(a, b + 1) will be studied by the function
# your code here
lst = []
n = 1
tot = 0
for i in range(a,b):
if i > 9:
spl = str(i).split()
for item in spl:
tot += int(item) ** n
n += 1
if tot == i:
lst.append(i)
else:
lst.append(i)
return lst
Tests are returning "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] should equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 89]".
I cannot figure out why it's passing 10 and not appending 89. I'm sure there's a more efficient way to do this as well but I'm still learning so want to be working in basics of loops, conditionals,etc.
This line is incorrect:
spl = str(i).split()
The split method will split a string on spaces by default and return a list. So passing i=10 gives back spl = ['10'], a list with one element. Instead, just iterate over each of the digits in the string.
for item in str(i):
...
Follow up: you can shorten your code by using enumerate to count the index of each digit.
def sum_dig_pow(a,b):
return [sum(int(y)**(i+1) for i,y in enumerate(str(x))) for x in range(a,b)]
Rather than spending a lot of time converting things from numbers to strings and back, try using arithmetic. To iterate over the digits of a number n, take n modulo ten (to get the least-significant digit) and then divide by ten (to peel off that least-significant digit). For example, the digits of 123 (in reverse order) are [(123 % 10), (12 % 10), (1 % 10)]
Thinking of it in terms of functions, first get the digits:
def digits_of_n(n):
result = []
while n > 0:
result.append(n % 10)
n = n / 10 # in python 3, use 3 // 10 for integer division
return reversed(result) # reverse list to preserve original order
then get the sum of the powers:
def sum_of_ith_powers(numbers):
result = 0
for i, n in enumerate(numbers): # the digits are ordered most-significant to least, as we would expect
result += n ** 1
return result
now you can just call sum_of_ith_powers(digits_of_n(n)) and you have an answer. If you like, you can give that operation a name:
def sum_of_digit_powers(n):
return sum_of_ith_powers(digits_of_n(n))
and you can then name the function that solves the kata:
def solve_kata(a, b):
return [sum_of_digit_powers(n) for n in range (a, b)]
You can use a generator, sum and enumerate in order to simplify your code like this example:
def sum_dig_pow(a,b):
for k in range(a,b+1):
if k > 9:
number_sum = sum(int(j)**i for i,j in enumerate(str(k), 1))
if k is number_sum:
yield k
else:
yield k
print(list(sum_dig_pow(1,10)))
print(list(sum_dig_pow(1,90)))
print(list(sum_dig_pow(1,10000)))
print(list(sum_dig_pow(10,1000)))
print(list(sum_dig_pow(1,900000)))
Output:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 89]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 89, 135, 175]
[89, 135, 175]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 89, 135, 175]
li = []
def sum_dig_pow(a, b):
for i in range(a, b+1):
sum1 = 0
for ind, val in enumerate(str(i), 1):
sum1 += pow(int(val), int(ind))
if i == sum1:
li.append(i)
return li
print(sum_dig_pow(1, 11))

Print for loop results in one line and sort

for n in range(1,1000000):
print(n)
result = []
for x in range(1,3000001):
if n%2==0:
x=n/2
else:
x=3*n+ 1
n=x
result.append(n)
if n==1:
break
print(len(result))
n+=1
I want these results to be printed in an array or something like that.I mean like this.
3,1,7,2,5,8,1,..
Then I want to take the highest element and its index.How can I do that?Thank you.
You can use str.join for the first task:
>>> result = [4, 0, 9, 7, 8, 3, 2, 6, 1, 5]
>>> print (', '.join(map(str, result)))
4, 0, 9, 7, 8, 3, 2, 6, 1, 5
And max with enumerate for the second task:
>>> ind, val = max(enumerate(result), key=lambda x:x[1])
>>> ind, val
(2, 9)
If you separate out the loop that does the work into its own function, this becomes much easier.
def collatz_length(n):
result = []
for x in range(1,3000001):
if n%2==0:
x=n/2
else:
x=3*n+ 1
n=x
result.append(n)
if n==1:
break
return len(result)
print(max((collatz_length(i + 1), i) for i in range(1000000)))
Since you're not using result, just its length, you could simplify (and speed up) the function a little by simply counting
You can tidy up the calculation of x by using a ternary expression
def collatz_length(n):
for c in range(1, 3000001):
x = 3 * n + 1 if n % 2 else n / 2
n = x
if n == 1:
return c

Categories

Resources