2016-03-25 39 views
8

Ho una stringa la cui sintassi corretta è la regex ^([0-9]+[abc])+$. Quindi esempi di stringhe valide sarebbero: '1a2b' o '00333b1119a555a0c'PHP: dividere una stringa di gruppi alternati di caratteri in una matrice

Per chiarezza, la stringa è un elenco di coppie (valore, lettera) e l'ordine è importante. Sono bloccato con la stringa di input, quindi non posso cambiarlo. Durante il test per la sintassi corretta sembra facile in linea di principio con l'espressione regolare di cui sopra, sto cercando di pensare il modo più efficiente in PHP per trasformare una stringa compatibile in una matrice di qualcosa di utilizzabile come questo:

ingresso:

'00333b1119a555a0c' 

uscita:

array (
    0 => array('num' => '00333', 'let' => 'b'), 
    1 => array('num' => '1119', 'let' => 'a'), 
    2 => array('num' => '555', 'let' => 'a'), 
    3 => array('num' => '0', 'let' => 'c') 
) 

sto avendo difficoltà ad usare preg_match per questo. Ad esempio questo non fornisce il risultato atteso, l'intento è quello di corrispondere in modo avido con EITHER \ d + (e salvare ciò) OPPURE [abc] (e salvarlo), ripetuto fino alla fine della stringa raggiunta.

$text = '00b000b0b'; 
$out = array(); 
$x = preg_match("/^(?:(\d+|[abc]))+$/", $text, $out); 

Questo non ha funzionato neanche, l'intento qui è quello di avidi-partita su \ d + [abc] (e salvare questi), ripetuta fino alla fine del stringa raggiunto, e dividerli in numeri e lettere in seguito.

$text = '00b000b0b'; 
$out = array(); 
$x = preg_match("/^(?:\d+[abc])+$/", $text, $out); 

avevo programmato per controllare la sintassi come parte della preg_match, quindi utilizzare l'uscita preg_match per greedy-match i 'blocchi' (o mantenere i delimitatori se si utilizza preg_split), quindi se necessario scorrere il risultato 2 articoli alla volta usando for (...; i+=2) per estrarre la lettera di valore nelle loro coppie.

Ma non riesco nemmeno a ottenere quell'approccio basilare preg_split() o preg_match() per funzionare senza intoppi, né tanto meno esplorare se c'è un modo "più ordinato" o più efficiente.

risposta

0

Tutto il lavoro sopra. Ma non sembravano avere l'eleganza che volevo - avevano bisogno di fare il loop, usare la mappatura dell'array, o (per preg_match_all()) avevano bisogno di un'altra regex quasi identica, solo per verificare che la stringa corrispondesse alla regex.

Alla fine ho trovato che preg_match_all() combinato con acquisizioni denominate risolto per me. Non avevo ancora usato le catture con nome per quello scopo e sembra potente.

Ho anche aggiunto un passaggio aggiuntivo facoltativo per semplificare l'output se non si prevedono duplicati (che non era nella domanda ma possono aiutare qualcuno).

$input = '00333b1119a555a0c'; 

preg_match_all("/(?P<num>\d+)(?P<let>[dhm])/", $input, $raw_matches, PREG_SET_ORDER); 
print_r($raw_matches); 

// if dups not expected this is also worth doing 
$matches = array_column($raw_matches, 'num', 'let'); 

print_r($matches); 

versione più completa con l'ingresso + duplicare il controllo

$input = '00333b1119a555a0c'; 
if (!preg_match("/^(\d+[abc])+$/",$input)) { 
    // OPTIONAL: detected $input incorrectly formatted 
} 
preg_match_all("/(?P<num>\d+)(?P<let>[dhm])/", $input, $raw_matches, PREG_SET_ORDER); 
$matches = array_column($raw_matches, 'num', 'let'); 
if (count($matches) != count($raw_matches)) { 
    // OPTIONAL: detected duplicate letters in $input 
} 
print_r($matches); 

Spiegazione:

Questo utilizza preg_match_all() come suggerito da @RomanPerekhrest e @exussum di uscire l'individuo gruppi e dividere i numeri e le lettere. Ho usato i gruppi con nome in modo che la matrice risultante di $ raw_matches sia stata creata con i nomi corretti già.

Ma se non si prevedono duplicati, ho utilizzato un passaggio aggiuntivo con array_column(), che estrae direttamente i dati da una matrice nidificata di voci e crea un array flat desiderato, senza necessità di loop, mapping, walking o assegnazione voce per voce: da

