Python List Creation Difference - python-3.x

I am trying to create a recursive function to sort the list from low to high.
Following code doesn't work
less = []
greater = []
def quicksort(array):
if len(array)<2:
return array
else:
pivot = array[0]
for i in array[1:]:
if i <= pivot:
less.append(i)
else:
greater.append(i)
return quicksort(less)+[pivot]+quicksort(greater)
print(quicksort([1,3,2,7,8]))
but I use a book code , it works. Would you advise me why?
def quicksort(array):
if len(array)<2:
return array
else:
pivot = array[0]
less = [i for i in array[1:] if i <= pivot]
greater = [i for i in array[1:] if i > pivot]
return quicksort(less)+[pivot]+quicksort(greater)
print(quicksort([1,3,2,7,8]))

You're using global less and greater lists, so you're going to end up building up the lists bigger and bigger and bigger, repeating your inputs many times (roughly proportional to the number of times you recursively call quicksort). less and greater keep growing until you blow the stack depth limit or run out of memory and Python dies to protect you from yourself.
Worse, you preserve state across calls, so the second and subsequent things you quicksort ends up including garbage from the prior sort operations, even if they're on inputs so short you could "sort" them trivially. Your code would work if you made less/greater local, initializing them afresh in each call:
def quicksort(array):
if len(array)<2:
return array
else:
pivot = array[0]
less = [] # Local!
greater = [] # Local!
for i in array[1:]:
if i <= pivot:
less.append(i)
else:
greater.append(i)
return quicksort(less)+[pivot]+quicksort(greater)

Related

Number of sub sequences of length K having total sum S, given 2d array

I wish to find Number of sub sequences of length K having total sum S, given an array.
Sample Input:
a=[1,1,1,2,2] & K=2 & S=2
Sample Output:
3 {because a[0],a[1]; a[1]a[2]; a[0]a[2] are only three possible for the case}
I have tried to write a recursive loop in Python for starter but it isn't giving output as expected.Please can you help me find a loophole I might be missing on.
def rec(k, sum1, arr, i=0):
#print('k: '+str(k)+' '+'sum1: '+str(sum1)) #(1) BaseCase:
if(sum1==0 and k!=0): # Both sum(sum1) required and
return 0 # numbers from which sum is required(k)
if(k==0 and sum1 !=0): # should be simultaneously zero
return 0 # Then required subsequences are 1
if(k==0 and sum1==0 ): #
return 1 #
base_check = sum1!=0 or k!=0 #(2) if iterator i reaches final element
if(i==len(arr) and base_check): # in array we should return 0 if both k
return 0 # and sum1 aren't zero
# func rec for getting sum1 from k elements
if(sum1<arr[0]): # takes either first element or rejects it
ans=rec(k-1,sum1,arr[i+1:len(arr)],i+1) # so 2 cases in else loop
print(ans) # i is taken in as iterator to provide array
else: # input to rec func from 2nd element of array
ans=rec(k-1, sum1-arr[0], arr[i+1:len(arr)],i+1)+rec(k, sum1, arr[i+1:len(arr)],i+1)
#print('i: '+str(i)+' ans: '+str(ans))
return(ans)
a=[1,1,1,2,2]
print(rec(2,2,a))
I am still unable to process how to make changes. Once this normal recursive code is written I might go to DP approach accordinlgy.
Using itertools.combinations
Function itertools.combinations returns all the subsequences of a given lengths. Then we filter to keep only subsequences who sum up to the desired value.
import itertools
def countsubsum(a, k, s):
return sum(1 for c in itertools.combinations(a,k) if sum(c)==s)
Fixing your code
Your code looks pretty good, but there are two things that appear wrong about it.
What is this if for?
At first I was a bit confused about if(sum1<arr[0]):. I think you can (and should) always go to the else branch. After thinking about it some more, I understand you are trying to get rid of one of the two recursive calls if arr[0] is too large to be taken, which is smart, but this makes the assumption that all elements in the array are nonnegative. If the array is allowed to contain negative numbers, then you can include a large a[0] in the subsequence, and hope for a negative element to compensate. So if the array can contain negative numbers, you should get rid of this if/else and always execute the two recursive calls from the else branch.
You are slicing wrong
You maintain a variable i to remember where to start in the array; but you also slice the array. Pretty soon your indices become wrong. You should use slices, or use an index i, but not both.
# WRONG
ans=rec(k-1, sum1-arr[0], arr[i+1:len(arr)],i+1)+rec(k, sum1, arr[i+1:len(arr)],i+1)
# CORRECT
ans = rec(k-1, sum1-arr[i], arr, i+1) + rec(k, sum1, arr, i+1)
# CORRECT
ans = rec(k-1, sum1-arr[0], arr[1:]) + rec(k, sum1, arr[1:])
To understand why using both slicing and an index gives wrong results, run the following code:
def iter_array_wrong(a, i=0):
if (a):
print(i, a)
iter_array_wrong(a[i:], i+1)
def iter_array_index(a, i=0):
if i < len(a):
print(i, a)
iter_array_index(a, i+1)
def iter_array_slice(a):
if a:
print(a)
iter_array_slice(a[1:])
print('WRONG')
iter_array_wrong(list(range(10)))
print()
print('INDEX')
iter_array_index(list(range(10)))
print()
print('SLICE')
iter_array_slice(list(range(10)))
Also note that a[i:len(a)] is exactly equivalent to a[i:] and a[0:j] is equivalent to a[:j].
Clean version of the recursion
Recursively count the subsequences who use the first element of the array, and the subsequences who don't use the first element of the array, and add the two counts. To avoid explicitly slicing the array repeatedly, which is an expensive operation, we keep a variable start to remember we are only working on subarray a[start:].
def countsubsum(a, k, s, start=0):
if k == 0:
return (1 if s == 0 else 0)
elif start == len(a):
return 0
else:
using_first_element = countsubsum(a, k-1, s-a[start], start+1)
notusing_first_elem = countsubsum(a, k, s, start+1)
return using_first_element + notusing_first_elem

