Reputation: 125
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)
Upvotes: 0
Views: 6540
Reputation: 390
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
Upvotes: 0
Reputation: 77912
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....
Upvotes: 4