Optimized way to find in which range does a number lie - python

I have multiple ranges lets say 1-1000, 1000-2000, 2000-3000, 3000-4000, 4000-5000. I get a number from the user and now i need to find in which range it lies. One way to do this would be to create multiple if statement and check from there like so:
if num>=1 and num < 1000:
print "1"
elif num >=1000 and num < 2000:
print "2"
....
This method would create a lot of branches.
Is there an optimized way to do this without so many branches and in the least complexity?
PS: I just wrote the code in python since its shorter to write but this can be case in any language. Also the range and output can be very different.
The range and output are examples and can be anything like 1-100, 100-1000, 1000-1500 etc and output like "Very Low, low, medium" something like that.

Store the starting or ending of the range in the list and sort it along with number to find its exact range.
import numpy as np
start = [1,1000,2000,3000,4000]
print(list(np.sort(start+[num])).index(num))

If your ranges don't follow any particular logic, there's not much you can do except testing them one by one, but you can still simplify the code by using a loop:
ranges = [[0,1000],[1500,1600],[1200,1220]]
def find_range(num, ranges)
for low, high in ranges:
if low <= num < high:
return low, high # or any other formating using a dict for example
Of course you can optimize a bit by sorting your ranges and then doing a binary search instead of linear...

my_range=(1,1000), (1000,2000), (2000,3000), (3000,4000), (4000,5000)
my_output='Very Low, Low, Medium, High, Very High'.split(', ')
num=3565
for k,i in enumerate(my_range):
if i[0]<=num<i[1]:print(my_output[k]);break
else:
print('Out of range')

How about something like this:
ranges = {
0: 'undefined range',
1000: '1',
1500: '2',
2500: '3'
}
num = 500
print(ranges[max(ranges, key=lambda x: num < x)])
Output: 1