Execution timed out (12000ms) kata Generate Numbers from Digits #2 on Code Wars (Python)

Could you give me a hint where the time consuming part of this code is?
It's my temporary solutions for the kata Generate Numbers from Digits #2 from codewars.com.
Thanks!
from collections import Counter
from itertools import permutations
def proc_arrII(arr):
length = Counter(arr).most_common()[-1][1]
b = [''.join(x) for x in list(set(permutations(arr,length)))]
max_count = [max(Counter(x).values()) for x in b]
total = 0
total_rep = 0
maximum_pandigit = 0
for i in range(len(b)):
total+=1
if max_count[i] > 1:
total_rep+=1
elif int(b[i]) > maximum_pandigit:
maximum_pandigit = int(b[i])
if maximum_pandigit == 0:
return([total])
else:
return([total,total_rep,maximum_pandigit])
When posting this,
it would have been helpful to offer example input,
or link to the original question,
or include some python -m cProfile output.
Here is a minor item, it inflates the running time very very slightly.
In the expression [''.join(x) for x in list(set(permutations(arr, length)))]
there's no need to call list( ... ).
The join just needs an iterable, and a set works fine for that.
Here is a bigger item.
permutations already makes the promise that
"if the input elements are unique, there will be no repeat values in each permutation."
Seems like you want to dedup (with set( ... )) on the way in,
rather than on the way out,
for an algorithmic win -- reduced complexity.
The rest looks nice enough.
You might try benching without the elif clause,
using the expression max(map(int, b)) instead.
If there's any gain it would only be minor,
turning O(n) into O(n) with slightly smaller coefficient.
Similarly, you should just assign total = len(b) and be done with it,
no need to increment it that many times.

A strategy-proof method of finding the time complexity of complex algorithms?

