Übereinstimmende Zeichen entfernen und entfernen: Ersetzen Sie mehrere (3+) nicht aufeinanderfolgende Vorkommen

9

Ich suche nach einem regexMuster, das dem dritten, vierten, ... Vorkommen jedes Zeichens entspricht. Schauen Sie unten zur Verdeutlichung:

Zum Beispiel habe ich die folgende Zeichenfolge:

111aabbccxccybbzaa1

Ich möchte alle duplizierten Zeichen nach dem zweiten Vorkommen ersetzen. Die Ausgabe wird sein:

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

Einige Regex-Muster, die ich bisher ausprobiert habe:

Mit dem folgenden regulären Ausdruck kann ich das letzte Vorkommen jedes Zeichens finden: (.)(?=.*\1)

Oder mit diesem kann ich es für aufeinanderfolgende Duplikate tun, aber nicht für irgendwelche Duplikate: ([a-zA-Z1-9])\1{2,}

M--
quelle
1
Welche Regex-Engine möchten Sie mit dem Regex verwenden?
Wiktor Stribiżew
1
Sie können dies nur mit einem Regex tun, der Lookbehind mit unendlicher Breite unterstützt. Ihre einzige Option ist also das Python PyPi-Regex-Modul. Verwenden Sie es mit (.)(?<=^(?:(?:(?!\1).)*\1){2,}(?:(?!\1).)*\1)Regex. Demo .
Wiktor Stribiżew
3
@ WiktorStribiżew Ist das besser als (.)(?<=(.*\1){3})?
Stefan Pochmann
2
@StefanPochmann Nun, (.)(?<=(?:.*\1){3})ich werde den Job auch machen, aber all dies ist nicht gut, da übermäßiges Backtracking Probleme mit längeren Strings verursachen kann. Ich würde lieber eine Nicht-Regex-Methode schreiben, um das Problem zu lösen.
Wiktor Stribiżew
2
@ WiktorStribiżew Wenn ich den Teststring mehrmals in Regexstorm kopiere und daraus einen riesigen String mache, (.)(?<=(?:.*\1){3})erhalte ich einen Leistungsunterschied, z. B. Ihr Muster 750ms, 25ms, (.)(?<=(?:\1.*?){2}\1)3ms. Sie können sich einfach selbst testen. Ihr Muster scheint das am wenigsten effiziente zu sein, und es ist am schwersten zu lesen.
Bobble Bubble

Antworten:

8

Nicht-Regex-R-Lösung. Saite teilen. Ersetzen Sie Elemente dieses Vektors mit der Zeilen-ID> = 3 * durch '-'. Fügen Sie es wieder zusammen.

x <- '111aabbccxccybbzaa1'

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

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

* rowid(x)ist ein ganzzahliger Vektor, wobei jedes Element die Häufigkeit darstellt, mit der der Wert aus dem entsprechenden Element von xrealisiert wurde. Also , wenn das letzte Element xist 1, und das ist das vierte Mal 1in aufgetreten ist x, das letzte Element rowid(x)ist 4.

IceCreamToucan
quelle
4

Sie können dies leicht ohne Regex erreichen:

Siehe hier verwendeten Code

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)

Ergebnis:

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

So funktioniert das:

  1. for u in set(s) Ruft eine Liste eindeutiger Zeichen in der Zeichenfolge ab: {'c','a','b','y','1','z','x'}
  2. for i in ... Schleifen über die Indizes, die wir in 3 sammeln.
  3. [i for i in range(len(s)) if s[i]==u][2:]Durchläuft jedes Zeichen in der Zeichenfolge und prüft, ob es übereinstimmt u(ab Schritt 1). Anschließend wird das Array vom 2. Element bis zum Ende aufgeteilt (wobei die ersten beiden Elemente gelöscht werden, falls vorhanden).
  4. Stellen Sie die Zeichenfolge so ein, dass s[:i]+'-'+s[i+1:]- die Teilzeichenfolge bis zum Index mit -und dann die Teilzeichenfolge nach dem Index verkettet wird , wobei das ursprüngliche Zeichen effektiv weggelassen wird.
ctwheels
quelle
3

Eine Option mit 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---"

Daten

x <- '111aabbccxccybbzaa1'
akrun
quelle
2

Kein Regex Python Einzeiler:

s = "111aabbccxccybbzaa1"

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

Dies zählt durch die Zeichenfolge auf, zählt das Vorkommen des aktuellen Zeichens dahinter und setzt das Zeichen nur, wenn es eines der ersten 2 ist, andernfalls einen Strich.

ParkerD
quelle
1

Ein anderer Weg, es mit zu tun 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())

Ausgabe :

11-aabbccx--y--z---
CypherX
quelle
0

Vielen Dank an Wiktor Stribiżew , Stefan Pochmann und Bobble Bubble . Der Vollständigkeit halber veröffentliche ich mögliche regexLösungen, die in den Kommentaren erörtert werden.

Dies ist nur mit einem regulären Ausdruck möglich, der das Lookbehind mit unendlicher Breite unterstützt. Mit dem Python PyPi Regex-Modul können wir Folgendes tun:

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