Expresiones regulares - Aserciones

Las aserciones (Assertion) son metacaracteres en las expresiones regulares que se utilizan para especificar la posición de la coincidencia. No coinciden con ningún carácter real, sino con la posición entre caracteres.

Supongamos que queremos encontrar todos los números que van después de los precios en un texto largo, en lugar de encontrar todos los números. Una expresión regular común podría coincidir con todos los números, pero con las aserciones puedes especificar con precisión:Solo coincidir con los números que van inmediatamente después del precio。

Este artículo te guiará para aprender sistemáticamente los cuatro tipos principales de aserciones en expresiones regulares: lookahead positivo, lookahead negativo, lookbehind positivo y lookbehind negativo.

Características de las aserciones

  1. Ancho cero: No ocupa la posición de los caracteres coincidentes
  2. Comprobación de condiciones: Solo comprueba si se cumple una condición específica
  3. No afecta el resultado de la coincidencia: Actúa únicamente como una restricción para la coincidencia

Ejemplo

// Ejemplo: hacer coincidir foo seguido de bar
const regex = /foo(?=bar)/;
console.log(regex.test("foobar"));  // true
console.log(regex.test("food"));    // false

Tipos de aserciones

Las aserciones en las expresiones regulares se dividen principalmente en dos categorías y cuatro tipos:

Tipo de aserción Sintaxis de regex Alias Dirección de comprobación Condición esperada Explicación sencilla
Lookahead positivo (?=pattern) Lookahead positivo Hacia la derecha (hacia adelante) Existe pattern La posición que busco, sulado derecho debeser...
Lookahead negativo (?!pattern) Lookahead negativo Hacia la derecha (hacia adelante) No existe pattern La posición que busco, sulado derecho no debeser...
Lookbehind positivo (?<=pattern) Lookbehind positivo Hacia la izquierda (hacia atrás) Existe pattern La posición que busco, sulado izquierdo debeser...
Lookbehind negativo (?<!pattern) Lookbehind negativo Hacia la izquierda (hacia atrás) No existe pattern La posición que busco, sulado izquierdo no debeser...

Lookahead positivo(?=...)

Lookahead positivoSe utiliza para hacer coincidir una posición tal que, después de esta posición (a la derecha), debe aparecer inmediatamente el patrón especificado....。

Sintaxis y parámetros

  • Sintaxis:(?=pattern)
  • Función: Comprueba si el lado derecho de la posición actual coincide.patternSi coincide, la aserción es exitosa y el motor regresa a la posición actual para continuar con la coincidencia posterior.
  • Característica clave:Ancho cero, es decir, solo comprueba y no "consume" ningún carácter.patternEl contenido dentro de no formará parte del resultado final de la coincidencia.

Ejemplo de código 1: Extraer números de precio

Supongamos que tenemos un texto y necesitamos extraer todos losprecios:los números de importe que les siguen.

Ejemplo: JavaScript

const text = "Precio del producto A: 299 yuanes, precio del producto B: 599 yuanes, gastos de envío: 20 yuanes.";

// Lookahead positivo
// Coincide con uno o más dígitos, pero requiere que a la derecha siga inmediatamente "元"
// "元" en sí no participa en el resultado de la coincidencia
const pattern = /\d+(?=元)/g;

const matches = text.match(pattern);

console.log("Números de precio encontrados:", matches);
// Salida: ['299', '599', '20']

Ejemplo: Python

import re

text = "Precio del producto A: 299 yuanes, precio del producto B: 599 yuanes, gastos de envío: 20 yuanes."

# Usar lookahead positivo
# Coincide con uno o más dígitos (\d+), pero requiere que a la derecha de este dígito siga inmediatamente "元"
# Nota: "元" en sí no será incluido en el resultado
pattern = r'\d+(?=元)'
matches = re.findall(pattern, text)

print("Números de precio encontrados:", matches)
# Salida: Números de precio encontrados: ['299', '599', '20']

Análisis del código:

  1. \d+Es la expresión principal, coincide con uno o más dígitos.
  2. (?=yuan)Es la aserción, que comprueba\d+de la cadena de dígitos coincidentelado derechosi le sigue inmediatamente un carácter "元".
  3. El motor primero encuentra299, luego mira a la derecha, encuentra "元", la aserción es exitosa, por lo que299se registra.
  4. Luego encuentra599, a la derecha está "元", exitoso, se registra.
  5. Encuentra20, a la derecha está "元", exitoso, se registra. Al final, solo obtenemos la parte numérica.

Ejemplo de código 2: Validar contraseñas complejas

Se requiere que la contraseña contenga al menos una letra mayúscula, una letra minúscula y un número.

Ejemplo: JavaScript

