Là biểu thức chính quy là một ngôn ngữ lập trình?


27

Theo nghĩa học thuật, các biểu thức chính quy có đủ điều kiện là ngôn ngữ lập trình không?

Động lực cho sự tò mò của tôi là một câu hỏi SO tôi chỉ nhìn vào đó hỏi "có thể regex làm X không?" và nó làm tôi tự hỏi những gì có thể được nói theo nghĩa chung về các giải pháp có thể sử dụng chúng.

Về cơ bản tôi đang hỏi, "các biểu thức chính quy Turing đã hoàn thành chưa"?


9
Vì vậy, về cơ bản, bạn đang hỏi "các biểu thức thông thường Turing đã hoàn thành" chưa?
Thất vọngWithFormsDesigner

Sẽ thật tuyệt nếu có ai đó xây dựng thêm, nhưng đúng vậy
Aaron Anodide

4
"Các biểu thức chính quy hoàn chỉnh" đòi hỏi sự hiểu biết về các loại ngôn ngữ và chữ tượng hình chomsky

5
(1 phút sau so với chỉnh sửa) và nếu bạn muốn đi xuống con đường câu hỏi và giải thích đó, bạn có thể muốn xem qua trao đổi lý thuyết cs . Các Bổ đề bơm là phản bác đơn giản nhất để "một ngôn ngữ thông thường có thể phù hợp với một ^ nb ^ n" (đó là matchable bởi một máy Turing).

1
Tôi nghĩ rằng anh ấy hỏi liệu anh ấy có thể đưa nó vào sơ yếu lý lịch của mình trong phần "Ngôn ngữ lập trình" không. Câu trả lời trong trường hợp đó là không. Điều đó nằm trong phần "Công nghệ".
Neil

Câu trả lời:


46

Biểu thức chính quy là một loại ngữ pháp chính thức đặc biệt được sử dụng để phân tích các chuỗi và thông tin văn bản khác được gọi là "Ngôn ngữ thông thường" trong lý thuyết ngôn ngữ chính thức. Họ không phải là một ngôn ngữ lập trình như vậy. Chúng là một cách viết tắt của mã hóa mà nếu không thì cực kỳ tẻ nhạt để thực hiện và thậm chí còn khó hiểu hơn so với Regex đôi khi trông phức tạp.

Ngôn ngữ lập trình thường được định nghĩa là ngôn ngữ Turing Complete . Các ngôn ngữ như vậy phải có khả năng xử lý bất kỳ chức năng tính toán nào . Regex không phù hợp với thể loại này.

Nếu bạn muốn một ngôn ngữ giống như Regex, hãy thử J.


1
+1, tôi đã xem nhưng không thể tìm thấy một cuộc thảo luận tốt / không chắc chắn về tính hoàn chỉnh của Turing trong các biểu thức thông thường.
Thất vọngWithFormsDesigner

1
@ davidk01 - Cellata automata có thể được hoàn thành (mặc dù trình biên dịch tốt rất khó tìm), các biểu thức thông thường thì không. Bạn có thể thực hiện các tính toán không tầm thường, vâng, nhưng có những điều khá tầm thường bạn không thể làm tốt. Turing automata di động hoàn chỉnh có thể được coi là ngôn ngữ lập trình, vì về nguyên tắc bạn có thể viết bất kỳ chương trình nào với chúng mà bạn có thể làm với bất kỳ ngôn ngữ nào khác.
psr

