Faites correspondre et supprimez les caractères en double: remplacez plusieurs (3+) occurrences non consécutives

9

Je recherche un regexmotif qui correspondra à la troisième, quatrième, ... occurrence de chaque caractère. Regardez ci-dessous pour des éclaircissements:

Par exemple, j'ai la chaîne suivante:

111aabbccxccybbzaa1

Je veux remplacer tous les caractères dupliqués après la deuxième occurrence. La sortie sera:

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

Quelques modèles d'expression régulière que j'ai essayés jusqu'à présent:

En utilisant l'expression régulière suivante, je peux trouver la dernière occurrence de chaque caractère: (.)(?=.*\1)

Ou en utilisant celui-ci, je peux le faire pour les doublons consécutifs mais pas pour les doublons: ([a-zA-Z1-9])\1{2,}

M--
la source
1
Quel moteur d'expression régulière envisagez-vous d'utiliser avec l'expression régulière?
Wiktor Stribiżew
1
Vous ne pouvez le faire qu'avec une expression régulière qui prend en charge la recherche de largeur infinie, votre seule option est alors le module d'expression régulière Python PyPi. Utilisez-le avec (.)(?<=^(?:(?:(?!\1).)*\1){2,}(?:(?!\1).)*\1)regex. Démo .
Wiktor Stribiżew
3
@ WiktorStribiżew Est-ce mieux que (.)(?<=(.*\1){3})?
Stefan Pochmann
2
@StefanPochmann Eh bien, (.)(?<=(?:.*\1){3})cela fera aussi l'affaire, mais tout cela n'est pas bon car un retour en arrière excessif peut causer des problèmes avec des chaînes plus longues. Je préfère écrire une méthode non regex pour résoudre le problème.
Wiktor Stribiżew
2
@ WiktorStribiżew Si je copie la chaîne de test dans regexstorm plusieurs fois, ce qui en fait une énorme chaîne, j'obtiens une différence de performance, par exemple votre modèle 750 ms, 25 (.)(?<=(?:.*\1){3})ms, 3 (.)(?<=(?:\1.*?){2}\1)ms. Vous pouvez simplement vous tester. Le vôtre semble être le modèle le moins efficace et il est le plus difficile à lire.
Bobble bubble

Réponses:

8

Solution non regex R. Chaîne fendue. Remplacez les éléments de ce vecteur ayant rowid> = 3 * par '-'. Collez-le ensemble.

x <- '111aabbccxccybbzaa1'

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

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

* rowid(x)est un vecteur entier dont chaque élément représente le nombre de fois où la valeur de l'élément correspondant de xa été réalisée. Donc, si le dernier élément de xest 1, et c'est la quatrième fois que cela 1se produit x, le dernier élément de rowid(x)est 4.

IceCreamToucan
la source
4

Vous pouvez facilement accomplir cela sans regex:

Voir le code utilisé ici

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)

Résultat:

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

Comment cela fonctionne:

  1. for u in set(s) obtient une liste de caractères uniques dans la chaîne: {'c','a','b','y','1','z','x'}
  2. for i in ... boucle sur les indices que nous rassemblons en 3.
  3. [i for i in range(len(s)) if s[i]==u][2:]boucle sur chaque caractère de la chaîne et vérifie s'il correspond u(à partir de l'étape 1.), puis il tranche le tableau du 2e élément à la fin (en supprimant les deux premiers éléments s'ils existent)
  4. Définissez la chaîne pour s[:i]+'-'+s[i+1:]- concaténer la sous-chaîne jusqu'à l'index avec -, puis la sous-chaîne après l'index, en omettant effectivement le caractère d'origine.
ctwheels
la source
3

Une option avec 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---"

Les données

x <- '111aabbccxccybbzaa1'
akrun
la source
2

Pas de doublure en 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---"

Cela énumère à travers la chaîne, en comptant les occurrences du caractère actuel derrière lui et en ne mettant le caractère que s'il est l'un des 2 premiers, sinon tiret.

ParkerD
la source
1

Une autre façon de le faire avec 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())

Sortie :

11-aabbccx--y--z---
CypherX
la source
0

Merci à Wiktor Stribiżew , Stefan Pochmann et bobble bubble . Par souci d’achèvement, je publie les regexsolutions possibles discutées dans les commentaires;

Cela n'est possible qu'avec un regex qui prend en charge la recherche de largeur infinie. En utilisant le module regex Python PyPi, nous pouvons effectuer les opérations suivantes:

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

Extrait .

M--
la source