Search optimisation in Python - python-3.x

def CountingVallys(PathsTaken):
#Converts the Strings U and D into 1 and -1 respectively
Separate_Paths = [i for i in PathsTaken]
for index, i in enumerate(Separate_Paths):
if i == "D":
Separate_Paths[index] = -1
else:
Separate_Paths[index] = 1
Total_travels = [sum(Separate_Paths[0:i+1]) for i in range(len(Separate_Paths))]
#ValleyDistance shows the indexes where the traveller is below sea level and Valley Depth shows the depth at those
#Indexes
ValleyDistance = []
ValleyDepth = []
for Distance, Depth in enumerate(Total_travels):
if Depth < 0:
ValleyDistance.append(Distance)
ValleyDepth.append(Depth)
#Checks the distance between each index to shows if the valley ends (Difference > 1)
NumberOfValleys = []
DistanceOfValleys = []
TempDistance = 1
for index, Distance in enumerate(ValleyDistance):
# Check if final value, if so, check if the valley is distance 1 or 2 and append the final total of valleys
if ValleyDistance[index] == ValleyDistance[-1]:
if ValleyDistance[index] - ValleyDistance[index - 1] == 1:
TempDistance = TempDistance + 1
DistanceOfValleys.append(TempDistance)
NumberOfValleys.append(1)
elif ValleyDistance[index] - ValleyDistance[index - 1] > 1:
DistanceOfValleys.append(TempDistance)
NumberOfValleys.append(1)
#For all indexes apart from the final index
if ValleyDistance[index] - ValleyDistance[index-1] == 1:
TempDistance = TempDistance + 1
elif ValleyDistance[index] - ValleyDistance[index-1] > 1:
DistanceOfValleys.append(TempDistance)
NumberOfValleys.append(1)
TempDistance = 1
NumberOfValleys = sum(NumberOfValleys)
return NumberOfValleys
if __name__ == "__main__":
Result = CountingVallys("DDUDUUDUDUDUD")
print(Result)
An avid hiker keeps meticulous records of their hikes. Hikes always start and end at sea level, and each step up (U) or down (D) represents a unit change in altitude. We define the following terms:
A valley is a sequence of consecutive steps below sea level, starting with a step down from sea level and ending with a step up to sea level.
Find and print the number of valleys walked through.
In this question I was flagged due to my execution being too long and im wondering if there is any clear optimisations I could make to make it faster. I believe the use of "for-loops" is to blame but im not sure of any other ways to execute my steps.