1
Cũng cần lưu ý rằng regex thực hiện kiểm tra tính nguyên thủy ( montreal.pm.org/tech/neil_kandalgaonkar.shtml#primality_regex ) sử dụng các tính năng của các biểu thức perl mạnh hơn "Biểu thức chính quy" theo nghĩa học thuật - cụ thể là các nhóm được lưu trữ . Ngôn ngữ thông thường không thể yêu cầu bộ nhớ tùy ý.
Eric W.

5
@WorldEngineer: Có những ngôn ngữ lập trình thú vị và hữu ích chưa hoàn thành. Datalog, SQL và ACL2 là một vài ví dụ xuất hiện trong tâm trí, cũng như bất kỳ số lượng tính toán lambda bình thường hóa mạnh nào được sử dụng trong những thứ như các lý thuyết dựa trên lý thuyết loại.
Ryan Culpepper

1
Không phải tất cả các ngôn ngữ lập trình là hoàn thành. Ví dụ, các ngôn ngữ khai báo hoàn toàn không có ngữ cảnh như XML không hoàn thành mà không được ghép nối với một trình thông dịch có thể được coi là ngôn ngữ lập trình. Tất cả phụ thuộc vào định nghĩa của bạn về 'ngôn ngữ lập trình'. Tất cả những gì bạn cần để chuyển đổi ngôn ngữ 'thông thường' sang ngôn ngữ 'không ngữ cảnh' là một chồng đẩy xuống. Sau đó, nó là rùa xuống.
Evan Plaice

14

Rất khó để trả lời câu hỏi kiểu "là X một Y ", nếu người tham gia về việc sử dụng cuộc tranh luận định nghĩa khác nhau của XY . Có thể là đối với một số định nghĩa, câu trả lời là "có" và đối với một số định nghĩa, câu trả lời là "không". Đặc biệt nếu câu trả lời phụ thuộc vào chi tiết kỹ thuật trong đó các định nghĩa khác nhau khác nhau. Ngoài ra cuộc thảo luận này có chứa một số thông tin sai lệch, vì vậy xin hãy kiên nhẫn với câu trả lời dài hơn.

" Ngôn ngữ lập trình " nghĩa là gì?

Một câu trả lời đơn giản có thể là "một ngôn ngữ được sử dụng để tạo chương trình". Chắc chắn, nhưng: loại chương trình nào? Thế còn một ngôn ngữ có thể được sử dụng để tạo ra một số loại chương trình, nhưng không phải là các loại chương trình khác thì sao? Dưới đây là hai ví dụ cụ thể để minh họa các trường hợp cực đoan:

1) Một ngôn ngữ tưởng tượng gọi là M hoạt động như thế này: Nếu chương trình chứa một chữ cái "m", nó sẽ tạo ra một trò chơi của Minesweeper. Mọi thứ khác là một lỗi cú pháp.

Theo trực giác, đây không phải là những gì chúng tôi muốn nói bằng cách nói "một ngôn ngữ lập trình". Nhưng bộ phận tiếp thị của M có thể lập luận rằng về mặt kỹ thuật nó đáp ứng định nghĩa, bởi vì nó có thể được sử dụng để tạo ra một chương trình. Chắc chắn, trình biên dịch thực hiện một số phần quan trọng cho bạn, nhưng đó là những gì trình biên dịch làm, phải không? Một trình biên dịch của ngôn ngữ C cũng dịch một số từ đơn giản thành hàng tá hướng dẫn của bộ xử lý. Trình biên dịch M chỉ đi xa hơn và làm cho công việc của bạn thậm chí đơn giản hơn.

2) Nếu bạn cài đặt phiên bản gốc của Turbo Pascal nổi tiếng, bạn có thể viết nhiều loại chương trình. Nhưng bạn không thể viết một trò chơi chạy trong trình duyệt web, vì API cần thiết đơn giản là không có ở đó.

Vậy chính xác thì điều gì làm cho Turbo Pascal trở thành ngôn ngữ lập trình, nhưng M không có nó? Nói một cách đơn giản, bạn có thể làm nhiều hơn trong Pascal so với ở M. Nhưng hãy tưởng tượng chúng ta có một M.NET, tạo ra một trò chơi Minesweeper chạy trong trình duyệt web. Vì vậy, bây giờ chúng ta có một cái gì đó mà Pascal có thể làm và M.NET không thể, nhưng chúng ta cũng có một cái gì đó mà M.NET có thể làm và Pascal thì không thể. Tại sao chúng ta nên coi những lợi thế của Pascal là quan trọng và những lợi thế của M.NET không liên quan?

Câu trả lời là bạn có thể viết tất cả các loại thuật toán trong Pascal, nhưng bạn không thể viết các thuật toán trong M hoặc M.NET. Chắc chắn, M biên dịch lệnh của bạn "m" và C biên dịch lệnh của bạn "strcmp". Nhưng bạn có thể đặt "strcmp" trong ngữ cảnh lớn hơn, ví dụ so sánh hai tệp theo từng dòng hoặc đọc hàng nghìn chuỗi và sắp xếp chúng theo thứ tự bảng chữ cái hoặc ... tốt, hàng triệu thứ khác. Và chính xác là khả năng này sử dụng các lệnh đã cho trong bất kỳ thuật toán nào tạo nên bản chất của ngôn ngữ lập trình.

