Repository: johnsyweb/python_sparse_list

Issue:
Invalid implementation of `del my_list[a:b]`
Deleting slices does not behave like Python's lists:

```python
>>> standard_list = [1,2,3]
>>> del standard_list[:2]
>>> standard_list
[3]
>>> sparse_list = SparseList([1,2,3])
>>> del sparse_list[:2]
>>> sparse_list
[None, None, 3]
```

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 1df6b51..08d9ac6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,10 @@
 language: python
 python:
   - 2.7
-  - 3.3
   - 3.4
   - 3.5
   - 3.6
-  - 3.7-dev
+  - 3.7
   - nightly
   - pypy
 install: pip install -r requirements.txt
diff --git a/sparse_list.py b/sparse_list.py
index e6a0cd1..abf2648 100644
--- a/sparse_list.py
+++ b/sparse_list.py
@@ -69,14 +69,26 @@ class SparseList(object):
     def __delitem__(self, item):
         if isinstance(item, slice):
             indices = xrange(*item.indices(self.size))
+        elif item < 0:
+            indices = (self.size + item, )
         else:
             indices = (item, )
 
-        for i in indices:
-            try:
-                del self.elements[i]
-            except KeyError:
-                pass
+        offset = 0
+
+        for k in sorted(self.elements.keys()):
+            if k < indices[0]:
+                continue
+            elif offset < len(indices) and k > indices[offset]:
+                offset += 1
+
+            if offset:
+                self.elements[k - offset] = self.elements[k]
+
+            del self.elements[k]
+
+        self.size -= len(indices)
+
 
     def __delslice__(self, start, stop):
         '''
@@ -184,7 +196,6 @@ class SparseList(object):
             raise IndexError('pop from empty SparseList')
         value = self[-1]
         del self[-1]
-        self.size -= 1
         return value
 
     def remove(self, value):
diff --git a/time_sparse_list.py b/time_sparse_list.py
index 429097d..68dadef 100755
--- a/time_sparse_list.py
+++ b/time_sparse_list.py
@@ -37,5 +37,27 @@ class Benchmark_Retrieval(benchmark.Benchmark):
     def test_list(self):
         self.list[100]
 
+class Benchmark_Slice_Deletion(benchmark.Benchmark):
+    def setUp(self):
+        self.sparse_list = sparse_list.SparseList(xrange(1000))
+        self.list = list(xrange(1000))
+
+    def test_sparse_list(self):
+        del self.sparse_list[1::2]
+
+    def test_list(self):
+        del self.list[1::2]
+
+class Benchmark_Deletion(benchmark.Benchmark):
+    def setUp(self):
+        self.sparse_list = sparse_list.SparseList(xrange(1000))
+        self.list = list(xrange(1000))
+
+    def test_sparse_list(self):
+        del self.sparse_list[100]
+
+    def test_list(self):
+        del self.list[100]
+
 if __name__ == '__main__':
     benchmark.main(format="markdown", numberFormat="%.4g")


Test patch:
diff --git a/test_sparse_list.py b/test_sparse_list.py
index ca22327..dc6feb7 100755
--- a/test_sparse_list.py
+++ b/test_sparse_list.py
@@ -141,32 +141,32 @@ class TestSparseList(unittest.TestCase):
     def test_present_item_removal(self):
         sl = sparse_list.SparseList({0: 1, 4: 1}, 0)
         del sl[0]
-        self.assertEqual([0, 0, 0, 0, 1], sl)
+        self.assertEqual([0, 0, 0, 1], sl)
 
     def test_missing_item_removal(self):
         sl = sparse_list.SparseList({0: 1, 4: 1}, 0)
         del sl[1]
-        self.assertEqual([1, 0, 0, 0, 1], sl)
+        self.assertEqual([1, 0, 0, 1], sl)
 
     def test_slice_removal(self):
         sl = sparse_list.SparseList(xrange(10), None)
         del sl[3:5]
-        self.assertEqual([0, 1, 2, None, None, 5, 6, 7, 8, 9], sl)
+        self.assertEqual([0, 1, 2, 5, 6, 7, 8, 9], sl)
 
     def test_unbounded_head_slice_removal(self):
         sl = sparse_list.SparseList(xrange(10), None)
         del sl[:3]
-        self.assertEqual([None, None, None, 3, 4, 5, 6, 7, 8, 9], sl)
+        self.assertEqual([3, 4, 5, 6, 7, 8, 9], sl)
 
     def test_unbounded_tail_slice_removal(self):
         sl = sparse_list.SparseList(xrange(10), None)
         del sl[5:]
-        self.assertEqual([0, 1, 2, 3, 4, None, None, None, None, None], sl)
+        self.assertEqual([0, 1, 2, 3, 4], sl)
 
     def test_stepped_slice_removal(self):
         sl = sparse_list.SparseList(xrange(6), None)
         del sl[::2]
-        self.assertEqual([None, 1, None, 3, None, 5], sl)
+        self.assertEqual([1, 3, 5], sl)
 
     def test_append(self):
         sl = sparse_list.SparseList(1, 0)
@@ -330,23 +330,23 @@ class TestSparseList(unittest.TestCase):
     def test_set_slice_observes_stop(self):
         sl = sparse_list.SparseList(4, None)
         sl[0:2] = [1, 2, 3]
-        self.assertEquals([1, 2, None, None], sl)
+        self.assertEqual([1, 2, None, None], sl)
 
     def test_set_slice_resizes(self):
         sl = sparse_list.SparseList(0, None)
         sl[4:] = [4, 5]
-        self.assertEquals([None, None, None, None, 4, 5], sl)
-        self.assertEquals(len(sl), 6)
+        self.assertEqual([None, None, None, None, 4, 5], sl)
+        self.assertEqual(len(sl), 6)
 
     def test_set_slice_extends_past_end(self):
         sl = sparse_list.SparseList(5, None)
         sl[3:] = [6, 7, 8]
-        self.assertEquals([None, None, None, 6, 7, 8], sl)
+        self.assertEqual([None, None, None, 6, 7, 8], sl)
 
     def test_set_slice_with_step(self):
         sl = sparse_list.SparseList(6, None)
         sl[::2] = [1, 2, 3]
-        self.assertEquals([1, None, 2, None, 3, None], sl)
+        self.assertEqual([1, None, 2, None, 3, None], sl)
 
 if __name__ == '__main__':
     unittest.main()