1

Conditions updated

There is often a situation where you want to extract a substring upto (immediately before) certain characters. For example, suppose you have a text that:

  • Does not start with a semicolon or a period,
  • Contains several sentences,
  • Does not contain any "\n", and
  • Ends with a period,

and you want to extract the sequence from the start upto the closest semicolon or period. Two strategies come to mind:

  1. /[^;.]*/
  2. /.*?[;.]/

I do either of these quite randomly, with slight preference to the second strategy, and also see both ways in other people's code. Which is the better way? Is there a clear reason to prefer one over the other, or are there better ways? I personally feel, efficiency aside, that negating something (as with [^]) is conceptually more complex than not doing it. But efficiency may also be a good reason to chose one over the other.

sawa
  • 165,429
  • 45
  • 277
  • 381

5 Answers5

2

I came up with my answer. The two regexes in my question were actually not expressing the same thing. And the better approach depends on what you want.

  1. If you want a match up to and including a certain character, then using

    /.*?[;.]/

is simpler.

  1. If you want a match up to right before (excluding) a certain character, then you should use:

    /[^;.]*/

sawa
  • 165,429
  • 45
  • 277
  • 381
1

I personally prefer the first one because it does exactly as you would expect. Get all characters except ...

But it's mostly a matter of preference. There are nearly always multiple ways to write a regular expression and it's mostly style that matters.

For example... do you prefer [0-9], [:digit:] or \d? They all do exactly* the same.

* In case of unicode the [:digit:] and \d classes match some other characters too.

Wolph
  • 78,177
  • 11
  • 137
  • 148
  • In your example, I will prefer `[:digit:]` or `\d` because they are conceptually simple whereas `[0-9]` is more complex. – sawa Mar 27 '11 at 23:55
  • 1
    \d and [:digit:] are not the same as [0-9]. http://stackoverflow.com/questions/890686/should-i-use-d-or-0-9-to-match-digits-in-a-perl-regex this link talks about perl but it is true for most languages. – CrayonViolent Mar 28 '11 at 00:14
  • @Crayon Violent: you're right, in the case of unicode it can indeed be different. That was not my point however, my point was that regexes can usually be written in many different ways ;) – Wolph Mar 28 '11 at 02:27
1

Well, the first way is probably more efficient, not that it's likely to matter. By the way, \z in a character class does not mean "end of input"--in fact, it's a syntax error in every flavor I know of. /[^;.]*/ is all you need anyway.

Alan Moore
  • 73,866
  • 12
  • 100
  • 156
  • It works without error in ruby. But is not necessary in the first strategy, as you pointed out. You still need it in the second strategy though. I fixed it. – sawa Mar 27 '11 at 23:59
  • 1
    Ruby seems to ignore the backslash, treating `[\z]` the same as `[z]`. Your first regex will stop matching prematurely if there happens to be a `z` in the string, and the second regex fails to match at all if the string doesn't contain a `;`, `.`, or `z`. – Alan Moore Mar 28 '11 at 00:25
  • I see. I just confirmed what you wrote. Okay, so my example needs to be fixed. – sawa Mar 28 '11 at 00:29
0

I think that it is mostly a matter of opinion as to which regular expression you use. On the note of efficiency, though, I think that adding \A to the beginning of a regular expression in this case would make the process faster because well designed regular expression engines should only try to match once in that case. For example:

/\A[^.;]/m

Note the m option; it indicates that newline characters can also be matched. This is just a technicality I would add for generic examples, but may not apply to you.

Although adding more to the solution might be viewed as increasing complexity, it can also serve to clarify meaning.

Aaa
  • 1,854
  • 12
  • 18
0

you left out one other strategy. string split?

"my sentence; blahblah".split(/[;.]/,2)[0] 
kurumi
  • 25,121
  • 5
  • 44
  • 52