Add some unit tests
Are we sure that the function works? Will it still work when we make changes (perhaps to improve its performance)?
Python comes with some unit-testing facilities to help us ensure that we don't break the function. One that's very easy to use is doctest
, which lets us put the tests right by the code itself.
We could start by testing an empty list:
def remove_repeated_numbers(z):
"""
Return a sorted copy of the input list, with duplicates removed.
>>> remove_repeated_numbers([])
[]
"""
⋮
⋮
We write each test as a line beginning with >>>
and an expression to evaluate, with the expected result on the following line.
We can run the tests with some easy boilerplate (which we don't need to understand right now):
if __name__ == '__main__':
import doctest
doctest.testmod()
To be sure that our tests work properly, let's add a test that we know will fail:
>>> remove_repeated_numbers([])
None
That gives us a useful diagnostic:
**********************************************************************
File "268315.py", line 7, in __main__.remove_repeated_numbers
Failed example:
remove_repeated_numbers([])
Expected:
None
Got:
[]
**********************************************************************
Let's remove that, and add some more tests:
>>> remove_repeated_numbers([0])
[0]
>>> remove_repeated_numbers([0, 0])
[0]
>>> remove_repeated_numbers([0, 1])
[0, 1]
>>> remove_repeated_numbers([1, 0])
[0, 1]
>>> remove_repeated_numbers([0, 0, 1, 1, 1])
[0, 1]
>>> remove_repeated_numbers([0, 1, 1, 1, 0])
[0, 1]
"""
That's good, and we now have more confidence in what we're doing.
We can now change the implementation to use a set
, as suggested in a different answer, but I'll propose a smaller change, and that is to sort the input first:
for i in sorted(z):
That initially seems like it's making more work (after all, the input could be much longer than the output), but it will reduce the later operations, because now all the duplicates are adjacent, so we don't need to examine the whole list to find them.
Now, instead of the expensive del z[y]
(which needs to shuffle all the subsequent elements in the list), we can copy to a new list to return to the user. Note that this changes the behaviour - I believe for the better. The code presented modifies the actual array passed to the function (though the modified array is only deduplicated, not sorted). It's generally less surprising to return a copy so that the original data is unchanged.
Just to demonstrate, try this code with the function as posted:
a = [ 2, 1, 1, 2]
print(remove_repeated_numbers(a))
print(a)
With this code, the function returns [1, 2]
and a
is changed to [2, 1]
.
So let's create our result array:
result = []
⋮
return result
Now our loop is very simple: for each element we see, if it's the same as the one we most recently added to result
, then ignore it, else add it to result
:
for i in sorted(z):
if i != result[-1]:
result.append(i)
That's not quite right, because result
starts off empty. We need to always include the first element, so we must change the test to also succeed when result
is empty - i.e. not result
:
for i in sorted(z):
if not result or i != result[-1]:
result.append(i)
I think that's easier to understand than code that del
etes items from the input list.
Full modified code
For reference, here's the full program from this approach:
def remove_repeated_elements(input_list):
"""
Return a sorted copy of the input list, with duplicates removed.
>>> remove_repeated_elements([])
[]
>>> remove_repeated_elements([0])
[0]
>>> remove_repeated_elements([0, 0])
[0]
>>> remove_repeated_elements([0, 1])
[0, 1]
>>> remove_repeated_elements([1, 0])
[0, 1]
>>> remove_repeated_elements([0, 0, 1, 1, 1])
[0, 1]
>>> remove_repeated_elements([0, 1, 1, 1, 0])
[0, 1]
>>> remove_repeated_elements(['review', 'code', 'code', 'review'])
['code', 'review']
"""
result = []
for element in sorted(input_list):
if not result or result[-1] < element:
result.append(element)
return result
if __name__ == '__main__':
import doctest
doctest.testmod()
Note that I changed the name slightly, as this will work with any type that can be ordered (such as strings - see the last test), not just numbers. The answer that recommends using set
also works with more than just numbers (but requires a type that can be hashed, as well as ordered).
I also changed the test to use <
, so it will work for any (admittedly theoretical, and highly improbable) type that can be sorted but not compared for inequality.
sorted(set(l))
? This is extremely basic,set
is a built-in data type that is ordered without duplicates with fast membership checking that is exactly what is designed to do this sort of thing, and then you just need to usesorted
function to return a sorted (shallow) copy of the list. Type casting is by far the most efficient and readable way to do this. \$\endgroup\$sorted(set(l))
. Didn't work. GotNameError: name 'l' is not defined
. \$\endgroup\$NameError: name 'z' is not defined
. \$\endgroup\$