How to make this greedy function faster? - python

I am trying to solve a problem and my code fails at one test case where the list is of length 25000. Is there any way I can make this faster. I tried using functools.lru_cache and I still can not run within the time required to complete.
This is the problem from the site
Given an array of non-negative integers nums, you are initially
positioned at the first index of the array.
Each element in the array represents your maximum jump length at that
position.
Determine if you are able to reach the last index.
This is what I have tried
def can_jump(input_list):
#lru_cache
def helper(idx = 0):
if idx == len(input_list) - 1:
return True
return any(helper(new_idx) for x in range(input_list[idx]) \
if (new_idx := idx + x + 1) < len(input_list)) # increasing order of jumps
return helper()
Sample test cases work
input_list = [2,3,1,1,4]
print(can_jump(input_list)) # True
input_list = [3,2,1,0,4]
print(can_jump(input_list)) # False
I have also tried going from the other direction,
return any(helper(new_idx) for x in range(input_list[idx], 0, -1) \
if (new_idx := idx + x) < len(input_list)) # decreasing order of jumps
But I still can not make this run fast enough to clear the last test case of 25000 element list, what is it that I am doing wrong here?

Ok, I think I get it. Can you try this? Please note, this is taken straight from: https://codereview.stackexchange.com/questions/223612/jump-game-leetcode
def canjump(nums):
maximum_reachable_index = 0
for index, max_jump in enumerate(nums):
if index > maximum_reachable_index:
return False
maximum_reachable_index = max(index + max_jump, maximum_reachable_index)
return True

Related

Python noob question. Looping through all to find correct combination

Sorry for noob question. Just started learning coding with Python - beginners level.
Wasn't able to find in the net what I require.
Creating function to loop through the entire line, in order to find right combination - 7,8,9 - regardless of input length and of target position, and return 'true' if found. Wasn't able to devise the function correctly. Not sure how to devise function clearly and at all this far.
Your help is much appreciated.
This is what I came up with so far (not working of course):
def _11(n):
for loop in range(len(n)):
if n[loop]==[7,8,9]:
return True
else:
return False
print(_11([1000,10,11,34,67,89,334,5567,6534,765,2,3,5,6,112,7,8,9,11111]))
It always returns False. Tried with (*n) to no avail.
The answer offered by #Carson is entirely correct.
I offer this not really as an answer to the question but as an alternative and more efficient approach.
In OP's question he is looking for an occurrence of 3 consecutive values described by way of a list. Let's call that a triplet.
If we iterate over the input list one element at a time we create lots of triplets before comparing them.
However, we can make this more efficient by searching the input list for any occurrence of the first item in the target triplet. In that way we are likely to slice the input list far less often.
Here are two implementations with timings...
from timeit import timeit
def _11(n, t):
offset = 0
lt = len(t)
m = len(n) - lt
while offset < m:
try:
offset += n[offset:].index(t[0])
if n[offset:offset+lt] == t:
return True
offset += 1
except ValueError:
break
return False
def _11a(n, t):
for index in range(len(n) - len(t)):
if n[index:index + len(t)] == t:
return True
return False
n = [1000,10,11,34,67,89,334,5567,6534,765,2,3,5,6,112,7,8,9,11111]
t = [7, 8, 9]
for func in _11, _11a:
print(func.__name__, timeit(lambda: func(n, t)))
Output:
_11 0.43439731000012216
_11a 1.8685798310000337
There are two mistakes with your code.
Indexing into a loop returns 1 element, not multiple. When you write n[loop], you're getting 1 value, not a list.
You shouldn't return false that early. Your code exits after the first step in the loop, but it should go through the entire loop before returning false.
Consider the following snippet:
def has_subarr(arr, subarr):
"""Tests if `subarr` exists in arr"""
for i in range(len(arr)):
if arr[i:i+len(subarr)] == subarr:
return True
return False
This code is more general than your example, it accepts the value to check for as another argument. Notice the use of : in the array access. This allows you to return multiple elements in an array. Also notice how the return False is only reached once the entire loop has completed.
First, n[loop] return a single element, not a sublist. You should use n[loop+3]. But this will introduce a problem where loop+3 exceeds the length of the list. So the solution may be:
def _11(n):
for loop in range(len(n)-3):
if n[loop:loop+3]==[7,8,9]:
return True
else:
return False
print(_11([1000,10,11,34,67,89,334,5567,6534,765,2,3,5,6,112,7,8,9,11111]))
Your actual code return during the first iteration. You only test once. You must modify the indentation as in:
def _11(n):
target = [7,8,9]
for index in range( len(n) - len(target)):
if n[index:index + len(target)] == [7,8,9]:
return True
return False
print(_11([1000,10,11,34,67,89,334,5567,6534,765,2,3,5,6,112,7,8,9,11111]))
You can try checking the str representation of the 2 lists:
import re
def _11(n):
if re.search("(?<![0-9-.'])7, 8, 9(?![0-9.])",str(n)):
return True
return False
print(_11([27,8,9]))
The Output:
False

