Parece que deve ser bem trivial, mas sou novo em Python e quero fazer isso da maneira mais pitônica.
Quero encontrar o índice correspondente à enésima ocorrência de uma substring dentro de uma string.
Deve haver algo equivalente ao que EU QUERO fazer, que é
mystring.find("substring", 2nd)
Como você pode fazer isso em Python?
Respostas:
A abordagem iterativa de Mark seria a maneira usual, eu acho.
Esta é uma alternativa com divisão de string, que muitas vezes pode ser útil para encontrar processos relacionados:
E aqui está uma linha rápida (e um tanto suja, em que você tem que escolher uma palha que não combina com a agulha):
fonte
.rfind('XXX')
, mas isso iria desmoronar se'XXX'
aparecer mais tarde na entrada de qualquer maneira.Aqui está uma versão mais pitônica da solução iterativa direta:
Exemplo:
Se você deseja encontrar a enésima ocorrência de sobreposição de
needle
, você pode incrementar por em1
vez delen(needle)
, assim:Exemplo:
É mais fácil de ler do que a versão de Mark e não requer a memória extra da versão de divisão ou módulo de expressão regular de importação. Ele também segue algumas das regras do Zen do python , ao contrário das várias
re
abordagens:fonte
Isso encontrará a segunda ocorrência de substring na string.
Edit: Não pensei muito sobre o desempenho, mas uma recursão rápida pode ajudar a encontrar a enésima ocorrência:
fonte
n
ocorrências da substring. (Neste caso, o valor de retorno percorrerá periodicamente todas as posições de ocorrência).Entendendo que regex nem sempre é a melhor solução, provavelmente usaria uma aqui:
fonte
(m.start() for m in re.finditer(r"ab",s))[2]
itertools.islice
função:next(islice(re.finditer(r"ab",s), 2, 2+1)).start()
Estou oferecendo alguns resultados de benchmarking comparando as abordagens mais proeminentes apresentadas até agora, nomeadamente @bobince's
findnth()
(baseado emstr.split()
) vs. @tgamblin's ou @Mark Byers 'find_nth()
(baseado emstr.find()
). Também vou comparar com uma extensão C (_find_nth.so
) para ver o quão rápido podemos ir. Aqui estáfind_nth.py
:Claro, o desempenho é mais importante se a string for grande, então suponha que queremos encontrar a 1000001ª nova linha ('\ n') em um arquivo de 1,3 GB chamado 'bigfile'. Para economizar memória, gostaríamos de trabalhar em uma
mmap.mmap
representação de objeto do arquivo:Já existe o primeiro problema com
findnth()
, uma vez que osmmap.mmap
objetos não suportamsplit()
. Portanto, temos que copiar todo o arquivo para a memória:Ai! Felizmente,
s
ainda cabe nos 4 GB de memória do meu Macbook Air, então vamos avaliarfindnth()
:Claramente um desempenho terrível. Vamos ver como a abordagem baseada em
str.find()
:Muito melhor! Claramente,
findnth()
o problema de é que ele é forçado a copiar a string durantesplit()
, que já é a segunda vez que copiamos 1,3 GB de dados depoiss = mm[:]
. Aí vem a segunda vantagem defind_nth()
: Podemos usá-lomm
diretamente, de forma que nenhuma cópia do arquivo seja necessária:Parece haver uma pequena penalidade de desempenho operando em
mm
vs.s
, mas isso ilustra quefind_nth()
pode nos dar uma resposta em 1,2 s em comparação comfindnth
o total de 47 s.Não encontrei nenhum caso em que a
str.find()
abordagem baseada fosse significativamente pior do que astr.split()
abordagem baseada, então, neste ponto, eu diria que a resposta de @tgamblin ou @Mark Byers deve ser aceita em vez da de @bobince.Em meus testes, a versão
find_nth()
acima foi a solução Python puro mais rápida que eu poderia criar (muito semelhante à versão de @Mark Byers). Vamos ver o quanto podemos fazer melhor com um módulo de extensão C. Aqui está_find_nthmodule.c
:Aqui está o
setup.py
arquivo:Instale normalmente com
python setup.py install
. O código C tem uma vantagem aqui, pois se limita a encontrar caracteres únicos, mas vamos ver o quão rápido isso é:Claramente um pouco mais rápido ainda. Curiosamente, não há diferença no nível C entre os casos in-memory e mmapped. Também é interessante ver que
_find_nth2()
, que é baseado nostring.h
'smemchr()
função de biblioteca, perde-se contra a implementação direta em_find_nth()
: Os 'otimizações' adicionaismemchr()
estão aparentemente frustrada ...Concluindo, a implementação em
findnth()
(com base emstr.split()
) é realmente uma má ideia, uma vez que (a) ela tem um desempenho péssimo para strings maiores devido à cópia necessária e (b) não funciona emmmap.mmap
objetos. A implementação emfind_nth()
(com base emstr.find()
) deve ser preferida em todas as circunstâncias (e, portanto, ser a resposta aceita para essa pergunta).Ainda há bastante espaço para melhorias, já que a extensão C rodou quase um fator de 4 mais rápido do que o código Python puro, indicando que pode haver um caso para uma função de biblioteca Python dedicada.
fonte
Maneira mais simples?
fonte
Eu provavelmente faria algo assim, usando a função find que usa um parâmetro de índice:
Não é particularmente Pythônico, eu acho, mas é simples. Você poderia fazer isso usando recursão em vez disso:
É uma forma funcional de resolver, mas não sei se isso o torna mais pitônico.
fonte
for _ in xrange(n):
pode ser usado em vez dewhile n: ... n-=1
return find_nth(s, x, n - 1, i + 1)
deveria serreturn find_nth(s, x, n - 1, i + len(x))
. Não é grande coisa, mas economiza algum tempo de computação.Isso lhe dará uma matriz dos índices iniciais para correspondências com
yourstring
:Então, sua enésima entrada seria:
Claro, você deve ter cuidado com os limites do índice. Você pode obter o número de instâncias
yourstring
como este:fonte
Aqui está outra abordagem usando re.finditer.
A diferença é que isso só olha para o palheiro na medida do necessário
fonte
Aqui está outra versão
re
+itertools
que deve funcionar ao pesquisar por astr
ou aRegexpObject
. Admito francamente que provavelmente isso é um excesso de engenharia, mas por algum motivo me divertiu.fonte
Com base na resposta do modle13 , mas sem a
re
dependência do módulo.Eu meio que gostaria que este fosse um método de string embutido.
fonte
fonte
Fornecendo outra solução "complicada", que usa
split
ejoin
.No seu exemplo, podemos usar
fonte
fonte
find_nth('aaa', 'a', 0)
retorna1
enquanto deveria retornar0
. Você precisa de algo parecidoi = s.find(substr, i) + 1
e depois voltari - 1
.Solução sem usar loops e recursão.
fonte
Substituir um forro é ótimo, mas só funciona porque XX e barra têm o mesmo lentgh
Uma boa definição geral seria:
fonte
Esta é a resposta que você realmente deseja:
fonte
Aqui está minha solução para encontrar
n
a ocorrência deb
na stringa
:É puro Python e iterativo. Para 0 ou
n
muito grande, retorna -1. É de uma linha e pode ser usado diretamente. Aqui está um exemplo:fonte
Para o caso especial em que você procura a enésima ocorrência de um caractere (ou seja, substring de comprimento 1), a seguinte função funciona construindo uma lista de todas as posições de ocorrências do caractere fornecido:
Se houver menos de
n
ocorrências do personagem dado, ele daráIndexError: list index out of range
.Isso é derivado da resposta de @Zv_oDD e simplificado para o caso de um único caractere.
fonte
Def:
Usar:
Resultado:
fonte
E se:
fonte