20

I found this code on this site to find the second largest number:

def second_largest(numbers):
    m1, m2 = None, None
    for x in numbers:
        if x >= m1:
            m1, m2 = x, m1
        elif x > m2:
            m2 = x
    return m2

Source: Get the second largest number in a list in linear time

Is it possible to modify this code to find the second smallest number? So for example

print second_smallest([1, 2, 3, 4])
2
Community
  • 1
  • 1
user2971812
  • 379
  • 2
  • 3
  • 9

20 Answers20

31
a = [6,5,4,4,2,1,10,1,2,48]
s = set(a) # used to convert any of the list/tuple to the distinct element and sorted sequence of elements
# Note: above statement will convert list into sets 
print sorted(s)[1] 
Vardhman Patil
  • 517
  • 6
  • 22
Nita Mandavkar
  • 319
  • 3
  • 2
  • 4
    While the answer may be correct, please add some explanation rather than just a code dump. Also, if I may add, Try to format your code so that there are not blank lines between the lines of code – nbryans Feb 02 '17 at 18:35
  • 6
    In this case, the code is so simple and self-explanatory, that a textual explanation won't add anything useful. – cmrichards Dec 04 '17 at 17:24
  • 2
    I agree, it's self-explanatory here: `sorted(set(a))[1]`. – Basj Dec 04 '19 at 20:23
  • 1
    It is, however, a waste of CPU cycles, putting *all* numbers in exact sorted order. *And* it doesn't answer the stated question: *Is it possible to modify this code ...* – Martijn Pieters Apr 20 '21 at 19:48
22

The function can indeed be modified to find the second smallest:

def second_smallest(numbers):
    m1 = m2 = float('inf')
    for x in numbers:
        if x <= m1:
            m1, m2 = x, m1
        elif x < m2:
            m2 = x
    return m2

The old version relied on a Python 2 implementation detail that None is always sorted before anything else (so it tests as 'smaller'); I replaced that with using float('inf') as the sentinel, as infinity always tests as larger than any other number. Ideally the original function should have used float('-inf') instead of None there, to not be tied to an implementation detail other Python implementations may not share.

Demo:

>>> def second_smallest(numbers):
...     m1 = m2 = float('inf')
...     for x in numbers:
...         if x <= m1:
...             m1, m2 = x, m1
...         elif x < m2:
...             m2 = x
...     return m2
... 
>>> print(second_smallest([1, 2, 3, 4]))
2

Outside of the function you found, it's almost just as efficient to use the heapq.nsmallest() function to return the two smallest values from an iterable, and from those two pick the second (or last) value. I've included a variant of the unique_everseen() recipe to filter out duplicate numbers:

from heapq import nsmallest
from itertools import filterfalse

def second_smallest(numbers):
    s = set()
    sa = s.add
    un = (sa(n) or n for n in filterfalse(s.__contains__, numbers))
    return nsmallest(2, un)[-1]

Like the above implementation, this is a O(N) solution; keeping the heap variant each step takes logK time, but K is a constant here (2)!

Whatever you do, do not use sorting; that takes O(NlogN) time.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • A problem with this though that the original doesn't have, if the input sequence is empty or of length 1 getting `float('inf')` returned back is a bit confusing compared to `None` in the original – GP89 Nov 06 '14 at 12:51
  • @GP89: it is easy enough to add a `if len(numbers) < 2: return None` at the top. – Martijn Pieters Nov 06 '14 at 12:51
  • Right, just a warning :) – GP89 Nov 06 '14 at 12:53
  • @MartijnPieters It returns 1 for the list `[1, 2, 3, 4, 5, 1]` instead of 2. Please see http://stackoverflow.com/a/32829246/793428 – Ozgur Vatansever Sep 28 '15 at 19:17
  • @ozgur: it is debatable if 1 or 2 is the second lowest. When sorted, `1` is both the first and the second value in the sequence. – Martijn Pieters Sep 28 '15 at 21:00
  • what if array has only sigle value? `[0]`, then it fails, returns the float('inf') – RegarBoy May 26 '17 at 08:30
  • @developer: what did you expect a function that is named `second_smallest()` to return for a list with just 1 value? I'd say `float('inf')` is exactly the right answer in that case, *or* you can put a test at the top that throws an exception if there is just one element. – Martijn Pieters May 26 '17 at 08:38
  • Python 3.6+, it is easier to uniqueify a list (and maintain order) with `un={}.fromkeys(numbers).keys()` – dawg Dec 30 '21 at 17:12
  • @dawg: why the literal dict? You probably don't want to get the [dictionary view](https://docs.python.org/3/library/stdtypes.html#dict-views) either. For this code, where we directly iterate, just use `dict.fromkeys(numbers)`. I'm not using that because using `dict.fromkeys()` **assumes the input is small enough to fit in memory**. My version can handle iterables of any number of items, using a small, fixed amount of memory. – Martijn Pieters Jan 19 '22 at 21:52
11

Or just use heapq:

import heapq
def second_smallest(numbers):
    return heapq.nsmallest(2, numbers)[-1]

