There are a number of solutions. Have you understood so far how we can **slice** a string or list?

Let’s take this all the way back to looping and indexing, and pretend we don’t know about slicing.

```
>>> def remove_middle(lst, start, end):
result = []
for x in range(start): # capture everything on the left
result.append(lst[x])
for x in range(end, len(lst)): # capture everything on the right
result.append(lst[x])
return result
>>> remove_middle([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 6)
[1, 2, 3, 7, 8, 9]
>>> remove_middle([4, 8, 15, 16, 23, 42], 1, 3)
[4, 16, 23, 42]
>>>
```

Now see if you can apply what you understand about slicing to this model to simplify the code.

We’re seeing a number of attempted implementations, some of them successful, and others hit and miss. The idea of deleting an element comes with side effects.

Consider,

No matter which element we remove, the list will collapse down to the new size.

That means the indices of all the shifted elements have changed. What was at index[4] is now at index[3]; index[7] no longer exists.

The implementations that fail usually indicate a lack of awareness of this side effect. No fault of the writer. This is a common issue for learners.

The mistake most people make is using the index generated in a for loop. That number keeps climbing, yet the list has shifted so the next element is in the same position as the one just removed.

Examine the problem. What fixed numbers do we have? Answer: start, end, AND end - start. Therein is the clue to using a remove technique. We know *how many elements to remove*. That means we can count, but not the indices, the iterations.

Eg.

```
>>> def rem_middle(lst, start, end):
for x in range(end - start):
lst.remove(lst[start])
return lst
>>> rem_middle([4, 8, 15, 16, 23, 42], 1, 3)
[4, 16, 23, 42]
>>>
```

Notice we keep removing the contents of the same element? That’s the trick in this approach.

Just because it works with our trial examples does not mean it will work with all lists. Take an empty list, for example…

```
>>> rem_middle([], 1, 3)
Traceback (most recent call last):
File "<pyshell#261>", line 1, in <module>
rem_middle([], 1, 3)
File "<pyshell#259>", line 3, in rem_middle
lst.remove(lst[start])
IndexError: list index out of range
>>>
```

Or an index that is out of range…

```
>>> rem_middle([], -1, 3)
Traceback (most recent call last):
File "<pyshell#262>", line 1, in <module>
rem_middle([], -1, 3)
File "<pyshell#259>", line 3, in rem_middle
lst.remove(lst[start])
IndexError: list index out of range
>>> rem_middle([4, 8, 15, 16, 23, 42], 1, 10)
Traceback (most recent call last):
File "<pyshell#265>", line 1, in <module>
rem_middle([4, 8, 15, 16, 23, 42], 1, 10)
File "<pyshell#259>", line 3, in rem_middle
lst.remove(lst[start])
IndexError: list index out of range
>>>
```

This is where slices help to prevent exceptions. It doesn’t mean we still won’t make mistakes, but it means Python will at least try the slice and either work, or work weirdly, or do nothing. It won’t raise an exception if the slice doesn’t make sense.

Knowing that all the errors are of the same class (`IndexError`

) means we can validate to prevent that, or write exception handling for that class so our program keeps reporting. Any other type of error will still raise an exception. I’ll stop here, on this, since that comes up in a later lesson. Keep this problem in mind when you reach that point. It’s a good one to come back to.

Using what we know, the first thing we need to determine is if the indices are in range. We should allow negative indices.

```
def in_range(s, x):
return -len(s) <= x < len(s)
```

That would be a helper function that we call on each index.

```
if not in_range(lst, start) or not in_range(lst, end): return False
```

When we get past that line we only need to know if end is greater than start, or more importantly, `end - start > 0`

```
if not end - start > 0: return False
```

That should get rid of errant inputs that can raise an IndexError. If we are still raising errors then we need to give this another good looking over to find what state(s) we have omitted.

```
>>> def in_range(s, x):
return -len(s) <= x < len(s)
>>> def rem_middle(lst, start, end):
if not in_range(lst, start) or not in_range(lst, end): return False
if not end - start > 0: return False
for x in range(end - start):
lst.remove(lst[start])
return lst
>>> rem_middle([4, 8, 15, 16, 23, 42], 1, -1)
False
>>> rem_middle([4, 8, 15, 16, 23, 42], 1, 1)
False
>>> rem_middle([4, 8, 15, 16, 23, 42], 1, 10)
False
>>> rem_middle([], -1, 3)
False
>>> rem_middle([], 1, 3)
False
>>> rem_middle([4, 8, 15, 16, 23, 42], 1, 3)
[4, 16, 23, 42]
>>> rem_middle([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 6)
[1, 2, 3, 7, 8, 9]
>>>
```

We’re not out of the woods, yet. Still need to be sure negative indices can be implemented. There is a glitch…

```
>>> rem_middle([4, 8, 15, 16, 23, 42], 1, -3)
False
>>>
```

The list is long enough so there are no range issues. How are we going to get over this last hurdle? I’ll keep thinking on this, and hope you will, too. Pipe in if you find a solution.

That’s a lot to juggle, and posts this long seldom get any likes. No matter. It’s food for thought, and little lubricant for the brain, even it it’s not cemented yet.

How would we use `del`

in the above context? That will be your assignment.

Solution to negative indexing problem

```
>>> def in_range(s, x):
return -len(s) <= x < len(s)
>>> def pos_index(s, x):
return x if x >= 0 else len(s) + x
>>> def rem_middle(lst, start, end):
if not in_range(lst, start) or not in_range(lst, end): return False
start = pos_index(lst, start)
end = pos_index(lst, end)
if not end - start > 0: return False
for x in range(end - start):
lst.remove(lst[start])
return lst
>>> rem_middle([4, 8, 15, 16, 23, 42], 1, -3)
[4, 16, 23, 42]
>>>
```

Had to add another helper function.