Leetcode jumpgame recursive approach

Given the following problem:
You are given an integer array nums. You are initially positioned at the array's first index, and each element in the array represents your maximum jump length at that position.
Return true if you can reach the last index, or false otherwise.
Example 1:
Input: nums = [2,3,1,1,4]
Output: True
Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.
Example 2:
Input: nums = [3,2,1,0,4]
Output: False
Explanation: You will always arrive at index 3 no matter what. Its maximum jump length is 0, which makes it impossible to reach the last index.
I am trying to come up with a recursive solution. This is what I have so far. I am not looking for the optimal solution. I am just trying to solve using recursion for now. If n[i] is 0 I want the loop to go back to the previous loop and continue recursing, but I can't figure out how to do it.
def jumpGame(self, n: []) -> bool:
if len(n) < 2:
return True
for i in range(len(n)):
for j in range(1, n[i]+1):
next = i + j
return self.jumpGame(n[next:])
return False
If you want to do recursively and you said no need to be optimal ( so not memoized ), you could go with the below method. You don't need nested loops.
Also no need to explore all paths, you could optimize by looking at the step that you are going by checking i + (jump) < n
def jumpGame(a, i):
if i > len(a) - 1:
return False
if i == len(a) - 1:
return True
reached = False
for j in range(1, a[i] + 1):
if i + j < len(a):
reached = jumpGame(a, i + j)
if reached:
return True
return reached
print(jumpGame([2, 3, 1, 1, 4], 0))
print(jumpGame([3,2,1,0,4], 0))
True
False
When considering recursive solutions, the first thing you should consider is the 'base case', followed by the 'recursive case'. The base case is just 'what is the smallest form of this problem for which I can determine an answer', and the recursive is 'can I get from some form n of this problem to some form n - 1'.
That's a bit pedantic, but lets apply it to your situation. What is the base case? That case is if you have a list of length 1. If you have a list of length 0, there is no last index and you can return false. That would simply be:
if len(ls) == 0:
return False
if len(ls) == 1:
return True
Since we don't care what is in the last index, only at arriving at the last index, we know these if statements handle our base case.
Now for the recursive step. Assuming you have a list of length n, we must consider how to reduce the size of the problem. This is by making a 'jump', and we know that we can make a jump equal to a length up to the value of the current index. Then we just need to test each of these lengths. If any of them return True, we succeed.
any(jump_game(n[jump:] for jump in range(1, n[0] + 1)))
There are two mechanisms we are using here to make this easy. any takes in a sequence and quits as soon as one value is True, returning True. If none of them are true, it will return False. The second is a list slice, n[jump:] which takes a slice of a list from the index jump to the end. This might result in an empty list.
Putting this together we get:
def jump_game(n: list) -> bool:
# Base cases
if len(n) == 0:
return False
if len(n) == 1:
return True
# Recursive case
return any(jump_game(n[jump:]) for jump in range(1, n[0] + 1))
The results:
>>> jump_game([2,3,1,1,4])
True
>>> jump_game([3,2,1,0,1])
False
>>> jump_game([])
False
>>> jump_game([1])
True
I'm trying to lay out the rigorous approach here, because I think it helps to clarify where recursion goes wrong. In your recursive case you do need to iterate through your options - but that is only one loop, not the two you have. In your solution, in each recursion, you're iterating (for i in range(len(n))) through the entire list. So, you're really hiding an iterative solution inside a recursive one. Further, your base case is wrong, because a list of length 0 is considered a valid solution - but in fact, only a list of length 1 should return a True result.
What you should focus on for recursion is, again, solving the smallest possible form(s) of the problem. Here, it is if the list is one or zero length long. Then, you need to step each other possible size of the problem (length of the list) to a base case. We know we can do that by examining the first element, and choosing to jump anywhere up to that value. This gives us our options. We try each in turn until a solution is found - or until the space is exhausted and we can confidently say there is no solution.

