Empareja y elimina caracteres duplicados: reemplaza múltiples (3+) ocurrencias no consecutivas

9

Estoy buscando un regexpatrón que coincida con la tercera, cuarta, ... aparición de cada personaje. Mire abajo para aclaraciones:

Por ejemplo, tengo la siguiente cadena:

111aabbccxccybbzaa1

Quiero reemplazar todos los caracteres duplicados después de la segunda aparición. El resultado será:

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

Algunos patrones de expresiones regulares que probé hasta ahora:

Usando la siguiente expresión regular puedo encontrar la última aparición de cada carácter: (.)(?=.*\1)

O usando este, puedo hacerlo por duplicados consecutivos pero no por duplicados: ([a-zA-Z1-9])\1{2,}

METRO--
fuente
1
¿Qué motor de expresiones regulares planea usar con las expresiones regulares?
Wiktor Stribiżew
1
Solo puede hacer eso con una expresión regular que admita mirar hacia atrás de ancho infinito, por lo que su única opción es el módulo de expresión regular Python PyPi. Úselo con (.)(?<=^(?:(?:(?!\1).)*\1){2,}(?:(?!\1).)*\1)expresiones regulares. Demostración .
Wiktor Stribiżew
3
@ WiktorStribiżew ¿Eso es mejor que (.)(?<=(.*\1){3})?
Stefan Pochmann
2
@StefanPochmann Bueno, (.)(?<=(?:.*\1){3})también hará el trabajo, pero todo esto no es bueno ya que el retroceso excesivo puede causar problemas con cadenas más largas. Prefiero escribir un método no regex para resolver el problema.
Wiktor Stribiżew
2
@ WiktorStribiżew Si copio la cadena de prueba en regexstorm varias veces, convirtiéndola en una cadena enorme, obtengo una diferencia de rendimiento, por ejemplo, su patrón 750ms, 25ms (.)(?<=(?:.*\1){3}), (.)(?<=(?:\1.*?){2}\1)3ms. Puedes ponerte a prueba. El tuyo parece ser el patrón menos eficiente y es el más difícil de leer.
Bubble Bubble

Respuestas:

8

Solución no regex R. Cuerda partida Reemplace los elementos de este vector que tengan rowid> = 3 * con '-'. Pegar de nuevo juntos.

x <- '111aabbccxccybbzaa1'

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

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

* rowid(x)es un vector entero con cada elemento que representa la cantidad de veces que xse ha realizado el valor del elemento correspondiente de . Entonces, si el último elemento de xes 1, y esa es la cuarta vez que 1ocurre x, el último elemento de rowid(x)es 4.

IceCreamToucan
fuente
4

Puede lograr esto fácilmente sin expresiones regulares:

Ver código en uso aquí

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---

Cómo funciona esto:

  1. for u in set(s) obtiene una lista de caracteres únicos en la cadena: {'c','a','b','y','1','z','x'}
  2. for i in ... recorre los índices que reunimos en 3.
  3. [i for i in range(len(s)) if s[i]==u][2:]recorre cada carácter en la cadena y comprueba si coincide u(desde el paso 1), luego corta la matriz desde el segundo elemento hasta el final (eliminando los dos primeros elementos si existen)
  4. Configure la cadena para s[:i]+'-'+s[i+1:]concatenar la subcadena hasta el índice con -y luego la subcadena después del índice, omitiendo efectivamente el carácter original.
ruedas dentadas
fuente
3

Una opción con 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---"

datos

x <- '111aabbccxccybbzaa1'
akrun
fuente
2

No regex python one-liner:

s = "111aabbccxccybbzaa1"

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

Esto enumera a través de la cadena, contando las ocurrencias del carácter actual detrás de él y solo coloca el carácter si es uno de los primeros 2, de lo contrario, el guión.

ParkerD
fuente
1

Otra forma de hacerlo 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())

Salida :

11-aabbccx--y--z---
CypherX
fuente
0

Gracias a Wiktor Stribiżew , Stefan Pochmann y Bobble Bubble . En aras de la finalización, estoy publicando posibles regexsoluciones discutidas en los comentarios;

Esto solo se puede hacer con una expresión regular que admita mirar hacia atrás de ancho infinito. Usando el módulo Pyge Pyge regex podemos hacer lo siguiente:

#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---

Fragmento .

METRO--
fuente