Corresponder e remover caracteres duplicados: Substitua várias (3+) ocorrências não consecutivas

9

Estou procurando um regexpadrão que corresponda à terceira, quarta, ... ocorrência de cada personagem. Veja abaixo os esclarecimentos:

Por exemplo, eu tenho a seguinte string:

111aabbccxccybbzaa1

Quero substituir todos os caracteres duplicados após a segunda ocorrência. A saída será:

11-aabbccx--y--z---

Alguns padrões de regex que eu tentei até agora:

Usando o seguinte regex, posso encontrar a última ocorrência de cada caractere: (.)(?=.*\1)

Ou, usando este, eu posso fazer isso por duplicatas consecutivas, mas não por duplicatas: ([a-zA-Z1-9])\1{2,}

M--
fonte
11
Qual mecanismo de regex você planeja usar com o regex?
Wiktor Stribiżew
11
Você só pode fazer isso com um regex compatível com largura infinita, portanto sua única opção é o módulo regex Python PyPi. Use-o com (.)(?<=^(?:(?:(?!\1).)*\1){2,}(?:(?!\1).)*\1)regex. Demo .
Wiktor Stribiżew
3
@ WiktorStribiżew Isso é melhor do que (.)(?<=(.*\1){3})?
Stefan Pochmann
2
@StefanPochmann Bem, (.)(?<=(?:.*\1){3})também fará o trabalho, mas tudo isso não é bom, pois o retorno excessivo pode causar problemas com seqüências mais longas. Prefiro escrever um método que não seja regex para resolver o problema.
Wiktor Stribiżew
2
@ WiktorStribiżew Se eu copiar o teststring para o regexstorm várias vezes, tornando-o uma string enorme, obtenho uma diferença de desempenho, por exemplo, seu padrão 750ms, (.)(?<=(?:.*\1){3})25ms, (.)(?<=(?:\1.*?){2}\1)3ms. Você pode apenas testar a si mesmo. O seu parece ser o padrão menos eficiente e mais difícil de ler.
Bubble Bobble

Respostas:

8

Solução não-regex R. Seqüência de caracteres dividida. Substitua os elementos deste vetor com rowid> = 3 * por '-'. Cole-o novamente.

x <- '111aabbccxccybbzaa1'

xsplit <- strsplit(x, '')[[1]]
xsplit[data.table::rowid(xsplit) >= 3] <- '-'
paste(xsplit, collapse = '')

# [1] "11-aabbccx--y--z---"

* rowid(x)é um vetor inteiro com cada elemento representando o número de vezes que o valor do elemento correspondente de xfoi realizado. Portanto, se o último elemento de xé 1, e é a quarta vez 1que ocorre x, o último elemento de rowid(x)é 4.

IceCreamToucan
fonte
4

Você pode fazer isso facilmente sem regex:

Veja o código em uso aqui

s = '111aabbccxccybbzaa1'

for u in set(s):
    for i in [i for i in range(len(s)) if s[i]==u][2:]:
        s = s[:i]+'-'+s[i+1:]

print(s)

Resultado:

11-aabbccx--y--z---

Como isso funciona:

  1. for u in set(s) obtém uma lista de caracteres únicos na string: {'c','a','b','y','1','z','x'}
  2. for i in ... loops sobre os índices que reunimos em 3.
  3. [i for i in range(len(s)) if s[i]==u][2:]faz um loop sobre cada caractere na string e verifica se ele corresponde u(da etapa 1.), em seguida, corta a matriz do segundo elemento até o final (descartando os dois primeiros elementos, se existirem)
  4. Defina a sequência para s[:i]+'-'+s[i+1:]- concatenar a substring até o índice com -e depois a substring após o índice, omitindo efetivamente o caractere original.
ctwheels
fonte
3

Uma opção com gsubfn

library(gsubfn)
p <- proto(fun = function(this, x) if (count >=3) '-' else x)
for(i in c(0:9, letters)) x <- gsubfn(i, p, x)
x
#[1] "11-aabbccx--y--z---"

dados

x <- '111aabbccxccybbzaa1'
akrun
fonte
2

Sem uma linha liner python regex:

s = "111aabbccxccybbzaa1"

print("".join(char if s.count(char, 0, i) < 2 else "-" for i, char in enumerate(s)))
# ==> "11-aabbccx--y--z---"

Isso enumera através da sequência, contando as ocorrências do caractere atual por trás dele e colocando o caractere apenas se for um dos 2 primeiros, caso contrário, traço.

ParkerD
fonte
1

Outra maneira de fazer isso pandas.

import pandas as pd

s = '111aabbccxccybbzaa1'
# 11-aabbccx--y--z---

df = pd.DataFrame({'Data': list(s)})
df['Count'] = 1
df['cumsum'] = df[['Data', 'Count']].groupby('Data').cumsum()
df.loc[df['cumsum']>=3, 'Data'] = '-'
''.join(df.Data.to_list())

Saída :

11-aabbccx--y--z---
CypherX
fonte
0

Agradecimentos a Wiktor Stribiżew , Stefan Pochmann e bobble bubble . Para fins de conclusão, estou publicando possíveis regexsoluções discutidas nos comentários;

Isso só é possível com um regex que suporta uma largura infinita. Usando o módulo regex Python PyPi, podemos fazer o seguinte:

#python 2.7.12

import regex

s = "111aabbccxccybbzaa1"

print(regex.sub(r'(.)(?<=^(?:(?:(?!\1).)*\1){2,}(?:(?!\1).)*\1)', '-', s)) #Wiktor Stribizew
     ## 11-aabbccx--y--z---

print(regex.sub(r'(.)(?<=(.*\1){3})', '-', s)) #Stefan Pochmann
     ## 11-aabbccx--y--z---

print(regex.sub(r'(.)(?<=(?:.*\1){3})', '-', s)) #Wiktor Stribizew
     ## 11-aabbccx--y--z---

print(regex.sub(r'(.)(?<=(?:\1.*?){2}\1)', '-', s)) #bobble bubble
     ## 11-aabbccx--y--z---

Snippet .

M--
fonte