I have a question in regard to time complexity (big-O) in Python. I want to understand the general method I would need to implement when trying to find the big-O of a complex algorithm. I have understood the reasoning behind calculating the time complexity of simple algorithms, such as a for loop iterating over a list of n elements having a O(n), or having two nested for loops each iterating over 2 lists of n elements each having a big-O of n**2. But, for more complex algorithms that implement multiple if-elif-else statements coupled with for loops, I would want to see if there is a strategy to, simply based on the code, in an iterative fashion, to determine the big-O of my code using simple heuristics (such as, ignoring constant time complexity if statements or always squaring the n upon going over a for loop, or doing something specific when encountering an else statement).
I have created a battleship game, for which I would like to find the time complexity, using such an aforementioned strategy.
from random import randint
class Battle:
def __init__(self):
self.my_grid = [[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False]]
def putting_ship(self,x,y):
breaker = False
while breaker == False:
r1=x
r2=y
element = self.my_grid[r1][r2]
if element == True:
continue
else:
self.my_grid[r1][r2] = True
break
def printing_grid(self):
return self.my_grid
def striking(self,r1,r2):
element = self.my_grid[r1][r2]
if element == True:
print("STRIKE!")
self.my_grid[r1][r2] = False
return True
elif element == False:
print("Miss")
return False
def game():
battle_1 = Battle()
battle_2 = Battle()
score_player1 = 0
score_player2 = 0
turns = 5
counter_ships = 2
while True:
input_x_player_1 = input("give x coordinate for the ship, player 1\n")
input_y_player_1 = input("give y coordinate for the ship, player 1\n")
battle_1.putting_ship(int(input_x_player_1),int(input_y_player_1))
input_x_player_2 = randint(0,9)
input_y_player_2 = randint(0,9)
battle_2.putting_ship(int(input_x_player_2),int(input_y_player_2))
counter_ships -= 1
if counter_ships == 0:
break
while True:
input_x_player_1 = input("give x coordinate for the ship\n")
input_y_player_1 = input("give y coordinate for the ship\n")
my_var = battle_1.striking(int(input_x_player_1),int(input_y_player_1))
if my_var == True:
score_player1 += 1
print(score_player1)
input_x_player_2 = randint(0,9)
input_y_player_2 = randint(0,9)
my_var_2 = battle_2.striking(int(input_x_player_2),int(input_y_player_2))
if my_var_2 == True:
score_player2 += 1
print(score_player2)
counter_ships -= 1
if counter_ships == 0:
break
print("the score for player 1 is",score_player1)
print("the score for player 2 is",score_player2)
print(game())
If it's just nested for loops and if/else statements, you can take the approach ibonyun has suggested - assume all if/else cases are covered and look at the deepest loops (being aware that some operations like sorting, or copying an array, might hide loops of their own.)
However, your code also has while loops. In this particular example it's not too hard to replace them with fors, but for code containing nontrivial whiles there is no general strategy that will always give you the complexity - this is a consequence of the halting problem.
For example:
def collatz(n):
n = int(abs(n))
steps = 0
while n != 1:
if n%2 == 1:
n=3*n+1
else:
n=n//2
steps += 1
print(n)
print("Finished in",steps,"steps!")
So far nobody has been able to prove that this will even finish for all n, let alone shown an upper bound to the run-time.
Side note: instead of the screen-breaking
self.my_grid = [[False,False,...False],[False,False,...,False],...,[False,False,...False]]
consider something like:
grid_size = 10
self.my_grid = [[False for i in range(grid_size)] for j in range(grid_size)]
which is easier to read and check.
Empirical:
You could do some time trials while increasing n (so maybe increasing the board size?) and plot the resulting data. You could tell by the curve/slope of the line what the time complexity is.
Theoretical:
Parse the script and keep track of the biggest O() you find for any given line or function call. Any sorting operations will give you nlogn. A for loop inside a for loop will give you n^2 (assuming their both iterating over the input data), etc. Time complexity is about the broad strokes. O(n) and O(n*3) are both linear time, and that's what really matters. I don't think you need to worry about the minutia of all your if-elif-else logic. Maybe just focus on worst case scenario?

Finding palindrome of sum

