Búsqueda de string con comodín en otro string

eXtreM3

Buenas, cuál es la mejor manera de hacer una búsqueda por reemplazo de un carácter, en un texto dado? Ejemplo:

<?php

$search = "xx123456789xx";
$text = "BLAU BLAU\n trewcxvxc\n824582\nsdlfkdjslfjds\nblaworeiwo\nxx123456789xx\n230 728 93843 bruisss\nreiwewo3282";

if (str_contains($text, $search)) echo "found!";

Eso funciona perfectamente, pero teniendo en cuenta que lo que tengo que buscar ($search) y donde lo tengo que buscar ($text) son dinámicos y no conozco cómo son.

La cosa es que, en el ejemplo, a lo mejor me cambia un carácter, y en lugar de xx123456789xx podría ser xx423456789xx, y la búsqueda sobre ese mismo texto, debería devolver positivo.

Lacsas

Si el $search cambia de valor y el $text no, y deja de contenerlo, ¿cómo esperas que dé positivo?

Quiero decir, el str_contains espera dos argumentos y le da igual cuáles sean. En el ejemplo tú los estás inicializando de forma estática, pero no dejan de ser variables cuyo valor podría asignarse de cualquier otra forma. Si el primero contiene al segundo devolverá true, y si no, pues false.

No tengo claro estar entendiendo lo que buscas.

2 respuestas
cabron

Para eso son las expresiones regulares

#2

Yo entendido que quiere buscar en plan foo*bar

2 respuestas
neoline

Como dice #3
usa regex para buscar dentro del string y ya está, si no recuerdo mal, se podía hacer con preg_match()
la expresión si es como dices en #1, sería buscar ⁿ caracteres dentro de "xxx____xxx" o ya lo que tu quieras.

Para probar regex yo siempre uso ésta web: https://regexr.com/
o ésta que usaba antes (tiene más años): https://regexper.com/

1 respuesta
eXtreM3

#3 #4 pero la movida es que no sé lo que me voy a encontrar ni en $search ni en $text, ambas son variables.

Lo que sí sé, es que sólo puede variar 1 carácter en $text, y puede ser alfanumérico.

#2 pues quiero buscar una coincidencia que sea 100% igual (que ya lo tengo) o que sea todo igual salvo un carácter.

Cómo sería la expresión regular? Tendría que hacer algo así?

  • recorrer todos los caracteres
  • para cada carácter, meter un reemplazo comodín, rollo {*}23456789, 1{*}3456789, 12{*}456789 ...
  • ejecutar preg_match

o eso es una burrada

He encontrado esto https://www.php.net/manual/es/function.similar-text.php que quizás pueda servirme porque conozco la longitud de la cadena, y podría quedarme con las cadenas que cumplan la misma longitud dentro del string a buscar.

1 respuesta
Amazon

#5 https://www.php.net/manual/en/function.preg-replace.php

Lecherito

A ver, si solo es un caracter lo que va a cambiar, pues al final es como buscar en un array donde todas las posiciones son correctas menos una.

Algo del tipo:

  1. Si la longitud es menor que lo que busco, false
  2. Si es mayor:
    1. Miro caracteres de 1 en 1 hasta que 2 fallen (o coincidan todos)
    2. En caso de fallar, paso al siguiente caracter y seguimos.

No se la longitud de las cadenas, si es del orden de K o G, pero algo asi es inmensamente paralelizable ya que no dependes de escritura, solo de la lectura asi que puedes tener N threads leyendo diferentes partes. La expresion regular esta bien, pero creo que es muy muy overkill cuando se puede hacer algo sencillo como lo de arriba