Chính xác thì thuật toán là gì và quan trọng hơn là "thuật toán bất kỳ" là gì? Trong khoa học máy tính, chúng tôi sử dụng các từ Turing-Complete . Ý tưởng là có một bộ ngôn ngữ máy tính, trong đó mỗi ngôn ngữ có thể mô phỏng tất cả chúng. Một trong những ngôn ngữ đó là máy Turing, đó là lý do tại sao chúng được gọi như vậy. Pascal ở đó, C ở đó, Java ở đó, Python ở đó, Lisp ở đó, Smalltalk ở đó, thậm chí XSLT ở đó. M và M.NET giả thuyết của chúng tôi không có ở đó. Bạn có thể tìm hiểu thêm về điều này tại bất kỳ trường đại học nào cung cấp khóa học khoa học máy tính đàng hoàng, nhưng ý tưởng là một ngôn ngữ hoàn chỉnh Turing có thể làm bất cứ điều gìrằng một ngôn ngữ hoàn chỉnh Turing khác có thể làm được, nếu bạn cung cấp cho họ API cần thiết tối thiểu. (Nếu bạn cung cấp một số API trình duyệt web cho Pascal, bạn có thể tạo tất cả các loại trò chơi trong trình duyệt web. Nếu bạn cung cấp API trình duyệt web cho M, bạn vẫn chỉ có thể tạo Minesweeper.) Chúng tôi có thể nói một cách ẩn dụ rằng nếu bạn xóa tất cả các API khỏi ngôn ngữ lập trình, nội dung quan trọng là những gì còn lại.

" Biểu thức chính quy " nghĩa là gì?

Các ngôn ngữ lập trình khác nhau thực hiện chúng hơi khác nhau. Nhưng ý tưởng ban đầu là các biểu thức chính quy thể hiện cái gọi là ngôn ngữ thông thường . Lưu ý rằng chúng ta không nói về ngôn ngữ lập trình ở đây, mà là về ngôn ngữ của con người (giả). Hãy tưởng tượng rằng bạn tìm thấy một bộ lạc kỳ lạ nói một ngôn ngữ chỉ bao gồm các từ "ba", "baba", "bababa", v.v. Bạn có thể mô tả ngôn ngữ này bằng lời nói là "một âm tiết 'ba' lặp đi lặp lại một hoặc nhiều lần" hoặc sử dụng một biểu thức thông thường là "(ba) +".

Các biểu thức chính quy được cho là thể hiện: "không có gì", "lá thư này", "cái này, tiếp theo là cái kia", "cái này hay cái kia", "cái này, lặp đi lặp lại một hoặc nhiều lần" và "không phải cái này". - Đó là định nghĩa toán học . Bất cứ điều gì khác chỉ là một phím tắt thuận tiện được xây dựng từ các thành phần trước đó. Ví dụ: "cái này, lặp lại hai hoặc ba lần" có thể được dịch là "cái này, tiếp theo là cái này, theo sau (cái này hoặc không có gì)", nhưng có thể thuận tiện hơn khi viết "ba {2,3}" so với "baba (ba)?".

Trong cuộc sống thực, một cách thực hiện điển hình của "biểu thức chính quy" thực hiện nhiều hơn thế này. Ví dụ: sử dụng định nghĩa toán học, ngôn ngữ của "aba", "aabaa", "aaabaaa", v.v. - bất kỳ số "a" nào, theo sau là "b", theo sau là cùng một số "a" "S - không phải là ngôn ngữ thông thường. Tuy nhiên, nhiều "biểu thức chính quy" được sử dụng ngày nay có thể phát hiện ra nó, bằng cách sử dụng khái niệm bổ sung "cùng một thứ mà chúng ta đã tìm thấy trước đây", được viết là "(a +) b \ 1". Sử dụng khái niệm bổ sung này, chúng ta có thể làm một số điều thú, ví dụ như phát hiện từ bao gồm các thủ số của các chữ cái. Tuy nhiên, chúng tôi không thể thực hiện bất kỳ thuật toán nào ... để giải thích tại sao,

Vì vậy, quay lại chủ đề ban đầu: các biểu thức chính quy (được định nghĩa là: các biểu thức mô tả các ngôn ngữ thông thường trong hệ thống phân cấp Chomsky; hoặc là: trước đây, cộng với thao tác \ 1) là ngôn ngữ lập trình (được định nghĩa là: Hoàn thành Turing)? Câu trả lời là không . Không, bạn không thể thực hiện bất kỳ thuật toán nào bằng cách sử dụng các biểu thức thông thường và khả năng thực hiện bất kỳ thuật toán nào là điều mà mọi người nghiên cứu về khoa học máy tính thường hiểu là bản chất của ngôn ngữ lập trình.

Tất nhiên, bất cứ ai cũng có thể thay đổi câu trả lời bằng cách nhấn mạnh vào một định nghĩa khác . Như tôi đã viết lúc đầu, các chi tiết kỹ thuật rất quan trọng ở đây. Nếu bạn nhận được chúng sai, bạn nhận được một câu trả lời sai.

