web-dev-qa-db-fra.com

Trouver l'index du nième élément dans une liste

Je veux trouver l'index de la nième occurrence d'un article dans une liste. par exemple., 

x=[False,True,True,False,True,False,True,False,False,False,True,False,True]

Quel est l'index du nième vrai? Si je voulais la cinquième occurrence (4ème si indexé à zéro), la réponse est 10.

Je suis venu avec:

indargs = [ i for i,a in enumerate(x) if a ]
indargs[n]

Notez que x.index renvoie la première occurrence ou la première après un certain point et, par conséquent, autant que je sache, n’est pas une solution.

Il existe également une solution numérique pour les cas similaires à ceux décrits ci-dessus, par ex. en utilisant cumsum et where, mais j'aimerais savoir s'il existe un moyen, sans numérotation, de résoudre le problème.

Je suis préoccupé par la performance depuis que je l’ai rencontré pour la première fois alors que je mettais en place un tamis d’Ératosthène pour un problème de Project Euler , mais c’est une question plus générale que j’ai rencontrée dans d’autres situations.

EDIT: J'ai eu beaucoup de bonnes réponses, alors j'ai décidé de faire des tests de performance. Vous trouverez ci-dessous timeit temps d'exécution en secondes pour les listes avec lenéléments recherchant le 4000'th/1000'th True. Les listes sont aléatoires Vrai/Faux. Code source lié ci-dessous; c'est une touche désordonnée. J'ai utilisé des versions courtes/modifiées des noms des affiches pour décrire les fonctions, à l'exception de listcomp, qui correspond à la compréhension de liste simple ci-dessus.

