Używanie Regex do generowania ciągów zamiast ich dopasowywania

110

Piszę narzędzie Java, które pomaga mi generować mnóstwo danych do testowania wydajności. Byłoby naprawdę fajnie móc określić wyrażenie regularne dla Strings, tak aby mój generator wypluwał elementy, które pasują do tego. Czy jest już coś upieczonego, czego mogę użyć do tego? A może jest biblioteka, która prowadzi mnie tam przez większość drogi?

Dzięki

Andrew Harmel-Law
źródło
1
Oto przydatna biblioteka java, która zapewnia wiele funkcji używania wyrażeń regularnych do generowania ciągu (generowanie losowe, generowanie ciągu na podstawie jego indeksu, generowanie całego ciągu ..) sprawdź tutaj
Mifmif
Inna alternatywa może być taka
Vladislav Varslavans

Odpowiedzi:

41

Edycja: jak wspomniano w komentarzach, w Google Code dostępna jest biblioteka umożliwiająca to: https://code.google.com/archive/p/xeger/

Zobacz także https://github.com/mifmif/Generex zgodnie z sugestią Mifmif

Wiadomość oryginalna:

Po pierwsze, sądzę, że przy wystarczająco złożonym wyrażeniu regularnym może to być niemożliwe. Ale powinieneś być w stanie złożyć coś razem dla prostych wyrażeń regularnych.

Jeśli spojrzysz na kod źródłowy klasy java.util.regex.Pattern, zobaczysz, że używa ona wewnętrznej reprezentacji instancji Node. Każdy z różnych składników wzorców ma własną implementację podklasy Node. Te węzły są zorganizowane w drzewo.

Tworząc gościa, który przemierza to drzewo, powinieneś być w stanie wywołać przeciążoną metodę generatora lub jakiś rodzaj Buildera, który łączy coś razem.

Cheekysoft
źródło
2
Nie jestem pewien, czy Xeger jest taki dobry. Nie obsługuje klas znaków. Nie rozpoznaje prostego [\w]. Spojrzenie na ostatnią linię ich wiki mówi nam o tym.
John Red
2
Zauważ również, że zależą one od, dk.brics.automatonwięc przygotuj się na dodanie zależności pom innych firm. Większości ludzi to nie przeszkadza, ale chciałbym, żeby było coś bardziej kompaktowego.
Sridhar Sarnobat
Istnieje alternatywa dla xeger i generex. Brakuje wszystkich tych wad i nie jest przestarzały. Przewiń w dół do mojej odpowiedzi.
Vladislav Varslavans
„Po pierwsze, sądzę, że przy wystarczająco złożonym wyrażeniu regularnym może to być niemożliwe”. - to nie jest do końca prawdą : każde wyrażenie regularne, które przekazuje coś przeciwko czemuś, może również generować prawidłowe dane wejściowe. Wyjaśnienie: wyrażenia regularne są typu 3 w hierarchii Chomsky'ego, co oznacza, że ​​można je wyrazić jako FSM. Podczas przechodzenia przez FSM, każda krawędź jest interpretowana jako reguła dla następnego znaku, dlatego FSM może być używany do analizowania lub generowania sekwencji. Jeśli FSM ma ścieżkę do terminala, można określić prawidłową sekwencję. Tak więc jest to „niemożliwe” tylko wtedy, gdy nie ma ścieżki do terminala (co byłoby bezużytecznym wyrażeniem regularnym).
Lawrence Wagerfield
22

Jest za późno, aby pomóc oryginalnemu plakatowi, ale może to pomóc nowicjuszowi. Generex to przydatna biblioteka java, która zapewnia wiele funkcji do używania wyrażeń regularnych do generowania ciągów (generowanie losowe, generowanie ciągu na podstawie jego indeksu, generowanie wszystkich ciągów ...).

Przykład:

Generex generex = new Generex("[0-3]([a-c]|[e-g]{1,2})");

// generate the second String in lexicographical order that matches the given Regex.
String secondString = generex.getMatchedString(2);
System.out.println(secondString);// it print '0b'

// Generate all String that matches the given Regex.
List<String> matchedStrs = generex.getAllMatchedStrings();

// Using Generex iterator
Iterator iterator = generex.iterator();
while (iterator.hasNext()) {
    System.out.print(iterator.next() + " ");
}
// it prints 0a 0b 0c 0e 0ee 0e 0e 0f 0fe 0f 0f 0g 0ge 0g 0g 1a 1b 1c 1e
// 1ee 1e 1e 1f 1fe 1f 1f 1g 1ge 1g 1g 2a 2b 2c 2e 2ee 2e 2e 2f 2fe 2f 2f 2g
// 2ge 2g 2g 3a 3b 3c 3e 3ee 3e 3e 3f 3fe 3f 3f 3g 3ge 3g 3g 1ee

// Generate random String
String randomStr = generex.random();
System.out.println(randomStr);// a random value from the previous String list

Ujawnienie

Projekt wspomniany w tym poście należy do użytkownika odpowiadającego (Mifmif) na pytanie. Zgodnie z zasadami należy o tym poruszyć.

