Find values in a list that are the same as the index where they are. How to optimize?

2

I'm learning programming with Python 3.6 and I'm taking part in a challenge site.

I need to find the smallest number whose value corresponds to its index in a list. If the list contains only one element, it should return the same. If there is more than one element that meets the criteria, it should return 1. If there is no such element, it should return -1. My code works, but needs to be optimized and run faster, under 1.5 seconds. Can anyone help? Here is the code below.

Thank you.

def index_equals_value(arr):
    hits = 0
    if len(arr)==1:
        return arr[0]
    for number in arr:
        if arr.index(number) == number:
            hits += 1
            hit_nmbr = number
            if hits > 1:
                return 1
                break
    if hits == 1:
        return hit_nmbr
   else:
       return -1
    
asked by anonymous 19.08.2018 / 15:49

2 answers

1

Your algorithm has an important bug. From the documentation:

  

list.index (x [ start [ end]])

     

Return zero-based index in the list of the first item whose value is equal to x

This means that your algorithm only compares number to with the first occurrence of that element in the list. That is, it fails the following case, for example:

l = [2, 0, 2]

def index_equals_value(arr):
    hits = 0
    if len(arr)==1:
        return arr[0]
    for number in arr:
        if arr.index(number) == number:
            hits += 1
            hit_nmbr = number
            if hits > 1:
                return 1
    if hits == 1:
        return hit_nmbr
    else:
        return -1

print(index_equals_value(l))  # -1

Here, the 2 in the 2 position satisfies the desired condition, but the comparison always happens with index 0, because that is the first occurrence of the number 2.

Incidentally, index is also a very slow function because it scans the entire list until it finds the first element passed to it. I mean, if you look for a x number and it occurs only in element 30000000 in the list, the index will go through 30000000 elements before finding and returning, when in fact we just need to know if arr[x] == x .

Measuring runtime:

# Criar lista aleatória
import random
l = [random.randint(0, 2000) for _ in range(2000)]

-

%%timeit
index_equals_value(l)  # Medir tempo de execução da função original
# 27.6 ms ± 2.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

-

def index_equals_value_2(arr):
    hits = 0
    if len(arr)==1:
        return arr[0]
    for number in arr:
        if arr[number] == number:
            hits += 1
            hit_nmbr = number
            if hits > 1:
                return 1
    if hits == 1:
        return hit_nmbr
    else:
        return -1

-

%%timeit
index_equals_value_2(l)  # Medir tempo de execução da função modificada
# 23.7 µs ± 108 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

As you can see, fixing the bug also speeds the function by 3 orders of magnitude.

You should now treat the exception resulting from a if arr[number] == number if number is greater than the size of the list, or implement a check so that this check does not happen. We also do not want negative numbers to be counted backwards, so we have to ignore them:

def index_equals_value_2(arr):
    hits = 0
    if len(arr)==1:
        return arr[0]
    for number in arr:
        if number < 0 or number >= len(arr):
            continue  # Número não pode satisfazer condição
        if arr[number] == number:
            hits += 1
            hit_nmbr = number
            if hits > 1:
                return 1
    if hits == 1:
        return hit_nmbr
    else:
        return -1

As Anderson's suggestion, we can also make a solution that iterates under the enumerate list and directly compares the index to the value, and we do not have to worry about it:

def index_equals_value_3(arr):
    hits = 0
    if len(arr)==1:
        return arr[0]
    for ind, number in enumerate(arr):
        if number == ind:
            hits += 1
            hit_nmbr = number
            if hits > 1:
                return 1
    if hits == 1:
        return hit_nmbr
    else:
        return -1
    
19.08.2018 / 16:12
0

I tried the solution below for all cases raised by @Pedro von Hertwig and they worked instantly:

import random
random.seed(42)

def listSearch(x):
    ''' Procura o primeiro índice i tal que x[i]=i.
        Retorna -1 caso não encontre.'''
    for i in range(len(x)):
            if x[i]==i : return i
    return -1

# Caso 1: 
random.seed(2)
l = [random.randint(0, 2000) for _ in range(200000)]
l[199999]=199999
print('O índice é: ',listSearch(l)) # O índice é: 199999

# Caso 2: 
l = [0]
print('O índice é: ',listSearch(l)) # O índice é: 0

# Caso 3: 
l = [3]
print('O índice é: ',listSearch(l)) # O índice é: -1

# Caso 4: 
random.seed(42)
l = [random.randint(0, 20000000) for _ in range(200000)]
l[199999]=199999
print('O índice é: ',listSearch(l)) # O índice é: 873

# Caso 5: 
random.seed(2)
l = [random.randint(0, 2000) for _ in range(200000)]
l[0] = 199999
l[199999]=199999
print('O índice é: ',listSearch(l)) # O índice é: 199999

# Caso 6:
random.seed(2) 
l = [random.randint(0, 2000) for _ in range(200000)]
print('O índice é: ',listSearch(l)) # O índice é: -1
    
23.08.2018 / 07:14