So khớp nhóm Ruby Regexp, gán biến trên 1 dòng


125

Tôi hiện đang cố gắng xp lại một chuỗi thành nhiều biến. Chuỗi ví dụ:

ryan_string = "RyanOnRails: This is a test"

Tôi đã đối sánh nó với regexp này, với 3 nhóm:

ryan_group = ryan_string.scan(/(^.*)(:)(.*)/i)

Bây giờ để truy cập từng nhóm, tôi phải làm như sau:

ryan_group[0][0] (first group) RyanOnRails
ryan_group[0][1] (second group) :
ryan_group[0][2] (third group) This is a test

Điều này có vẻ khá nực cười và có cảm giác như tôi đang làm sai. Tôi mong đợi có thể làm điều gì đó như thế này:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)

Điều này có khả thi không? Hay có cách nào tốt hơn cách tôi đang làm không?

Câu trả lời:


199

Bạn không muốn scanđiều này, vì nó có chút ý nghĩa. Bạn có thể sử dụng String#matchnó sẽ trả về một MatchDatađối tượng, sau đó bạn có thể gọi #capturesđể trả về một Mảng chụp. Một cái gì đó như thế này:

#!/usr/bin/env ruby

string = "RyanOnRails: This is a test"
one, two, three = string.match(/(^.*)(:)(.*)/i).captures

p one   #=> "RyanOnRails"
p two   #=> ":"
p three #=> " This is a test"

Lưu ý rằng nếu không tìm thấy kết quả phù hợp nào, String#matchsẽ trả về nil, vì vậy, một cái gì đó như thế này có thể hoạt động tốt hơn:

if match = string.match(/(^.*)(:)(.*)/i)
  one, two, three = match.captures
end

Mặc dù scankhông có ý nghĩa gì cho điều này. Nó vẫn thực hiện công việc, bạn chỉ cần làm phẳng Mảng được trả về trước.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten


6
Lưu ý rằng nếu không tìm thấy kết quả phù hợp nào, kết quả khớp sẽ trả về nil và bạn nhận được lỗi NilError. Nếu bạn đang ở trong Rails, tôi đề nghị bạn thay đổi: one, two, three = string.match(/(^.*)(:)(.*)/i).captures thành: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Andrea Salicetti

5
@AndreaSalicetti tôi đã chỉnh sửa bài viết của tôi, tôi không thêm mã Rails cụ thể để nó vì vậy tôi đã thay đổi nó với một phiên bản để xử lý các đối tượng bằng không trở lại
Lee Jarvis

3
Bạn cũng có thể &.điều hành viên mới để lấy lại trên một dòng và thậm chí sử dụng nó hai lần khi chỉ có một nhóm chụp. Ví dụ: ..,string.match(regex)&.captures&.first
Gerry Shaw

46

Thay vào đó, bạn có thể sử dụng Khớp hoặc = ~ để cung cấp cho bạn một đối sánh duy nhất và bạn có thể truy cập dữ liệu đối sánh theo cách tương tự hoặc chỉ sử dụng các biến đối sánh đặc biệt $ 1, $ 2, $ 3

Cái gì đó như:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end

5
@Gaston đó là thực sự là gốc cú pháp regexp có nguồn gốc từ Perl :)
ohaleck

28

Bạn có thể đặt tên cho các trận đấu đã chụp của mình

string = "RyanOnRails: This is a test"
/(?<one>^.*)(?<two>:)(?<three>.*)/i =~ string
puts one, two, three

Nó không hoạt động nếu bạn đảo ngược thứ tự của chuỗi và regex.


6

Bạn phải quyết định xem đó có phải là một ý tưởng hay hay không, nhưng ruby ​​regexp có thể (tự động) xác định các biến cục bộ cho bạn!

Tôi không chắc liệu tính năng này có tuyệt vời hay hoàn toàn điên rồ, nhưng regex của bạn có thể xác định các biến cục bộ.

ryan_string = "RyanOnRails: This is a test"
/^(?<webframework>.*)(?<colon>:)(?<rest>)/ =~ ryan_string
# This defined three variables for you. Crazy, but true.
webframework # => "RyanOnRails"
puts "W: #{webframework} , C: #{colon}, R: #{rest}"

(Hãy xem tại http://ruby-doc.org/core-2.1.1/Regexp.html , tìm kiếm "biến cục bộ").

Lưu ý: Như đã chỉ ra trong một nhận xét, tôi thấy rằng có một câu trả lời tương tự và sớm hơn cho câu hỏi này của @toonsend ( https://stackoverflow.com/a/21412455 ). Tôi không nghĩ rằng tôi đã "ăn cắp", nhưng nếu bạn muốn công bằng với những lời khen ngợi và tôn vinh câu trả lời đầu tiên, hãy thoải mái :) Tôi hy vọng không có động vật nào bị hại.


Câu trả lời này ngoại hình khá giống với stackoverflow.com/a/21412455/525478 , đó là hơn một năm cũ ...
Brad Werth

@BradWerth Tôi đoán là tôi không thấy điều đó. Nhưng tôi đã cập nhật câu trả lời của mình để bao gồm mối quan tâm của bạn.
Felix

5

scan() sẽ tìm thấy tất cả các kết quả phù hợp không chồng chéo của regex trong chuỗi của bạn, vì vậy thay vì trả về một mảng trong nhóm của bạn như bạn có vẻ mong đợi, nó sẽ trả về một mảng của mảng.

Bạn có thể tốt hơn nên sử dụng match(), và sau đó nhận mảng chụp bằng cách sử dụng MatchData#captures:

g1, g2, g3 = ryan_string.match(/(^.*)(:)(.*)/i).captures

Tuy nhiên, bạn cũng có thể làm điều này scan()nếu bạn muốn:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)[0]
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.