Suspect that you have many breaks and require an optimized search, you can go with bisection on an ordered list of breakpoints resulting in a logarithmic time consumption:
import random
import time
def split_pivot(intervals, number):
"""Divide and conquer recursively."""
if len(intervals) == 1:
return intervals[0]
if len(intervals) == 2:
if number >= intervals[1][1][0]:
return intervals[1]
elif number < intervals[0][1][1]:
return intervals[0]
else:
raise
pivot = int(len(intervals) // 2.0)
if number < intervals[pivot][1][1]:
return split_pivot(intervals[:pivot + 1], number)
elif number >= intervals[pivot + 1][1][0]:
return split_pivot(intervals[pivot + 1:], number)
else:
raise
if __name__ == '__main__':
UPPER_BOUND = 10000000
newbreak = 0
manybreaks = []
while newbreak < UPPER_BOUND:
step = int(random.random() * 10) + 1
manybreaks.append(newbreak + step)
newbreak = manybreaks[-1]
print('Breaks: {:d}'.format(len(manybreaks)))
intervals = [
(idx, (manybreaks[idx], manybreaks[idx + 1]))
for idx in range(len(manybreaks) - 1)
]
print('Intervals: {:d}'.format(len(intervals)))
print(
' Example: idx {tpl[0]:d}, lower {tpl[1][0]:d}, upper {tpl[1][1]:d}'
.format(tpl=random.choice(intervals)))
thenumber = int(random.random() * UPPER_BOUND)
print('Number: {:d}'.format(thenumber))
t0 = time.time()
result = split_pivot(intervals, thenumber)
t1 = time.time()
print('Result: {e[0]:d} ({e[1][0]:d}, {e[1][1]:d})'.format(e=result))
print(' Done in {:.4f}s'.format(t1 - t0))
The result of the search itself is (on my machine) below 0.05 seconds. The generation of breakpoints and corresponding intervals runs for roughly 4.5 seconds:
Breaks: 1818199
Intervals: 1818198
Example: idx 605849, lower 3330441, upper 3330446
Number: 6951844
Result: 1263944 (6951843, 6951847)
Done in 0.0436s

maybe just divide by 1000 and take the entire part :
here example in python :
>>> x=3608
>>> int(x/1000+1)
4
Following your comment/edit in your post, if you need a different output (a string for example) you can (in python) use a dict :
>>> Output={'1': 'very low', '2': 'low', '3': 'medium','4':'high' }
>>> x=2954
>>> Output[str(int(x/1000+1))]
'medium'

Related

Increasing itertools.permutations performance

I am completing a problem where I have create a function that takes a positive integer and returns the next bigger number that can be formed by rearranging its digits. For example: 12 --> 21, 513 --> 531, 12435 --> 12453, 9817121211 --> 9817122111.
I've recompiled my code over and over increasing performance but have eventually come unto a stop where I can't get it any faster. Does anyone have any advice? Its the itertools.permutations line which is taking the vast majority of the time.
def next_bigger(n):
num = str(n)
num1 = set(int(x) for x in str(num))
if num == num[0] *len(num):
return -1
#full_set = set(num)
lis = set(int(''.join(nums)) for nums in itertools.permutations(num, len(num)))
lis = sorted(lis)
try:
return int(lis[lis.index(n)+1])
except Exception:
return -1
Link to problem: https://www.codewars.com/kata/55983863da40caa2c900004e/train/python
If you are looking for better performance "time complexity wise", The approach would be to find the "key" of the algorithm. In this case you should ask yourself, what does it means to create the next bigger nummber? The answer is just as simple as a swap between two adjacent numbers. The code would be like this.
def next_bigger(n):
num_string = list(str(n))
for i in range(1, len(num_string)):
if i == len(num_string):
return -1
#find two the two numbers one bigger than the other with the minimun order
if num_string[-i] > num_string[-i-1]:
compare_reference = num_string[-i]
index_reference = -i
#check if the current number is smaller than any of the tail
for k, current in enumerate(num_string[-i:]):
if num_string[-i-1] < current and current < compare_reference:
compare_reference = current
index_reference = -i+k
#interchange the locations:
num_string[index_reference] = num_string[-i-1]
num_string[-i-1] = compare_reference
#check if the tail is larger than one digit
if i > 1:
#order the rest of the vector to create the smaller number (ordering it).
lower_part_ordered = sort_ascendant(num_string[-i:])
else:
lower_part_ordered = [num_string[-i]]
# create a string from the list
return int("".join(num_string[:-i] + lower_part_ordered))
# no match found means a number like 65311
return -1
While not a way to increase the permutations function performance per se, this was the method I found to increase performance of the code. many thanks to all that offered help!
def next_bigger(n):
num_string = list(str(n))
a = []
for i in range(1, len(num_string)):
if i == len(num_string):
return -1
p = int(num_string[-i])
q = int (num_string[-(i+1)])
if p > q:
a.append(num_string[:-(i+1)])
lis = list(num_string[-(i+1):])
if len(lis) > 1:
lis2 = list(set(lis))
lis2.sort()
qindex = lis2.index(str(q))
first = lis2[qindex+1]
a[0].append(first)
lis.remove(first)
lis.sort()
for j in range (len(lis)):
a[0].append(lis[j])
return int("".join(a[0]))
return -1

How to assign an if statement's result into an equation?

I'm new to python and I have to run a program to get the correct interest rate based on user input and use the interest rate obtained to compute monthly interest earned.
For the computation of interest earned, I'm trying to use the print result to create a formula for calculating monthly interest earned. However, I've tried so many things and I'm not sure how to correct this.
transaction_category = [2000, 2500, 5000, 15000, 30000]
first_50k_1_category_rates = [0.05, 1.55, 1.85, 1.90, 2.00, 2.08]
if (count == 1) and (account_balance <= 50000) and (total_eligible_monthly_transactions < transaction_category[0]):
print(f'Interest rate applicable is: {first_50k_1_category_rates[0]: .2f}%')
if (count == 1) and (account_balance <= 50000) and (transaction_category[0] <= total_eligible_monthly_transactions < transaction_category[1]):
print(f'Interest rate applicable is: {first_50k_1_category_rates[1]: .2f}%')
Your question is rather unclear, but I guess you are looking for something like
if (count == 1) and (account_balance <= 50000) and (transaction_category[3] <= total_eligible_monthly_transactions < transaction_category[4]):
applicable_interest_rate = first_50k_1_category_rates[4]
elif (count == 1) and (account_balance <= 50000) and (total_eligible_monthly_transactions >= transaction_category[4]):
applicable_interest_rate = first_50k_1_category_rates[5]
print(f'Interest rate applicable is: {applicable_interest_rate: .2f}%')
This is just a sketch; you will have to make sure the new variable is always defined, then use that in your final equation instead.
Probably the repeated conditions should be refactored, too, so you don't compare the same things over and over.
if (count == 1) and (account_balance <= 50000):
if transaction_category[3] <= total_eligible_monthly_transactions < transaction_category[4]:
applicable_interest_rate = first_50k_1_category_rates[4]
elif total_eligible_monthly_transactions >= transaction_category[4]:
applicable_interest_rate = first_50k_1_category_rates[5]
but again, without seeing the complete script, it's not clear exactly how to refactor. This is just one example to illustrate the idea.
So you could do if/else in one code block, or turn those print statements into variables and return them. Both would say result as var name.
def foo(condition1, condition2):
if condition1 < condition2:
result = (1 + 1)
if 1 == False:
result = (1 - 1)
return result
print(foo(1, 2))
there’s better ways to do this in a more functional programming aspect like this: (lambda :f"b:{b}",lambda :f"a:{a}")[a>b](), but it seems this code style is imperative so stick w that.
If you are having trouble with scope, you can try this (safe) hack:
_scope = {
"applicable_interest_rate1": first_50k_1_category_rates[4],
"applicable_interest_rate2": first_50k_1_category_rates[5],
}
def foo(condition1, condition2):
if condition1 < condition2:
result = _scope["applicable_interest_rate1"]
if 1 == False:
result = _scope["applicable_interest_rate2"]
return result
print(foo(1, 2))

Confused about Backtracking?

I'm trying to solve this Leetcode Question but I'm getting an error where I'm exceeding the time limit.
class Solution:
def readBinaryWatchHelper(self,hours, minutes, num, pastHours, pastMinutes):
if num == 0:
hour, minute = sum(pastHours), sum(pastMinutes)
if self.isValidTime(hour, minute):
time = str(hour) + ":" + str(minute).zfill(2)
self.times.add(time)
else:
for i in minutes:
cMinutesTemp = list(minutes)
pMinutesTemp = list(pastMinutes)
pMinutesTemp.append(i)
cMinutesTemp.remove(i)
if self.isValidTime(sum(pastHours), sum(pMinutesTemp)):
self.readBinaryWatchHelper(hours, cMinutesTemp, num - 1, pastHours, pMinutesTemp)
for i in hours:
cHoursTemp = list(hours)
pHoursTemp = list(pastHours)
pHoursTemp.append(i)
cHoursTemp.remove(i)
if self.isValidTime(sum(pHoursTemp), sum(pastMinutes)):
self.readBinaryWatchHelper(cHoursTemp, minutes, num - 1, pHoursTemp, pastMinutes)
#staticmethod
def isValidTime(hours, minutes):
if hours < 12 and minutes < 60:
return True
return False
def readBinaryWatch(self, num):
self.times = set()
hChoices = [1,2,4,8]
mChoices = [1,2,4,8,16,32]
self.readBinaryWatchHelper(hChoices[::-1], mChoices[::-1], num, [],[])
return list(self.times)
That's the solution I've written up using Backtracking. I was hoping I could get feedback on why it's too slow? One of the valid solutions is just getting all the combinations of hours from 0 - 12 and minutes from 0 - 60 and checking the sum of the bits to see if they add up to the correct sum. I'm confused as to how that solution is faster than mine? Shouldn't mine be faster due to the "tree pruning"? Thanks guys.
A solution w/o backtracking but using itertools and list comp would be:
class Solution:
def readBinaryWatch(self, num):
"""
:type num: int
:rtype: List[str]
"""
from itertools import combinations, product
hhmin = min(num,3)+1 # only generate at much as needed
mmmin = min(num,5)+1 # only generate as much as needed
nBits = [1,2,4,8,16,32]
# {0: ['0'], 1: ['1','2','4','8'], 2: ['3','5','9','6','10'], 3: ['7','11']} for>2
h = { bitCount: list(str(sum(x)) for x in combinations(nBits,bitCount)
if sum(x) < 12) for bitCount in range(hhmin)}
m = { bitCount: list(str(sum(x)).zfill(2) for x in combinations(nBits,bitCount)
if sum(x) < 60) for bitCount in range(mmmin)}
ranges = ((h[subl[0]],m[subl[1]]) for subl in (x for x in product(range(num + 1),
range(num + 1)) if sum(x) == num and x[0]<hhmin and x[1]<mmmin))
return ["{0}:{1}".format(hh,mm) for (hhhh,mmmm) in ranges
for hh in hhhh for mm in mmmm]
Test with:
s = Solution()
for n in range(8):
print(s.readBinaryWatch(n))
print()
It dials in between 55ms and 64ms depending on whatever between different submits. According to the page this reaches 77% - there are some much shorter and more elegnant solutions. You can inspect some of them once you submit one. Yours unfortunately does not run at all, recursion needs recursive which seems to be too slow.
Funny enough the "brute force" if.. elif .. elif ... else with prefabbed lists is below 50% - just had to try it ;)

