From 5f2d19905470b10fa437ea87854905197a165687 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Mon, 20 Mar 2017 21:28:41 +0300 Subject: [PATCH] up --- .../article.md | 43 ++++++++---------- .../bad_backtrack_greedy1.png | Bin 2357 -> 0 bytes .../bad_backtrack_greedy11.png | Bin 1119 -> 0 bytes .../bad_backtrack_greedy2.png | Bin 2503 -> 0 bytes .../bad_backtrack_greedy3.png | Bin 2505 -> 0 bytes .../bad_backtrack_greedy4.png | Bin 2552 -> 0 bytes .../bad_backtrack_greedy5.png | Bin 2462 -> 0 bytes 7 files changed, 19 insertions(+), 24 deletions(-) delete mode 100644 10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png delete mode 100644 10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png delete mode 100644 10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy2.png delete mode 100644 10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png delete mode 100644 10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy4.png delete mode 100644 10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy5.png diff --git a/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/article.md b/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/article.md index fae4e5f6..bb4c7d6f 100644 --- a/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/article.md +++ b/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/article.md @@ -233,45 +233,40 @@ Decreasing the count of `pattern:\d+` can not help to find a match, there's no m (1234)(56789)z ``` -Если вернуться к более реальному примеру `pattern:<(\s*\w+=\w+\s*)*>` то -сам алгоритм поиска, который у нас в голове, предусматривает, что мы "просто" ищем тег, а потом пары `атрибут=значение` (сколько получится). +Let's get back to more real-life example: `pattern:<(\s*\w+=\w+\s*)*>`. We want it to find pairs `name=value` (as many as it can). There's no need in backtracking here. -Никакого "отката" здесь не нужно. +In other words, if it found many `name=value` pairs and then can't find `>`, then there's no need to decrease the count of repetitions. Even if we match one pair less, it won't give us the closing `>`: -В современных регулярных выражениях для решения этой проблемы придумали "possessive" (сверхжадные? неоткатные? точный перевод пока не устоялся) квантификаторы, которые вообще не используют бэктрегинг. +Modern regexp engines support so-called "possessive" quantifiers for that. They are like greedy, but don't backtrack at all. Pretty simple, they capture whatever they can, and the search continues. There's also another tool called "atomic groups" that forbid backtracking inside parentheses. -То есть, они даже проще, чем "жадные" -- берут максимальное количество символов и всё. Поиск продолжается дальше. При несовпадении никакого возврата не происходит. +Unfortunately, but both these features are not supported by JavaScript. -Это, с одной стороны, уменьшает количество возможных результатов, но, с другой стороны, в ряде случаев очевидно, что возврат (уменьшение количество повторений квантификатора) результата не даст. А только потратит время, что как раз и доставляет проблемы. Как раз такие ситуации и описаны выше. +Although we can get a similar affect using lookahead. There's more about the relation between possessive quantifiers and lookahead in articles [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) and [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups). -Есть и другое средство -- "атомарные скобочные группы", которые запрещают перебор внутри скобок, по сути позволяя добиваться того же, что и сверхжадные квантификаторы, +The pattern to take as much repetitions as possible without backtracking is: `pattern:(?=(a+))\1`. -К сожалению, в JavaScript они не поддерживаются. +In other words, the lookahead `pattern:?=` looks for the maximal count `pattern:a+` from the current position. And then they are "consumed into the result" by the backreference `pattern:\1`. -Однако, можно получить подобный эффект при помощи предпросмотра. Подробное описание соответствия с учётом синтаксиса сверхжадных квантификаторов и атомарных групп есть в статьях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) и [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups), здесь же мы останемся в рамках синтаксиса JavaScript. +There will be no backtracking, because lookahead does not backtrack. If it found like 5 times of `pattern:a+` and the further match failed, then it doesn't go back to 4. -Взятие максимального количества повторений `a+` без отката выглядит так: `pattern:(?=(a+))\1`. - -То есть, иными словами, предпросмотр `pattern:?=` ищет максимальное количество повторений `pattern:a+`, доступных с текущей позиции. А затем они "берутся в результат" обратной ссылкой `pattern:\1`. Дальнейший поиск -- после найденных повторений. - -Откат в этой логике в принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 штук `pattern:a+`, и в результате поиск не удался, то он не будет откатываться на 4 повторения. Эта возможность в предпросмотре отсутствует, а в данном случае она как раз и не нужна. - -Исправим регэксп для поиска тега с атрибутами `pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`, описанный в начале главы. Используем предпросмотр, чтобы запретить откат на меньшее количество пар `атрибут=значение`: +Let's fix the regexp for a tag with attributes from the beginning of the chapter`pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`. We'll use lookahead to prevent backtracking of `name=value` pairs: ```js run -// регэксп для пары атрибут=значение -let attr = /(\s*\w+=(\w+|"[^"]*")\s*)/ +// regexp to search name=value +let attrReg = /(\s*\w+=(\w+|"[^"]*")\s*)/ -// используем его внутри регэкспа для тега -let reg = new RegExp('<\\w+(?=(' + attr.source + '*))\\1>', 'g'); +// use it inside the regexp for tag +let reg = new RegExp('<\\w+(?=(' + attrReg.source + '*))\\1>', 'g'); let good = '...... ...'; -let bad = ", -alert( bad.match(reg) ); // null (нет результатов, быстро) +alert( bad.match(reg) ); // null (no results, fast!) ``` -Отлично, всё работает! Нашло как длинный тег `match:`, так и одинокий `match:`. +Great, it works! We found a long tag `match:` and a small one `match:` and didn't hang the engine. + +Please note the `attrReg.source` property. `RegExp` objects provide access to their source string in it. That's convenient when we want to insert one regexp into another. diff --git a/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png b/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png deleted file mode 100644 index 8f207c4d06a706af96c42287d48882cf83a5645e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2357 zcmV-53Ci|~P)P(WY+-|yez;^O}$B{-Zo_xty-n>_vMEvuVs#k-@CensWb zF{qeir_-mhq4Lg-NNBKHkJBzO$Igz@y;X)X=>- z>)AA|pMLq`A-S-a(8;-2tXQm|fvAuaChY`Q|3m&B4~u#H5o|(7iC~=i-8L7~|Xpk!JDl>zs^SyQw0CcIaB$dQU>g7czrVjQFfiZW-zNY79==-o000I2Nkl;HY=sD!rHF5t_&-!EAyD4V`gS%W@ct)W@d(eCi!ejvVE7ly|gdd z+wZ;0MLNOnqu=lMl>lfT8v_1QRvH(Yo5w5V=1B%{l9Gp~RBV@Uf8`I4^f+-ZJn>{n za;Wq;UU+&?5hyK@9%pEcBLY3#5P>1_czkE_iOY#o?t~(q@@l6%l}aVaCnafkdejIw zk|$d}Jg#8ZB&EjzCFN8Rws3S|W8pmZ><2$CUHbf8mmbpYOV>~tNF_^Cr6rZgIK?X6 z&@@jyJ@4CPi@tj2nR(}LwPC^oD&v*qU;`^aCja&13k!bu{+sTft$2UYBNw+jPt8Vp zc$W3<+qV^n0KE8|u5y@41{rhvE!)`E5yWxM7*95de(dy=!B`=@SRG zTrqKd*T%*HufHPiot?{oJ@)QgSJ!!)&d#T3}p`gJ3AbGLru zj=S-3Dg}c($zX#SO>GtZ+f)Ub_Bv>6|Ngg5oi=Ui!mIm_J^Xsw-${yy_+I`7YWrsT z)DEkv1=}7t;hG!nxO3)=88dIY?%Hz>JK`q!a4JZ$mG$XMuO2XR{DjLUJv@E(?CJMU zI^*O~K)UGsy0W5a6??OdJodD!Cf_@2)^dMvB9@>h%we# zJFSp5hS|Wh1ca8~uwdR4euQSlxE=+<&>)j;1xL^{cwUV7o)~tGEv?Y4AiuJ+IY!*6B=SA5O)zP)SPb(x8m4Qh z7_l?cobyLm<7ZO8c%0DrDF~Wc#FmObRLw7$MVS4jSfw?hcxl~?{ZMYV-x)E~J+X@!LQOVE6aUj+r zadehRx_e~U8)|r)GGzBZ10&VXN7sx(5sJhKL`N0)AhwI+*oCqkGfXXCBrFja4zh(F z6SG3KbZQDJa9q(iT>3ikC5mGJ(R&ex6s%(Lxu#Irf_mZLI9|d8WCy2ttiDH<^COiE zB#363ukF@x#9X8C#hkA;h9H3BNEX5-#}_F{SU6OYL%fEPN`WM&)R>=1^xO=HpdCp5 z09`9MCc-w?B=IGx3pKWw8oP}X=W?gn6f2aYUKk7kM--U+EfY}(Oh*jlCWV45F4ujTYXeSXYy=$lcBhwqFoKDI z5Q;dWAJA=u!p!;3#DUMqqHi7%rvOb@y2Gn#oV%_cLa_vv@D>Oz#MenIKi}fI!Z$oO zl^8||UANdbWJYb@zK)A{Em8DN0IIM5j*JbYb|$`*;Q;tb)&IGbZ74TQUiE@KL} zhTw59I*xhKQT-n8JXHF$*TcnUaebB|-lC2p3XECeUcm!Zbu!K5+qxAzHI0nXHR09} bJ!bX~{I$l4p1@;800000NkvXXu0mjf5J=fH diff --git a/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png b/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png deleted file mode 100644 index 713532aeabedbb9f44be5db8f094efbd2e1c32a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1119 zcmV-l1fctgP)~(pZ9ne( zTZTX2<65+ym&>l`-~TDw9ZB-@-+j=}#2@JIA3LGHzo#(V@1J|4PvUp?kH_u4H~Rad zhkuad?XolaXGfCHTi?3y(RJPB+I8LM9_V>{ko>ssGHVC%NBZmD=y`i4?8jwy^p6|K z_Z!KN>;AN7_ayn_+FiTnt$W|mOJNd!kFT@=f7kRp=nwjX{(q$Z{`SS}`>)WyeKk?v z4*G-spg-sj`h)(UKj;s7^4nLV|Mn~N$G?-mM<2tiJA_!YV+i3tB3O8ii>2b$;vgBV zApabV0AiZd(TD($(`>GaxYTgco5@FbllK^r#ULvH#7-27gKGVK}0B z%1!!KP-o=MnIdT|JZJPGBBsuRV`sH%EjmN`d;uUPRUt*C&_I^j8(41ATlAT}k;k_8v{J=P5`RpD z0NIqj20Ynj8983-X_&riE$Yv-&MFRB`_!RL#gApKoifim7)n!Ubyb4AbcnOEuo09xnwodAG| zFQVrK0Aw2D0EqF+F^03H04Nwn8}qbHJ!9Evy>o8miQR;$Fg4yeKTGxKUDcFt`DHRW l=d=B_6GtY0>844n!h5u1e%*_8hJpYA-{{R60{{R3bCI3-T|D&V-=H~y|+5f-4|NqR)l$8H;bpQWj zWB)un^Yj1zqoV+T0RQIZ|G&SnvH$>{_QIA-!b&uA^-9#_TC`>@ht4>=K19(`{yS7>MH;KEdTN>-pwcZ=PQwU z5dX2UaJX>N%)gn1L-+Uh`sgeF?kk*(V8Xhi*u^m6+|csY8MdV#XlQ8f@9+KYD?mU% z+;DLJ^)0jj09{TJ|M4iOnI7)jDa5?1|M)KVIGi}t&Aq0U zUHs}Tr_-nU`S|$cC8Ur$+SJ6ZplzYvHJTU%R?$B)moG5O&i zn22HQ)FYRKOWW4X$-%11zMK5%C$5}Vq>)mdi$n0!8OFV=_24DQz^~EEy`hd$<(#*oxU|`wEH=>L__x1DK$|szN zM4N;;yRo3b1O!-ESf{6_aBy(l%2Lt1J=ee}@$c-anOf-7E%5E?#HS<0s5hyZWZ~V` z)WAHOiyWks9GHh0wW3VscGAjme@%gY`eB=nx>GpYq~P7EAz_C9rHS7W@ct)W@ct)X5MrxozJfQ z^^(}%xA%Sa`E;iz{od32O9o(bZ4?Vy$4WsvS(KheDix4ljwUJ%_iiJgcpuM1jPK-T z4__9cZY!c}`=rPMV#AxCBKkIL0#&p=5F2-r#j?rZfa0tl5Z8t&;@EImgbfVHO?{l> zBFQ!cV#~~-j7uaH7?7Q!2)-t{inaqPH3liQiZ}(zIF3t2@mDgCDvFzBgGC`34u_-o zlWc9GNSV9FgR3FCIyom_eOQ)F;ZL77Ns$c{4Gzfk#cfIyp1gC=pxdqrHbtclNm(jD zAR|Q_RZ(d`^WXg9gIDgm`S$D12oT8F1mpTwI2vSzjOn{IL|M-)Y(}%sg_<^CfUVCc5DQYzV zHTI$2EV5iT)5!%#vwzi8%{WPLXiTMTi5;j zkFTEf{+toZhR@lyrFwk7v(7G7AWIVVJfNj|*6_v=3$AX&K-F9K->yi3Kz(^#-zorh z-K$^o^b4mfd1l^}A(>3V z`mxEU9B~vVLZHjBU>{ZwKvj81u+MQ>F+*fxu<+KmYa5A3nV2f@47e0Z}|zmt57eQ_ZXU;NdSJ!>|}1G*O4(Wk5Lyv*C$ z>ubUPR`jOuo(7&*JTLHkYYok-bc>ocl#=J*3WFfoLy-`KNOH$f5pIB}#M2ESc~h;ArLmV7hBcq2>HP(r`S3S`VlP4_DkZbA?;F_1to;mIoR%+~a)2t>j1h6#|# z3y56C7)BH&N)rSI{A0Wm`eFm8j z%<%XmR&v7a);FROBv2l8N2)+Js`{bC6$Qr5pg;~L!R#BYcV-M3GxBI)*Sk?LdWFy@ z0}@)aGD8CiRAenRQ0N`b$eiXf%bwlKZ%fGz#Q4AOOii`6d8ZLNUN`#CF!b=C1@H|!phTI?UQZ511Ud7Ag z;P|(BQV3ax002ovPDHLkV1ksJ4b1=m diff --git a/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png b/10-regular-expressions-javascript/15-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png deleted file mode 100644 index 8d0cd5221fb120c82386dbd4f79c771dc6a23875..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2505 zcmV;)2{!hLP)nAQBhI<%*_8hJpYA-{{R60{{R3bCIA10h5yXV+1dZUzyIdu|D&V-l$8H;bpQW6 zJpbnA^Yi}zfB^r$zyD)n|8#W!l$5ct|No<-|0N~=@hg0Me50eI&(F{M=_>!(*}wn* z&*#to?JV=p&unaLy1KfquCC(Z;_vtGK%hYX_$~i1F#q%O{~#dQ+1dH!C;8+i&_F=@ z@9&Fr5Q>V5?Ck9Q>?_d8yZ-Ge|L!a9?C4QJ5wV{i-OMNd^DY1BB7|}k|FNMn4& zaHyGG_xJb3y`}NpGV|Oa@$c;a@G1T6EAQ{`$iTDq-XNlq9Y8=p+;DKT0095>EnZF& z{O2e3+8h7!DgXW}?b|5#{2|Nk$SgBGu!9{=hhy`eAa z#|!h&5sGpUP^eIrmX<(Z0K~+^v!irdTU)4@UDC_C+Qu>Z`S-7(Z>5ns*wMk!%Dvpl zHk*b;`QaeYw=$TBWBTSM+0)7F)FPOKOZVU)-|ye>-!aR;oUNQ&{OKn!m@u4*M)u() zrISN#F>Obp^Z^!XlReekG-{?5Qq@LxT5#__gJi0zu&*A zo_p=<b{Vwj@DB|AI*~d4ejXm_; zA@bH5z_+NKh)DPL^qPY^!2|?YSXkM~Cvb3ZX=!Pvr>DlLBkAVhshDEOsyFi17|pmo z)V(RRp-tw}E%Wj2+sIYFx1_F^Pp6MPtD7C1iyF|pJ?qL7|K}q0^YPZXA*qZD#jR1t zrx_an0Jx@P=HuSMq#MYqEif=J{qQWmzrXb2C$Xg<=+-bN007_L-`U6~zO|p<&L_B~ zQoOO8>fS2OyG-)oD58oI+_?_>=qSjxX5-H-*TpWWm>c}+E2x_v|LZJKKoO069_r5; z!l_8&!xPD>7Os&B;k^y{)DZR25BAa#hjbNyZWZjv3Y2{kfa5iN000JrNklqKBaGNb%DQi=vb;Xs}b;Xro%-k_EGcz+YGcz+Y^LEl%(%o6M z>n{%IeP870{O-N`AN=^8m4~qme){j0P)Mqm`Y9hjk4~ zB1N$p@Oq6_bOs<#*THsHNFwfGv?SOWcwE;}v(AfyCu&D2ng|5SIu(!Ox-<|hjl_$C z$Bt4-0DqPRBAti_Bx(tqGK(H6i-RYcAZ%kXcUh&$aU~U{__1+^9e|^WJ>o!TWAFzWsPAf<@

gs__Jps7tfww>XZo!Ig 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