function validatePassword(password) {
  // Múltiples lookaheads positivos
  const pattern = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$/;
  // ^ Inicio de la cadena
  // (?=.*[A-Z]) Debe existir al menos una letra mayúscula a la derecha
  // (?=.*[a-z]) Debe existir al menos una letra minúscula a la derecha
  // (?=.*\d) Debe existir al menos un dígito a la derecha
  // .{8,} Longitud total de al menos 8
  // $ Fin de la cadena

  return pattern.test(password);
}

// Datos de prueba
const passwords = ["Weak", "strong123", "STRONG123", "Strong123"];

passwords.forEach(pwd => {
  console.log(`Contraseña'${pwd}'es válida: ${validatePassword(pwd)}`);
});

// Salida:
// Contraseña 'Weak' es válida: false
// Contraseña 'strong123' es válida: false
// Contraseña 'STRONG123' es válida: false
// Contraseña 'Strong123' es válida: true

Ejemplo: Python

import re

def validate_password(password):
    # Usar múltiples lookaheads positivos para comprobar cada condición por separado
    pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$'
    # ^ representa el inicio de la cadena
    # (?=.*[A-Z]) Aserción: desde la posición actual (inicio) mirando a la derecha, debe poder encontrar una letra mayúscula después de cualquier carácter (.*)
    # (?=.*[a-z]) Aserción: igualmente, mirando a la derecha desde el inicio, debe poder encontrar una letra minúscula
    # (?=.*\d) Aserción: mirando a la derecha desde el inicio, debe poder encontrar un dígito
    # .{8,} Expresión principal: coincide con cualquier carácter al menos 8 veces (requisito de longitud total)
    # $ representa el fin de la cadena
    if re.match(pattern, password):
        return True
    else:
        return False

# Datos de prueba
passwords = ["Weak", "strong123", "STRONG123", "Strong123"]
for pwd in passwords:
    print(f"Contraseña '{pwd}' es válida: {validate_password(pwd)}")

# Salida:
# Contraseña 'Weak' es válida: False # Longitud insuficiente y falta un dígito
# Contraseña 'strong123' es válida: False # Falta una letra mayúscula
# Contraseña 'STRONG123' es válida: False # Falta una letra minúscula
# Contraseña 'Strong123' es válida: True # Cumple todas las condiciones

Lookahead negativo(?!...)

Lookahead negativoEs lo opuesto al lookahead positivo. Coincide con una posición tal que, después de esta posición (a la derecha),no debeaparecer inmediatamente el patrón especificado....。

Sintaxis y parámetros

  • Sintaxis:(?!pattern)
  • Función: comprueba si a la derecha de la posición actualno hay coincidencia pattern. Si no hay coincidencia, la aserción es exitosa.
  • Característica clave: También es de ancho cero.

Ejemplo de código: Buscar palabras que no terminan en "ing"

En una oración, encuentra todas las palabras que noingterminan en.

Ejemplo: JavaScript

// Ejemplo 1: lookahead negativo
const text1 = "playing swimming run walk jumping sing";
const pattern1 = /\b\w+(?<!ing)\b/g;
console.log(text1.match(pattern1));
// ['run', 'walk', 'sing']

// Ejemplo 2: coincide con palabras que no terminan en q (versión con semántica correcta)
const text2 = "I like faq apple Iraq you banana q";
const pattern2 = /\b\w*[^q\W]\b/g;
// Significado:
// [^q\W] el último carácter de la palabra no es q
// Está más en línea con la necesidad real de "no termina en q" que (?!q)
console.log(text2.match(pattern2));
// ['I', 'like', 'apple', 'you', 'banana']

// Ejemplo 3: coincide con nombres de archivo que no terminan en .js
const files = "index.js app.ts config.json main.js readme.md";
const pattern3 = /\b\w+\.(?!js\b)\w+\b/g;
// Significado: \.(?!js\b) después del punto no puede haber "js"
console.log(files.match(pattern3));
// ['app.ts', 'config.json', 'readme.md']

Lookbehind positivo(?<=...)

Lookbehind positivoSe utiliza para hacer coincidir una posición en la que, inmediatamente antes (a la izquierda), debe aparecer el patrón especificado....。

Nota: no todos los motores de expresiones regulares de los lenguajes de programación admiten el lookbehind. JavaScript lo admite completamente desde ES2018, y el módulo de Pythonrelo admite.

Sintaxis y parámetros

  • Sintaxis:(?<=pattern)
  • Función: comprueba si a la izquierda de la posición actual coincidepattern. Si coincide, la aserción es exitosa.
  • Característica clave: Ancho cero.patternDebe tener una longitud fija (no pueden ser*o+cuantificadores de longitud variable, etc., en algunas implementaciones).

Ejemplo de código: Extraer el importe después del símbolo de moneda

Extrae el número que sigue al símbolo de dólar o de libra, pero sin incluir el símbolo en sí.