Total_travels = [sum(Separate_Paths[0:i+1]) for i in range(len(Separate_Paths))]
In above code, why do you want to repeat the computation which you have already performed?
sum(Separate_Paths[0:i+1]) = sum(Separate_Paths[0:i] + Separate_Paths[i+1]
You can make the list Total_travels in efficient manner. That should take care of the too long execution time of your program.
>>> a
[2, 6, 4, 9, 10, 3]
>>> cumulative_sum = []
>>> sum_till_now = 0
>>> for x in a:
... sum_till_now += x
... cumulative_sum.append(sum_till_now)
...
>>> cumulative_sum
[2, 8, 12, 21, 31, 34]
>>>
numpy has a built-in cumsum which I believe to be overkill for your problem.

Here is an alternative solution:
By using a stack it would be easy to find the valleys.
for every new path of the same type ("D" or "U") push it into the stack
Before pushing, check for the last item and if they are opposite then remove it (if(stack[-1]!=path[i]): stack.pop()) from the stack and remember at ( last_path=path[i] )
if the stack is empty so it means there was and cutting with sea level (valley or mountain). If the last path was "U" so it means the was a valley so we count it (if(len(stack)==0): if(last_path=='U'): valleys+=1).
there is a possibility of cutting with the sea level at the end of the path so we need to handle it outside of the loop as well ( if(len(stack)==0 and last_path=='U'): valleys+=1 ).
Here is the code:
def countingValleys(steps, path):
stack=[]
valleys=0
last_path=None
for i in range(len(path)):
if(len(stack)==0):
if(last_path=='U'):
valleys+=1
stack.append(path[i])
else:
if(stack[-1]!=path[i]):
stack.pop()
last_path=path[i]
else:
stack.append(path[i])
if(len(stack)==0 and last_path=='U'):
valleys+=1
return valleys
print(countingValleys(12,"DUDUDUUUUDDDDU"))

Related

Why is my python function not working properly when I call it recursively?

I'm doing a question from a previous Waterloo ccc competition (https://cemc.uwaterloo.ca/contests/computing/2020/ccc/juniorEF.pdf problem J5)
and my code isn't working the way I expected
Here's the sample input I'm using:
3
4
3 10 8 14
1 11 12 12
6 2 3 9
Here's my code so far
y_size = int(input())
x_size = int(input())
mat = []
"ok".split()
for i in range(y_size):
row = input().split()
mat.append(row)
pos_list = [[0, 0]]
current_num = int(mat[0][0])
a = 0
def canEscape():
global a
global mat
global pos_list
global current_num
end = y_size * x_size
if y_size -1 * x_size -1 == current_num:
return True
for i in range(y_size):
print("______")
for j in range(x_size):
v = (i + 1) * (j + 1)
print(v)
print(current_num)
if v == current_num:
print("ok")
if v == end:
print("ok")
a += 1
current_num = mat[i][j]
pos_list.append([i, j])
canEscape()
pos_list.pop(-1)
a -= 1
current_num = mat[pos_list[a][0]][pos_list[a][1]]
canEscape()
The problem I'm having is that I expect if v == current_num: to be true when I call it again. Both current_num and v are equal to 8 but the code seems to carry on with the for-in loop and break, without entering the if statement. I've made the output print v followed by current_num for every iteration of the for loop to try and figure out the problem but it seems that both variables == 8 so I really don't know what I did wrong. Did I make a silly mistake or did I structure my whole program wrong?
I'm having trouble following what your program is doing at all. This problem involves integer factoring, and I do not see where you're factoring integers. You definitely are not understanding that aspect of the problem.
When you calculate what cells you can go to you look at the value of your current cell. Lets say it is 6. 6 has the factors 1, 2, 3, and 6 because all of those numbers can be multiplied by another number to equal 6. So, you can go to the cells (1, 6), (6, 1), (2, 3), and (3, 2), because those are the pairs of numbers that can be multiplied together to equal 6.
Also, you never convert the lines of input into integers. When you append to the matrix, you are appending a list of strings that happen to be numbers. You must convert those into integers.
Anyways, this program will solve the problem. I copy and pasted the factoring algorithm from other threads:
n_rows = int(input())
n_cols = int(input())
mat = []
for i in range(n_rows):
mat.append(list(map(lambda x: int(x), input().split()))) # Convert input strings to integers.
def reduce(f, l):
# This is just needed for the factoring function
# It's not relevant to the problem
r = None
for e in l:
if r is None:
r = e
else:
r = f(r, e)
return r
def factors(n):
# An efficient function for calculating factors.
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(n**0.5) + 1) if n % i == 0)))
def get_pairs(items):
for i in range(len(items) // 2):
yield (items[i],items[len(items) - 1 - i]) # use yield to save memory
if(len(items) % 2 != 0): # This is for square numbers.
n = items[len(items) // 2]
yield (n,n)
checked_numbers = set()
def isPath(r=1, c=1):
# check if the testing row or column is to large.
if r > n_rows or c > n_cols:
return False
y = r - 1
x = c - 1
n = mat[y][x]
# If we've already checked a number with a certain value we dont need to check it again.
if n in checked_numbers:
return False
checked_numbers.add(n)
# Check if we've reached the exit.
if(r == n_rows and c == n_cols):
return True
# Calculate the factors of the number, and then find all valid pairs with those factors.
pairs = get_pairs(sorted(list(factors(n))))
# Remember to check each pair with both combinations of every pair of factors.
# If any of the pairs lead to the exit then we return true.
return any([isPath(pair[0], pair[1]) or isPath(pair[1], pair[0]) for pair in pairs])
if isPath():
print("yes");
else:
print("no");
This works and it is fast. However, it if you are limited on memory and/or have a large data input size your program could easily run out of memory. I think it is likely that this will happen with some of the testing inputs but I'm not sure.. It is surely possible to write this program in a way that would use a fraction of the memory, perhaps by converting the factors function to a function that uses iterators, as well as converting the get_pairs function to somehow iterate as well.
I would imagine that this solution solves most of the testing inputs they have but will not solve the ones towards the end, because they will be very large and it will run out of memory.

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]

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)

