I found some Python exercises that were made by Google in their Python classes and decided to spend some time with them.
Given the following description:
E. Given two lists sorted in increasing order, create and return a merged ist of all the elements in sorted order. You may modify the passed in lists. deally, the solution should work in "linear" time, making a single pass of both lists.
So, knowing that comparing two characters is \$O(1)\$, Python's sorted()
function in this situation is \$O(n \log{n})\$ and thinking that merging two lists into a new one is \$O(k)\$ where \$k\$ the number of elements in list1 + list2
, which is quite "linear" in the sense of what was asked, I did...
def linear_merge(list1, list2):
return sorted(list1 + list2)
However, when looking at the problem's solution, I found it to be somewhat different:
def linear_merge(list1, list2):
result = []
# Look at the two lists so long as both are non-empty.
# Take whichever element [0] is smaller.
while len(list1) and len(list2):
if list1[0] < list2[0]:
result.append(list1.pop(0))
else:
result.append(list2.pop(0))
# Now tack on what's left
result.extend(list1)
result.extend(list2)
return result
which is followed by the following comment:
Note: the solution above is kind of cute, but unforunately
list.pop(0)
is not constant time with the standard Python list implementation, so the above is not strictly linear time. An alternate approach usespop(-1)
to remove the endmost elements from each list, building a solution list which is backwards. Then use reversed() to put the result back in the correct order. That solution works in linear time, but is more ugly.
This confused me a little bit, since my solution looks... better in general (code and complexity, given the last comment paragraph).
Are any of my assumptions about my version of the code wrong?
Keep in mind that I wrote this version using Python3, when instead google's python classes uses Python2. I'm not really sure, but this may have something to do with it.
Here is the rest of the related source to give a full example:
def test(got, expected):
if got == expected:
prefix = ' OK '
else:
prefix = ' X '
print('{} got: {} expected: {}'.format(prefix, repr(got), repr(expected)))
# Calls the above functions with interesting inputs.
def main():
print('linear_merge')
test(linear_merge(['aa', 'xx', 'zz'], ['bb', 'cc']),
['aa', 'bb', 'cc', 'xx', 'zz'])
test(linear_merge(['aa', 'xx'], ['bb', 'cc', 'zz']),
['aa', 'bb', 'cc', 'xx', 'zz'])
test(linear_merge(['aa', 'aa'], ['aa', 'bb', 'bb']),
['aa', 'aa', 'aa', 'bb', 'bb'])
if __name__ == '__main__':
main()
sorted()
function is cheating. Furthermore, it could be considered overkill, since it fails to take advantage of the knowledge that the inputs are each already sorted. On the other hand, if you were developing a larger application and this were one programming task among the dozens of features you had to implement in a day, I would absolutely consider take your shortcut. \$\endgroup\$