Estoy intentando utilizar el findall de Python para tratar de encontrar todos los identificadores y no hipenatados en una cadena (esto es para conectarse al código existente, por lo que usar cualquier construcción más allá de findall no funcionará). Si imaginas un código como este:

regex = ...
body = "foo-bar foo-bar-stuff stuff foo-word-stuff"

ids = re.compile(regex).findall(body)

Me gustaría que el valor ids sea ['foo', 'bar', 'word', 'foo-bar', 'foo-bar-stuff', and 'stuff'] (aunque no bar-stuff, porque está separado por guiones, pero no aparece como un identificador independiente separado por espacios). El orden de la matriz / conjunto no es importante.

Una expresión regular simple que coincide con los identificadores no hipenatados es \w+ y una que coincide con los identificadores hipenatados es [\w-]+. Sin embargo, no puedo entender uno que haga ambas cosas simultáneamente (no tengo control total sobre el código, así que no puedo concatenar las listas juntas; me gustaría hacer esto en una expresión regular si es posible).

He intentado \w|[\w-]+ pero como la expresión es codiciosa, esto pierde bar, por ejemplo, solo coincide -bar ya que foo ya ha sido igualado y no volverá a intentarlo patrón desde la misma posición inicial. Me gustaría encontrar coincidencias para (por ejemplo) tanto foo como foo-bar que comienzan (están anclados) en la misma posición de cadena (que creo que findall simplemente no tiene en cuenta).

He estado probando algunos trucos como lookaheads / lookbehinds como se mencionó, pero no puedo encontrar ninguna manera de que sean aplicables a mi escenario.

Cualquier ayuda sería apreciada.

1
Andrew Ferrier 7 sep. 2018 a las 18:36

3 respuestas

La mejor respuesta

Puede usar

import re
s = "foo-bar foo-bar-stuff stuff" #=> {'foo-bar', 'foo', 'bar', 'foo-bar-stuff', 'stuff'}
# s = "A-B-C D" # => {'C', 'D', 'A', 'A-B-C', 'B'}
l = re.findall(r'(?<!\S)\w+(?:-\w+)*(?!\S)', s)
res = []
for e in l:
    res.append(e)
    res.extend(e.split('-'))
print(set(res))

Detalles del patrón

  • (?<!\S): sin espacios en blanco justo antes
  • \w+ - 1+ caracteres de palabras
  • (?:-\w+)* - cero o más repeticiones de
    • - - un guión
    • \w+ - 1+ caracteres de palabras
  • (?!\S): sin espacios en blanco justo después.

Consulte la demostración del patrón en línea.

Tenga en cuenta que para obtener todos los elementos, divido las coincidencias con - y agrego estos elementos a la lista resultante. Luego, con set, elimino cualquier posible engaño.

1
Wiktor Stribiżew 13 sep. 2018 a las 14:43

Si no tienes que usar regex

Solo use dividir (a continuación se muestra un ejemplo)

result = []
for x in body.split():
    if x not in result:
            result.append(x)
    for y in x.split('-'):
            if y not in result:
                    result.append(y)
1
omitsuhashi 7 sep. 2018 a las 16:28

Esto no es posible con findall solo, ya que encuentra todos coincidencias no superpuestas , como dice la documentación.

Todo lo que puede hacer es encontrar todas las coincidencias más largas con \w[-\w]* o algo así, y luego generar todos los intervalos válidos a partir de ellos (muy probablemente a partir de su split en '-').

Tenga en cuenta que \w[-\w]* también coincidirá con 123, 1-a y a--, por lo que podría ser algo como
(?=\D)\w[-\w]* o (?=\D)\w+(?:-\w+)* preferible (pero aún tendría que filtrar el 1 de a-1).

1
Walter Tross 7 sep. 2018 a las 17:16