ฉันต้องการนิพจน์ทั่วไปเพื่อเลือกข้อความทั้งหมดระหว่างวงเล็บสองด้านนอก
ตัวอย่าง: some text(text here(possible text)text(possible text(more text)))end text
ผลลัพธ์: (text here(possible text)text(possible text(more text)))
ฉันต้องการนิพจน์ทั่วไปเพื่อเลือกข้อความทั้งหมดระหว่างวงเล็บสองด้านนอก
ตัวอย่าง: some text(text here(possible text)text(possible text(more text)))end text
ผลลัพธ์: (text here(possible text)text(possible text(more text)))
คำตอบ:
นิพจน์ทั่วไปเป็นเครื่องมือที่ไม่ถูกต้องสำหรับงานเนื่องจากคุณกำลังจัดการกับโครงสร้างที่ซ้อนกันเช่นการเรียกซ้ำ
แต่มีขั้นตอนวิธีการง่ายๆในการทำเช่นนี้ซึ่งผมได้อธิบายไว้ในคำตอบนี้ไปยังคำถามก่อนหน้านี้
ฉันต้องการเพิ่มคำตอบนี้สำหรับการอ้างอิงอย่างรวดเร็ว อย่าลังเลที่จะอัปเดต
\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)
ตำแหน่งที่c
ใช้เป็นตัวนับความลึก
\((?:[^)(]+|(?R))*+\)
การสาธิตที่ regex101 ; หรือไม่มีการสลับ:
\((?:[^)(]*(?R)?)*+\)
การสาธิตที่ regex101 ; หรือม้วนสำหรับการทำงาน:
\([^)(]*+(?:(?R)[^)(]*)*+\)
การสาธิตที่ regex101 ; รูปแบบจะถูกวางที่ซึ่งหมายถึง(?R)
(?0)
Perl, PHP, Notepad ++ R : Perl = TRUE , งูหลาม : แพคเกจ Regexกับ(?V1)
พฤติกรรม Perl
ทับทิมใช้สาย subexpression
ด้วย Ruby 2.0 \g<0>
สามารถใช้ในการโทรแบบเต็ม
\((?>[^)(]+|\g<0>)*\)
การสาธิตที่ Rubular ; Ruby 1.9 สนับสนุนการเรียกซ้ำแบบกลุ่มเท่านั้น:
(\((?>[^)(]+|\g<1>)*\))
การสาธิตที่ Rubular ( การจัดกลุ่มอะตอมตั้งแต่ Ruby 1.9.3)
JavaScript API :: XRegExp.matchRecursive
XRegExp.matchRecursive(str, '\\(', '\\)', 'g');
JS, Java และรสชาติอื่น ๆ ของ regex โดยไม่ต้องทำการซ้อนซ้ำถึง 2 ระดับ:
\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)
การสาธิตได้ที่ regex101 ต้องเพิ่มการซ้อนแบบลึกลงในลวดลาย
ที่จะล้มเหลวได้เร็วขึ้นในวงเล็บไม่สมดุลวาง+
ปริมาณ
Java : ที่น่าสนใจความคิดโดยใช้การอ้างอิงไปข้างหน้าโดย @jaytea
(?>[^)(]+|(?R))*+
(?:[^)(]+|(?R))*+
สิ่งเดียวกันสำหรับรูปแบบต่อไป เกี่ยวกับเวอร์ชันที่ไม่ได้ควบคุมคุณสามารถใส่ตัวระบุความเป็นเจ้าของได้ที่นี่: [^)(]*+
เพื่อป้องกันการย้อนรอย (ในกรณีที่ไม่มีวงเล็บปิด)
(...(..)..(..)..(..)..(..)..)
) ในสตริงหัวเรื่อง) คุณสามารถใช้กลุ่มที่ไม่ได้จับภาพอย่างง่ายและล้อมรอบทั้งหมดในกลุ่มอะตอม: (?>(?:[^)(]+|\g<1>)*)
( สิ่งนี้จะทำงานเหมือนกับตัวนับความเป็นเจ้าของ) ใน Ruby 2.x, quantifier ที่เป็นเจ้าของนั้นมีอยู่
คุณสามารถใช้การเรียกซ้ำ regex :
\(([^()]|(?R))*\)
Unrecognized grouping construct
ฉันได้รับข้อผิดพลาดต่อไปนี้สำหรับรูปแบบนี้:
[^\(]*(\(.*\))[^\)]*
[^\(]*
จับคู่ทุกอย่างที่ไม่ใช่วงเล็บเปิดที่จุดเริ่มต้นของสตริง(\(.*\))
จับสตริงย่อยที่ต้องการอยู่ในวงเล็บและ[^\)]*
จับคู่ทุกอย่างที่ไม่ใช่วงเล็บเหลี่ยมปิดที่ท้ายสตริง โปรดทราบว่าการแสดงออกนี้ไม่ได้พยายามจับคู่วงเล็บ; ตัวแยกวิเคราะห์แบบง่าย (ดูคำตอบของเดห์มันน์ ) จะเหมาะสมกว่าสำหรับสิ่งนั้น
(?<=\().*(?=\))
หากคุณต้องการเลือกข้อความระหว่างวงเล็บสองอันที่ตรงกันคุณไม่มีโชคด้วยการแสดงออกปกติ นี้เป็นไปไม่ได้(*)
regex นี้จะส่งคืนข้อความระหว่างการเปิดครั้งแรกและวงเล็บปิดสุดท้ายในสตริงของคุณ
(*)เว้นแต่ว่าเครื่องมือ regex ของคุณมีคุณสมบัติเช่นกลุ่มสมดุลหรือการเรียกซ้ำ จำนวนเครื่องยนต์ที่รองรับคุณสมบัติดังกล่าวกำลังเพิ่มขึ้นอย่างช้า ๆ แต่ก็ยังไม่สามารถใช้งานได้ทั่วไป
คำตอบนี้อธิบายถึงข้อ จำกัด ทางทฤษฎีว่าทำไมนิพจน์ทั่วไปไม่ใช่เครื่องมือที่เหมาะสมสำหรับงานนี้
นิพจน์ทั่วไปไม่สามารถทำได้
Finite State Automata (FSA)
การแสดงออกปกติจะขึ้นอยู่กับแบบจำลองคอมพิวเตอร์ที่เรียกว่า ดังที่ชื่อบ่งชี้ a FSA
สามารถจดจำสถานะปัจจุบันเท่านั้นไม่มีข้อมูลเกี่ยวกับสถานะก่อนหน้า
ในแผนภาพด้านบน S1 และ S2 เป็นสองสถานะโดยที่ S1 เป็นขั้นตอนเริ่มต้นและขั้นสุดท้าย ดังนั้นถ้าเราลองกับสตริง0110
การเปลี่ยนแปลงจะเป็นดังนี้:
0 1 1 0
-> S1 -> S2 -> S2 -> S2 ->S1
ในขั้นตอนข้างต้นเมื่อเราอยู่ที่สองS2
คือหลังจากการแยก01
ของ0110
ที่ FSA มีข้อมูลเกี่ยวกับก่อนหน้านี้ไม่มี0
ใน01
ขณะที่มันสามารถจำเพียงสถานะปัจจุบันและเป็นสัญลักษณ์การป้อนข้อมูลต่อไป
ในปัญหาข้างต้นเราจำเป็นต้องทราบว่าไม่มีวงเล็บเปิด นี่หมายความว่าจะต้องมีการจัดเก็บในบางสถานที่ แต่เนื่องจากFSAs
ไม่สามารถทำได้นิพจน์ทั่วไปจึงไม่สามารถเขียนได้
อย่างไรก็ตามอัลกอริทึมสามารถเขียนได้เพื่อทำงานนี้ Pushdown Automata (PDA)
อัลกอริทึมโดยทั่วไปมักจะตกอยู่ภายใต้ เป็นหนึ่งในระดับดังกล่าวข้างต้นPDA
FSA
PDA มีสแต็กเพิ่มเติมเพื่อเก็บข้อมูลเพิ่มเติมบางอย่าง พีดีเอสามารถใช้ในการแก้ปัญหาข้างต้นเพราะเราสามารถ ' push
' วงเล็บเปิดในสแต็กและ ' pop
' พวกเขาเมื่อเราพบวงเล็บปิด หากในตอนท้ายสแต็กว่างเปล่าจากนั้นเปิดวงเล็บและปิดวงเล็บที่ตรงกัน ไม่อย่างนั้น
จริงๆแล้วมันเป็นไปได้ที่จะทำโดยใช้. NET นิพจน์ปกติ แต่มันไม่สำคัญดังนั้นอ่านอย่างระมัดระวัง
คุณสามารถอ่านบทความที่ดีที่นี่ คุณอาจต้องอ่านนิพจน์ปกติ. NET คุณสามารถเริ่มต้นการอ่านที่นี่
ใช้วงเล็บมุม<>
เพราะไม่ต้องการการหลบหนี
นิพจน์ทั่วไปมีลักษณะดังนี้:
<
[^<>]*
(
(
(?<Open><)
[^<>]*
)+
(
(?<Close-Open>>)
[^<>]*
)+
)*
(?(Open)(?!))
>
นี่คือ regex ที่ชัดเจน:
\(
(?<arguments>
(
([^\(\)']*) |
(\([^\(\)']*\)) |
'(.*?)'
)*
)
\)
ตัวอย่าง:
input: ( arg1, arg2, arg3, (arg4), '(pip' )
output: arg1, arg2, arg3, (arg4), '(pip'
โปรดทราบว่าการ'(pip'
จัดการอย่างถูกต้องเป็นสตริง (พยายามควบคุม: http://sourceforge.net/projects/regulator/ )
ฉันเขียนไลบรารี่ JavaScript เล็กน้อยที่ชื่อว่าสมดุลเพื่อช่วยงานนี้ คุณสามารถทำได้โดยการทำ
balanced.matches({
source: source,
open: '(',
close: ')'
});
คุณสามารถเปลี่ยนได้:
balanced.replacements({
source: source,
open: '(',
close: ')',
replace: function (source, head, tail) {
return head + source + tail;
}
});
เพิ่มไปยังคำตอบของฟองกลมมีรสชาติ regex อื่น ๆ ที่ได้รับการสนับสนุนการก่อสร้างซ้ำ
Lua
ใช้%b()
( %b{}
/ %b[]
สำหรับวงเล็บปีกกา / วงเล็บเหลี่ยม):
for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end
(ดูตัวอย่าง )Perl6 :
การจับคู่วงเล็บที่สมดุลหลายรายการไม่ทับซ้อนกัน:
my regex paren_any { '(' ~ ')' [ <-[()]>+ || <&paren_any> ]* }
say "Extract (a(b)c) and ((d)f(g))" ~~ m:g/<&paren_any>/;
# => (「(a(b)c)」 「((d)f(g))」)
การทับซ้อนวงเล็บที่สมดุลหลายรายการตรงกัน:
say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)
Python re
โซลูชั่นที่ไม่ใช่ regex
ดูคำตอบของการกระตุ้นของสำหรับวิธีการที่จะได้รับการแสดงออกระหว่างวงเล็บสมดุล
โซลูชันที่ไม่ใช่ regex ที่ปรับแต่งได้ของ Java
นี่คือโซลูชันที่ปรับแต่งได้ซึ่งอนุญาตให้ใช้ตัวคั่นตัวอักษรเดี่ยวใน Java:
public static List<String> getBalancedSubstrings(String s, Character markStart,
Character markEnd, Boolean includeMarkers)
{
List<String> subTreeList = new ArrayList<String>();
int level = 0;
int lastOpenDelimiter = -1;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == markStart) {
level++;
if (level == 1) {
lastOpenDelimiter = (includeMarkers ? i : i + 1);
}
}
else if (c == markEnd) {
if (level == 1) {
subTreeList.add(s.substring(lastOpenDelimiter, (includeMarkers ? i + 1 : i)));
}
if (level > 0) level--;
}
}
return subTreeList;
}
}
ตัวอย่างการใช้งาน:
String s = "some text(text here(possible text)text(possible text(more text)))end text";
List<String> balanced = getBalancedSubstrings(s, '(', ')', true);
System.out.println("Balanced substrings:\n" + balanced);
// => [(text here(possible text)text(possible text(more text)))]
นิพจน์ทั่วไปที่ใช้ Ruby (เวอร์ชัน 1.9.3 หรือสูงกว่า):
/(?<match>\((?:\g<match>|[^()]++)*\))/
คุณต้องการวงเล็บแรกและวงเล็บสุดท้าย ใช้สิ่งนี้:
str.indexOf ('('); - มันจะทำให้คุณเกิดขึ้นครั้งแรก
str.lastIndexOf ( ')'); - อันสุดท้าย
ดังนั้นคุณต้องการสตริงระหว่าง
String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');
"""
Here is a simple python program showing how to use regular
expressions to write a paren-matching recursive parser.
This parser recognises items enclosed by parens, brackets,
braces and <> symbols, but is adaptable to any set of
open/close patterns. This is where the re package greatly
assists in parsing.
"""
import re
# The pattern below recognises a sequence consisting of:
# 1. Any characters not in the set of open/close strings.
# 2. One of the open/close strings.
# 3. The remainder of the string.
#
# There is no reason the opening pattern can't be the
# same as the closing pattern, so quoted strings can
# be included. However quotes are not ignored inside
# quotes. More logic is needed for that....
pat = re.compile("""
( .*? )
( \( | \) | \[ | \] | \{ | \} | \< | \> |
\' | \" | BEGIN | END | $ )
( .* )
""", re.X)
# The keys to the dictionary below are the opening strings,
# and the values are the corresponding closing strings.
# For example "(" is an opening string and ")" is its
# closing string.
matching = { "(" : ")",
"[" : "]",
"{" : "}",
"<" : ">",
'"' : '"',
"'" : "'",
"BEGIN" : "END" }
# The procedure below matches string s and returns a
# recursive list matching the nesting of the open/close
# patterns in s.
def matchnested(s, term=""):
lst = []
while True:
m = pat.match(s)
if m.group(1) != "":
lst.append(m.group(1))
if m.group(2) == term:
return lst, m.group(3)
if m.group(2) in matching:
item, s = matchnested(m.group(3), matching[m.group(2)])
lst.append(m.group(2))
lst.append(item)
lst.append(matching[m.group(2)])
else:
raise ValueError("After <<%s %s>> expected %s not %s" %
(lst, s, term, m.group(2)))
# Unit test.
if __name__ == "__main__":
for s in ("simple string",
""" "double quote" """,
""" 'single quote' """,
"one'two'three'four'five'six'seven",
"one(two(three(four)five)six)seven",
"one(two(three)four)five(six(seven)eight)nine",
"one(two)three[four]five{six}seven<eight>nine",
"one(two[three{four<five>six}seven]eight)nine",
"oneBEGINtwo(threeBEGINfourENDfive)sixENDseven",
"ERROR testing ((( mismatched ))] parens"):
print "\ninput", s
try:
lst, s = matchnested(s)
print "output", lst
except ValueError as e:
print str(e)
print "done"
คำตอบนั้นขึ้นอยู่กับว่าคุณต้องการจับคู่ชุดของวงเล็บที่ตรงกันหรือเพียงเปิดครั้งแรกจนถึงปิดสุดท้ายในข้อความอินพุต
หากคุณต้องการจับคู่วงเล็บเหลี่ยมแบบซ้อนคุณต้องมีอะไรมากกว่านิพจน์ทั่วไป - ดูที่@dehmann
หากเป็นครั้งแรกที่เปิดให้ล่าสุดปิดดู@Zach
ตัดสินใจเลือกสิ่งที่คุณต้องการจะเกิดขึ้นกับ:
abc ( 123 ( foobar ) def ) xyz ) ghij
คุณต้องตัดสินใจว่ารหัสของคุณต้องตรงกันในกรณีนี้
เนื่องจาก js regex ไม่สนับสนุนการจับคู่แบบเรียกซ้ำฉันจึงไม่สามารถทำงานในวงเล็บที่สมดุลได้
ดังนั้นนี่คือจาวาสคริปต์อย่างง่ายสำหรับรุ่นวนรอบที่สร้างสตริง "method (ARG)" ในอาร์เรย์
push(number) map(test(a(a()))) bass(wow, abc)
$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)
const parser = str => {
let ops = []
let method, arg
let isMethod = true
let open = []
for (const char of str) {
// skip whitespace
if (char === ' ') continue
// append method or arg string
if (char !== '(' && char !== ')') {
if (isMethod) {
(method ? (method += char) : (method = char))
} else {
(arg ? (arg += char) : (arg = char))
}
}
if (char === '(') {
// nested parenthesis should be a part of arg
if (!isMethod) arg += char
isMethod = false
open.push(char)
} else if (char === ')') {
open.pop()
// check end of arg
if (open.length < 1) {
isMethod = true
ops.push({ method, arg })
method = arg = undefined
} else {
arg += char
}
}
}
return ops
}
// const test = parser(`$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)`)
const test = parser(`push(number) map(test(a(a()))) bass(wow, abc)`)
console.log(test)
ผลลัพธ์ที่ได้คือ
[ { method: 'push', arg: 'number' },
{ method: 'map', arg: 'test(a(a()))' },
{ method: 'bass', arg: 'wow,abc' } ]
[ { method: '$$', arg: 'groups' },
{ method: 'filter',
arg: '{type:\'ORGANIZATION\',isDisabled:{$ne:true}}' },
{ method: 'pickBy', arg: '_id,type' },
{ method: 'map', arg: 'test()' },
{ method: 'as', arg: 'groups' } ]
ในขณะที่คำตอบมากมายพูดถึงสิ่งนี้ในบางรูปแบบโดยบอกว่า regex ไม่สนับสนุนการจับคู่แบบเรียกซ้ำและอื่น ๆ เหตุผลหลักสำหรับเรื่องนี้อยู่ในรากของทฤษฎีการคำนวณ
{a^nb^n | n>=0} is not regular
ภาษาของแบบฟอร์ม Regex สามารถจับคู่สิ่งต่าง ๆ ที่เป็นส่วนหนึ่งของชุดภาษาปกติเท่านั้น
อ่านเพิ่มเติม @ ที่นี่
ฉันไม่ได้ใช้ regex เพราะมันยากที่จะจัดการกับรหัสที่ซ้อนกัน ดังนั้นตัวอย่างนี้ควรจะช่วยให้คุณสามารถคว้าส่วนของรหัสด้วยวงเล็บเหลี่ยมที่สมดุล:
def extract_code(data):
""" returns an array of code snippets from a string (data)"""
start_pos = None
end_pos = None
count_open = 0
count_close = 0
code_snippets = []
for i,v in enumerate(data):
if v =='{':
count_open+=1
if not start_pos:
start_pos= i
if v=='}':
count_close +=1
if count_open == count_close and not end_pos:
end_pos = i+1
if start_pos and end_pos:
code_snippets.append((start_pos,end_pos))
start_pos = None
end_pos = None
return code_snippets
ฉันใช้สิ่งนี้เพื่อแยกข้อมูลโค้ดจากไฟล์ข้อความ
ฉันยังติดอยู่ในสถานการณ์เช่นนี้ที่รูปแบบซ้อนกันมา
นิพจน์ทั่วไปเป็นสิ่งที่ถูกต้องในการแก้ปัญหาข้างต้น ใช้รูปแบบด้านล่าง
'/(\((?>[^()]+|(?1))*\))/'
อันนี้ก็ใช้งานได้
re.findall(r'\(.+\)', s)
สิ่งนี้อาจมีประโยชน์สำหรับบางคน:
ที่นี่คุณสามารถเห็นการใช้งาน regexp ที่สร้างขึ้น
/**
* get param content of function string.
* only params string should be provided without parentheses
* WORK even if some/all params are not set
* @return [param1, param2, param3]
*/
exports.getParamsSAFE = (str, nbParams = 3) => {
const nextParamReg = /^\s*((?:(?:['"([{](?:[^'"()[\]{}]*?|['"([{](?:[^'"()[\]{}]*?|['"([{][^'"()[\]{}]*?['")}\]])*?['")}\]])*?['")}\]])|[^,])*?)\s*(?:,|$)/;
const params = [];
while (str.length) { // this is to avoid a BIG performance issue in javascript regexp engine
str = str.replace(nextParamReg, (full, p1) => {
params.push(p1);
return '';
});
}
return params;
};
สิ่งนี้ไม่ได้ตอบคำถาม OP อย่างสมบูรณ์ แต่ฉันคิดว่ามันอาจจะมีประโยชน์สำหรับบางคนที่มาที่นี่เพื่อค้นหา regexp โครงสร้างที่ซ้อนกัน