57 votes

Extraction de paires de mots à l'aide de String.split()

Étant donné :

String input = "one two three four five six seven";

Existe-t-il une expression rationnelle qui fonctionne avec String.split() pour saisir (jusqu'à) deux mots à la fois, de sorte que :

String[] pairs = input.split("some regex");
System.out.println(Arrays.toString(pairs));

résulte en ceci :

[one two, three four, five six, seven]

Cette question porte sur la regex fractionnée . Il est pas de "trouver une solution de contournement" ou d'autres solutions "pour que ça marche d'une autre manière".

7 votes

Pourquoi ? Est-ce un puzzle ou un réel problème ?

3 votes

C'est une énigme... mais cela m'a suffisamment intéressé pour la poser, parce que les look-behinds doivent être de longueur limitée, donc cela semble être un problème non trivial.

3 votes

+1. Question très intéressante.

80voto

Pshemo Points 34648

Actuellement (dernier test sur Java 17), il est possible de le faire avec split() mais dans le monde réel, n'utilisez pas cette approche car elle semble être basée sur un bogue, puisque le look-behind en Java devrait avoir une longueur maximale évidente. \w+ qui ne respecte pas cette limitation et qui, d'une manière ou d'une autre, fonctionne toujours. S'il s'agit d'un bogue qui sera corrigé dans les versions ultérieures, cette solution cessera de fonctionner.

Utilisez plutôt Pattern y Matcher avec des expressions rationnelles comme \w+\s+\w+ qui, en plus d'être plus sûr, évite l'enfer de la maintenance pour les personnes qui hériteront de ce code (n'oubliez pas de " Toujours coder comme si la personne qui finit par maintenir votre code est un psychopathe violent qui sait où vous vivez. ").


Est-ce que c'est ce que vous recherchez ?
(vous pouvez remplacer <code>\w</code> avec <code>\S</code> pour inclure tous les caractères qui ne sont pas des espaces, mais pour cet exemple je laisserai <code>\w</code> puisqu'il est plus facile de lire les expressions rationnelles avec <code>\w\s</code> entonces <code>\S\s</code> )

String input = "one two three four five six seven";
String[] pairs = input.split("(?<!\\G\\w+)\\s");
System.out.println(Arrays.toString(pairs));

sortie :

[one two, three four, five six, seven]

\G est la correspondance précédente, (?<!regex) est un regard négatif en arrière.

Sur split nous essayons de

  1. trouver des espaces -> \\s
  2. qui ne sont pas prédits -> (?<!negativeLookBehind)
  3. par un mot -> \\w+
  4. avec le précédemment apparié (espace) -> \\G
  5. avant qu'il -> \\G\\w+ .

La seule confusion que j'ai eue au début était de savoir comment cela fonctionnerait pour le premier espace puisque nous voulons que cet espace soit ignoré. Une information importante est que \\G au début correspond au début de la chaîne ^ .

Ainsi, avant la première itération, la regex en look-behind négatif ressemblera à ceci (?<!^\\w+) et depuis le premier espace faire ont ^\\w+ avant, ça ne peut pas correspondre à la division. L'espace suivant n'aura pas ce problème, il sera donc mis en correspondance et les informations le concernant (comme son numéro d'immatriculation) seront prises en compte. position en input String) sera stocké dans \\G et utilisé plus tard dans le prochain look-behind négatif.

Ainsi, pour le troisième espace, la regex vérifiera s'il y a un espace correspondant précédemment. \\G et le mot \\w+ avant lui. Puisque le résultat de ce test sera positif, le look-behind négatif ne l'acceptera pas et cet espace ne sera pas apparié, mais le 4ème espace n'aura pas ce problème parce que l'espace qui le précède ne sera pas le même que celui stocké en \\G (il aura une position différente dans input String).


De même, si quelqu'un souhaite se séparer, disons, d'un espace sur trois, il peut utiliser ce formulaire (basé sur le principe de la séparation des espaces). @maybeWeCouldStealAVan 's respuesta qui a été supprimé lorsque j'ai posté ce fragment de réponse)

input.split("(?<=\\G\\w{1,100}\\s\\w{1,100}\\s\\w{1,100})\\s")

Au lieu de 100, vous pouvez utiliser une valeur plus grande qui sera au moins égale à la longueur du mot le plus long de la chaîne.


Je viens de remarquer que l'on peut aussi utiliser + au lieu de {1,maxWordLength} si nous voulons diviser avec chaque nombre impair comme chaque 3ème, 5ème, 7ème par exemple.

String data = "0,0,1,2,4,5,3,4,6,1,3,3,4,5,1,1";
String[] array = data.split("(?<=\\G\\d+,\\d+,\\d+,\\d+,\\d+),");//every 5th comma

0 votes

Vous devriez probablement changer \w+ a \S+ au cas où les "mots" notionnels ne seraient pas en fait des mots. En outre, pourriez-vous ajouter une description/explication détaillée de la raison pour laquelle cela fonctionne ? C'est une excellente regex, il serait bon de s'assurer que tout le monde la comprenne bien aussi.

1 votes

En guise d'incitation, si vous ajoutez une bonne explication dans l'heure qui suit, je vous offre une prime de +50 ! :) (en fait, c'est n'importe quoi - j'accorde la prime de toute façon - vous la méritez, car pour ma part j'ai appris quelque chose)

0 votes

@Bohème Pourriez-vous vérifier si l'explication dans ma réponse mise à jour est suffisante ?

9voto

Cela fonctionne, mais la longueur maximale des mots doit être fixée à l'avance :

String input = "one two three four five six seven eight nine ten eleven";
String[] pairs = input.split("(?<=\\G\\S{1,30}\\s\\S{1,30})\\s");
System.out.println(Arrays.toString(pairs));

Je préfère la réponse de Pshemo, qui est plus courte et utilisable sur des longueurs de mots arbitraires, mais celle-ci (comme @Pshemo l'a souligné) a l'avantage de pouvoir s'adapter à des groupes de plus de 2 mots.

1 votes

Je vous donne un +1, mais cela ne répond pas à la question d'avoir des mots arbitrairement longs. Au moins, vous avez réussi à faire fonctionner quelque chose.

2 votes

+1 pour une réponse qui peut être adaptée facilement à un nombre quelconque de mots à regrouper.

0 votes

J'ai testé les deux solutions dans un problème similaire de division d'un tableau en paires et c'est votre regex qui a fonctionné pour moi.

0voto

alpha bravo Points 2481

Cela a fonctionné pour moi (\w+\s*){2}\K\s exemple ici

  • un mot obligatoire suivi d'un espace facultatif (\w+\s*)
  • répété deux fois {2}
  • ignorer les caractères déjà trouvés \K
  • l'espace nécessaire \s

-1voto

Alex Points 2444

Vous pouvez essayer ceci :

[a-z]+\s[a-z]+

Mis à jour :

([a-z]+\s[a-z]+)|[a-z]+

enter image description here

Mis à jour :

 String pattern = "([a-z]+\\s[a-z]+)|[a-z]+";
 String input = "one two three four five six seven";

 Pattern splitter = Pattern.compile(pattern);
 String[] results = splitter.split(input);

 for (String pair : results) {
 System.out.println("Output = \"" + pair + "\"");

0 votes

Est-ce que cela va saisir le seven ne correspond pas à une deuxième paire de mots ?

9 votes

Cela ne répond pas à la question. Votre regex correspond au contenu cible, mais split() nécessite une expression rationnelle pour correspondre au séparateurs . Votre regex ne fonctionne pas (avec split() )

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X