Project Euler Project 67 - Python

I am doing the Project Euler #67 in Python. My program, which worked for Project 18, does not work for Project 67.
Code (excludes the opening of the file and the processing of information):
for i in range(len(temp)):
list1 = temp[i]
try:
list2 = temp[i+1]
trynum1 = list1[lastinput] + max(list2[lastinput],list2[lastinput+1])
try:
trynum2 = list1[lastinput+1] + max(list2[lastinput+1],list2[lastinput+2])
if trynum1 > trynum2:
outputlist.append(list1[lastinput])
else:
outputlist.append(list1[lastinput+1])
lastinput += 1
except IndexError:
outputlist.append(list1[0])
except IndexError:
if list1[lastinput] > list1[lastinput+1]:
outputlist.append(list1[lastinput])
else:
outputlist.append(list1[lastinput+1])
Variables:
temp is the triangle of integers
outputlist is a list which stores the numbers chosen by the program
I know the answer is 7273, but my program finds 6542. I cannot find an error which causes the situation. Please may you help me on it.
Logic
My approach to this program is to find one number (list1[lastinput]) and add it up with the larger number of the two below it (trynum1), compare with the number to the right of the first number (list1[lastinput+1]), adding the larger number of two below it (trynum2). I append the larger one to the output list.
This approach is logically flawed. When you're in row 1, you don't have enough information to know whether moving right or left will lead you to the largest sum, not with only a 2-row lookahead. You would need to look all the way to the bottom to ensure getting the best path.
As others have suggested, start at the bottom and work up. Remember, you don't need the entire path, just the sum. At each node, add the amount of the better of the two available paths (that's the score you get in taking that node to the bottom). When you get back to the top, temp[0][0], that number should be your final answer.
I thought day and night about problem 18 and I solved it, the same way I solved this one.
P.S. 100_triangle.txt is without 1st string '59'.
# Maximum path sum II
import time
def e67():
start = time.time()
f=open("100_triangle.txt")
summ=[59]
for s in f:
slst=s.split()
lst=[int(item) for item in slst]
for i in range(len(lst)):
if i==0:
lst[i]+=summ[i]
elif i==len(lst)-1:
lst[i]+=summ[i-1]
elif (lst[i]+summ[i-1])>(lst[i]+summ[i]):
lst[i]+=summ[i-1]
else:
lst[i]+=summ[i]
summ=lst
end = time.time() - start
print("Runtime =", end)
f.close()
return max(summ)
print(e67()) #7273
Though starting from the bottom is more efficient, I wanted to see if I could implement Dijkstra's algorithm on this one; it works well and only takes a few seconds (didn't time it precisely):
from math import inf
f = open("p067_triangle.txt", "r")
tpyramid = f.read().splitlines()
f.close()
n = len(tpyramid)
pyramid = [[100 - int(tpyramid[i].split()[j]) for j in range(i+1)] for i in range(n)]
paths = [[inf for j in range(i+1)] for i in range(n)]
paths[0][0] = pyramid[0][0]
def mini_index(pyr):
m = inf
for i in range(n):
mr = min([i for i in pyr[i] if i >= 0]+[inf])
if mr < m:
m, a, b = mr, i, pyr[i].index(mr)
return m, a, b
counter = 0
omega = inf
while counter < n*(n+1)/2:
min_weight, i, j = mini_index(paths)
if i != n-1:
paths[i+1][j] = min( paths[i+1][j], min_weight + pyramid[i+1][j])
paths[i+1][j+1] = min( paths[i+1][j+1], min_weight + pyramid[i+1][j+1])
else:
omega = min(omega, min_weight)
paths[i][j] = -1
counter += 1
print(100*n - omega)
Here is my solution. Indeed you have to take the bottom - up approach.
Result confirmed with PE. Thanks!
def get_triangle(listLink):
triangle = [[int(number) for number in row.split()] for row in open(listLink)]
return triangle
listOfLists = get_triangle('D:\\Development\\triangle.txt')
for i in range(len(listOfLists) - 2, -1, -1):
for j in range(len(listOfLists[i])):
listOfLists[i][j] += max(listOfLists[i+1][j], listOfLists[i+1][j+1])
print(listOfLists[0][0])

Using Range Function

My goal is to make a program that takes an input (Battery_Capacity) and ultimately spits out a list of the (New_Battery_Capacity) and the Number of (Cycle) it takes for it ultimately to reach maximum capacity of 80.
Cycle = range (160)
Charger_Rate = 0.5 * Cycle
Battery_Capacity = float(raw_input("Enter Current Capacity:"))
New_Battery_Capacity = Battery_Capacity + Charger_Rate
if Battery_Capacity < 0:
print 'Battery Reading Malfunction (Negative Reading)'
elif Battery_Capacity > 80:
print 'Battery Reading Malfunction (Overcharged)'
elif float(Battery_Capacity) % 0.5 !=0:
print 'Battery Malfunction (Charges Only 0.5 Interval)'
while Battery_Capacity >= 0 and Battery_Capacity < 80:
print New_Battery_Capacity
I was wondering why my Cycle = range(160) isn't working in my program?
Your first problem is that you have the first two lines in the wrong order. You need a "Cycle" variable to exist before you can use it.
You'll still get an error when you swap them, though. You can't multiply a list by a float. A list comprehension is more what you want:
Charger_Rate = [i * .5 for i in Cycle]
As far as I can tell, the range(160) part is fine.

Categories

Resources