Và nếu bạn không quan tâm đến các chi tiết kỹ thuật, câu trả lời có thể là: Bạn có thể sử dụng các biểu thức thông thường (và không có gì khác) để tạo một chương trình không? Không. Vậy tại sao gọi nó là ngôn ngữ lập trình? (Tuy nhiên, một câu trả lời như thế này đã được tải xuống và xóa ở đây, đó là lý do tại sao tôi viết phiên bản dài hơn này.)

EDIT: Ngoài ra, bất kỳ ai cũng có thể tạo một thư viện triển khai biến thể "biểu thức chính quy" mới của riêng họ với một số tính năng mới được thêm vào. Tại một thời điểm nào đó, các tính năng mới thể đủ để toàn bộ hệ thống trở nên hoàn chỉnh. Một ví dụ tầm thường sẽ nhúng ngôn ngữ Turing-Complete bằng một số cú pháp mới; nhưng nó cũng có thể xảy ra ít rõ ràng hơn Có lẽ nó đã xảy ra rồi.


0

Trong .Net, Regex không chỉ có thể xử lý nhiều dạng điều kiện, sử dụng các kết hợp xen kẽ và nhìn khác nhau, mà còn có thể điều khiển ngăn xếp của chính nó.

(?xm)
    (?>
        <(?<Tagname>table)[^>]*>
    )
(?(Tagname)
    (
        </(?(?!\k'Tagname')(?<-Tagname>))*\k'Tagname'>(?<-Tagname>)
    |
        (?>
            <(?<Tagname>[a-z][^\s>]*)[^>]*>
        )
    |
        [^<]+
    )+?
    (?(Tagname)(?!))
)

Ví dụ, đây là một đoạn nhỏ tôi đã viết để lấy Bảng HTML. Không giống như các công cụ regex khác, điều này kiểm soát chồng các bộ sưu tập chụp (đẩy, nhìn trộm và bật) và có thể xử lý các đối tượng lồng nhau. Tôi có một cái phức tạp hơn, nhưng nó là độc quyền.

Tôi nghĩ trong ví dụ này, Regex có thể được xem là có tất cả các yêu cầu cơ bản của ngôn ngữ lập trình. Nó có các biến, bộ nhớ nội tuyến, điều kiện, đầu vào và đầu ra, nó biên dịch bằng một trong nhiều công cụ biên dịch regex (.Net trong trường hợp này).

Để đáp ứng với việc sử dụng quá mức cho phép (KHÔNG BAO GIỜ) Parse HTML với Regex, tôi đã tiếp tục và đăng một phản hồi được gõ trước mà tôi có thể đăng: Phân tích cú pháp HTML

Ví dụ về anoter (chỉ là một cuộc biểu tình) như sau:

Function Regex("<(td>)((?:[^<]*(?(?!</\1)<))*)</\1")
    Group(0) = "<"
    Group(1) = "td>"
    Group(0) += Group(1)
    Group(2) = LoopMethod()
    Group(0) += Group(2)
    Group(0) += "</" & Group(1)
    Return Group()
End Function

Function LoopMethod()
    retGroup = ""
    Do
        tmpGroup = Everything that is NOT an Opening HTML Delimeter
        If the Text following tmpGroup Does NOT Equal "</" & Group(1) Then
            tmpGroup += "<"
            retGroup += tmpGroup
        Else
            Exit Do
        End If
    Loop
    Return retGroup
End Function

Một lần nữa, đối với vẹt HTML: Phân tích cú pháp HTML

Điều này cho thấy một regex đơn giản hơn thực hiện các vòng lặp và điều kiện (thuật toán?). Điều duy nhất còn thiếu là tính toán thực tế. Đây là Biểu thức chính quy chi tiết hơn, chỉ cần kéo một tế bào TD hiệu quả hơn phương thức "(. *?)" Điển hình.

Nhưng ngay cả với tư cách là một người đam mê Regex và là người tự xưng, tôi sẽ không đi vòng quanh nói với bất kỳ ai Regex là ngôn ngữ lập trình. Lập luận của riêng tôi chống lại bản thân mình là nó không thể đứng một mình, nó phải được chạy qua công cụ riêng của nó trong khi được hỗ trợ bởi một công cụ ngôn ngữ lập trình khác.


Nếu bạn "kiểm tra" cái này và nó không hoạt động, bạn phải nhận ra rằng hầu hết "người kiểm tra" công cụ regex không xử lý .Net Regex (Nhóm cân bằng). Bạn phải thực sự sử dụng điều này trong một chương trình .Net.
Suamere

