Dopasuj i usuń zduplikowane znaki: Zamień wiele (3+) nie następujących po sobie wystąpień

9

Szukam regexwzoru, który będzie pasował do trzeciego, czwartego, ... wystąpienia każdej postaci. Spójrz na wyjaśnienia poniżej:

Na przykład mam następujący ciąg:

111aabbccxccybbzaa1

Chcę zastąpić wszystkie zduplikowane znaki po drugim wystąpieniu. Dane wyjściowe będą:

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

Niektóre wzorce wyrażeń regularnych, które próbowałem do tej pory:

Korzystając z następującego wyrażenia regularnego, mogę znaleźć ostatnie wystąpienie każdej postaci: (.)(?=.*\1)

Lub korzystając z tego mogę zrobić to dla kolejnych duplikatów, ale nie dla żadnych duplikatów: ([a-zA-Z1-9])\1{2,}

M--
źródło
1
Jakiego silnika wyrażeń regularnych zamierzasz używać z wyrażeniami regularnymi?
Wiktor Stribiżew,
1
Możesz to zrobić tylko z wyrażeniem regularnym, które obsługuje nieskończoną szerokość wyglądu, więc jedyną opcją jest moduł wyrażeń regularnych Python PyPi. Użyj go z (.)(?<=^(?:(?:(?!\1).)*\1){2,}(?:(?!\1).)*\1)wyrażeniem regularnym. Demo .
Wiktor Stribiżew,
3
@ WiktorStribiżew Czy to jest lepsze niż (.)(?<=(.*\1){3})?
Stefan Pochmann,
2
@StefanPochmann Cóż, (.)(?<=(?:.*\1){3})również wykona zadanie, ale nie są one dobre, ponieważ nadmierne cofanie może powodować problemy z dłuższymi ciągami. Wolę napisać metodę niebędącą wyrażeniem regularnym, aby rozwiązać problem.
Wiktor Stribiżew,
2
@ WiktorStribiżew Jeśli kilkakrotnie skopiuję łańcuch testowy do wyrażenia regularnego, co czyni go ogromnym łańcuchem, dostanę różnicę wydajności, np. Twój wzorzec 750ms, (.)(?<=(?:.*\1){3})25ms, (.)(?<=(?:\1.*?){2}\1)3ms. Możesz po prostu sprawdzić się. Twój wydaje się być najmniej wydajnym wzorcem i najtrudniej go odczytać.
bobble bubble

Odpowiedzi:

8

Rozwiązanie typu non-regex R. Podzielony ciąg. Zamień elementy tego wektora o rowid> = 3 * na '-'. Wklej to z powrotem razem.

x <- '111aabbccxccybbzaa1'

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

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

* rowid(x)jest wektorem liczb całkowitych, w którym każdy element reprezentuje liczbę xzrealizowanych wartości z odpowiedniego elementu . Więc jeśli ostatni element xjest 1, i to po raz czwarty 1wystąpił w x, ostatni element rowid(x)jest 4.

IceCreamToucan
źródło
4

Możesz to łatwo zrobić bez wyrażenia regularnego:

Zobacz używany kod tutaj

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)

Wynik:

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

Jak to działa:

  1. for u in set(s) pobiera listę unikalnych znaków w ciągu: {'c','a','b','y','1','z','x'}
  2. for i in ... pętle nad indeksami, które zbieramy w 3.
  3. [i for i in range(len(s)) if s[i]==u][2:]zapętla każdy znak w ciągu i sprawdza, czy pasuje u(od kroku 1.), a następnie przecina tablicę od drugiego elementu do końca (upuszczając dwa pierwsze elementy, jeśli istnieją)
  4. Ustaw ciąg, aby s[:i]+'-'+s[i+1:]- połączyć podłańcuch do indeksu, -a następnie podłańcuch po indeksie, skutecznie pomijając oryginalny znak.
ctwheels
źródło
3

Opcja z 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---"

dane

x <- '111aabbccxccybbzaa1'
akrun
źródło
2

Brak wyrażenia regularnego Python w jednym wierszu:

s = "111aabbccxccybbzaa1"

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

Wylicza to przez ciąg, licząc wystąpienia bieżącego znaku za nim i umieszczając znak tylko, jeśli jest jednym z pierwszych 2, w przeciwnym razie myślnik.

ParkerD
źródło
1

Kolejny sposób na zrobienie tego 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())

Wyjście :

11-aabbccx--y--z---
CypherX
źródło
0

Podziękowania dla Wiktora Stribiżew , Stefana Pochmanna i bobble bubble . W celu uzupełnienia zamieszczam możliwe regexrozwiązania omówione w komentarzach;

Jest to możliwe tylko w przypadku wyrażenia regularnego obsługującego wygląd nieskończonej szerokości. Za pomocą modułu regex Python PyPi możemy wykonać następujące czynności:

#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--
źródło