Dopasowanie wzorca - Pattern matching
W informatyce , pasujące do wzorca jest aktem sprawdzając daną sekwencję z żetonów na obecność składników jakiegoś wzoru . W przeciwieństwie do rozpoznawania wzorców dopasowanie zazwyczaj musi być dokładne: „albo będzie, albo nie będzie”. Wzorce mają zazwyczaj postać sekwencji lub struktur drzewiastych . Zastosowania dopasowywania wzorców obejmują wyprowadzanie lokalizacji (jeśli istnieją) wzorca w sekwencji symboli, aby wyprowadzić jakiś komponent dopasowanego wzorca i zastąpienie wzorca dopasowania inną sekwencją symboli (tj. search and replace ).
Wzorce sekwencji (np. ciąg tekstowy) są często opisywane za pomocą wyrażeń regularnych i dopasowywane za pomocą technik takich jak wycofywanie .
Wzorce drzew są używane w niektórych językach programowania jako ogólne narzędzie do przetwarzania danych na podstawie ich struktury, np. C# , F# , Haskell , ML , Python , Ruby , Rust , Scala , Swift oraz symboliczny język matematyki Mathematica mają specjalną składnię do wyrażania drzewa wzorców i konstrukcji języka do warunkowego wykonywania i wyszukiwania wartości na ich podstawie.
Często możliwe jest podanie alternatywnych wzorców, które są wypróbowywane jeden po drugim, co daje potężną konstrukcję programowania warunkowego . Dopasowywanie wzorców czasami obejmuje wsparcie dla strażników .
Algorytmy analizowania często opierają się na dopasowaniu wzorców do przekształcania ciągów znaków w drzewa składni .
Historia
Wczesne języki programowania z konstrukcjami dopasowywania wzorców obejmują COMIT (1957), SNOBOL (1962), Refal (1968) z dopasowywaniem wzorców opartym na drzewie, Prolog (1972), SASL (1976), NPL (1977) i KRC (1981).
Wiele edytorów tekstu obsługuje różnego rodzaju dopasowywanie wzorców: edytor QED obsługuje wyszukiwanie według wyrażeń regularnych , a niektóre wersje TECO obsługują operator OR w wyszukiwaniu.
Systemy algebry komputerowej generalnie obsługują dopasowywanie wzorców na wyrażeniach algebraicznych.
prymitywne wzory
Najprostszym wzorcem w dopasowywaniu wzorców jest jawna wartość lub zmienna. Rozważmy na przykład prostą definicję funkcji w składni Haskella (parametry funkcji nie są w nawiasach, ale są oddzielone spacjami, = nie jest przypisaniem, ale definicją):
f 0 = 1
Tutaj 0 jest pojedynczym wzorcem wartości. Teraz, gdy jako argument f podano 0, wzorzec dopasowuje się i funkcja zwraca 1. Przy każdym innym argumencie dopasowanie, a tym samym funkcja, kończy się niepowodzeniem. Ponieważ składnia obsługuje alternatywne wzorce w definicjach funkcji, możemy kontynuować definicję rozszerzając ją o bardziej ogólne argumenty:
f n = n * f (n-1)
Tutaj pierwszy n
jest pojedynczym wzorcem zmiennej, który dopasuje absolutnie każdy argument i zwiąże go z nazwą n, która będzie używana w pozostałej części definicji. W Haskell (w przeciwieństwie przynajmniej do Hope ) wzorce są próbowane w kolejności, więc pierwsza definicja nadal obowiązuje w bardzo specyficznym przypadku, gdy dane wejściowe są równe 0, podczas gdy dla każdego innego argumentu funkcja zwraca, n * f (n-1)
gdzie n jest argumentem.
Wzorzec wieloznaczny (często zapisywany jako _
) jest również prosty: podobnie jak nazwa zmiennej, pasuje do dowolnej wartości, ale nie wiąże jej z żadną nazwą. Algorytmy dopasowywania symboli wieloznacznych w prostych sytuacjach dopasowywania ciągów zostały opracowane w wielu odmianach rekurencyjnych i nierekurencyjnych.
Wzory drzew
Bardziej złożone wzorce można budować z prymitywnych wzorców z poprzedniej sekcji, zwykle w taki sam sposób, jak wartości są budowane przez łączenie innych wartości. Różnica polega więc na tym, że w przypadku części zmiennych i symboli wieloznacznych, wzorzec nie tworzy pojedynczej wartości, ale pasuje do grupy wartości, które są kombinacją konkretnych elementów i elementów, które mogą się zmieniać w ramach struktury wzorca .
Wzorzec drzewa opisuje część drzewa, zaczynając od węzła i określając niektóre gałęzie i węzły, pozostawiając niektóre nieokreślone ze zmiennym lub wzorcem wieloznacznym. Pomocne może być myślenie o abstrakcyjnym drzewie składni języka programowania i algebraicznych typach danych .
W Haskell poniższy wiersz definiuje algebraiczny typ danych, Color
który ma pojedynczy konstruktor danych, ColorConstructor
który otacza liczbę całkowitą i łańcuch.
data Color = ColorConstructor Integer String
Konstruktor jest węzłem w drzewie, a liczba całkowita i łańcuch to liście w gałęziach.
Gdy chcemy napisać funkcji , aby uczynić Color
to abstrakcyjny typ danych , pragniemy funkcji zapisu do interfejsu z typem danych, a tym samym chcemy wyodrębnić pewne dane od typu danych, na przykład, tylko ciąg, czy tylko część całkowitą Color
.
Jeśli przekażemy zmienną typu Color, jak możemy uzyskać dane z tej zmiennej? Na przykład, aby funkcja pobierała część całkowitą z Color
, możemy użyć prostego wzorca drzewa i napisać:
integerPart (ColorConstructor theInteger _) = theInteger
Również:
stringPart (ColorConstructor _ theString) = theString
Tworzenie tych funkcji można zautomatyzować dzięki składni rekordów danych Haskella .
Ten przykład Ocamla, który definiuje czerwono-czarne drzewo i funkcję do ponownego zrównoważenia go po wstawieniu elementu, pokazuje, jak dopasować się do bardziej złożonej struktury generowanej przez rekurencyjny typ danych.
type color = Red | Black
type 'a tree = Empty | Tree of color * 'a tree * 'a * 'a tree
let rebalance t = match t with
| Tree (Black, Tree (Red, Tree (Red, a, x, b), y, c), z, d)
| Tree (Black, Tree (Red, a, x, Tree (Red, b, y, c)), z, d)
| Tree (Black, a, x, Tree (Red, Tree (Red, b, y, c), z, d))
| Tree (Black, a, x, Tree (Red, b, y, Tree (Red, c, z, d)))
-> Tree (Red, Tree (Black, a, x, b), y, Tree (Black, c, z, d))
| _ -> t (* the 'catch-all' case if no previous pattern matches *)
Filtrowanie danych za pomocą wzorców
Dopasowywanie wzorców może służyć do filtrowania danych o określonej strukturze. Na przykład, w Haskell do tego rodzaju filtrowania można użyć rozumienia listy :
[A x|A x <- [A 1, B 1, A 2, B 2]]
ocenia do
[A 1, A 2]
Dopasowywanie wzorców w Mathematica
W Mathematica jedyną istniejącą strukturą jest drzewo wypełnione symbolami. W dotychczas używanej składni Haskella można to zdefiniować jako
data SymbolTree = Symbol String [SymbolTree]
Przykładowe drzewo mogłoby wtedy wyglądać
Symbol "a" [Symbol "b" [], Symbol "c" []]
W tradycyjnej, bardziej odpowiedniej składni, symbole są pisane []
takimi, jakimi są, a poziomy drzewa są reprezentowane za pomocą , tak że na przykład a[b,c]
drzewo ma a jako rodzica, a b i c jako dzieci.
Wzór w Mathematica polega na umieszczeniu „_” na pozycjach w tym drzewie. Na przykład wzór
A[_]
dopasuje elementy takie jak A[1], A[2] lub bardziej ogólnie A[ x ], gdzie x jest dowolną jednostką. W tym przypadku A
jest to element betonowy, natomiast _
oznacza kawałek drzewa, który można zmieniać. Symbol dodany do _
wiąże dopasowanie z nazwą tej zmiennej, podczas gdy symbol dodany do _
ogranicza dopasowania do węzłów tego symbolu. Zauważ, że nawet same puste miejsca są wewnętrznie reprezentowane jako Blank[]
for _
i Blank[x]
for _x
.
Funkcja Mathematica Cases
filtruje elementy pierwszego argumentu, które pasują do wzorca w drugim argumencie:
Cases[{a[1], b[1], a[2], b[2]}, a[_] ]
ocenia do
{a[1], a[2]}
Dopasowywanie wzorców dotyczy struktury wyrażeń. W poniższym przykładzie
Cases[ {a[b], a[b, c], a[b[c], d], a[b[c], d[e]], a[b[c], d, e]}, a[b[_], _] ]
zwroty
{a[b[c],d], a[b[c],d[e]]}
ponieważ tylko te elementy będą pasować do a[b[_],_]
powyższego wzoru .
W Mathematica możliwe jest również wyodrębnianie struktur tak, jak powstają w trakcie obliczeń, niezależnie od tego, jak i gdzie się pojawiają. Funkcja Trace
może służyć do monitorowania obliczeń i zwracania powstałych elementów, które pasują do wzorca. Na przykład możemy zdefiniować ciąg Fibonacciego jako
fib[0|1]:=1
fib[n_]:= fib[n-1] + fib[n-2]
Następnie możemy zadać pytanie: Mając fib[3], jaka jest sekwencja rekurencyjnych wywołań Fibonacciego?
Trace[fib[3], fib[_]]
zwraca strukturę, która reprezentuje wystąpienia wzorca fib[_]
w strukturze obliczeniowej:
{fib[3],{fib[2],{fib[1]},{fib[0]}},{fib[1]}}
Programowanie deklaratywne
W symbolicznych językach programowania łatwo jest mieć wzorce jako argumenty funkcji lub jako elementy struktur danych. Konsekwencją tego jest możliwość używania wzorców do deklaratywnego tworzenia oświadczeń o fragmentach danych i elastycznego instruowania funkcji, jak mają działać.
Na przykład funkcja MathematicaCompile
może służyć do tworzenia bardziej wydajnych wersji kodu. W poniższym przykładzie szczegóły nie mają szczególnego znaczenia; ważne jest to, że podwyrażenie {{com[_], Integer}}
instruuje, Compile
że wyrażenia postaci com[_]
mogą być przyjmowane jako liczby całkowite dla celów kompilacji:
com[i_] := Binomial[2i, i]
Compile[{x, {i, _Integer}}, x^com[i], {{com[_], Integer}}]
Skrzynki pocztowe w Erlangu również działają w ten sposób.
Korespondencja Curry-Howard między dowodami i programów dotyczy ML -Style dopasowywania wzorców do analizy przypadków i dowodów przez wyczerpanie .
Dopasowanie wzorców i ciągi
Zdecydowanie najczęstszą formą dopasowywania wzorców są ciągi znaków. W wielu językach programowania do reprezentowania wyrażeń regularnych, które są wzorcami opisującymi znaki łańcuchowe, używana jest określona składnia łańcuchów.
Jednak możliwe jest wykonanie dopasowania niektórych wzorców ciągów w ramach tej samej struktury, która została omówiona w tym artykule.
Wzory drzew dla ciągów
W Mathematica łańcuchy są reprezentowane jako drzewa korzenia StringExpression, a wszystkie znaki w kolejności jako dzieci korzenia. Tak więc, aby dopasować „dowolną liczbę końcowych znaków”, potrzebny jest nowy symbol wieloznaczny ___, w przeciwieństwie do _, który pasowałby tylko do jednego znaku.
W Haskell i ogólnie w funkcjonalnych językach programowania, łańcuchy są reprezentowane jako funkcjonalne listy znaków. Lista funkcjonalna jest definiowana jako pusta lista lub element skonstruowany na istniejącej liście. W składni Haskella:
[] -- an empty list
x:xs -- an element x constructed on a list xs
Struktura listy z niektórymi elementami jest więc element:list
. Podczas dopasowywania wzorców zapewniamy, że pewna część danych jest równa pewnemu wzorcowi. Na przykład w funkcji:
head (element:list) = element
Twierdzimy, że pierwszy element head
argumentu nosi nazwę element, a funkcja go zwraca. Wiemy, że jest to pierwszy element ze względu na sposób definiowania list, pojedynczy element skonstruowany na liście. Ten pojedynczy element musi być pierwszy. Pusta lista w ogóle nie pasowałaby do wzorca, ponieważ pusta lista nie ma nagłówka (pierwszego konstruowanego elementu).
W przykładzie nie ma dla nas zastosowania list
, więc możemy go zignorować i w ten sposób napisać funkcję:
head (element:_) = element
Równoważna transformacja Mathematica jest wyrażona jako
head[element, ]:=element
Przykładowe wzorce ciągów
Na przykład w Mathematica
StringExpression["a",_]
dopasuje ciąg, który ma dwa znaki i zaczyna się od "a".
Ten sam wzór w Haskell:
['a', _]
Encje symboliczne można wprowadzić, aby reprezentować wiele różnych klas odpowiednich cech łańcucha. Na przykład,
StringExpression[LetterCharacter, DigitCharacter]
dopasuje ciąg składający się najpierw z litery, a następnie z liczby.
W Haskell strażników można było użyć do osiągnięcia tych samych meczów:
[letter, digit] | isAlpha letter && isDigit digit
Główną zaletą manipulacji ciągami symbolicznymi jest to, że można je całkowicie zintegrować z resztą języka programowania, zamiast być oddzielną jednostką podrzędną specjalnego przeznaczenia. Całą moc języka można wykorzystać do budowania samych wzorców lub analizowania i przekształcania programów, które je zawierają.
SNOBOL
SNOBOL ( ang. String Oriented and symBOlic Language ) to język programowania komputerowego opracowany w latach 1962-1967 w AT&T Bell Laboratories przez Davida J. Farbera , Ralpha E. Griswolda i Ivana P. Polonsky'ego .
SNOBOL4 wyróżnia się na tle większości języków programowania przez posiadanie wzorców jako pierwszorzędnego typu danych ( tj. typu danych, którego wartościami można manipulować na wszystkie sposoby dozwolone dla dowolnego innego typu danych w języku programowania) oraz poprzez zapewnienie operatorów do łączenia wzorców i alternatywy . Ciągi generowane podczas wykonywania mogą być traktowane jako programy i wykonywane.
SNOBOL był dość powszechnie nauczany na większych amerykańskich uniwersytetach w późnych latach 60. i wczesnych 70. XX wieku i był szeroko stosowany w latach 70. i 80. jako język manipulacji tekstem w humanistyce .
Od czasu powstania SNOBOL, nowsze języki, takie jak Awk i Perl, sprawiły, że manipulacja ciągami znaków za pomocą wyrażeń regularnych stała się modna. Jednak wzorce SNOBOL4 zawierają gramatyki BNF , które są odpowiednikami gramatyk bezkontekstowych i mają większe możliwości niż wyrażenia regularne .
Zobacz też
- AIML dla języka AI opartego na dopasowaniu wzorców w mowie
- Język AWK
- Wzór Coccinelle pasuje do kodu źródłowego C
- Pasujące symbole wieloznaczne
- glob (programowanie)
- Rachunek różniczkowy
- Rozpoznawanie wzorów dla wzorów rozmytych
- Wyrażenia regularne zgodne z PCRE Perl, powszechna nowoczesna implementacja dopasowywania wzorców ciągów przeniesiona na wiele języków
- REBOL analizuje dialekt do dopasowywania wzorców używany do implementacji dialektów językowych
- Integracja symboliczna
- Tagged związek
- Tom (język dopasowania wzorca)
- SNOBOL dla języka programowania opartego na jednym rodzaju dopasowywania wzorców
- Język wzorców — metaforyczny, zaczerpnięty z architektury
- Dopasowanie wykresu
Bibliografia
- Księga Mathematica, rozdział Sekcja 2.3: Wzorce
- Raport Haskell 98, rozdział 3.17 Dopasowywanie wzorców .
- Podręcznik Pythona, rozdział 6.3 Instrukcje przypisania .
- Czysta Programming Language, rozdział 4.3: Wzorce
Linki zewnętrzne
- Views: Rozszerzenie do Haskell Pattern Matching
- Nikolaas N. Oosterhof, Philip KF Hölzenspies i Jan Kuper. Wzory aplikacji . Prezentacja na Trendy w Programowaniu Funkcjonalnym, 2005
- JMatch : język programowania Java rozszerzony o dopasowanie wzorców
- ShowTrend : Dopasowywanie wzorców online dla cen akcji
- Niepełna historia edytora tekstu QED autorstwa Dennisa Ritchie - zawiera historię wyrażeń regularnych w programach komputerowych
- The Implementation of Functional Programming Languages, strony 53–103 Simon Peyton Jones, opublikowana przez Prentice Hall, 1987.
- Nemerle, dopasowanie do wzoru .
- Erlang, dopasowywanie wzorców .
- Prop: język dopasowywania wzorców oparty na C++, 1999
- PatMat: biblioteka dopasowywania wzorców C++ oparta na SNOBOL / SPITBOL
- Temur Kutsia. Dopasowanie płaskie . Journal of Symbolic Computing 43(12): 858-873. Szczegółowo opisuje dopasowywanie płaskie w Mathematica.
- Język dopasowujący język do wzorca EasyPattern dla nie-programistów