Entra

View Full Version : [JAVA - regex]


DeltaDirac
10-07-2011, 16:02
Ciao a tutti i Java-Guru

Sto cercando di controllare il formato di due file di testo con la libreria java.util.regex, al momento senza grosse soddisfazioni :(

Il primo file contiene una serie di dati nel seguente formato:


Amico1 Amico2 distanza12 Amico3 distanza13 Amico4 distanza14
Amico3 Amico5 distanza35
Amico5 Amico6 distanza56 Amico7 distanza57

esempio:

Anton.Luca Bianca 150 Claudio 14 Ciro 280
Claudio Francesco 45
Francesco Giovanni 100 Luca 50 Piero 200

Non ho molta confidenza con le espressioni regolari ed ho qualche difficoltà a concatenare i controlli sui set di caratteri di input.

Ho tentato di costruire una classe ( controlloPattern ) che riuscisse a verificare se il file di input aderisce allo standard oppure o no, ma l'unica cosa che son riuscito ad ottenere è una "verifica per parti" ovvero son riuscito a leggere o solo i nomi o solo le distanze ma non OK se la verifica è positiva:



public class controlloPattern {

String regex = "[A-Za-z.]+[a-zA-Z0-9]{2,3}";

String input = "Anton.Luca Bianca 150 Claudio 14 Ciro 280 Claudio Francesco 45
Francesco Giovanni 100 Luca 50 Piero 200";


Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);

while (matcher.find())
System.out.println(matcher.group());
}


Ottengo in uscita:

Anton.Luca
Bianca
Claudio
Ciro
Claudio
Francesco
Francesco
Giovanni
Luca
Piero

L'espressione regolare voleva catturare anche nomi dati nel formato Nome.Nome, ecco perché ho inserito il .

Con una regex simile riesco ad ottenere le distanze a cui si trovano gli amici:

String regex = "[0-9]{2,3}";


IL PROBLEMA E' CHE NON RIESCO A VERIFICARE se il file di input rispetta complessivamente il formato dato :muro:

Idee?

Il secondo file è simile ma contiene anche un altro dato (affinità) introdotto subito dopo la distanza e separato dal carattere pipe (|). Me ne occuperò dopo..

wingman87
11-07-2011, 09:10
Ho provato a scrivere una regex ma non so se funziona:
(([A-Za-z\.]+\s){2}\d+(\s[A-Za-z\.]+\s\d+)*[\n\r]*)+
Te la spiego a pezzi;
([A-Za-z\.]+\s){2}\d+
Questo serve a matchare i primi due nomi più il numero: [A-Za-z\.] matcha le lettere più il punto, con il + ripeti il match, con \s matchi lo spazio e con {2} ripeti il match precedente (quello tra parentesi tonde), con \d matchi i digit, cioè i numeri;
(\s[A-Za-z\.]+\s\d+)*
Questo matcha le coppie nome-numero successive, con * il match può avvenire 0 o più volte;
[\n\r]*
Per matchare il fine riga (non sapendo se è presente solo \n o anche \r ho usato la classe, con * e non + perché non so se il file termina con un ritorno a capo);
(tutto)+
Matcha una o più righe, puoi sostituire il + con un * se il file può anche essere vuoto.

Non avendola testata non sono sicuro che sia corretta, comunque credo che ti serva una cosa del genere.

DeltaDirac
11-07-2011, 18:46
Così come è scritta non riesco a farla funzionare per sintassi gobba (illegal escape caracter, 6 volte) ; duplicando tutti i caratteri \ come segue:

"(([A-Za-z\\.]+\\s){2}\\d+(\\s[A-Za-z\\.]+\\s\\d+)*[\n\r]*)+"

ma non riesco a fare la validazione.

wingman87
11-07-2011, 19:40
In tal caso credo che tu debba raddoppiare anche i backslash di n ed r. Comunque non ho capito se ha funzionato

DeltaDirac
12-07-2011, 06:46
Ad es.


String regex = "(([A-Za-z\\.]+\\s){2}\\d+(\\s[A-Za-z\\.]+\\s\\d+)*[\\n\\r]*)+";
String input = "Anton.Luca Bianca 150 Claudio 14 Ciro 280 Claudio Francesco 45 Francesco Giovanni 100 Luca 50 Piero 200 2 2 2";

Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);

while (matcher.find())
System.out.println(matcher.group());


Dovrebbe fallire perché mancano sostanzialmente tutti i caratteri ( : ) che delimitano i due campi e c'è una ripetizione di numeri alla fine della linea.

Una stringa di input valida sarebbe invece "Franco Luca 10:12 Giovanni 6:3 Matteo 19:199"

Questa stringa contiene anche il secondo problema di cui accennavo all'inizio: vorrei validare ogni linea del file solo se aderisce a quel formato (nome1 nome2 numero1:numero2 nome3 numero4:numero5 ... ).
Invece non riesco proprio ad uscirne :muro

wingman87
12-07-2011, 08:27
Scusa ma mi stai un po' confondendo... in quest'ultimo post hai usato una sintassi diversa da quella del primo post. Cosa devi validare esattamente?

banryu79
12-07-2011, 12:40
Volevo proporre una soluzione non basata sull'uso delle regex.
Definisco una classe astratta (TextValidator) che rappresenta l'algoritmo generico di validazione di una sorgente di testo; in particolare astrae la semantica di validazione e la semantica di scomposizione in token di una data linea nella sorgente:

