www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - prefix match of a regex and optimized dirEntries for regex search

reply Timothee Cour via Digitalmars-d <digitalmars-d puremagic.com> writes:
I made a simple modification to std.regex to allow an option to prefix
match a regex.
Formally, if L(R) is the language recognized by a regex R, the language
recognized by prefix matching of R  is:

L(p(R)) = prefix(L(R)) = {u : uv in L(R) for some v}

Trying to come up (by hand or algorithmically) with a regex R' such that
L(R') L(p(R)) is awkward and inefficient, eg:

R='hello';
R'=`|h|he|hell|hello` = `(h(e(l(l(o)?)?)?)?)?`;

However thinking in terms of state machine this is much easier and
efficient.

It looks like this:
assert("hel".match(`hello\d+`.regex("p")); //p for prefix match

If there's interest in adding this I can prepare a pull request and we can
discuss more.

Example use case:
I wrote a function to search a file given a regex, and it is optimized to
prune directories early on if they fail to prefix match the regex, eg:

dirEntriesOptimized(`abc/folder_\d+/\w+\.cpp`)
when encountering `abc/bad_subfolder/` it will not recurse on this as it
fails the prefix regex match.
Jun 18 2014
parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
18-Jun-2014 21:38, Timothee Cour via Digitalmars-d пишет:
 I made a simple modification to std.regex to allow an option to prefix
 match a regex.
 Formally, if L(R) is the language recognized by a regex R, the language
 recognized by prefix matching of R  is:

 L(p(R)) = prefix(L(R)) = {u : uv in L(R) for some v}
 Trying to come up (by hand or algorithmically) with a regex R' such that
 L(R') L(p(R)) is awkward and inefficient, eg:

So essentially it's just a set of all partial matches?
 R='hello';
 R'=`|h|he|hell|hello` = `(h(e(l(l(o)?)?)?)?)?`;

 However thinking in terms of state machine this is much easier and
 efficient.

 It looks like this:
 assert("hel".match(`hello\d+`.regex("p")); //p for prefix match

Well, since "p" would apply to operation rather then regex I think adding a partialMatch function would be simpler. Plus it may need to return extended information like where in the pattern it stopped, so the return type/API may need to be different from normal match.
 If there's interest in adding this I can prepare a pull request and we
 can discuss more.

Sure partial matching wold be nice to have and any help in this direction is warmly welcome. Would be cool if you could take a look at https://github.com/D-Programming-Language/phobos/pull/2216 It's nothing special except splitting up std.regex into a package but it's a stepping stone of going forward with it.
 Example use case:
 I wrote a function to search a file given a regex, and it is optimized
 to prune directories early on if they fail to prefix match the regex, eg:

I'm not sure I follow - a normal regex match would do essentially the same by first checking the prefix and failing on wrong ones. Where is optimization? What could be interesting is to limit matching to not go beyond say 80 code units and report if it's (partial) match of given pattern.
 dirEntriesOptimized(`abc/folder_\d+/\w+\.cpp`)
 when encountering `abc/bad_subfolder/` it will not recurse on this as it
 fails the prefix regex match.

-- Dmitry Olshansky
Jun 18 2014
parent reply Timothee Cour via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Wed, Jun 18, 2014 at 12:37 PM, Dmitry Olshansky via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 18-Jun-2014 21:38, Timothee Cour via Digitalmars-d =D0=BF=D0=B8=D1=88=D0=

=B5=D1=82:
  I made a simple modification to std.regex to allow an option to prefix
 match a regex.
 Formally, if L(R) is the language recognized by a regex R, the language
 recognized by prefix matching of R  is:

 L(p(R)) =3D prefix(L(R)) =3D {u : uv in L(R) for some v}
 Trying to come up (by hand or algorithmically) with a regex R' such that
 L(R') L(p(R)) is awkward and inefficient, eg:

  So essentially it's just a set of all partial matches?


Not sure, that depends on your definition of partial matches; how do you formally define partial matches? There's a subtlety, let's call my proposal prefixMatch: "hel".prefixMatch("^hello") //true "".prefixMatch("^hello") //true "mel".prefixMatch("^hello") //false But if the regex doesn't start with `^` then it is not useful: "whatever".prefixMatch("hello") //true because the scanner will try each position in the input string and fail, except for the last one where it'll succeed trivially ("" is a prefix of "hello"). In other words, this is more useful for one shot matching. But we can also consider partial partial matches if we provide additional constraints given via a lambda: "blabla hel blabla".partialMatch!(a=3D>a.hit.length>=3D3) ("hello") =3D> finds a match ("hel") because the lambda succeeds on this match. partialMatch could also be used to find the longest partial match (either in terms of length of matching string or length of the regex): auto longestPartialMatch(R)(string input, R regex){ string longest; while(true){ auto m=3DpartialMatch!(a=3D>a.hit.length>longest.length) (input,regex); if(m) longest=3Da.hit; else return longest; } This could be useful for debugging complex regex to see why it didn't capture a given string for example.
  R=3D'hello';
 R'=3D`|h|he|hell|hello` =3D `(h(e(l(l(o)?)?)?)?)?`;

 However thinking in terms of state machine this is much easier and
 efficient.

 It looks like this:
 assert("hel".match(`hello\d+`.regex("p")); //p for prefix match

Well, since "p" would apply to operation rather then regex I think adding a partialMatch function would be simpler.

 Plus it may need to return extended information like where in the pattern
 it stopped, so the return type/API may need to be different from normal
 match.

Good point, I agree.
  If there's interest in adding this I can prepare a pull request and we
 can discuss more.

  Sure partial matching wold be nice to have and any help in this

direction is warmly welcome.

ok, I wanted to make sure there was interest.
 Would be cool if you could take a look at
 https://github.com/D-Programming-Language/phobos/pull/2216

 It's nothing special except splitting up std.regex into a package but it'=

s
 a stepping stone of going forward with it.

That's a very welcome change, this module is too big currently. That seems like it would be the 1st phobos module to take advantage of package feature?
  Example use case:
 I wrote a function to search a file given a regex, and it is optimized
 to prune directories early on if they fail to prefix match the regex, eg=


:

I'm not sure I follow - a normal regex match would do essentially the sam=

e
 by first checking the prefix and failing on wrong ones. Where is
 optimization?

The optimization is avoiding unnecessary file system access by pruning early on directories that we can prove will not contain any file which will match the regex. Without optimization, you have to consider (and reject) `abc/bad_subfolder/file_1`. With optimization, you never have to consider that file (nor ask the file system to list it), as `abc/bad_subfolder/` fails the prefix regex test (see my example). A normal regex needs to be applied to the whole path, and you need prefix matching to reject partial paths (ie, directories).
 What could be interesting is to limit matching to not go beyond say 80
 code units and report if it's (partial) match of given pattern.

That'd be nice; it needs to be as generic as possible; I propose using a user defined lambda (see above).
 --
 Dmitry Olshansky

Jun 19 2014
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
19-Jun-2014 11:36, Timothee Cour via Digitalmars-d пишет:
 On Wed, Jun 18, 2014 at 12:37 PM, Dmitry Olshansky via Digitalmars-d
 <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>> wrote:

     18-Jun-2014 21:38, Timothee Cour via Digitalmars-d пишет:

         I made a simple modification to std.regex to allow an option to
         prefix
         match a regex.
         Formally, if L(R) is the language recognized by a regex R, the
         language
         recognized by prefix matching of R  is:

         L(p(R)) = prefix(L(R)) = {u : uv in L(R) for some v}
         Trying to come up (by hand or algorithmically) with a regex R'
         such that
         L(R') L(p(R)) is awkward and inefficient, eg:

     So essentially it's just a set of all partial matches?


 Not sure, that depends on your definition of partial matches; how do you
 formally define partial matches?

 There's a subtlety, let's call my proposal prefixMatch:
 "hel".prefixMatch("^hello") //true
 "".prefixMatch("^hello") //true
 "mel".prefixMatch("^hello") //false

 But if the regex doesn't start with `^` then it is not useful:

 "whatever".prefixMatch("hello") //true
 because the scanner will try each position in the input string and fail,
 except for the last one where it'll succeed trivially ("" is a prefix of
 "hello").

Well, I think partialMatch is only defined in the current position. Unlike normal match we can't provide default meaningful action e.g. if a trivial "" result means search next or just report trivial partial match.
 In other words, this is more useful for one shot matching.

Yes, it makes that much more sense for one shot matching IMO.
 But we can also consider partial partial matches if we provide
 additional constraints given via a lambda:

 "blabla hel blabla".partialMatch!(a=>a.hit.length>=3) ("hello")
 => finds a match ("hel") because the lambda succeeds on this match.

 partialMatch could also be used to find the longest partial match
 (either in terms of length of matching string or length of the regex):

 auto longestPartialMatch(R)(string input, R regex){
 string longest;
 while(true){
 auto m=partialMatch!(a=>a.hit.length>longest.length) (input,regex);
 if(m) longest=a.hit; else return longest;
 }

Seems doable as helpers on top, but this kind of thing could be made faster if built-in in std.regex like you suggest. I also can't see anything better then lambda at the moment.
     Would be cool if you could take a look at
     https://github.com/D-__Programming-Language/phobos/__pull/2216
     <https://github.com/D-Programming-Language/phobos/pull/2216>

     It's nothing special except splitting up std.regex into a package
     but it's a stepping stone of going forward with it.


 That's a very welcome change, this module is too big currently. That
 seems like it would be the 1st phobos module to take advantage of
 package feature?

There is competition of std.container! https://github.com/D-Programming-Language/phobos/pull/2174 Truth be told it's the most obvious candidate, as containers have nothing to do with each other.
         Example use case:
         I wrote a function to search a file given a regex, and it is
         optimized
         to prune directories early on if they fail to prefix match the
         regex, eg:


     I'm not sure I follow - a normal regex match would do essentially
     the same by first checking the prefix and failing on wrong ones.
     Where is optimization?


 The optimization is avoiding unnecessary file system access by pruning
 early on directories that we can prove will not contain any file which
 will match the regex. Without optimization, you have to consider (and
 reject) `abc/bad_subfolder/file_1`. With optimization, you never have to
 consider that file (nor ask the file system to list it), as
 `abc/bad_subfolder/` fails the prefix regex test (see my example). A
 normal regex needs to be applied to the whole path, and you need prefix
 matching to reject partial paths (ie, directories).

Now I see - you don't have the full input while traversing directories, cool idea.
     What could be interesting is to limit matching to not go beyond say
     80 code units and report if it's (partial) match of given pattern.


 That'd be nice; it needs to be as generic as possible; I propose using a
 user defined lambda (see above).

Well, why not but I'd suggest to focus on getting the partialMatch at the current position as a good generic building block to extend from. -- Dmitry Olshansky
Jun 19 2014