True Test (100'th True in a list containing True/False)
         nelements      eyquem_occur eyquem_occurrence            graddy            taymon          listcomp       hettinger26         hettinger
             3000:          0.007824          0.031117          0.002144          0.007694          0.026908          0.003563          0.003563
            10000:          0.018424          0.103049          0.002233          0.018063          0.088245          0.003610          0.003769
            50000:          0.078383          0.515265          0.002140          0.078074          0.442630          0.003719          0.003608
           100000:          0.152804          1.054196          0.002129          0.152691          0.903827          0.003741          0.003769
           200000:          0.303084          2.123534          0.002212          0.301918          1.837870          0.003522          0.003601
True Test (1000'th True in a list containing True/False)
         nelements      eyquem_occur eyquem_occurrence            graddy            taymon          listcomp       hettinger26         hettinger
             3000:          0.038461          0.031358          0.024167          0.039277          0.026640          0.035283          0.034482
            10000:          0.049063          0.103241          0.024120          0.049383          0.088688          0.035515          0.034700
            50000:          0.108860          0.516037          0.023956          0.109546          0.442078          0.035269          0.035373
           100000:          0.183568          1.049817          0.024228          0.184406          0.906709          0.035135          0.036027
           200000:          0.333501          2.141629          0.024239          0.333908          1.826397          0.034879          0.036551
True Test (20000'th True in a list containing True/False)
         nelements      eyquem_occur eyquem_occurrence            graddy            taymon          listcomp       hettinger26         hettinger
             3000:          0.004520          0.004439          0.036853          0.004458          0.026900          0.053460          0.053734
            10000:          0.014925          0.014715          0.126084          0.014864          0.088470          0.177792          0.177716
            50000:          0.766154          0.515107          0.499068          0.781289          0.443654          0.707134          0.711072
           100000:          0.837363          1.051426          0.501842          0.862350          0.903189          0.707552          0.706808
           200000:          0.991740          2.124445          0.498408          1.008187          1.839797          0.715844          0.709063
Number Test (750'th 0 in a list containing 0-9)
         nelements      eyquem_occur eyquem_occurrence            graddy            taymon          listcomp       hettinger26         hettinger
             3000:          0.026996          0.026887          0.015494          0.030343          0.022417          0.026557          0.026236
            10000:          0.037887          0.089267          0.015839          0.040519          0.074941          0.026525          0.027057
            50000:          0.097777          0.445236          0.015396          0.101242          0.371496          0.025945          0.026156
           100000:          0.173794          0.905993          0.015409          0.176317          0.762155          0.026215          0.026871
           200000:          0.324930          1.847375          0.015506          0.327957          1.536012          0.027390          0.026657

La solution de Hettinger est presque toujours la meilleure. Les solutions de taymon et de graddy sont les meilleures dans la plupart des situations, bien que l’approche de compréhension de liste puisse être meilleure pour les tableaux de taille réduite lorsque vous voulez la nième instance telle que n est élevée ou que les listes contiennent moins de n occurrences. S'il y a un risque qu'il y ait moins de n occurrences, la vérification count initiale permet de gagner du temps. De plus, Graddy est plus efficace lorsque vous recherchez des nombres au lieu de Vrai/Faux ... vous ne savez pas pourquoi. Les solutions d'Eyquem sont essentiellement équivalentes à d'autres solutions légèrement plus ou moins lourdes; eyquem_occur est approximativement identique à la solution de taymon, tandis que eyquem_occurrence est similaire à listcomp.

39
keflavich

La réponse de @Taymon avec list.index était géniale.

FWIW, voici une approche fonctionnelle utilisant le module itertools . Cela fonctionne avec n'importe quelle entrée, pas seulement des listes:

>>> from itertools import compress, count, imap, islice
>>> from functools import partial
>>> from operator import eq

>>> def nth_item(n, item, iterable):
        indicies = compress(count(), imap(partial(eq, item), iterable))
        return next(islice(indicies, n, None), -1)

L'exemple est Nice car il montre comment combiner efficacement l'ensemble d'outils fonctionnels de Python. Notez que, une fois le pipeline configuré, la boucle eval de Python n’est plus déclenchée: tout se fait à la vitesse C, avec un faible encombrement mémoire, avec une évaluation paresseuse, sans affectation de variable et avec des composants testables séparément. OOW, c'est tout ce dont rêvent les programmeurs fonctionnels :-)

Échantillon échantillon:

>>> x = [False,True,True,False,True,False,True,False,False,False,True,False,True]
>>> nth_item(50, True, x)
-1
>>> nth_item(0, True, x)
1
>>> nth_item(1, True, x)
2
>>> nth_item(2, True, x)
4
>>> nth_item(3, True, x)
6
35
Raymond Hettinger

Je ne peux pas dire avec certitude que c'est le moyen le plus rapide, mais j'imagine que ce serait très bien:

i = -1
for j in xrange(n):
    i = x.index(True, i + 1)

La réponse est i.

27
Taymon

si l'efficacité est une préoccupation, je pense qu'il est préférable de l'itérer normalement (O(N)) au lieu de la compréhension de liste qui prend O(L) où L est la longueur de la liste

Exemple: Considérez une très longue liste et si vous voulez trouver la première occurrence N = 1, il est évidemment préférable d’arrêter dès que vous trouvez la première occurrence.

count = 0
for index,i in enumerate(L):
    if i:
        count = count + 1
        if count==N:
            return index
2
Graddy

Si vous êtes préoccupé par les performances, vous feriez mieux de savoir s’il existe des optimisations algorithmiques à faire. Par exemple, si vous appelez cette fonction plusieurs fois sur les mêmes valeurs, vous pouvez souhaiter mettre en cache les calculs précédents (par exemple, une fois que vous avez trouvé la 50ème occurrence d'un élément, vous pouvez rechercher toute occurrence précédente dans O(1) time).

Sinon, vous voulez vous assurer que votre technique fonctionne sur des itérateurs (paresseux).

La manière la plus * in * élégante et heureuse de fonctionner est la suivante:

def indexOfNthOccurrence(N, element, stream):
    """for N>0, returns index or None"""
    seen = 0
    for i,x in enumerate(stream):
        if x==element:
            seen += 1
            if seen==N:
                return i

(Si vous vous souciez vraiment de la différence de performance entre énumération et d'autres techniques, vous devrez recourir au profilage, en particulier avec les fonctions numpy, qui peuvent recourir à C)

Pour prétraiter le flux entier et prendre en charge les requêtes O(1):

from collections import *
cache = defaultdict(list)
for i,elem in enumerate(YOUR_LIST):
    cache[elem] += [i]

# e.g. [3,2,3,2,5,5,1]
#       0 1 2 3 4 5 6
# cache: {3:[0,2], 1:[6], 2:[1,3], 5:[4,5]}
2
ninjagecko

Une solution qui crée d’abord un objet liste et retourne l’élément nth-1 de cette liste: function occurrenceence ()

Et une solution qui remplit également les flux de programmeurs fonctionnels, je pense, en utilisant des générateurs, parce que je les adore: function se produit ()  

S = 'stackoverflow.com is a fantastic amazing site'
print 'object S is string %r' % S
print "indexes of 'a' in S :",[indx for indx,elem in enumerate(S) if elem=='a']

def occurence(itrbl,x,nth):
    return [indx for indx,elem in enumerate(itrbl)
            if elem==x ][nth-1] if x in itrbl \
           else None

def occur(itrbl,x,nth):
    return (i for pos,i in enumerate(indx for indx,elem in enumerate(itrbl)
                                     if elem==x)
            if pos==nth-1).next() if x in itrbl\
            else   None

print "\noccurence(S,'a',4th) ==",occurence(S,'a',4)
print "\noccur(S,'a',4th) ==",occur(S,'a',4)

résultat

object S is string 'stackoverflow.com is a fantastic amazing site'
indexes of 'a' in S : [2, 21, 24, 27, 33, 35]

occur(S,'a',4th) == 27

occurence(S,'a',4th) == 27

La deuxième solution semble complexe mais ce n’est pas vraiment le cas. Il n'est pas nécessaire d'exécuter complètement l'itéré: le processus s'arrête dès que l'occurrence recherchée est trouvée.

2
eyquem
[y for y in enumerate(x) if y[1]==True][z][0]

Note: Ici Z est la nième occurrence,

2
avasal

Voici un autre moyen de trouver l'occurrence nth de x dans une liste itrbl:

def nthoccur(nth,x,itrbl):
    count,index = 0,0
    while count < nth:
        if index > len(itrbl) - 1:
            return None
        Elif itrbl[index] == x:
            count += 1
            index += 1
        else:
            index += 1
    return index - 1
2
apolune

Je pense que cela devrait fonctionner.

def get_nth_occurrence_of_specific_term(my_list, term, n):
    assert type(n) is int and n > 0
    start = -1
    for i in range(n):
        if term not in my_list[start + 1:]:
            return -1
        start = my_list.index(term, start + 1)
    return start
0
Johnny Woo

Vous pouvez utiliser count :

from itertools import count

x = [False, True, True, False, True, False, True, False, False, False, True, False, True]


def nth_index(n, item, iterable):
    counter = count(1)
    return next((i for i, e in enumerate(iterable) if e == item and next(counter) == n), -1)


print(nth_index(3, True, x))

Sortie

4

L'idée est qu'en raison de la nature de court-circuit de e == item and next(counter) == n), l'expression next(counter) == n n'est évaluée que lorsque e == item, de sorte que vous ne comptez que les éléments égaux à item.

0
Daniel Mesejo

voici un moyen:
pour l'exemple ci-dessus: 

x=[False,True,True,False,True,False,True,False,False,False,True,False,True]

on peut définir une fonction find_index

def find_index(lst, value, n):
    c=[]
    i=0
    for element in lst :
          if element == value :
              c .append (i)
          i+=1    
    return c[n]

et si on applique la fonction: 

nth_index = find_index(x, True, 4)
print nth_index

le résultat est: 

10
0
mzn.rft

Vous pouvez utiliser next avec enumerate et une expression génératrice. itertools.islice vous permet de découper un itératif comme requis.

from itertools import islice

x = [False,True,True,False,True,False,True,False,False,False,True,False,True]

def get_nth_index(L, val, n):
    """return index of nth instance where value in list equals val"""
    return next(islice((i for i, j in enumerate(L) if j == val), n-1, n), -1)

res = get_nth_index(x, True, 3)  # 4

Si l'itérateur est épuisé, c'est-à-dire que l'occurrence n / de la valeur spécifiée n'existe pas, next peut renvoyer une valeur par défaut, dans cet exemple -1:

0
jpp