In [4]:
def compact(iterable):
    """Return new iterable with adjacent duplicate values removed."""
    current = object()
    
    for e in iterable:
        if e != current:
            current = e
            yield e

In [5]:
import unittest

class CompactTests(unittest.TestCase):

    """Tests for compact."""

    def assertIterableEqual(self, iterable1, iterable2):
        self.assertEqual(list(iterable1), list(iterable2))

    def test_no_duplicates(self):
        self.assertIterableEqual(compact([1, 2, 3]), [1, 2, 3])

    def test_adjacent_duplicates(self):
        self.assertIterableEqual(compact([1, 1, 2, 2, 3]), [1, 2, 3])

    def test_non_adjacent_duplicates(self):
        self.assertIterableEqual(compact([1, 2, 3, 1, 2]), [1, 2, 3, 1, 2])

    def test_lots_of_adjacent_duplicates(self):
        self.assertIterableEqual(compact([1, 1, 1, 1, 1, 1]), [1])

    def test_empty_values(self):
        self.assertIterableEqual(compact([None, 0, "", []]), [None, 0, "", []])

    def test_empty_list(self):
        self.assertIterableEqual(compact([]), [])

    # To test the Bonus part of this exercise, comment out the following line
    # @unittest.expectedFailure
    def test_accepts_iterator(self):
        nums = (n**2 for n in [1, 2, 3])
        self.assertIterableEqual(compact(nums), [1, 4, 9])

    # To test the Bonus part of this exercise, comment out the following line
    # @unittest.expectedFailure
    def test_returns_iterator(self):
        output = compact([1, 2, 3])
        self.assertEqual(iter(output), iter(output))
        
if __name__ == "__main__":
    unittest.main(argv=['first-arg-is-ignored'], exit=False)


........
----------------------------------------------------------------------
Ran 8 tests in 0.004s

OK

Key Takeaways

  1. object() - This will give an unique object, which can used instead of None

Another solution: Will not work for iterator objects passed as parameters

Reason: the *items will consume the iterator


In [28]:
def compact(items):
    """Return new iterable with adjacent duplicate values removed."""
    for item, prev in zip(items, [object(), *items]):       
        if item != prev:
            yield item

Note the neat trick of shifting the items by 1 and then using the zip function to compare elements of the iterable


In [29]:
import unittest

class CompactTests(unittest.TestCase):

    """Tests for compact."""

    def assertIterableEqual(self, iterable1, iterable2):
        self.assertEqual(list(iterable1), list(iterable2))

    def test_no_duplicates(self):
        self.assertIterableEqual(compact([1, 2, 3]), [1, 2, 3])

    def test_adjacent_duplicates(self):
        self.assertIterableEqual(compact([1, 1, 2, 2, 3]), [1, 2, 3])

    def test_non_adjacent_duplicates(self):
        self.assertIterableEqual(compact([1, 2, 3, 1, 2]), [1, 2, 3, 1, 2])

    def test_lots_of_adjacent_duplicates(self):
        self.assertIterableEqual(compact([1, 1, 1, 1, 1, 1]), [1])

    def test_empty_values(self):
        self.assertIterableEqual(compact([None, 0, "", []]), [None, 0, "", []])

    def test_empty_list(self):
        self.assertIterableEqual(compact([]), [])

    # To test the Bonus part of this exercise, comment out the following line
    @unittest.expectedFailure
    def test_accepts_iterator(self):
        nums = (n**2 for n in [1, 2, 3])
        self.assertIterableEqual(compact(nums), [1, 4, 9])

    # To test the Bonus part of this exercise, comment out the following line
    # @unittest.expectedFailure
    def test_returns_iterator(self):
        output = compact([1, 2, 3])
        self.assertEqual(iter(output), iter(output))
        
if __name__ == "__main__":
    unittest.main(argv=['first-arg-is-ignored'], exit=False)


x.......
----------------------------------------------------------------------
Ran 8 tests in 0.004s

OK (expected failures=1)

In [ ]: