Search code examples
pythonlistfunctionloopsmin

Finding two smallest values in a list


I am trying to return the two smallest values for a numeric list as a tuple. However the next code keeps returning first two values of a list.

def test(): 
  list = [4, 5, 1, 9, -2, 0, 3, -5] 
  min1 = list[0]
  min2 = list[1]

  length = len(list)
  
  for i in range(1, length):
    if list[i] < list[0]:
        if list[0] < list[1]:
            list[i] = list[1]
        else:
            list[i] = list[1] 
    else:
        if list[i] < list[1]:
            list[i] = list[1]
    print(min1, min2)

    return (min1, min2) 

test()

Console output:

4,5

Is there any way to do this by iterating?


Solution

  • The variables min1 and min2 don't update, they are not references to the first and second element of the list, they are references to the values that were at indexes 0 and 1 when the assignment takes place. That you later change list[0] and list[1] doesn't matter.

    In Python, both list indexes and variables are just references to the actual objects. Think of Python objects like balloons, and variables and indices are just labels tied to the a string attached to the balloon. You can attach multiple labels to the balloons, but if you than move the label to a different balloon, the other labels tied to the old balloon don't follow along.

    Here, min1 and min2 were tied to balloons that already had the 0 and 1 index labels tied to them. Later on, when you assign to list[i], you retied a specific index label to another balloon, but the min1 and min2 labels didn't change.

    As a side note, this part of your code has a rather obvious error:

    if list[0] < list[1]:
        list[i] = list[1]
    else:
        list[i] = list[1] 
    

    Both branches do the exact same thing, assign list[1] to list[i].

    You are otherwise doing completely incorrect assignments even if you hoped that changing list[0] and list[1] inside the loop would make a change to the values of min1 and min2, you are changing list[i], the other value in the list, the one that's supposed to be smaller.

    So for i = 2, list[i] is list[2] and list[2] < list[0] is true (1 < 4), you then test if list[0] < list[1] (also true, 4 < 5), so you do list[i] = list[1], setting list[2] = 5, leaving list[0] set to 4, list[1] set to 5, and in effect throwing away the 1 value that was present at list[2] before.

    Rather than compare with list[0] or list[1], have your loop update min1 and min2:

    # min1 is always smaller than min2
    min1, min2 = list[:2]
    if min2 < min1:
        min1, min2 = min2, min1
    
    for i in range(1, length):
        if list[i] < min1:
            min1 = list[i]
        elif list[i] < min2:  # but equal to or greater than min1!
            min2 = list[i]
    

    I also made sure that min1 < min2 at the start, that makes the loop a lot simpler because if list[i] < min1 is not true then it can perhaps be smaller than min2 but you don't need to test against min1 a second time.

    Note that we assign the list[i] values to min1 and min2 here, you want to update those two variables with the value you just tested, provided list[i] is indeed smaller than what you had before.