Binary search on array with duplicate

First time posting here, so apologies in advance if I am not following best practices. My algorithm is supposed to do the following in a sorted array with possible duplicates.
Return -1 if the element does not exist in the array
Return the smallest index where the element is present.
I have written a binary search algorithm for an array without duplicate. This returns a position of the element or -1. Based on blackbox testing, I know that the non-duplicate version of the binary search works. I have then recursively called that function via another function to search from 0 to position-1 to find the first incidence of the element, if any.
I am currently failing a black box test. I am getting a wrong answer error and not a time out error. I have tried most of the corner cases that I could think of and also ran a brute force test with the naive search algorithm and could not find an issue.
I am looking for some guidance on what might be wrong in the implementation rather than an alternate solution.
The format is as follow:
Input:
5 #array size
3 4 7 7 8 #array elements need to be sorted
5 #search query array size
3 7 2 8 4 #query elements
Output
0 2 -1 4 1
My code is shown below:
class BinarySearch:
def __init__(self,input_list,query):
self.array=input_list
self.length=len(input_list)
self.query=query
return
def binary_search(self,low,high):
'''
Implementing the binary search algorithm with distinct numbers on a
sorted input.
'''
#trivial case
if (self.query<self.array[low]) or (self.query>self.array[high-1]):
return -1
elif (low>=high-1) and self.array[low]!=self.query:
return -1
else:
m=low+int(np.floor((high-low)/2))
if self.array[low]==self.query:
return low
elif (self.array[m-1]>=self.query):
return self.binary_search(low,m)
elif self.array[high-1]==self.query:
return high-1
else:
return self.binary_search(m,high)
return
class DuplicateBinarySearch(BinarySearch):
def __init__(self,input_list,query):
BinarySearch.__init__(self,input_list,query)
def handle_duplicate(self,position):
'''
Function handles the duplicate number problem.
Input: position where query is identified.
Output: updated earlier position if it exists else return
original position.
'''
if position==-1:
return -1
elif position==0:
return 0
elif self.array[position-1]!=self.query:
return position
else:
new_position=self.binary_search(0,position)
if new_position==-1 or new_position>=position:
return position
else:
return self.handle_duplicate(new_position)
def naive_duplicate(self,position):
old_position=position
if position==-1:
return -1
else:
while position>=0 and self.array[position]==self.query:
position-=1
if position==-1:
return old_position
else:
return position+1
if __name__ == '__main__':
num_keys = int(input())
input_keys = list(map(int, input().split()))
assert len(input_keys) == num_keys
num_queries = int(input())
input_queries = list(map(int, input().split()))
assert len(input_queries) == num_queries
for q in input_queries:
item=DuplicateBinarySearch(input_keys,q)
#res=item.handle_duplicate(item.binary_search(0,item.length))
#res=item.naive_duplicate(item.binary_search(0,item.length))
#assert res_check==res
print(item.handle_duplicate(item.binary_search(0,item.length)), end=' ')
#print(item.naive_duplicate(item.binary_search(0,item.length)), end=' ')
When I run a naive duplicate algorithm, I get a time out error:
Failed case #56/57: time limit exceeded (Time used: 10.00/5.00, memory used: 42201088/536870912.)
When I run the binary search with duplicate algorithm, I get a wrong answer error on a different test case:
Failed case #24/57: Wrong answer
(Time used: 0.11/5.00, memory used: 42106880/536870912.)
The problem statement is as follows:
Problem Statement
Update:
I could make the code work by making the following change but I have not been able to create a test case to see why the code would fail in the first case.
Original binary search function that works with no duplicates but fails an unknown edge case when a handle_duplicate function calls it recursively. I changed the binary search function to the following:
def binary_search(self,low,high):
'''
Implementing the binary search algorithm with distinct numbers on a sorted input.
'''
#trivial case
if (low>=high-1) and self.array[low]!=self.query:
return -1
elif (self.query<self.array[low]) or (self.query>self.array[high-1]):
return -1
else:
m=low+(high-low)//2
if self.array[low]==self.query:
return low
elif (self.array[m-1]>=self.query):
return self.binary_search(low,m)
elif self.array[m]<=self.query:
return self.binary_search(m,high)
elif self.array[high-1]==self.query:
return high-1
else:
return -1
Since you are going to implement binary search with recursive, i would suggest you add a variable 'result' which act as returning value and hold intermediate index which equal to target value.
Here is an example:
def binarySearchRecursive(nums, left, right, target, result):
"""
This is your exit point.
If the target is not found, result will be -1 since it won't change from initial value.
If the target is found, result will be the index of the first occurrence of the target.
"""
if left > right:
return result
# Overflow prevention
mid = left + (right - left) // 2
if nums[mid] == target:
# We are not sure if this is the first occurrence of the target.
# So we will store the index to the result now, and keep checking.
result = mid
# Since we are looking for "first occurrence", we discard right half.
return binarySearchRecursive(nums, left, mid - 1, target, result)
elif target < nums[mid]:
return binarySearchRecursive(nums, left, mid - 1, target, result)
else:
return binarySearchRecursive(nums, mid + 1, right, target, result)
if __name__ == '__main__':
nums = [2,4,4,4,7,7,9]
target = 4
(left, right) = (0, len(nums)-1)
result = -1 # Initial value
index = binarySearchRecursive(nums, left, right, target, result)
if index != -1:
print(index)
else:
print('Not found')
From your updated version, I still feel the exit point of your function is a little unintuitive.(Your "trivial case" section)
Since the only condition that your searching should stop, is that you have searched all possible section of the list. That is when the range of searching area is 0, there is no element left to be search and check. In implementation, that is when left < right, or high < low, is true.
The 'result' variable, is initialized as -1 when the function first been called from main. And won't change if there is no match find. And after each successful matching, since we can not be sure if it is the first occurrence, we will just store this index into the result. If there are more 'left matching', then the value will be update. If there is not, then the value will be eventually returned. If the target is not in the list, the return will be -1, as its original initialized value.

