I am new to O-notation and am trying to find the worst-case runtime for some of my codes. The only issue is that I'm confused on how O-notation runs with indexing and appending so I thought I'd ask for help with the following sample codes:
def sums_1(L):
n = len(L)
tot = 0
M = []
for i in L[:n//2]:
M.append(i)
for i in L[n//2:]:
M.extend(L)
return sum(M)
def sums_2(s):
def help_e(s, pos):
if pos >= len(s):
return ''
return help_e(s, pos+1) + s[pos]
return help_e(s, 0)
I think both codes would run o(n) times but I wanted some clarification on indexing and how that may affect the runtime, thanks!
Here you have the wiki file for the big-o notation for almost every python data-structure operations: https://wiki.python.org/moin/TimeComplexity
Related
def fibonaci(i,memo):
if i == 0 or i == 1:
return i
if memo[i]==-1:
memo[i] = fibonaci(i-1,memo) + fibonaci(i-2,memo)
return memo[i]
def fibo(n):
a = []
return fibonaci(n,a)
print(fibo(2))
I'm a Java programmer learning python. This algorithm computes the nth fibonacci number using recursion + memoization. I don't understand why I'm seeing this error "IndexError: list index out of range" when running the program in python. Can anybody help? Thanks a ton!
As suggested in the comments to your question, there is no way memo[i]==-1 could be true.
I understand your want to test something like "if the value for fibonacci(i) has not yet been memoized", but the way Python will tell you in index is not present is certainly not by returning some magic value (like -1), but instead by raising an exception.
Looking up "EAFP" (easier to ask for forgiveness than permission) on your favorite search engine might show you why exceptions are not to be understood as errors, in python.
In addition, memoization will be preferably implemented by a dictionary, rather than a list (because dictionaries allow to map a value to any possible key, not necessarily to the next integer index value).
Without changing too much to the structure of your program, I would suggest the following :
def fibonaci(i,memo):
if i == 0 or i == 1:
return i
try:
memo[i]
except KeyError:
memo[i] = fibonaci(i-1,memo) + fibonaci(i-2,memo)
return memo[i]
def fibo(n):
a = {}
return fibonaci(n,a)
print(fibo(2))
I made few changes in your code to get nth Fibonacci no.(there might be other way too)
def fibonaci(i,memo):
if i == 0 or i == 1:
return i
if memo[i] == -1:
memo[i] = fibonaci(i-1,memo) + fibonaci(i-2,memo)
return memo[i]
def fibo(n):
a = [-1] * n
return fibonaci(n-1,a)
print(fibo(5))
print(fibo(10))
print(fibo(13))
print(fibo(57))
And output is :-
3
34
144
225851433717
You are seeing the error because you create an empty list with a = [] and then you try to look into it. You need to create a list long enough to index from zero up to n-1.
That being said, an easy way to do memoisation in Python is using the cache function from functools. This code does the memoisation for you:
from functools import cache
#cache
def fib(n):
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)
for n in range(10):
print(f"{fib(n) = }")
It isn't quite as efficient in running time, but in programmer time it is.
I try to solve the problem 28. Implement Str on LeetCode.
However, I have some questions about the time complexity of the two versions of the implemented codes.
# Version 1
class Solution:
def strStr(self, haystack, needle):
len_h = len(haystack)
len_n = len(needle)
if not needle:
return 0
if len_n > len_h:
return -1
i = 0
while i<len_h :
found = True
if haystack[i] == needle[0]:
for j in range(len_n):
if i+j >= len_h or haystack[i+j] != needle[j]:
found = False
break
if found:
return i
i += 1
return -1
In this version, I try to find the needle substring in the haystack using the double loops.
I think the time complexity of the code is O(mn) where m is the length of the haystack and n is the length of the needle.
Unfortunately, the code cannot pass the tests due to the time exceeding.
Then, I try to optimize my code and get version 2 of the code.
# Version 2
class Solution:
def strStr(self, haystack, needle):
len_h = len(haystack)
len_n = len(needle)
if not needle:
return 0
if len_n > len_h:
return -1
i = 0
while i<len_h :
found = True
if haystack[i] == needle[0]:
if haystack[i:i+len_n] == needle:
return i
i += 1
return -1
I compare the needle and the substring of the haystack using string-slice and '==' instead of the manual comparison. Then, the code passes the tests.
Now, I have some questions:
What is the time complexity of the string slice?
What is the time complexity of the check operation (==) between two strings?
Why version 2 is fast than version 1 if the time complexity of the check operation is O(n)?
Thanks for any advice.
str.__eq__(self, other) (that is, equality for strings) is implemented in C and is lightning fast (as fast as any other language once it starts).
Your Python-implemented character-wise string comparison is slow for two reasons. First, the looping logic is implemented in Python, and Python loops are never very fast. Second, when you say needle[j] that is slicing one string to construct another one. That by itself is slow, and you do it in a nested loop, so the overall runtime will be disastrous. You end up calling str.__eq__ once per character, and every time it's called it has to check the length of the strings on each side (it does not know you just sliced a single character).
Let's say I have a string
S = "qwertyu"
And I want to build a list using recursion so the list looks like
L = [u, y, t, r, e, w, q]
I tried to write code like this:
def rec (S):
if len(S) > 0:
return [S[-1]].append(rec(S[0:-1]))
Ideally I want to append the last element of a shrinking string until it reaches 0
but all I got as an output is None
I know I'm not doing it right, and I have absolutely no idea what to return when the length of S reaches 0, please show me how I can make this work
(sorry the answer has to use recursion, otherwise it won't bother me)
Thank you very much!!!
There are many simpler ways than using recursion, but here's one recursive way to do it:
def rec (S):
if not S:
return []
else:
temp = list(S[-1])
temp.extend(rec(S[:-1]))
return temp
EDIT:
Notice that the base case ensures that function also works with an empty string. I had to use temp, because you cannot return list(S[-1]).extend(rec(S[:-1])) due to it being a NoneType (it's a method call rather than an object). For the same reason you cannot assign to a variable (hence the two separate lines with temp). A workaround would be to use + to concatenate the two lists, like suggested in Aryerez's answer (however, I'd suggest against his advice to try to impress people with convoluted one liners):
def rec (S):
if not S:
return []
else:
return list(S[-1]) + rec(S[:-1])
In fact using + could be more efficient (although the improvement would most likely be negligible), see answers to this SO question for more details.
This is the simplest solution:
def rec(S):
if len(S) == 1:
return S
return S[-1] + rec(S[:-1])
Or in one-line, if you really want to impress someone :)
def rec(S):
return S if len(S) == 1 else S[-1] + rec(S[:-1])
Since append mutates the list, this is a bit difficult to express recursively. One way you could do this is by using a separate inner function that passes on the current L to the next recursive call.
def rec(S):
def go(S, L):
if len(S) > 0:
L.append(S[-1])
return go(S[0:-1], L)
else:
return L
return go(S, [])
L = [i for i in S[::-1]]
It should work.
I am trying to write a piece of code that will generate a permutation, or some series of characters that are all different in a recursive fashion.
def getSteps(length, res=[]):
if length == 1:
if res == []:
res.append("l")
res.append("r")
return res
else:
for i in range(0,len(res)):
res.append(res[i] + "l")
res.append(res[i] + "r")
print(res)
return res
else:
if res == []:
res.append("l")
res.append("r")
return getSteps(length-1,res)
else:
for i in range(0,len(res)):
res.append(res[i] + "l")
res.append(res[i] + "r")
print(res)
return getSteps(length-1,res)
def sanitize(length, res):
return [i for i in res if len(str(i)) == length]
print(sanitize(2,getSteps(2)))
So this would return
"LL", "LR", "RR, "RL" or some permutation of the series.
I can see right off the bat that this function probably runs quite slowly, seeing as I have to loop through an entire array. I tried to make the process as efficient as I could, but this is as far as I can get. I know that some unnecessary things happen during the run, but I don't know how to make it much better. So my question is this: what would I do to increase the efficiency and decrease the running time of this code?
edit = I want to be able to port this code to java or some other language in order to understand the concept of recursion rather than use external libraries and have my problem solved without understanding it.
Your design is broken. If you call getSteps again, res won't be an empty list, it will have garbage left over from the last call in it.
I think you want to generate permutations recursively, but I don't understand where you are going with the getSteps function
Here is a simple recursive function
def fn(x):
if x==1:
return 'LR'
return [j+i for i in fn(x-1) for j in "LR"]
Is there a way to combine the binary approach and a recursive approach?
Yes, and #gribbler came very close to that in the post to which that comment was attached. He just put the pieces together in "the other order".
How can you construct all the bitstrings of length n, in increasing order (when viewed as binary integers)? Well, if you already have all the bitstrings of length n-1, you can prefix them all with 0, and then prefix them all again with 1. It's that easy.
def f(n):
if n == 0:
return [""]
return [a + b for a in "RL" for b in f(n-1)]
print(f(3))
prints
['RRR', 'RRL', 'RLR', 'RLL', 'LRR', 'LRL', 'LLR', 'LLL']
Replace R with 0, and L with 1, and you have the 8 binary integers from 0 through 7 in increasing order.
You should look into itertools. There is a function there called permutations which does exactly what you want to achieve here.
I am new to programming, and I am trying to write a Vigenère Encryption Cipher using python. The idea is very simple and so is my function, however in this line:
( if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')): )
It seems that I have an index problem, and I can't figure out how to fix it.
The error message is:
IndexError: string index out of range
I tried to replace the i+1 index by another variable equal to i+1, since I thought that maybe python is re-incrementing the i, but it still won't work.
So my questions are:
How to fix the problem, and what have I done wrong?
Looking at my code, what can I learn to improve my programming skills?
I want to build a simple interface to my program (which will contain all the encryption ciphers), and all I came up with from Google is pyqt, but it just seems too much work for a very simple interface, so is there a simpler way to build an interface? (I am working with Eclipse Indigo and pydev with Python3.x)
The Vigenère Encryption function (which contains the line that causes the problem) is:
def Viegner_Encyption_Cipher(Key,String):
EncryptedMessage = ""
i = 0
j = 0
BinKey = Bin_It(Key)
BinString = Bin_It(String)
BinKeyLengh = len(BinKey)
BinStringLengh = len(BinString)
while ((BinKeyLengh > i) and (BinStringLengh > j)):
if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')):
EncryptedMessage = EncryptedMessage + BinKey[i]
else:
EncryptedMessage = EncryptedMessage + Xor(BinKey[i],BinString[j])
i = i + 1
j = j + 1
if (i == BinKeyLengh):
i = i+j
return EncryptedMessage
This is the Bin_It function:
def Bin_It(String):
TheBin = ""
for Charactere in String:
TheBin = TheBin + bin(ord(Charactere))
return TheBin
And finally this is the Xor function:
def Xor(a,b):
xor = (int(a) and not int(b)) or (not int(a) and int(b))
if xor:
return chr(1)
else:
return chr(0)
In your while condition, you are ensuring that i < len(BinKey). This means that BinKey[i] will be valid, but BinKey[i+1] will not be valid at the last iteration of your loop, as you will then be accessing BinKey[len(BinKey)], which is one past the end of your string. Strings in python start at 0 and end at len-1 inclusive.
To avoid this, you can update your loop criterion to be
while BinKeyLength > i+1 and ...:
You can either change
while ((BinKeyLengh > i) and (BinStringLengh > j)):
to
while ((BinKeyLengh > i-1) and (BinStringLengh > j)):
or change
if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')):
to
if((BinKey[i] == 'b') or (BinKeyLengh > i-1 and BinKey[i+1] == 'b')):
This will avoid trying to go into BinKey[BinKeyLength], which is out of scope.
Looking at my code, what can I learn to improve my programming skills?
Looping over an index is not idiomatic Python. It's better to loop over the elements of an iterator where possible. After all, that's usually what you're interested in: for i in... is often followed by my_list[i].
In this example, you should use the built-in function zip (or itertools.izip if your code is lazy, though this isn't necessary in Python 3), which gives you pairs of values from two or more iterators, and stops when the shortest one is exhausted.
for key_char, string_char in zip(BinKey, BinString): # takes values sequentially from
# BinKey and BinString
# and binds them to the names
# key_char and string_char
# do processing on key_char and string_char
If you really must do a while loop on an index, then put the test the other way around so it's clearer what you're doing. Compare
while len(BinKey) > i and len(BinString) > j: # this looks like len(BinKey) and
# len(BinString) are varying and you're
# comparing them to static variables i and j
with
while i < len(BinKey) and j < len(BinString): # this looks like you're varying i and j
# and comparing them to len(BinKey) and len(BinString)
Which better communicates the purpose of the loop?
Finally, the clause
if (i == BinKeyLengh):
i = i+j
doesn't seem to do anything. If i == BinKeyLength then the while loop will stop in a moment anyway.
I think your error, as Python interpreter says, is that you accessing invalid array positions.
In order to solve this, unlike it's being said, you should change your code to
while (BinKeyLength > i+2 and ...):
This is because in the last step, BinKeyLength = i+2, then i+1 is BinKeyLength-1, which is the last position of your array.
Concerning your programming skills I recommend you two things:
Be the code. Sounds mystic but the most important thing that is missing here is figuring out which indices numbers are used.
As it has been said by rubik, follow some style guides like PEP8 style guide.