Abbina e rimuovi caratteri duplicati: sostituisci più (3+) occorrenze non consecutive


9

Sto cercando un regexmodello che corrisponda alla terza, quarta, ... occorrenza di ciascun personaggio. Guarda sotto per chiarimenti:

Ad esempio ho la seguente stringa:

111aabbccxccybbzaa1

Voglio sostituire tutti i caratteri duplicati dopo la seconda occorrenza. L'output sarà:

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

Alcuni schemi regex che ho provato finora:

Utilizzando la seguente regex posso trovare l'ultima occorrenza di ciascun personaggio: (.)(?=.*\1)

O usando questo posso farlo per duplicati consecutivi ma non per duplicati: ([a-zA-Z1-9])\1{2,}


1
Quale motore regex prevedi di utilizzare con regex?
Wiktor Stribiżew,

1
Puoi farlo solo con un regex che supporta un lookbehind a larghezza infinita, quindi l'unica opzione è il modulo regex Python PyPi. Usalo con (.)(?<=^(?:(?:(?!\1).)*\1){2,}(?:(?!\1).)*\1)regex. Demo .
Wiktor Stribiżew,

3
@ WiktorStribiżew È meglio di (.)(?<=(.*\1){3})?
Stefan Pochmann,

2
@StefanPochmann Bene, (.)(?<=(?:.*\1){3})farà anche il lavoro, ma tutti questi non sono buoni poiché un eccessivo backtracking può causare problemi con stringhe più lunghe. Preferirei scrivere un metodo non regex per risolvere il problema.
Wiktor Stribiżew,

2
@ WiktorStribiżew Se copio il teststring in regexstorm diverse volte, rendendolo una stringa enorme, ottengo una differenza di prestazioni, ad esempio il tuo modello 750ms, (.)(?<=(?:.*\1){3})25ms, (.)(?<=(?:\1.*?){2}\1)3ms. Puoi solo metterti alla prova. Il tuo sembra essere il modello meno efficiente ed è più difficile da leggere.
bobble bubble

Risposte:


8

Soluzione R non regex. Dividi stringa. Sostituisci elementi di questo vettore con rowid> = 3 * con '-'. Incollalo di nuovo insieme.

x <- '111aabbccxccybbzaa1'

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

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

* rowid(x)è un vettore intero con ogni elemento che rappresenta il numero di volte in cui xè stato realizzato il valore dall'elemento corrispondente di . Quindi, se l'ultimo elemento di xè 1, ed è la quarta volta in cui si 1è verificato x, l'ultimo elemento di rowid(x)è 4.


4

Puoi farlo facilmente senza regex:

Vedi il codice in uso qui

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)

Risultato:

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

Come funziona:

  1. for u in set(s) ottiene un elenco di caratteri univoci nella stringa: {'c','a','b','y','1','z','x'}
  2. for i in ... passa in rassegna gli indici che raccogliamo in 3.
  3. [i for i in range(len(s)) if s[i]==u][2:]passa in rassegna ogni carattere della stringa e verifica se corrisponde u(dal passaggio 1), quindi suddivide l'array dal 2 ° elemento alla fine (rilasciando i primi due elementi se esistono)
  4. Impostare la stringa su s[:i]+'-'+s[i+1:]- concatena la sottostringa fino all'indice con -e quindi la sottostringa dopo l'indice, omettendo efficacemente il carattere originale.

3

Un'opzione 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---"

dati

x <- '111aabbccxccybbzaa1'

2

Nessuna reiner in pitone 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---"

Questo enumera attraverso la stringa, contando le occorrenze del carattere corrente dietro di essa e inserendo il carattere solo se è uno dei primi 2, altrimenti trattino.


1

Un altro modo di farlo 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())

Uscita :

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

0

Grazie a Wiktor Stribiżew , Stefan Pochmann e alla bolla bobble . Per motivi di completamento, sto postando possibili regexsoluzioni discusse nei commenti;

Questo è possibile solo con una regex che supporta un lookbehind a larghezza infinita. Utilizzando il modulo regex Python PyPi possiamo fare quanto segue:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.