Binary search: weird middle point calculation

Regarding calculation of the list mid-point: why is there
i = (first +last) //2
and last is initialized to len(a_list) - 1? From my quick tests, this algorithm without -1 works correctly.
def binary_search(a_list, item):
"""Performs iterative binary search to find the position of an integer in a given, sorted, list.
a_list -- sorted list of integers
item -- integer you are searching for the position of
"""
first = 0
last = len(a_list) - 1
while first <= last:
i = (first + last) / 2
if a_list[i] == item:
return '{item} found at position {i}'.format(item=item, i=i)
elif a_list[i] > item:
last = i - 1
elif a_list[i] < item:
first = i + 1
else:
return '{item} not found in the list'.format(item=item)
The last legal index is len(a_list) - 1. The algorithm will work correctly, as first will always be no more than this, so that the truncated mean will never go out of bounds. However, without the -1, the midpoint computation will be one larger than optimum about half the time, resulting in a slight loss of speed.
Consider the case where the item you're searching for is greater than all the elements of the list. In that case the statement first = i + 1 gets executed repeatedly. Finally you get to the last iteration of the loop, where first == last. In that case i is also equal to last, but if last=len() then i is off the end of the list! The first if statement will fail with an index out of range.
See for yourself: https://ideone.com/yvdTzo
You have another error in that code too, but I'll let you find it for yourself.

Optimizing Python Function to Submit on Codewars

