FAQ: Code Challenge: Lists - Remove Middle

The error is because of indentation in the block above that line. Since it is a syntax error it didn’t have a chance to actually run the code or we would get this exception…

TypeError: 'range' object cannot be interpreted as an integer

As mentioned earlier, pop takes an integer, not a sequence.

Hi, I’m trying to use this solution:

def remove_middle (lst, start, end):
lst1=lst[:start]
lst2=lst[end:]+1
return lst1+lst2

But I’m getting “can only concatenate list (not “int”) to list” error. What am I doing wrong?

You can use the + operator to concatenate lists like so:

lst_a = [1, 2]
lst_b = [3, 4]
lst_c = lst_a + lst_b #[1, 2, 3, 4]

Your line of code above is trying to concatenate a list and an integer which, as the error has expressed, cannot be done. It appears that you’ve tried to take a slice of lst starting at the value assigned to end plus 1, so as to skip the element at index end. Adding 1 to the value assigned to end is the right idea, but that’s not what you’ve done here.

Segue

Since any comma separated list is treated as a sequence, we can actually leave off the brackets on the assignment…

>>> a = [2, 5, 9, 12]
>>> a += 18, 25, 33, 42
>>> a
[2, 5, 9, 12, 18, 25, 33, 42]
>>> 

Just something we can exploit should the need ever arise.


It follows that if we have a list of lists, or tuples, or dicts, then that structure would need to be maintained in the assignment.

>>> a = ['a', 'b', 'c', 'd']
>>> b = [1, 2, 3, 4]
>>> c = list(zip(a, b))
>>> c += ('e', 5), ('f', 6)
>>> c
[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6)]
>>> 
1 Like

Can someone explain their solution to me? I don’t understand how the program is “deleting” those numbers. I’m going to go through this chapter again.

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.

1 Like

I’m looking at the above code, and comparing it to the solution given.
def remove_middle(lst, start, end):
return lst[:start] + lst[end+1:]
#Uncomment the line below when your function is done
print(remove_middle([4, 8, 15, 16, 23, 42], 1, 3))

I understand that lst[:start] captures everything up to start, what confuses me is lst[end+1:] because I get that when the element is before the ‘:’ doesnt that mean it would select end (3)? I dont understand the + 1 nor how it removes those numbers, sorry for the stupidity this just isnt clicking in my head

[, , , , :start]

[end + 1: , , ,]

If we look at how a slice behaves,

>>> a = 2
>>> z = 8
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 0][a:z]
[3, 4, 5, 6, 7, 8]
>>> 

The list is 10 elements in length, and we have lobbed off the front two and the back two elements. Now look closely, The element at index [2] is included in the slice. However, the element at index [8] is not included in the slice.

When the value is omitted before the colon, it means start at the first element. When the value is omitted after the colon it means go to the very end.

>>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 0][a:]
[3, 4, 5, 6, 7, 8, 9, 0]
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 0][:z]
[1, 2, 3, 4, 5, 6, 7, 8]
>>> 

In this exercise we wish to remove the slice from the middle and keep the ends intact. In the above example that means ending up with a list,

 [1, 2, 9, 0]

How do we exclude the 8 from being in the list?

ah okay i see i think.
to remove 8 we would do [:z - 1]?

Let’s try without adding or subtracting. Recall we want to remove the middle. How many will be removed? Answer, z - a, or 8 - 2 which is, 6.

>>> lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
>>> lst[:a] + lst[z:]
[1, 2, 9, 0]
>>> 

I guess that leaves both of us with the same question. Why end + 1? What do the instructions say? Are we to also remove the element at the second index given in the argument? If so, then that changes the scenario from above.

Consider,

    lst = [4, 8, 15, 16, 23, 42]
    #         1   2   3
    start = 1
    end = 3

3 - 1 is only 2. We need to remove 3. So,

>>> lst = [4, 8, 15, 16, 23, 42]
>>> start = 1
>>> end = 3
>>> lst[:start] + lst[end + 1:]
[4, 23, 42]
>>> 

Hope that answers your question. Be sure to review the instructions, and while you’re at it, please post a link to the exercise.

Thanks for all the help! I was able to understand what i was actually doing.
lst[:start] + lst[end+1:] = lst[:1] + lst[3+1:]

and lst[:start] selects up to, but not including index 1 ( 8 ), capturing 4. and then lst[3+1:] selects up to 16, then shifts over 1 to 23, capturing 23,42.
then we add em together then boom
4,23,42.

heres the link to the exercise: https://www.codecademy.com/paths/computer-science/tracks/cspath-flow-data-iteration/modules/dspath-lists/articles/advanced-python-code-challenges-lists

1 Like

You’re welcome, @tag3678980116.

I’m guessing you see the difference between the first example, and this exercise? We can make all manner of mistakes when dynamically coding slices so be sure to get as much practice as you can, going forward. We still like to see solutions that don’t use slicing, so give that a try, as well. See how many different solutions you can come up with over the Easter weekend.

ok, after reviewing the answers and tips, I finally came to the solution and how it works. I thought the new array should have been [:start] to [:end +1], but instead it took those out and the array actually started with the numbers that weren’t in that return list. Not sure if I’m confusing myself.

[:start]  =>  everything up to but not including index `start`

[end:]    => everything including index `end` through to the end

Out of genuine curiosity, why is this method not considered smart? If the ‘del’ function exists, is it not ok to take advantage of it if it yields the desired/same outcome?

I might be mistaken but I don’t think the particular knowledge to be able to achieve this challenge was teach prior to the challenge and it’s not the first time I notice that.
It’s a bit frustrating confidence-wise when learning something new for what it’s worth

Here was my simple solution using slice()

def remove_middle(lst,start,end):
  return lst[:start] + lst[end+1:]

I tried this mtf, but it returned [4, 23, 42]. Only after I added the +1 did the code work. Why is that?

def remove_middle(lst, start, end):
lst[start:end] =
return lst

#Uncomment the line below when your function is done
print(remove_middle([4, 8, 15, 16, 23, 42], 1, 3))

Will have to review the instructions to determine if the index 3 is included or excluded. If included then yes, +1 will be needed so the value at that index is also removed.

My example above excludes that index.

this doesnt make any sense. why does start pull out the 4 and why does end pull out the 16, 23, 42 and save them into a lst? Please, please please help