second_smallest([1, 2, 3, 4])
# Output: 2
csg
  • 8,096
  • 3
  • 14
  • 38
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
4

As per the Python in-built function sorted

sorted(my_list)[0]

gives back the smallest number, and sorted(my_list)[1] does accordingly for the second smallest, and so on and so forth.

gented
  • 1,620
  • 1
  • 16
  • 20
2

My favourite way of finding the second smallest number is by eliminating the smallest number from the list and then printing the minimum from the list would return me the second smallest element of the list. The code for the task is as below:

mylist=[1,2,3,4]
mylist=[x for x in mylist if x!=min(mylist)]  #deletes the min element from the list
print(min(mylist))
  • 2
    This runs in O(N^2) (squared) time, because `min(mylist)` is executed **for every value in `mylist`**. Assign the minimum to a variable first so it is only calculated _once_, and don't create a new list: `m = min(mylist)`, then `second_smallest = min(v for v in mylist if v != m)` – Martijn Pieters Apr 20 '21 at 19:52
1

Solution that returns second unique number in list with no sort:

def sec_smallest(numbers):
    smallest = float('+inf')
    small = float('+inf')
    for i in numbers:
        if i < smallest:
            small = smallest
            smallest = i
        elif i < small and i != smallest:
            small = i
    return small

print('Sec_smallest:', sec_smallest([1, 2, -8, -8, -2, 0]))
helenko11
  • 11
  • 4
0

Yes, except that code relies on a small quirk (that raises an exception in Python 3): the fact that None compares as smaller than a number.

Another value that works is float("-inf"), which is a number that is smaller than any other number.

If you use that instead of None, and just change -inf to +inf and > to <, there's no reason it wouldn't work.

Edit: another possibility would be to simply write -x in all the comparisons on x, e.g. do if -x <= m1: et cetera.

RemcoGerlich
  • 30,470
  • 6
  • 61
  • 79
  • Not just a quirk, an implementation detail. An artefact of the Python 2 requirement that heterogenous sequences are sortable, so a choice had to be made where `None` sorts. The choice is arbitrary however. – Martijn Pieters Nov 06 '14 at 12:44
0
 mi= min(input_list)
    second_min = float('inf')
    for i in input_list:
        if i != mi:
            if i<second_min:
                second_min=i
    if second_min == float('inf'):
        print('not present')
    else:
        print(second_min)
             

##input_list = [6,6,6,6,6]
#input_list = [3, 1, 4, 4, 5, 5, 5, 0, 2, 2]
#input_list = [7, 2, 0, 9, -1, 8]
# Even if there is same number in the list then Python will not get confused.
  • If you are going to use `min()` to find the lowest value, why not use `min()` again to find the lowest value in the filtered list? `second_min = min(v for v in input_list if v != mi)` – Martijn Pieters Apr 20 '21 at 19:54
0

I'd like to add another, more general approach:
Here's a recursive way of finding the i-th minimums of a given list of numbers

def find_i_minimums(numbers,i):
    minimum = float('inf')
    if i==0:
        return []
    less_than_i_minimums = find_i_minimums(numbers,i-1)
    for element in numbers:
        if element not in less_than_i_minimums and element < minimum:
            minimum = element
    return less_than_i_minimums + [minimum]

For example,

>>> find_i_minimums([0,7,4,5,21,2,6,1],3) # finding 3 minimial values for the given list
[0, 1, 2]