Ejemplo: JavaScript

// Ejemplo 1: coincide con el importe después de $ o £ (clásico y semánticamente correcto)
const text1 = "Price: $199, £89, ¥1200";
const pattern1 = /(?<=\$|£)\d+/g;
// Significado:
// (?<=\$|£) a la izquierda debe haber $ o £
// \d+ coincide con los dígitos en sí
console.log(text1.match(pattern1));
// ['199', '89']

// Ejemplo 2: coincide con el número después de los dos puntos
const text2 = "port:8080 pid:1234 uid:1000";
const pattern2 = /(?<=:)\d+/g;
// A la izquierda debe haber :
console.log(text2.match(pattern2));
// ['8080', '1234', '1000']

// Ejemplo 3: coincide con la versión secundaria en el número de versión (después del número de versión principal)
const text3 = "v1.2 v2.15 v10.3";
const pattern3 = /(?<=v\d+\.)\d+/g;
// A la izquierda debe haber v + número + punto
console.log(text3.match(pattern3));
// ['2', '15', '3']

// Ejemplo 4: coincide con el nombre de usuario después de @ (sin incluir @)
const text4 = "hello @alice and @bob_smith";
const pattern4 = /(?<=@)[a-zA-Z_]\w*/g;
console.log(text4.match(pattern4));
// ['alice', 'bob_smith']

// Ejemplo 5: coincide con el número de capítulo en '第 X 章' (chino)
const text5 = "第1章 第12章 第3章";
const pattern5 = /(?<=第)\d+(?=章)/g;
// Restricción simultánea por ambos lados, más claro.
console.log(text5.match(pattern5));
// ['1', '12', '3']

Lookbehind negativo(?<!...)

Lookbehind negativoEs lo opuesto al lookbehind positivo. Coincide con una posición en la que, antes de esta posición (a la izquierda),no puedeaparecer inmediatamente el patrón especificado....。

Sintaxis y parámetros

  • Sintaxis:(?<!pattern)
  • Función: comprueba si a la izquierda de la posición actualno coincide. pattern。
  • Característica clave: Ancho cero; normalmente se requierepatternlongitud fija.

Ejemplo de código: Buscar enteros no negativos

En un texto, coincide con todos los enteros que no son negativos (es decir, números que no tienen un signo negativo a la izquierda-).

Ejemplo: JavaScript

const text = La temperatura de hoy es -5 grados, mañana subirá a 3 grados, pasado mañana la temperatura será -1 grado, y la temperatura interior se mantiene en 22 grados.;

// Lookbehind negativo
// Coincide con dígitos, pero exige que a la izquierda no haya un signo negativo -
const pattern = /(?<!-)\b\d+\b/g;

const matches = text.match(pattern);

console.log(Enteros no negativos:, matches);
// Salida: ['3', '22']
// En -5 y -1, el lado izquierdo es '-', la aserción falla y no coincide.

Aplicación integral y diagrama de flujo

Para comprender de forma más intuitiva el flujo de trabajo de las cuatro aserciones, podemos usar el siguiente diagrama de flujo para representar un proceso de coincidencia complejo que incluye múltiples aserciones:

Explicación del diagrama de flujo: Este diagrama muestra cómo el motor de expresiones regulares maneja las aserciones al realizar la coincidencia. Lo esencial es quela aserción es un nodo de verificación independiente.. El motor, en laposición actual, donde la expresión principal intenta coincidir, según el tipo de aserción, examina el contexto hacia la izquierda o hacia la derecha. Solo si se cumplen todas las condiciones de aserción, la coincidencia puede continuar y "consumir" caracteres; de lo contrario, el motor retrocede e intenta otras posibilidades o declara que la coincidencia falló.

Cambio de pestañas
Lookahead positivo
Lookahead negativo
Lookbehind positivo
Lookbehind negativo
Práctica integral
Prueba interactiva
Lookahead positivo
Lookahead positivo
(?=pattern)
Lookahead negativo
Lookahead negativo
(?!pattern)
Lookbehind positivo
Lookbehind positivo
(?<=pattern)
Lookbehind negativo
Lookbehind negativo
(?<!pattern)
Práctica integral
Ejemplo de práctica integral
Prueba interactiva
Zona de prueba interactiva

Cuestionario de aserciones en expresiones regulares

1. Para el patrón `Python(?=3)` en la cadena `"Me gusta Python3 y Python2"`, ¿cuál de las siguientes opciones es correcta?

2. ¿Qué aserción se puede usar para hacer coincidir un número `7` que no tenga la letra `q` delante?

3. ¿Cuál es el resultado de la coincidencia del patrón `(?<=@)\w+` en la cadena `"Contacta @admin y [email protected]"`?

4. ¿Cuáles son las características principales de las aserciones? (selección múltiple)

Otras extensiones