package stringsplitting.textvalidator;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.StringTokenizer;

/**
* Abstract class to perform validation of a text source.
*/
public abstract class TextValidator
{
/**
* Line separator in text file/stream
*/
public static final String NEWLINE = System.getProperty("line.separator");

/**
* Main algorithm to validate a given text source
* @param source a stream of text
* @return true if the text source is valid
*/
public boolean isValid(InputStream source) {
String txt = getText(source);
if (txt == null) return false;

int num = 1;
for (String line : getAllLines(txt)) {
String[] tokens = getTokens(line, num);
if (! isValid(tokens, num)) return false;
num++;
}

return true;
}

/* utility method to extract all text from the source */
private String getText(InputStream source) {
Scanner scan = null;
try {
scan = new Scanner(source);
return scan.useDelimiter("\\z").next();
} finally {
if (scan != null) scan.close();
}
}

/* utility method to extract all lines from the text */
private String[] getAllLines(String txt) {
StringTokenizer st = new StringTokenizer(txt, NEWLINE);
List<String> lines = new ArrayList<String>(st.countTokens());
while (st.hasMoreTokens()) lines.add(st.nextToken());
final int N = lines.size();
return lines.toArray(new String[N]);
}

/**
* Implementation-specific semantic for validating a given line
* @param tokens the tokens taht form the given line
* @param number the number of the line in the text
* @return true if the given line is valid
*/
abstract protected boolean isValid(String[] tokens, int number);

/**
* Implementation-specific semantic for tokenizing a given line
* @param line the line to tokenize
* @param number the number of the line in the text
* @return the given line decomposed into an array of tokens
*/
abstract protected String[] getTokens(String line, int number);
}



Quindi, a seconda del formato, definisco un'implementazione apposita.
Nell'esempio qui sotto, due implemantazioni di TextValidator per due formati che ho scelto traendoli dalle descrizioni dell'utente.
Tra il primo e il secondo formato (quello con i versetti presenti) c'è della logica condivisa il che mi permette di definire il secondo "validatore" come estensione del primo (sovvrascrivendo le operazioni differenti, e aggiungendo quelle mancanti):

package stringsplitting.textvalidator;

import java.io.InputStream;

/**
* Check the format of a given text file.<br>
*/
public class ValidatorExample
{
public static void main(String[] args) {
InputStream in = ValidatorExample.class.getResourceAsStream("input2.txt");

MyTextValidator2 check = new MyTextValidator2();
if (check.isValid(in))
System.out.println("File is valid.");
else
System.out.println("File is malformed.");
}

/**
* Custom implementation of a TextValidator
* format is:<br>
* nameOrDot [name value]
* <p>
* nameOrDot -> letters.letters | letters<br>
* name -> letters<br>
* value -> integer<br>
*/
static class MyTextValidator1 extends TextValidator
{
@Override
protected boolean isValid(String[] tokens, int number) {
String tok = tokens[0];
if (! isNameOrDot(tok)) return false;

if (tokens.length > 1) {
if (tokens.length % 2 == 0) return false;
for (int i=1; i<tokens.length-1; i+=2) {
String s1 = tokens[i];
if (! isName(s1)) return false;
String s2 = tokens[i+1];
if (! isValue(s2)) return false;
}
}

return true;
}

@Override
protected String[] getTokens(String line, int number) {
return line.split(" ");
}

// @impl
protected boolean isName(String str) {
for (char c : str.toCharArray())
if (! Character.isLetter(c)) return false;
return true;
}

// @impl
protected boolean isNameOrDot(String str) {
for (char c : str.toCharArray())
if ((! Character.isLetter(c)) && c != '.') return false;
return true;
}

// @impl
protected boolean isValue(String str) {
try {
Integer.parseInt(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

/**
* Custom implementation of another TextValidator
* format is:<br>
* nameOrDot [name verse]
* <p>
* nameOrDot -> letters.letters | letters<br>
* name -> letters<br>
* verse -> integer:integer
* <p>
* Reuse functionality already implemented in MyTextValidator1
*/
static class MyTextValidator2 extends MyTextValidator1
{
@Override
protected boolean isValid(String[] tokens, int number) {
if (tokens.length < 3) return false;
if (tokens.length % 2 == 0) return false;

String tok = tokens[0];
if (! isNameOrDot(tok)) return false;

for (int i=1; i<tokens.length-1; i+=2) {
String s1 = tokens[i];
if (! isName(s1)) return false;
String s2 = tokens[i+1];
if (! isVerse(s2)) return false;
}

return true;
}

// @impl
private boolean isVerse(String str) {
int index = str.indexOf(':');
if (index == -1) return false;

String s1 = str.substring(0, index);
if (! isValue(s1)) return false;

String s2 = str.substring(index+1);
if (! isValue(s2)) return false;

return true;
}
}
}



Il primo validatore l'ho provato con questo input:

Anton.Luca Bianca 150 Claudio 14 Ciro 280
Claudio Francesco 45
Francesco Giovanni 100 Luca 50 Piero 200

e il secondo con questo:

Franco Luca 10:12 Giovanni 6:3
Carlo Luca 10:12
Luisa Giovanni 6:3 Matteo 19:199
Antonio.Rossi Matteo 19:199
Mattia Luca 10:12 Giovanni 6:3 Matteo 19:199