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
Related
I have been coding for around half a year in uni and have done some side projects. This is one of them and although my code works for integers, I would like to know how it could be optimised using less lines of code. Coding at uni has taught me how to create many programs but not really how to optimise code and so any further tips would be greatly appreciated! <3
num = int(1230124013502)
def rem(num):
"""
Rem function separates the thousands in an intiger and converts to
a string. Function takes one input (num) which must be of intiger
form. Rem converts to string with commas separating the thousands
"""
num = str(num)
l = len(num)
remain = l%3
sum = ''
if remain == 0:
remain = 3
new = num[remain:]
pre = num[:remain]
#print(pre,new,remain)
l_new = len(new)
zeros = []
for i in range(3,l_new+3,3):
j = i - 3
post = new[j:i] + ','
zeros.append(post)
for i in range(len(zeros)):
sum += zeros[i]
tot = pre + ',' + sum
endpoint = len(tot) - 1
tot = tot[0:endpoint]
if l < 4:
print(num)
return num
else:
print(tot)
return tot
rem(num)
If you have a range of numbers from 1-49 with 6 numbers to choose from, there are nearly 14 million combinations. Using my current code (below), I have only 85,805 combinations remaining. I want to get all those 85,805 combinations to print into the Python shell showing every combination rather than the number of combinations possible as I'm currently seeing. Is that possible? Here's my code:
import functools
_MIN_SUM = 152
_MAX_SUM = 152
_MIN_NUM = 1
_MAX_NUM = 49
_NUM_CHOICES = 6
_MIN_ODDS = 2
_MAX_ODDS = 4
#functools.lru_cache(maxsize=None)
def f(n, l, s = 0, odds = 0):
if s > _MAX_SUM or odds > _MAX_ODDS:
return 0
if n == 0 :
return int(s >= _MIN_SUM and odds >= _MIN_ODDS)
return sum(f(n-1, i+2, s+i, odds + i % 2) for i in range(l, _MAX_NUM+1))
result = f(_NUM_CHOICES, _MIN_NUM)
print('Number of choices = {}'.format(result))
Thank you!
Printing to the console is rather slow. You might want to print it to a file instead.
print("Hello World")
# vs
with open("file.txt", "w") as f:
print("Hello World", file=f)
Try using for loops and recursion together:
def combinations(base, numbers, placesRemaining):
out = []
for i in numbers:
if placesRemaining <= 1:
out.append(base*i)
else:
out.extend(combinations(base*i, numbers, placesRemaining-1))
return out
places = 6
numbers = range(1, 50)
answer = combinations(1, numbers, places)
That solution is not likely to run into the recursion limit, as the maximum recursion depth is equal to places. I did not run this on the full problem, but it performed well on smaller ones. Altering the starting base will multiply every number you calculate by that number, so I do not recommend it.
I want to reverse digits in a number in python. Here are my two implementations.
One: convert the number into string and reverse each char in it
number = 2376674032
number_s = str(number)
index = len(number_s) - 1
str_list = []
while index > -1:
str_list.append(number_s[index])
index -= 1
result = int("".join(str_list))
print(result)
Two: using simple mathematics
number = 2376674032
N = 0
K = number
R = number % 10
while K > 0:
N = N*10 + R
K = K // 10
R = K % 10
result = N
print(result)
As I'm pretty new to python programming, so could someone help me with the following questions:
with the first approach, will "".join(str_list) produce a new string with each list element? if so is a better way to concatenate strings in python(something similar to StringBuffer in java)
which of the implementations is better from performance perspective?
You can reverse a string using -1 as the step in a slice. So this works:
number = 2376674032
number_s = str(number)
reverse_s = number_s[::-1]
reversed = int(reverse_s)
you want to reverse a number …..input it as string format , and do this:
number="8374783246837"
revnumber=number[::-1]
Done
a = 1234
a = int("".join(reversed(str(a))))
This will give a = 4321
reversed functions returns an iterable object.
If we do :
a = list(reversed(str(a)))
it will return [“3”,”2″,”1″]. We have then joined it and converted into int.
To make the number an integer type, we have to use the int function, as below:
numbers=str(123456)
#or numbers="123456"
print((int(numbers[::-1])))
print((type(int(numbers[::-1]))))
output:
654321
<class 'int'>
We can do this in a single line as well using [::-1]
print(int(str(int(input()))[::-1]))
#here is my answer . you can do it using simple recursion
# count digits recursively
def reverse_digits(n):
# base case
if n == 0:
pass
#recursive case
else:
print(n%10,end='')
return reverse_digits(n//10)
# reverse 123
reverse_digits(123)
````````````````````````````````````````````````````
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'
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])