I have a solution for this problem on codewars.com that works when I run it in Sublime, but when I try to submit, I get this error:
Process was terminated. It took longer than 12000ms to complete
Why did my code time out?
Our servers are configured to only allow a certain amount of time for your code to execute. In rare cases the server may be taking on too much work and simply wasn't able to run your code efficiently enough. Most of the time though this issue is caused by inefficient algorithms. If you see this error multiple times you should try to optimize your code further.
The goal of the function is to find the next biggest number after a given number that you can make by rearranging the digits of a given number. For example, if I was given 216, I would need to return 261.
This is the code I have now:
import itertools
def next_bigger(n):
# takes a number like 472 and puts it in a list like so: [4, 7, 2]
num_arr = [int(x) for x in str(n)]
perms = []
total = ''
# x would be a permutation of num_arr, like [7, 2, 4]
for x in itertools.permutations(num_arr):
for y in x:
total += str(y)
perms.append(int(total))
total = ''
# bigger is all permutations that are bigger than n,
# so bigger[0] is the next biggest number.
# if there are no bigger permutations, the function returns -1
bigger = sorted([x for x in perms if x > n])
return bigger[0] if bigger else -1
I'm new to coding in Python, so is there some mistake I am making which causes my code to be extremely inefficient? Any suggestions are welcome.
Thanks for all the help you guys gave me. I ended up finding a solution from here using the Next Lexicographical Permutation Algorithm
This is my tidied up version of the solution provided here:
def next_bigger(n):
# https://www.nayuki.io/res/next-lexicographical-permutation-algorithm/nextperm.py
# https://www.nayuki.io/page/next-lexicographical-permutation-algorithm
# Find non-increasing suffix
arr = [int(x) for x in str(n)]
i = len(arr) - 1
while i > 0 and arr[i - 1] >= arr[i]:
i -= 1
if i <= 0:
return -1
# Find successor to pivot
j = len(arr) - 1
while arr[j] <= arr[i - 1]:
j -= 1
arr[i - 1], arr[j] = arr[j], arr[i - 1]
# Reverse suffix
arr[i : ] = arr[len(arr) - 1 : i - 1 : -1]
return int(''.join(str(x) for x in arr))
Why are you getting TLE (time limit exceeded)?
Because your algorithm has wrong complexity. How much permutations you will find for list with 3 elements? Only 6. But what if we use list with 23 elements? 25852016738884976640000.
This is too much for time limit.
So, if you want to have solve this problem you have to find solution without permutations. Please rethink how the numbers are written. The number 271 is bigger then 216 because the number on the second position has bigger value 7>1.
So, your solution has to find two numbers and swap them position. The number on the left have to smaller then the second one.
For example - for 111115444474444 you should find 5 and 7.
Then you swap them - and now you should sort sublist on right from the first position.
For example after swapped the values (111117444454444) you have to sort (444454444) -> (444444445). Now merge all, and you have solution.
import functools
def next_bigger(a):
a = map(int, str(a))
tmp = list(reversed(a))
for i, item_a in enumerate(reversed(a)):
for j in (range(i)):
if item_a < tmp[j]:
#you find index of number to swap
tmp[i]=tmp[j]
print(list(reversed(tmp[i:])))
tmp[j]=item_a
fin = list(reversed(tmp[i:])) + sorted(tmp[:i])
return functools.reduce(lambda x,y: x*10+y, fin)
return -1
A simple backtracking approach is to consider the digits one at a time. Starting from the most significant digit, pick the smallest number you have left that doesn't prevent the new number from exceeding the input. This will always start by reproducing the input, then will have to backtrack to the next-to-last digit (because there aren't any other choices for the last digit). For inputs like 897654321, the backtracking will immediately cascade to the beginning because there are no larger digits left to try in any of the intermediate slots.
You should sorting the num_arr in desc order and creating a number by combining the result.
Since OP required, next largest, OP needs to check starting from right, which right digit is larger then its very left digit and rotate their position.
Here is the final code:
def next_bigger(n):
num_arr = [int(x) for x in str(n)]
i = 0
i = len(num_arr) - 1
while(i > 0):
if num_arr[i] > num_arr[i-1]:
a = num_arr[i]
num_arr[i] = num_arr[i-1]
num_arr[i-1] = a
break
else:
i = i-1
newbig = "".join(str(e) for e in num_arr)
return int(newbig)
Now I edit to calculate next bigger element.
def perms(s):
if(len(s)==1):
return [s]
result=[]
for i,v in enumerate(s):
result += [v+p for p in perms(s[:i]+s[i+1:])]
return result
a=input()
b=perms(str(a))
if len(b)!=1:
for i in range(0,len(b)):
if b[i]==a:
print (b[i+1])
break
else:
print ("-1")

Categories