List comprehension with if else conditions - Python - python

so I've been trying to figure out how do I convert the main for loop in this code to a list comprehension for efficiency, I've seen some examples, but none of them seem to cater to this sort of scenario. Any help is appreciated!
key = 'abcdefghij'
def getChunks(key):
''' Dividing the key into byte sized chunks'''
currVal = ''
remainder = ''
chunks = []
currVal = ''
for char in key:
if len(currVal) == 8:
chunks.append(currVal)
currVal = hex(ord(char))[2:]
else:
currVal += hex(ord(char))[2:]
if len(currVal) == 8:
chunks.append(currVal)
elif currVal:
remainder = currVal
return (chunks, remainder)
print(getChunks(key))
The desired output dividing the input string/key into byte sized chunks of hexadecimal values + any remainder in the following format
>> (['61626364', '65666768'], '696a')
Oh and this too:
for i in range(1, self.hashCount+1):
h = hash(item, i) % self.bitArraySize # Generate Hash
# set the bit True in bit_array
self.bitArray[h] = True
for i in range(1, self.hashCount+1):
h = hash(item, i) % self.bitArraySize
if self.bitArray[h] == False:
return False

None of these should be list comprehensions. List comprehensions should not have side-effects (they're a functional programming construct and violating functional programming expectations leads to unmaintainable/unreadable code). In all cases, your loops aren't just building a new list element by element in order, they're also making stateful changes and/or building the list out of order.
Side-note: if self.bitArray[h] == False: is a slower, unPythonic way to spell if not self.bitArray[h]:; comparing to True and False is almost always the wrong way to go, per the PEP8 style guide:
Don't compare boolean values to True or False using ==:
# Correct:
if greeting:
# Wrong:
if greeting == True:

For question #1
key = 'abcdefghij'
def getChunks(key):
''' Dividing the key into byte sized chunks'''
hex_string = key.encode().hex()
length = len(hex_string)
sep = length%8
return [hex_string[i:i+8] for i in range(0, length-sep, 8)], hex_string[-sep:] if sep !=0 else ''
print(getChunks(key))

Related

Why is my function returning two duplicate values?

I'm doing a practice problem where I need to find the longest common prefix in a list of strings.
Example:
Input: strs = ["flower","flow","flight"]
Output: "fl"
I'm approaching this as follows:
def my_function(strs):
### get the length of shortest string in list and assign to "window"
min_len = min([len(x) for x in strs])
window = min_len
### chop all items in list to length of shortest item
for i in range(window):
if window == 0:
return ''
strs = [x[:window] for x in strs]
### Check the length of the set() of all items. If it's longer than 1,
### shorten the window and re-check
if len(set(strs)) == 1:
answer = list(set(strs))
return answer[0]
else:
window += -1
When I use the test case given above, my function returns:
fl
fl
Why am I getting this duplication?
You're using a weird loop where you increment "window" with the "for", but decrement it based on a condition. This causes an inconsistent iteration.
Either use a while loop and modify the counter manually, or use a for loop and don't modify the counter.
That said your approach of not so efficient, rather check the characters in parallel position after position:
def prefix(strs):
out = []
for x in zip(*strs):
if len(set(x)) == 1:
out.append(x[0])
else:
break
return ''.join(out)
prefix(strs)
Output: 'fl'
With #mozway's help, I was able to successfully execute the function using a while statement:
def prefix(strs):
min_len = min([len(x) for x in strs])
window = min_len
for i in range(window):
while window > 0:
strs = [x[:window] for x in strs]
if len(set(strs)) == 1:
answer = list(set(strs))
return answer[0]
else:
window += -1
return ''

Smallest Positive Number - Python - Codility

I am using Codelite to train myself as I just started learning Python. In the excercise "Smallest Positive Number", I used the code below and some test cases passed, but most of them not. How can I improve my code? (test cases like [1,2,3,4] and repetitive numbers like [5,6,2,4,6].
A = [2,1,-2,5,-6]
m = max(A)
temp = []
index = []
result = []
if m < 1:
print ("1")
elif len(A) == 1:
if A[0] == 1:
print("2")
else:
print("1")
else:
for i in range(len(A)):
if A[i] > 0:
temp.append(A[i])
temp.sort()
print(temp)
for j in range(len(temp)):
index.append(j+1)
if temp[j] != index[j]:
result.append(index[j])
print(min(result))
Thanks!
First you would want to store the smallest positive number. Start it at the first element of the list.
small_positive = A[0]
You would then want to iterate through the input
for number in A:
Then check if the number is positive. The easiest way is to see if it is greater then zero.
if number > 0:
After, check if the number is smaller then the current smallest. If so, overwrite it.
if number < small_positive:
small_positive = number
Lastly, you need to check if there were all negatives in the list. If small_positive is negative after the loop, there were no positives in the list.
if small_positive < 0:
return None
This is the easiest way to understand the solution, but there are many built in functions which are much simpler then this.
A = [2,-2,5,-6]
def small(a,b):
if a >= b: return b
else: return a
positive_list = [ele for ele in A if ele > 0]
res = min(positive_list)
if len(positive_list) == 0:
print("no positive number.")
else:
for i in range(len(positive_list)):
res = small(res, positive_list[i])
print(f"Smallest Positive number : {res}")
by this, yon can easily get a result.
For a start, this wouldn't work for the list [-42]:
m = max(A)
if m < 1:
print ("1")
There's no 1 anywhere in that given list so it should not be reported as the solution. Python would usually give you None in that scenario but the puzzle you've been set may have other ideas. It also wouldn't work with the list [] as max() will complain bitterly about the empty list.
You may think that one or both of those cases are unlikely but that's one of the things that makes the difference between a good coder and a not-so-good one. The former assume their users and testers are psychotic geniuses who know how to break code with near-zero effort :-)
To code it up by yourself, you can use the following pseudo-code(1) as a basis:
num_list = (some list of numbers)
min_pos = nothing
for num in num_list:
if num > 0 and (min_pos is nothing or num < min_pos): # >= ?
min_pos = num
print(min_pos)
Of course, if you're learning Python rather than learning programming, you should probably just use the facilities of the language, things will be much easier:
pos_list = [item for item in the_list if item > 0] # >= ?
min_pos = min(pos_list) if len(pos_list) > 0 else None
print(min_pos)
And, if you consider zero to be positive, make sure you use >= 0 instead of > 0, in both code blocks above where indicated.
Both of these snippets will successfully handle lists with no positive numbers, giving you a sentinel value (Python None or pseudo-code nothing).
(1) Yes, it looks a bit like Python but that's only because Python is the ultimate pseudo-code language :-)

Failing hidden tests in codesignal. What seems to be the problem in my code?

https://app.codesignal.com/arcade/intro/level-7/PTWhv2oWqd6p4AHB9
Here, the problem is that "Given an array of equal-length strings, you'd like to know if it's possible to rearrange the order of the elements in such a way that each consecutive pair of strings differ by exactly one character. Return true if it's possible, and false if not."
and my code is
import itertools
def only_one_element_different(x, y):
list1 = []
for i in x:
list1.append(i)
list2 = []
for j in y:
list2.append(j)
list3 = []
for k in range(0, len(list1)):
if list1[k] == list2[k]:
list3.append(True)
else:
list3.append(False)
if list3.count(False) == 1:
return True
else:
return False
def if_list_is_linear(list):
list4 = []
for m in range(0, len(list)-1):
if only_one_element_different(list[m], list[m+1]):
list4.append(True)
else:
list4.append(False)
if False in list4:
return False
else:
return True
list5 = ['aba', 'bbb','bab','bba']
list6 = list(itertools.permutations(list5))
list7 = []
for n in list6:
if if_list_is_linear(n) == True:
list7.append(True)
else:
list7.append(False)
if True in list7:
print("Real True")
else:
print("False")
​
(In list5, The array is given)
this passed all the tests but
failed several hidden tests.
I don't know if it is because of the timeout or flaws in my code.
Please help
(sorry for the bad english)
As I don't know what the "hidden tests" are and am not willing to create an account on this site, read the specs (which are probably incomplete and ambiguous as usual) and take the whole test, I'll only address the perfs issues.
Your first function is about as inefficient as possible:
def only_one_element_different(x, y):
list1 = []
for i in x:
list1.append(i)
This can be rewritten as list1 = list(x), which is faster - but it's not even needed: strings are sequences, and sequences are iterables, so you can just use the strings directly.
list2 = []
for j in y:
list2.append(j)
idem
list3 = []
for k in range(0, len(list1)):
if list1[k] == list2[k]:
list3.append(True)
else:
list3.append(False)
First simplification: use zip() to iterate on both strings at once:
>>> print(list(zip("aba", "bbb")))
[('a', 'b'), ('b', 'b'), ('a', 'b')]
which would give:
for c1, c2 in zip(x, y):
if c1 == c2:
list3.append(True)
else:
list3.append(False)
Now c1 == c2 is an expression, which means it's evaluates to an object (a boolean in this case), so you can simplify this as
for c1, c2 in zip(x, y):
list3.append(c1 == c2)
Now this is a rather inefficient way to build a list - first because of the method calls overhead, and also because growing the list might lead to memory allocation, which is costly. A much faster solution is a "list comprehesion":
list3 = [c1 == c2 for c1, c2 in zip(x, y)]
and while we're at it, this:
if list3.count(False) == 1:
return True
else:
return False
is a convoluted way to write
return list3.count(False) == 1
IOW you can rewrite the whole thing as
return [c1 == c2 for c1, c2 in zip(x, y)].count(False) == 1
Now if the x and y strings were long, this would still be inefficient - you are inspecting the whole string when you could detect "non-matching" string way sooner:
def only_one_diff(x, y):
diff = False
for c1, c2 in zip(x, y):
if c1 != c2:
if diff:
# we already found another diff in a previous iteration,
# so there's more than one diff, so we are done
return False
# else it was the first diff, let's flag it:
diff = True
# if we didn't return from within the for loop,
# we have either one single diff or no diff at all:
return diff
Which of those two implementations will actually be faster depends on the strings length. For long strings, this second implemention should be faster, for 3 letters strings it's not garanteed, you'd have to benchmark both using the timeit module. In both cases, the second implementation will eat less memory...
The rest of your code suffers from similar issues:
def if_list_is_linear(list):
list4 = []
for m in range(0, len(list)-1):
if only_one_element_different(list[m], list[m+1]):
list4.append(True)
else:
list4.append(False)
if False in list4:
return False
else:
return True
here again, you'd save some time by returning as soon as you know one of the strings doesn't pass the only_one_diff predicate:
# NB : don't use `list` as a var name, it shadows the builtin `list` type
def if_list_is_linear(lst):
for x, y in zip(lst, lst[1:]):
if not only_one_diff(x, y):
# no need to test further
return False
# ok, we're good
return True
I think you get the point by now....
I am also working on CodeSignal's practice problems using python. Sometimes, but not always, if a print statement is included in the submitted solution - even if the line is never read - this can cause a hidden test to fail.
After removing or commenting out print statements double check for edge cases that you may have overlooked and were not tested in the visible tests. (i.e. 'abc','abc' is a discontinuity while 'abc','aba' is okay because exactly one letter changed)
For your reference, I outlined and annotated my solution below.
First: Make a dictionary that keeps a list of all strings that are only one letter different. i.e. for [ 'aba' , 'bbb' , 'bab' , 'bba' ]
dictionary={ 'aba' : ['bba'] , 'bbb' : ['bab','bba'] , ... }
string_dict={}
#iterate through strings comparing each key string to all other strings
for idx in range(len(inputArray)):
key=inputArray[idx]
string_dict[key]=[]
for string in np.concatenate([inputArray[:idx],inputArray[idx+1:]]):
#check to see if key is within one letter of the string
count=np.sum([k!=s for (k,s) in zip(key,string)])
#do not add value to key if value==key or 2 or more letters are different
if count>1 or count==0:
continue
else:
#Add string to dictionary as a value if it is within 1 letter from the key
string_dict[key]+=[string]
From now on you do not need to compute whether string1 is within 1 letter change from string2 since it is stored in your dictionary
Second: check all permutations of inputArray to see if any are a solution
Given that there will be 10 strings or less in the given inputArray it is not too expensive to check all possible permutations
arr_len=len(inputArray)
for permutation in list(permutations(range(arr_len))):
#order inputArray values according to each permutation
arr=inputArray[np.array(permutation)]
#Count how many adjacent strings are more than 1 letter different
discontinuities=np.sum([arr[idx+1] not in string_dict[arr[idx]] for idx in range(arr_len-1)])
if discontinuities==0:
#Made it to the end of the permutation and found no discontinuities. A solution exists!
return True
#Went through all permutations and all of them had at least one discontinuity. No solution exists.
return False

Passing modified list to each node of binary tree

I am writing a function to grow a tree:
def collect_append(collect,split):
collect.append(split)
return collect
def tree(string,passwords,collect): #collect is a list and passwords is also a list
matching_list = []
match = 0
if len(string)==0:
print(collect)
return 0
for j in passwords:
for i in range(min(len(j),len(string))):
if string[i]!=j[i]:
break
else :
matching_list.append(j)
match = match + 1
if match == 0:
return 1
else:
for split in matching_list:
x =tree(string.strip(split),passwords,collect_append(collect,split))
return x
My question is, for each split in matching_list(say two), I want to add different strings to the existing list at that point (i.e. I want two versions of list).
In this case the collect_append function I use is modifying the list in first iteration of the for loop and using the same for further iterations. What I want is to just modify the collect list just for the parameter and without permanently changing it. Is there a way to do this?
I see two serious errors in your code. First, this else clause is never executed:
for j in passwords:
for i in range(...):
if ...:
break
else:
...
Since the break is in the inner for loop, the outer for loop is never exited via a break so the else is never taken. Second, this doesn't do what you want:
string.strip(split)
You're trying to remove split from the beginning of string but you're removing all the letters in split from both ends of string, bruising it badly. Here's one way to do it correctly:
string[len(split):]
I'm going to go out on a limb, and rewrite your code to do what I think you want it to do:
def tree(string, passwords, collect):
length = len(string)
if length == 0:
return False
matching_list = []
for j in passwords:
i = min(len(j), length)
if string[:i] == j[:i]:
matching_list.append(j)
if not matching_list:
return False
result = False
for split in matching_list:
local_collection = list([split])
if split == string or tree(string[len(split):], passwords, local_collection):
collect.append(local_collection)
result = True
return result
collection = []
print(tree('dogcatcher', ['cat', 'catch', 'cher', 'dog', 'dogcat', 'dogcatcher', 'er'], collection))
print(collection)
OUTPUT
% python3 test.py
True
[['dog', ['cat', ['cher']], ['catch', ['er']]], ['dogcat', ['cher']], ['dogcatcher']]
%
Giving you a tree of all the ways to assemble string from the words in passwords.

Merge Sort on Python: Unusual pattern of result obtained

I have a unsorted array of 10,000 integers from 0 to 9,999. I wanted to apply merge sort on this unsorted array and I wrote the following code-
import sys
def merge_sort(data):
result = []
if len(data) <= 1:
return data
else:
mid = int(len(data)/2)
left = data[:mid]
right = data[mid:]
sorted_left = merge_sort(left)
sorted_right = merge_sort(right)
i = j = k = 0
total_len = len(sorted_left) + len(sorted_right)
for k in range(0, total_len):
if i < len(sorted_left) and j < len(sorted_right):
if sorted_left[i] < sorted_right[j]:
result.append(sorted_left[i])
i = i+1
k = k+1
elif sorted_left[i] > sorted_right[j]:
result.append(sorted_right[j])
j = j+1
k = k+1
elif i < len(sorted_left):
result.append(sorted_left[i])
i = i+1
k = k+1
elif j < len(sorted_right):
result.append(sorted_right[j])
j = j+1
k = k+1
else:
sys.exit("There is some issue with the code")
return result
print merge_sort(data)
So when I sort this data, I get a correct sort order except for a few entries. For example- towards the end I get this kind of result-
[...'9989', '999', '9990', '9991', '9992', '9993', '9994', '9995', '9996', '9997', '9998', '9999']
As you might observe, number '999' is at the wrong place. Not just in this snippet but it happens in other places too like '995' appearing between '9949' and '9950'.So anybody has any idea why this is happening?
P.S.- I ran this code for debug and it ran without errors producing these results
You are ordering strings: '9989' < '999' < '9990'. If you want to order integers, you'll have to convert your input list to integers.
Your data is in strings, not numbers. To convert to integers, use:
data = [int(x) for x in data]
Python will "compare" a wide variety of objects. For example:
>>> "a" < "ab"
True
>>> None < "a"
True
>>> 1 < "a"
True
If you compare such items, python will not object.
Comparison in python
For integers and strings, python has built-in methods for comparisons. For objects that you create, you can define your own comparison methods. Methods that you can define for your objects that python will use for comparison include:
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)
By defining your own methods for your objects, there is great flexibility.
Is your data coming in as strings or integers? Based on your sample output, they are strings.
In such a case, '1' comes just before '10'. If you're expecting integers, then you could convert to int to do the sort.

Categories