( And if you want only the i-th minimum number you'd extract the final value of the list )

The time-complexity of the above algorithm is bad though, it is O(N*i^2) ( Since the recursion depth is i , and at each recursive call we go over all values in 'numbers' list whose length is N and we check if the minimum element we're searching for isn't in a list of length i-1, thus the total complexity can be described by a geometric sum that will give the above mentioned complexity ).

Here's a similar but alternative-implementation whose time-complexity is O(N*i) on average. It uses python's built-in 'set' data-structure:

def find_i_minimums(numbers,i):
    minimum = float('inf')
    if i==0:
        return set()
    less_than_i_minimums = find_i_minimums(numbers,i-1)
    for element in numbers:
        if element not in less_than_i_minimums and element < minimum:
            minimum = element
    return less_than_i_minimums.union(set({minimum}))

If your 'i' is small, you can use the implementations above and then extract how many minimums you want ( or if you want the second minimum, then in your case run the code for i=2 and just extract the last element from the output data-structure ). But if 'i' is for example greater than log(N) , I'd recommend sorting the list of numbers itself ( for example, using mergesort whose complexity is O(N*log(N)) at worst case ) and then taking the i-th element. Why so? because as stated, the run-time of the algorithm above is not great for larger values of 'i'.

jacob12
  • 173
  • 8
0

You might find this code easy and understandable

def secsmall(numbers):
small = max(numbers)
for i in range(len(numbers)):
    if numbers[i]>min(numbers):
        if numbers[i]<small:
            small = numbers[i]
return small

I am assuming "numbers" is a list name.

D Sai Krishna
  • 178
  • 1
  • 13
0

Find the first and the second smallest numbers in an interger array

arr= [1,2,3,4,5,6,7,-1,0,-2,-10]
def minSecondmin(arr,n):
  i=1
  if arr[i-1] < arr[i]:
    f = arr[i-1]
    s = arr[i]
  else:
    f=arr[i]
    s=arr[i-1]

  for i in range(2,n):
    if arr[i]<f:
      s=f
      f = arr[i]
    elif arr[i]<s:
      s=arr[i]
  return f,s

minSecondmin(arr,len(arr))
-1

Here we want to keep an invariant while we scan the list of numbers, for every sublist it must be

m1<=m2<={all other elements}

the minimum length of a list for which the question (2nd smallest) is sensible is 2, so we establish the invariant examining the first and the second element of the list (no need for magic numbers), next we iterate on all the remaining numbers, maintaining our invariant.

def second_smaller(numbers):
    # if len(numbers)<2: return None or otherwise raise an exception

    m1, m2 = numbers[:2]
    if m2<m1: m1, m2 = m2, m1

    for x in numbers[2:]:
        if x <= m1:
            m1, m2 = x, m1
        elif x < m2:
            m2 = x
    return m2

Addendum

BTW, the same reasoning should be applied to the second_largest function mentioned by the OP

gboffi
  • 22,939
  • 8
  • 54
  • 85
-1
l = [41,9000,123,1337]

# second smallest
sorted(l)[1]
123


# second biggest
sorted(l)[-2]
1337
enthus1ast
  • 2,099
  • 15
  • 22
  • Not working for `l = [41,41,9000,123,1337]`, you need to convert to `set` first, see @Nita's answer – Basj Dec 04 '19 at 20:25
-1

I am writing the code which is using recursion to find the second smallest element in a list.

def small(l):
 small.counter+=1;
 min=l[0];

 emp=[]

 for i in range(len(l)):
    if l[i]<min:
        min=l[i]

 for i in range(len(l)):
    if min==l[i]:
     emp.append(i)

 if small.counter==2:
    print "The Second smallest element is:"+str(min)
 else:
   for j in range(0,len(emp)):

     l.remove(min)

   small(l)
small.counter = 0

list=[-1-1-1-1-1-1-1-1-1,1,1,1,1,1]
small(list)

You can test it with various input integers.

-1

There is a easy way to do . First sort the list and get the second item from the list.

def solution(a_list):

    a_list.sort()
    print a_list[1]

solution([1, 2, -8, -2, -10])
Christian Gollhardt
  • 16,510
  • 17
  • 74
  • 111
Ankan Roy
  • 17
  • 6
  • 1
    it is wrong in case if there r repeatable items, like [1, 1, 2, -8] – Reishin Aug 30 '17 at 14:45
  • That depends on context. If you have an array of distances of objects from the origin, you might want to know if the second closest object is the same distance as the closest. – AaronF Mar 25 '18 at 19:11
-1

You can use in built function 'sorted'

def second_smallest(numbers):

count = 0
l = []
for i in numbers:
    if(i not in l):
        l.append(i)
        count+=1
    if(count==2):
        break

return max(l)
  • Your code creates a list with two elements which are the first two elements from numbers and then returns the maximum of the two from the newly created list, hence it doesn't work as it should, for example try the input: numbers = [0,7,4,5,6,2,1]. Your code will output '7' which is false ( the output should be '1' ) – jacob12 Dec 30 '21 at 15:24
-1

To find second smallest in the list, use can use following approach which will work if two or more elements are repeated.

def second_smallest(numbers):
     s = sorted(set(numbers))
     return s[1]
Suraj Kumar
  • 5,547
  • 8
  • 20
  • 42
NPE
  • 432
  • 5
  • 13
-1

Here is:

def find_second_smallest(a: list) -> int:
    first, second = float('inf')
    for i in range(len(a)):
        if a[i] < first:
            first, second = a[i], first
        elif a[i] < second and a[i] != first:
            second = a[i]
    return second

input: [1, 1, 1, 2]
output: 2

  • 1
    How is this different from the accepted answer? Upvote that one instead of posting the exact same solution. – razdi Sep 05 '19 at 23:47
-1

This code is also works fine, To find the second smallest number in list. For this code first we have to sort the values in list. after that we have to initialize the variable as second index.

l1 = [12,32,4,34,64,3,43]
for i in range(0,len(l1)):
       for j in range(0,i+1):
          if l1[i]<l1[j]:
              l1[i],l1[j]=l1[j],l1[i]
min_val = l1[1]
for k in l1:
   if min_val>k:
       break
print(min_val)
user 98
  • 167
  • 2
  • 12
-2
def SecondSmallest(x):
    lowest=min(x[0],x[1])
    lowest2 = max(x[0],x[1])
    for item in x:
         if item < lowest:
            lowest2 = lowest
            lowest = item
         elif lowest2 > item and item > lowest:
            lowest2 = item
    return lowest2

SecondSmallest([10,1,-1,2,3,4,5])
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
Mike
  • 458
  • 4
  • 13
  • How does this differ from the implementation in my answer? What advantage does using the first two values have over using `float('inf')`? – Martijn Pieters Apr 20 '21 at 19:58