2
B
  1. Fuerza bruta, construir la regex al vuelo en base al input: /xx(?:\d23456789|1\d3456789|12\d456789|123\d56789|1234\d6789|12345\d789|123456\d89|1234567\d9|12345678\d)xx/mi
  2. Más lógica:
    1. Obtener la parte que interesa: /(xx\d{9}xx)/mi
    2. Mirar a grano fino:
      <?php
      $search = "xx123456789xx";
      $search_array = str_split($search);
      $input = "BLAU BLAU\n trewcxvxc\n824582\nsdlfkdjslfjds\nblaworeiwo\nxx123456789xx\nxx123456089xx\nxx628456089xx\n230 728 93843 bruisss\nreiwewo3282";
      $match = preg_match_all("/(xx\d{9}xx)/mi", $input, $groups);
      if ($match) {
          foreach ($groups[1] as $group) {
              $state = array_map(function($a, $b) {
                  return $a == $b ? 'y' : 'n';
              }, str_split($group), $search_array);
              $count = array_count_values($state);
              if (!array_key_exists('n', $count) || $count['n'] < 2) {
                  echo "Encontrada coincidencia: " . $group . "\n";
              }
          }
      }
      ?>

Hace eones que no toco PHP... desconozco si se puede usar algo más directo.
P.D: Odio PHP :)

Directamente con regex no se me ocurre la forma :/

1
JuAn4k4

Con regex es como más fácil, partes el string por * luego escapas cada parte y las juntas con .* y ya lo tienes.

Sin regex tampoco es difícil a fuerza bruta, es buscar la partes (tras partir por *) una a una seguidas, y volver para atrás (si hasta el principio del todo + 1 char) y volver a empezar, hasta acabar el string con el que se compara.

1
hda

Regex: (?:\d+?)\*(:?\d+?)

Explicación: hay dos grupos posibles pero no necesarios (?: ?) de uno o más dígitos \d+, y entre ellos lo que buscas, que es el asterisco \*, la barra es para escaparlo.

Edit: uff, ese regex es un poco duro. ¿No se pueden wildcards de dígitos y grupos opcionales en php?

Edit: releyendo #1 creo que estoy respondiendo a otro problema xddddddd

1
MisKo

Lo he leido rapido pero algo así no te sirve?

Si te sirve, ya automatizas tu la creación de los grupos


/(xx.23456789xx|xx1.3456789xx|xx12.456789xx|xx123.56789xx|xx1234.6789xx|xx12345.789xx)/

xx123456789xx
xx123455789xx
xx123356789xx

1
RaymaN

Si el texto donde buscar no es muy grande, puedes iterar sobre todos los trozos que tengan el mismo tamaño que la cadena a buscar y aplicar levenshtein() entre ambos.

1 1 respuesta
eXtreM3

#12 lo contemplé, lo contemplé. Pero para este caso no es lo que se busca.

Gracias a todos, al final lo solucioné ayer al poco rato de postear aquí :D

Surgió un nuevo requisito, y es que el string podía darse como válido aunque fallasen 1 ó 2 caracteres. Lo bueno, que no comenté aquí porque se me olvidó, es que la estructura que busco es la de un DNI español (8 números + 1 letra), y lo que me llega es siempre variable, pueden darse casos como:

  • 123456789A (fácil)
  • 123456789 (sin letra)
  • 423456789A (un número mal)
  • 423456789 (un número mal, sin letra)
  • 423456780A (dos números mal)
  • 423456780 (dos números mal, sin letra)

Ahora lo que hago es una primera regex que busca estructuras tanto en origen como en destino, y cuando saco los resultados posibles, que en mi caso son entre 3 y 4, comparo las similitudes entre ellos y me quedo con el que más % de parecido me retorne.

1 respuesta
hda

#13 pero si es un DNI, lo mejor es capturar la letra, sacar del diccionario de letras el int de la letra. Dividir el número del DNI y ver si el resto coincide con el número de la letra. Si no hay letra está mal del tiri. ¿No?

http://www.interior.gob.es/web/servicios-al-ciudadano/dni/calculo-del-digito-de-control-del-nif-nie

1 respuesta
eXtreM3

#14 la cosa es que la letra es uno de los carácter que puede no venir o directamente ser otra letra :P

El problema, evidentemente, no es validar la estructura del Dni :D

1

Usuarios habituales