Mifmif
źródło
11
Wygląda na to, że Generex to Twój własny projekt. Czy mógłbyś wspomnieć w swoim poście, że jest to Twój własny projekt, zgodnie z zasadami tutaj ?
Brian McCutchon
20

Xeger (Java) również to potrafi:

String regex = "[ab]{4,6}c";
Xeger generator = new Xeger(regex);
String result = generator.generate();
assert result.matches(regex);
Wilfred Springer
źródło
1
Xeger działa ładnie. ALE upewnij się, że masz słoik z automatem na ścieżce klasowej lub w swoim pom / gradle
Delicia Brummitt
5

W tym celu przeszedłem do korzeni mojej własnej biblioteki (w języku C #, ale powinno być łatwe do zrozumienia dla programisty Java).

Rxrdg powstał jako rozwiązanie problemu tworzenia danych testowych dla rzeczywistego projektu. Podstawową ideą jest wykorzystanie istniejących wzorców walidacji (wyrażeń regularnych) do tworzenia losowych danych zgodnych z takimi wzorcami. W ten sposób tworzone są prawidłowe dane losowe.

Nie jest trudno napisać parser dla prostych wzorców regex. Używanie abstrakcyjnego drzewa składni do generowania ciągów powinno być jeszcze łatwiejsze.

Goran
źródło
link nie wskazuje już repozytorium. Poszedłbym z openhub.net/p/rxrdg . Rozwiązanie jednak nie buduje?
Veverke
4

W podcastie 11 Stackoverflow:

Spolsky: Tak. Jest również nowy produkt, jeśli nie chcesz korzystać z systemu zespołu, nasi przyjaciele z Redgate mają produkt o nazwie SQL Data Generator [ http://www.red-gate.com/products/sql_data_generator/index.htm] . To 295 USD i generuje realistyczne dane testowe. I robi takie rzeczy, jak faktyczne generowanie prawdziwych miast w kolumnie miasta, które faktycznie istnieją, a potem, kiedy je generuje, naprawi stan, zamiast źle go popełniać lub umieszczać stany w niemieckich miastach i takie rzeczy jak ... wiesz, generuje całkiem realistycznie wyglądające dane. Nie jestem pewien, jakie są wszystkie funkcje.

Prawdopodobnie nie jest to to, czego szukasz, ale może to być dobry punkt wyjścia zamiast tworzenia własnego.

Nie mogę znaleźć niczego w Google, więc radziłbym rozwiązać ten problem, parsując dane wyrażenie regularne na najmniejsze jednostki pracy (\ w, [xx], \ d, itd.) I pisząc kilka podstawowych metod do obsługi te wyrażenia regularne.

Więc dla \ w miałbyś metodę getRandomLetter (), która zwraca dowolną losową literę, a także miałbyś getRandomLetter (char startLetter, char endLetter), która daje losową literę między dwiema wartościami.

Craig
źródło
4

To pytanie jest naprawdę stare, chociaż problem był dla mnie rzeczywisty. Próbowałem Xeger i Generex i wydaje się, że nie spełniają moich wymagań. W rzeczywistości nie potrafią przetworzyć niektórych wzorców regex (takich jak a{60000}) lub w przypadku innych (np. (A|B|C|D|E|F)) Po prostu nie wytwarzają wszystkich możliwych wartości. Ponieważ nie znalazłem innego odpowiedniego rozwiązania - stworzyłem własną bibliotekę.

https://github.com/curious-odd-man/RgxGen

Dostępny jest również artefakt na Maven Central.

Przykład użycia:

RgxGen rgxGen = new RgxGen(aRegex);                     // Create generator
String s = rgxGen.generate();                           // Generate new random value
Vladislav Varslavans
źródło
3

Wiem, że jest już zaakceptowana odpowiedź, ale korzystałem z generatora danych RedGate (tego, o którym mowa w odpowiedzi Craiga) i działa NAPRAWDĘ dobrze we wszystkim, co do niego rzuciłem. Jest to szybkie, a to sprawia, że ​​chcę użyć tego samego wyrażenia regularnego do wygenerowania prawdziwych danych dla rzeczy takich jak kody rejestracyjne, które wypluwa.

Potrzeba wyrażenia regularnego, takiego jak:

[A-Z0-9]{3,3}-[A-Z0-9]{3,3}

i generuje mnóstwo unikalnych kodów, takich jak:

LLK-32U

Czy to jakiś wielki tajny algorytm, który odkrył RedGate i wszyscy nie mamy szczęścia, czy może jest to coś, co moglibyśmy zrobić zwykli śmiertelnicy?

J Wynia
źródło
3

Lecę i właśnie zobaczyłem pytanie: napisałem najłatwiejsze, ale nieefektywne i niekompletne rozwiązanie. Mam nadzieję, że może ci to pomóc w rozpoczęciu pisania własnego parsera:

public static void main(String[] args) {

    String line = "[A-Z0-9]{16}";
    String[] tokens = line.split(line);
    char[] pattern = new char[100];
    int i = 0;
    int len = tokens.length;
    String sep1 = "[{";
    StringTokenizer st = new StringTokenizer(line, sep1);

    while (st.hasMoreTokens()) {
        String token = st.nextToken();
        System.out.println(token);

        if (token.contains("]")) {
            char[] endStr = null;

            if (!token.endsWith("]")) {
                String[] subTokens = token.split("]");
                token = subTokens[0];

                if (!subTokens[1].equalsIgnoreCase("*")) {
                    endStr = subTokens[1].toCharArray();
                }
            }

            if (token.startsWith("^")) {
                String subStr = token.substring(1, token.length() - 1);
                char[] subChar = subStr.toCharArray();
                Set set = new HashSet<Character>();

                for (int p = 0; p < subChar.length; p++) {
                    set.add(subChar[p]);
                }

                int asci = 1;

                while (true) {
                    char newChar = (char) (subChar[0] + (asci++));

                    if (!set.contains(newChar)) {
                        pattern[i++] = newChar;
                        break;
                    }
                }
                if (endStr != null) {
                    for (int r = 0; r < endStr.length; r++) {
                        pattern[i++] = endStr[r];
                    }
                }

            } else {
                pattern[i++] = token.charAt(0);
            }
        } else if (token.contains("}")) {
            char[] endStr = null;

            if (!token.endsWith("}")) {
                String[] subTokens = token.split("}");
                token = subTokens[0];

                if (!subTokens[1].equalsIgnoreCase("*")) {
                    endStr = subTokens[1].toCharArray();
                }
            }

            int length = Integer.parseInt((new StringTokenizer(token, (",}"))).nextToken());
            char element = pattern[i - 1];

            for (int j = 0; j < length - 1; j++) {
                pattern[i++] = element;
            }

            if (endStr != null) {
                for (int r = 0; r < endStr.length; r++) {
                    pattern[i++] = endStr[r];
                }
            }
        } else {
            char[] temp = token.toCharArray();

            for (int q = 0; q < temp.length; q++) {
                pattern[i++] = temp[q];
            }
        }
    }

    String result = "";

    for (int j = 0; j < i; j++) {
        result += pattern[j];
    }

    System.out.print(result);
}
R dhabalia
źródło
Możesz chcieć wskazać, jakiego rodzaju ciągi są używane jako dane wejściowe wzorca. Przede wszystkim nie jest łatwo ustalić takie rzeczy z kodu źródłowego. Po drugie, jeśli w kodzie źródłowym są jakieś błędy lub niejasności, nie ma sposobu, aby sprawdzić, czy są one zamierzone, czy nie.
Maarten Bodewes
StringTokenizer to starsza klasa, która jest zachowywana ze względu na zgodność, chociaż jej użycie jest odradzane w nowym kodzie. Zaleca się, aby każdy, kto szuka tej funkcji, użył metody rozdzielania String lub pakietu java.util.regex.
Rohit,
2

Będziesz musiał napisać swój własny parser, tak jak zrobił to autor String :: Random (Perl). W rzeczywistości nigdzie w tym module nie używa on wyrażeń regularnych, tylko do tego są przyzwyczajeni perl-koderzy.

Z drugiej strony, może możesz zajrzeć do źródła , aby uzyskać wskazówki.


EDYCJA: Cholera, Blair pobił mnie do pięści o 15 sekund.

Espo
źródło
1

Jest to dalekie od obsługi pełnego wyrażenia regularnego PCRE, ale napisałem następującą metodę Ruby, aby pobrać ciąg podobny do wyrażenia regularnego i utworzyć jego odmianę. (W przypadku CAPTCHA opartego na języku).

# q = "(How (much|many)|What) is (the (value|result) of)? :num1 :op :num2?"
# values = { :num1=>42, :op=>"plus", :num2=>17 }
# 4.times{ puts q.variation( values ) }
# => What is 42 plus 17?
# => How many is the result of 42 plus 17?
# => What is the result of 42 plus 17?
# => How much is the value of 42 plus 17?
class String
  def variation( values={} )
    out = self.dup
    while out.gsub!( /\(([^())?]+)\)(\?)?/ ){
      ( $2 && ( rand > 0.5 ) ) ? '' : $1.split( '|' ).random
    }; end
    out.gsub!( /:(#{values.keys.join('|')})\b/ ){ values[$1.intern] }
    out.gsub!( /\s{2,}/, ' ' )
    out
  end
end

class Array
  def random
    self[ rand( self.length ) ]
  end
end

źródło
0

Jeśli chcesz generować „krytyczne” ciągi, rozważ:

EGRET http://elarson.pythonanywhere.com/, który generuje „złe” ciągi obejmujące Twoje wyrażenia regularne

MUTREX http://cs.unibg.it/mutrex/ który generuje ciągi wykrywające błędy przez mutację regex

Oba są narzędziami akademickimi (jestem jednym z autorów tych ostatnich) i działają dość dobrze.

Angelo Gargantini
źródło