Maximum Number in Mountain Sequence

Given a mountain sequence of n integers which increase firstly and then decrease, find the mountain top.
Example
Given nums = [1, 2, 4, 8, 6, 3] return 8
Given nums = [10, 9, 8, 7], return 10
class Solution:
"""
#param nums: a mountain sequence which increase firstly and then decrease
#return: then mountain top
"""
def mountainSequence(self, nums):
# write your code here
if nums == []:
return None
if len(nums) <= 1:
return nums[0]
elif len(nums) <= 2:
return max(nums[0], nums[1])
for i in range(len(nums) -2):
if nums[i] >= nums[i + 1]:
return nums[i]
return nums[-1]
it stuck at [3,5,3]. Based on my analysis, it went wrong after running the for loop. But I cannot figure it out why the for loop failed.
this should be more efficient than your approach. it is a binary search customized for your use-case:
def top(lst):
low = 0
high = len(lst)
while low != high:
i = (high+low)//2
if lst[i] < lst[i+1]:
low = i+1
else:
high = i
return low
it starts in the middle of the list and checks if the series is still increasing there. if it is it sets low and will ignore all indices below low for the rest of the algorithm. if the series decreases already, high is set to the current index and all the elements above are ignored. and so on... when high == low the algorithm terminates.
if you have two or more of the same elements at the maximum of your list (a plateau) the algorithm will not even terminate.
and i skipped the tests for empty lists or lists of length 1.
This will get all triplets from your input, isolate all that are higher in the middle then left or right and return the one that is highest overall:
def get_mountain_top(seq):
triplets = zip(seq, seq[1:], seq[2:])
tops = list(filter(lambda x: x[0] < x[1] > x[2], triplets))
if tops:
# max not allowed, leverage sorted
return sorted(tops, key = lambda x:x[1])[-1]
# return max(tops,key = lambda x:x[1])
return None
print(get_mountain_top([1,2,3,4,3,2,3,4,5,6,7,6,5]))
print(get_mountain_top([1,1,1]))
Output:
(6,7,6)
None
It does not handle plateaus.
Doku:
zip(), filter() and max()

Binary Search implementation in Python

I am trying to implement a solution using binary search. I have a list of numbers
list = [1, 2, 3, 4, 6]
value to be searched = 2
I have written something like this
def searchBinary(list, sval):
low = 0
high = len(list)
while low < high:
mid = low + math.floor((high - low) / 2)
if list[mid] == sval:
print("found : ", sval)
elif l2s[mid] > sval:
high = mid - 1
else:
low = mid + 1
but when I am trying to implement this, I am getting an error like: index out of range. Please help in identifying the issue.
A few things.
Your naming is inconsistent. Also, do not use list as a variable name, you're shadowing the global builtin.
The stopping condition is while low <= high. This is important.
You do not break when you find a value. This will result in infinite recursion.
def searchBinary(l2s, sval): # do not use 'list' as a variable
low = 0
high = len(l2s)
while low <= high: # this is the main issue. As long as low is not greater than high, the while loop must run
mid = (high + low) // 2
if l2s[mid] == sval:
print("found : ", sval)
return
elif l2s[mid] > sval:
high = mid - 1
else:
low = mid + 1
And now,
list_ = [1, 2, 3, 4, 6]
searchBinary(list_, 2)
Output:
found : 2
UPDATE high = len(lst) - 1 per comments below.
Three issues:
You used l2s instead of list (the actual name of the parameter).
Your while condition should be low <= high, not low < high.
You should presumably return the index when the value is found, or None (or perhaps -1?) if it's not found.
A couple other small changes I made:
It's a bad idea to hide the built-in list. I renamed the parameter to lst, which is commonly used in Python in this situation.
mid = (low + high) // 2 is a simpler form of finding the midpoint.
Python convention is to use snake_case, not camelCase, so I renamed the function.
Fixed code:
def binary_search(lst, sval):
low = 0
high = len(lst) - 1
while low <= high:
mid = (low + high) // 2
if lst[mid] == sval:
return mid
elif lst[mid] > sval:
high = mid - 1
else:
low = mid + 1
return None
print(binary_search([1, 2, 3, 4, 6], 2)) # 1

Resources