Here's the problem statement:
For a given no. num, perform:
1. Add num and reverse(num)
2. Check whether the sum is palindrome or not. Else repeat.
Here is my solution. The program seems to be working for the 3 test cases given but when I am executing this program, for 1 private test case I am getting server time out error. Is my program not efficient?
flag=0
iteration=0
num = 195 # sample no. output produced is 9339 which is a palindrome.
while(flag!=1):
#print("iteration ",iteration)
num_rev= int(str(num)[::-1]) #finding rev of number
#print(num_rev)
total= num+num_rev #adding no and no_rev
#print(total)
total_rev= int((str(total))[::-1]) # finding total rev
iteration=iteration+1
if total==total_rev: #if equal, printing palindrome
print("palindrome")
flag=1
else:
num=total #else the new no becomes sum of old num and old_rev
Begin by profiling your code for each of your test cases to find out what is taking so long. I suggest using Jupyter Notebook's code profiling magic. Paste your code into a Jupyter Notebook cell and being the cell with %%prun. Along with other information, it will return a list of function calls, the number of times each function is called, and the amount of time each function takes to run. Compare the run time of your test cases to your server's timeout limit.
If the problem is not with your code and its evolving complexity as the number increases in size, it may be a problem with your server.
...
Note that a palindrome must contain at least 2 or 3 elements depending on your accepted definition.
Consider that one of your test cases may be venturing into arbitrarily large integers that take an unpredictable amount of time to compute. Consider that this compute time may exceed your server's timeout limit.
Furthermore, consider this modified version of your code that accepts an arbitrarily-defined max iteration depth per seed number. It also accepts an arbitrary seed number and a max seed number. Then returns an ordered list of all non-duplicate seed numbers, their respective palindrome sums, the number of iterations required to find the palindrome, and the approximate time required to perform the search:
#flag = 0
iteration = 0
maxiter = 1000
num = 0
max_num = 10000
used_seeds = []
palindromes = []
while num < max_num:
seed = num
while(iteration <= maxiter) and len(str(total))>2:
if num in used_seeds:
break
iteration+=1
num_reversed = int(str(num)[::-1])
total = num + num_reversed
total_reversed = int((str(total))[::-1])
if total == total_reversed:
used_seeds.append(num)
palindromes.append( [num, total] )
break
else:
num = total
num = seed+1
iteration = 0
palindromes.sort(key=lambda elem: elem[0])
print(palindromes)
The results are fascinating, by the way. Hopefully this helps.

Non-recursive Most Efficient Big-O Permutation Alghoritm Python3 (non-built-in)

Hi Guys For my Data Structure assignment I have to find the most efficient way (big-o wise) to calculate permutations of a list of objects.
I found recursive examples on the web but this doesn't seem to be the most efficient way; I tried my own code but then I realized that when I count the number of possible permutations I'm actually making my algorithm O(!n). Any suggestions? .-.
from random import sample
import time
start = time.time()
testList = list(x for x in range(7))
print('list lenght: %i objects' % len(testList))
nOfPerms = 1
for i in range(1,len(testList)+1):
nOfPerms *= i
print('number of permutations:', nOfPerms)
listOfPerms = []
n = 1
while n <= nOfPerms:
perm = tuple(sample(testList, len(testList)))
listOfPerms.append(perm)
permutations = set(listOfPerms)
if len(permutations) == len(listOfPerms):
n += 1
else:
del(listOfPerms[-1])
end = time.time() - start
print('time elapsed:', end)
OUTPUT:
list lenght: 7 objects
number of permutations: 5040
time elapsed: 13.142292976379395
If instead of 7 I put 8 or 9, or 10, those are the number of permutations (I won't show the time cause it's taking too long):
list lenght: 8 objects
number of permutations: 40320
list lenght: 9 objects
number of permutations: 362880
list lenght: 10 objects
number of permutations: 3628800
I believe this will be the best you can do. Generating the number of permutations of a list generates n! permutations. As you need to generate them all this is also how much time it will take (O(n!)). What you could try to do is to make it a python generator function so you will always only generate exactly as many as you need instead of precalculating them all and storing them in memory. If you want an example of this i could give you one.
Im sorry this might be a quite negative answer. It's a good question but im pretty sure this is about the best that you can do, asymptotically. You could optimize the code itself a bit to use less instructions but in the end that wont help too much.
Edit:
This is a python implementation of Heap's algorithm which i promised
(https://en.wikipedia.org/wiki/Heap%27s_algorithm) generating N! permutations where the generation of every one permutation takes amortized O(1) time and which uses O(n) space complexity (by alteri
def permute(lst, k=None):
if k == None:
k = len(lst)
if k == 1:
yield lst
else:
yield from permute(lst, k-1)
for i in range(k-1):
if i % 2 == 0:
#even
lst[i], lst[k-1] = lst[k-1], lst[i]
else:
#odd
lst[0], lst[k-1] = lst[k-1], lst[0]
yield from permute(lst, k-1)
for i in permute([1, 2, 3, 4]):
print(i)

Resources