3
Ôi trời, đây là bằng chứng prima facia cho lý do tại sao bạn không bao giờ nên sử dụng regexes để phân tích html . Không bao giờ.
Tacroy

@Tacroy Rất vui khi thấy ai đó theo đuổi lời khuyên của vẹt về phân tích cú pháp HTML bằng regex. Mặc dù không dành cho người yếu tim, nhưng việc kết hợp các biểu thức chính như ở trên với ngăn xếp là một công thức cơ bản (và hiệu quả) để xây dựng trình phân tích cú pháp không ngữ cảnh.
Evan Plaice

1
Để đáp ứng với Parrot Squawking. Tôi đã tạo cái này: Phân tích cú pháp HTML
Suamere

Đây không phải là Biểu thức chính quy nếu nó chấp nhận các ngôn ngữ nhạy cảm theo ngữ cảnh. Đó là một số DSL khác là siêu bộ của Regex. Tên nhà cung cấp không thay đổi điều đó
Caleth 15/03/2017

0

Mặc dù một tìm kiếm / thay thế trong biểu thức thông thường không phải là ngôn ngữ lập trình Turing hoàn chỉnh, như được giải thích bởi các câu trả lời trước đó, nếu bạn cho phép sử dụng các hành động thay thế lặp lại bằng biểu thức thông thường, thì có, bạn có thể mã hóa bất kỳ máy Turing nào bằng biểu thức chính quy:

Tìm kiếm lặp lại / Thay thế bằng các biểu thức thông thường là Ngôn ngữ lập trình hoàn chỉnh Turing

Kết quả là, bạn có thể tính toán bất kỳ hàm tính toán nào bằng cách sử dụng cùng một tìm kiếm và thay thế biểu thức chính quy javascript lặp đi lặp lại.

Để chứng minh tính đầy đủ của Turing, việc mã hóa máy Turing trong tìm kiếm / thay thế biểu thức thông thường là đủ. Giả sử rằng trạng thái của trình soạn thảo là:

0000#12345:01-5:0#0000000

có thể được đọc dưới dạng băng biểu tượng với đầu đọc trên đó:

[left symbols]#[set of states]:[set of symbols]-[current state]:[current symbol]#[right symbols]

Đối với quy tắc đọc 0 ở trạng thái 5, viết 1 và thay đổi trạng thái thành 3 và di chuyển sang trái, chúng tôi trừu tượng hóa nó bằng cách sử dụng ký hiệu sau:

5:0 => 1, 3:[left]

Chúng tôi mã hóa ký hiệu trước đó thành biểu thức chính quy tìm kiếm:

(\d)#(1)(2)(3)(4)(5):(0)(1)-5:0#

và biểu thức thay thế của nó (giống như javascript)

#12345:01-$4:$1#$8

Ok, bây giờ làm thế nào để mã hóa nhiều quy tắc? Chúng tôi sử dụng phép nối với ortoán tử |cho tìm kiếm biểu thức chính quy và chúng tôi kết hợp các kết quả thay thế, đánh số thứ tự số với các độ lệch. Ví dụ, chúng ta hãy xem xét bộ bốn quy tắc.

5:0 => 1, 3:left
3:0 => 1, 5:right
5:1 => 1, 5:right
3:1 => 1: 3:stop

Chúng tôi mã hóa chúng trong một biểu thức tìm kiếm và thay thế:

Search:
(\d)#(1)(2)(3)(4)(5):(0)(1)-5:0#|#(1)(2)(3)(4)(5):(0)(1)-3:0#(\d)|#(1)(2)(3)(4)(5):(0)(1)-5:1#(\d)|#(1)(2)(3)(4)(5):(0)(1)-3:1#

Replace by:
$15$23#12345:01-$4$13$21$27:$1$16$24$31#$8

Dùng thử trong công cụ javascript yêu thích của bạn:

function turingstep(s) {
  return s.replace(/(\d)#(1)(2)(3)(4)(5):(0)(1)-5:0#|#(1)(2)(3)(4)(5):(0)(1)-3:0#(\d)|#(1)(2)(3)(4)(5):(0)(1)-5:1#(\d)|#(1)(2)(3)(4)(5):(0)(1)-3:1#/g,"$15$23#12345:01-$4$13$21$27:$1$16$24$31#$8");
}

var tape = "0000#12345:01-5:0#0000000"
for(var i = 0; i < 6; i++) {
  console.log(tape)
  tape = turingstep(tape)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.