(group1 => (num1, let1), group2 => (num2, let2), ...) 

alla matrice "flat":

(let1 => num1, let2 => num2, ...) 

Se le partite regex nome si sente troppo avanzato allora possono essere ignorati - le partite saranno date i numeri comunque e questo sarà il lavoro altrettanto bene, dovresti assegnare manualmente lett ed è solo più difficile da seguire.

preg_match_all("/(\d+)([dhm])/", $input, $raw_matches, PREG_SET_ORDER); 
$matches = array_column($raw_matches, 1, 2); 

Se avete bisogno di controllare per le lettere duplicati (che non era nella questione, ma potrebbe essere utile), ecco come: Se le partite originale conteneva> 1 ingresso per qualsiasi lettera poi, quando viene utilizzato array_column() questa lettera diventa una chiave per il nuovo array e le chiavi duplicate non possono esistere. Viene mantenuta solo una voce per ogni lettera. Quindi testiamo solo se il numero di corrispondenze trovato originariamente è uguale al numero di corrispondenze nell'array finale dopo array_coulmn. In caso contrario, c'erano duplicati.

4

tuo regex ha bisogno di un paio di gruppi di corrispondenza

/([0-9]+?)([a-z])/i 

Ciò significa partita tutti i numeri in un gruppo, e tutte le lettere in un altro. Preg match all ottiene tutte le partite.

La chiave della regex è la flag non avida ? che corrisponde alla stringa più breve possibile.

match[0] è l'intera partita
match[1] è il primo gruppo match (i numeri)
match[2] è il secondo gruppo partita (la lettera)

esempio sotto

<?php 
$input = '00333b1119a555a0c'; 

$regex = '/([0-9]+?)([a-z])/i'; 

$out = []; 

$parsed = []; 

if (preg_match_all($regex, $input, $out)) { 
    foreach ($out[0] as $index => $value) { 
     $parsed[] = [ 
      'num' => $out[1][$index], 
      'let' => $out[2][$index], 
     ]; 
    } 
} 

var_dump($parsed); 

uscita

array(4) { 
    [0] => 
    array(2) { 
    'num' => 
    string(5) "00333" 
    'let' => 
    string(1) "b" 
    } 
    [1] => 
    array(2) { 
    'num' => 
    string(4) "1119" 
    'let' => 
    string(1) "a" 
    } 
    [2] => 
    array(2) { 
    'num' => 
    string(3) "555" 
    'let' => 
    string(1) "a" 
    } 
    [3] => 
    array(2) { 
    'num' => 
    string(1) "0" 
    'let' => 
    string(1) "c" 
    } 
} 
+0

Suggerire un controllo nell'ingresso $ input perché attualmente una stringa non valida produce comunque un risultato valido. Qualcosa come 'if (preg_match ('/^([0-9] + [abc]) + $ /', $ input) == 1) {...' – Tigger

3

Soluzione semplice con preg_match_all (con PREG_SET_ORDER bandiera) e array_map funzioni:

$input = '00333b1119a555a0c'; 

preg_match_all('/([0-9]+?)([a-z]+?)/i', $input, $matches, PREG_SET_ORDER); 
$result = array_map(function($v) { 
    return ['num' => $v[1], 'let' => $v[2]]; 
}, $matches); 

print_r($result); 

L'output:

Array 
(
    [0] => Array 
     (
      [num] => 00333 
      [let] => b 
     ) 

    [1] => Array 
     (
      [num] => 1119 
      [let] => a 
     ) 

    [2] => Array 
     (
      [num] => 555 
      [let] => a 
     ) 

    [3] => Array 
     (
      [num] => 0 
      [let] => c 
     ) 
) 
+0

Ho trovato una soluzione più ordinata senza loop, ma questo era la chiave per arrivarci così l'ho marcato – Stilez

2

È possibile utilizzare:

$str = '00333b1119a555a0c'; 
$arr=array(); 

if (preg_match_all('/(\d+)(\p{L}+)/', $str, $m)) { 
    array_walk($m[1], function ($v, $k) use(&$arr, $m) { 
     $arr[] = [ 'num'=>$v, 'let'=>$m[2][$k] ]; }); 
} 

print_r($arr); 

uscita:

Array 
(
    [0] => Array 
     (
      [num] => 00333 
      [let] => b 
     ) 

    [1] => Array 
     (
      [num] => 1119 
      [let] => a 
     ) 

    [2] => Array 
     (
      [num] => 555 
      [let] => a 
     ) 

    [3] => Array 
     (
      [num] => 0 
      [let] => c 
     ) 
)