Archive for the 'Ruby' Category



Regex syntax and semantics varies

Regex engines do differ in syntax and semantics. This is one reason why you can’t just find an expression with Google and use it in your code – without fully understand it.

Try for example the following in JIRB, the interactive JRuby tool:

irb(main):001:0> require 'java'
=> true
irb(main):002:0> java.util.regex.Pattern.compile("a$").matcher("a\nb").find
=> false
irb(main):003:0> "a\nb"[/a$/]
=> "a"

What happened?

The regex /a$/ matches the letter a just before something checked by a dollar sign assertion. The assert criteria is, by default, not the same in Ruby and Java. In Java, by default, the dollar sign matches at the end of the whole text and before any final line breaks. In Ruby, the dollar sign match at the end of every single line.

Here’s what the three lines of code above means:

  1. First, we need to include the Java libraries by writing require 'java'. This might not be necessary, depending on your setup.
  2. We compile a Java regex and test if part of the string of ‘a’ and ‘b’ with newline in-between can be matched. It can’t.
  3. We compile the same regex in Ruby and test if part of the string of ‘a’ and ‘b’ with newline in-between can be matched. It can.

This is just one of many examples. If you are going to use regexes in your program, you need to understand them. It’s as simple as that.

Pomodoro Technique Illustrated -- New book from The Pragmatic Programmers, LLC

Regex is not Regular

Regular expressions span exactly the same languages as the mathematical construction called Finite atomata. With the Pumping lemma, we can prove that it’s impossible to create regular expressions for many problems, e.g.:

  1. All palindromes
  2. All text strings that doesn’t consist of a prime number of identical characters

Since Regular expressions have become popular as extensions to imperative programming languages, new features have been added. Languages from Context-free grammars and Pushdown automata (essentially a finite automata with an attached stack), can be matched with these features. Regular expressions are Type-3 and Context-free grammars are type-2 in the Chomsky hierarchy.

To not be confused we call these built-in languages that are more than Type-3, Regex. Regexes are more powerful than Regular expressions.

Non regular prime number finder

With the (non regular) feature back-reference, it’s possible to create a regex that only match strings with a prime number of identical characters:

irb(main):001:0> r = /^.?$|^((.)\2+?)\1+$/
=> /^.?$|^((.)\2+?)\1+$/
irb(main):002:0> r.match "22"
=> nil
irb(main):003:0> r.match "333"
=> nil
irb(main):004:0> r.match "4444"
=> #<MatchData "4444" 1:"44" 2:"4">
irb(main):005:0> r.match "55555"
=> nil
irb(main):006:0> r.match "tttttt"
=> #<MatchData "tttttt" 1:"tt" 2:"t">

Non regular palindrome finder

If we also add the (non regular) feature recursion, we can match all palindromes:

irb(main):001:0> p =
/\A(?<palindrome>|.|(?:(?<prefix>.)\g<palindrome>\k<prefix+0>))\z/
=> /\A(?<palindrome>|.|(?:(?<prefix>.)\g<palindrome>\k<prefix+0>))\z/
irb(main):002:0> p.match "otto"
=> #<MatchData "otto" palindrome:"otto" prefix:"t">
irb(main):003:0> p.match "dallas sallad"
=> #<MatchData "dallas sallad" palindrome:"dallas sallad" prefix:"s">
irb(main):004:0> p.match "rais air"
=> nil
irb(main):005:0> p.match "1010"
=> nil

Recursive Regexes

Oniguruma supports recursive regular expressions. They can e.g. be used for matching a generative grammar like the following:

  1. expression -> expression + term
  2. expression -> expression - term
  3. expression -> term
  4. term -> ( expression )
  5. term -> digit

I wrote the following regex that corresponds to the grammar above:

  • /^(?<expression>(?<term>\d|\(\g<expression>\))([-+]\g<expression>)?)$/

Note that \g<expression> is invoked recursively – like rule number 4 in the grammar. I chose to only allow single digit numbers to make the expression crisper as example. This could of course easily be changed to general integers, floats or imaginary numbers.

Here goes some testing:

irb(main):001:0> r = /^(?<expression>(?<term>\d|\(\g<expression>\))([-+]\g<expression>)?)$/
=> /^(?<expression>(?<term>\d|\(\g<expression>\))([-+]\g<expression>)?)$/
irb(main):002:0> "2+4"[r]
=> "2+4"
irb(main):003:0> "2+45"[r]
=> nil
irb(main):004:0> "2-3(4+3)"[r]
=> nil
irb(main):005:0> "2-3(+3)"[r]
=> nil
irb(main):006:0> "2-31(+3)"[r]
=> nil
irb(main):007:0> "2-3+(+3)"[r]
=> nil
irb(main):008:0> "2-3+(4+3)"[r]
=> "2-3+(4+3)"

Expressions that return nil are obviously not correct.

Pomodoro Technique Illustrated -- New book from The Pragmatic Programmers, LLC

Regex extension — here’s the Integer Class

The IP address problem is well known in the regex community. Here I present an extended regex syntax that would make it possible to match IP addresses in a less chatty way.

Ip-address problem

Let’s say I want to validate an IP address. It is four integers with interleaved dots such as 123.125.126.107. The naïve regex would be:

  • \d*\.\d*\.\d*\.\d*

But it is so imprecise that it matches both 9999.9999.9999.9999 and . Since the four integers in a IP address must be in the range 0-255, I can be certain that they consist of one, two, or three digits. With the Limiting Repetition operator, I can formulate this requirement:

  • \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}

Now 0.1.2.3 and 101.0.202.1 matches, while 9999.9999.9999.9999 doesn’t match because there are too many digits in the integers. Unfortunately 999.888.777.666 matches as well. That’s not an acceptable IP address. I told you that the integers can be up to 255. But, don’t give up. In Friedl’s seminal book “Mastering Regular Expressions”, he describes two ways to shrink the match set. Both are, however, very chatty. The first way is to list all authorized integers in a super chubby alternation:

  • (0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20
    |21|22|23|24|25|26|27|28|29|30|31|32|33|34|35
    |36|37|38|39|40|41|42|43|44|45|46|47|48|49|50
    |51|52|53|54|55|56|57|58|59|60|61|62|63|64|65
    |66|67|68|69|70|71|72|73|74|75|76|77|78|79|80
    |81|82|83|84|85|86|87|88|89|90|91|92|93|94|95
    |96|97|98|99|100|101|102|103|104|105|106|107
    |108|109|110|111|112|113|114|115|116|117|118
    |119|120|121|122|123|124|125|126|127|128|129
    |130|131|132|133|134|135|136|137|138|139|140
    |141|142|143|144|145|146|147|148|149|150|151
    |152|153|154|155|156|157|158|159|160|161|162
    |163|164|165|166|167|168|169|170|171|172|173
    |174|175|176|177|178|179|180|181|182|183|184
    |185|186|187|188|189|190|191|192|193|194|195
    |196|197|198|199|200|201|202|203|204|205|206
    |207|208|209|210|211|212|213|214|215|216|217
    |218|219|220|221|222|223|224|225|226|227|228
    |229|230|231|232|233|234|235|236|237|238|239
    |240|241|242|243|244|245|246|247|248|249|250
    |251|252|253|254|255)

Note that the above is just one integer in the IP address. I have to write the super chubby alternation four times with interleaved dots to get the correct regex.

The second way to specify an integer in the range 0-255 is to decompose the problem into sub problems based on the initial character. If the first digit is 0 or 1, then all integers consisting of 1-3 digits are acceptable – I also allow leading zeros as in e.g. 054. When the initial digit is 2 and the second number is in the range 0-4, then I accept digits in the ranges 20-24 and 200-249. Finally, if the integer starts with 25, then I only tolerate it if it’s followed by a digit in the range 0-5. Like this:

  • [01]?\d\d?|2[0-4]\d|25[0-5]

(Does this regex really match 25? The third part of this alternation only matches integers in the range 250-255. Yes, it does. The first part matches any integer consisting of one or two digits.)

Again I must rewrite my regex four times with interleaved dots to match an entire IP address.

Integer Class – a new operator suggested

So far I’ve talked about how regex works right now. Let’s imagine now that I can add a new operator. I call the new operator Integer Class.

Both regexes above solve the problem, but they demand so many characters that it reminds me of chatter from a group of monkeys (i.e. not easy to understand). My proposal is to extend the regex syntax with a new operator: Integer Class. It matches integers of any length, if they are in a specific range. For IP numbers — which should be in the range 0-255 — it would look like this:

  • [0..255]

Square brackets surrounds two integers – a lower and an upper limit. There are double dots in-between the integers. Integer Class would work almost as a syntactic sugar for the super chubby alternation above — but not quite. Here are some details:

  • Backwards compability: most regex interpreters permits Character Classes with repeated characters. A regex [0..255] is syntactically correct already today. But now it means something entirely different than what I want. The regex interpreter doesn’t care about the double fives and the double dots. Right now, [0..255] is a redundant way of writing [0.25], i.e. it matches exactly one character and it must be either 0, dot, 2 or 5. With my syntax and semantics, the regex interpreter would notice the double dots and say “Hey, this isn’t a Character Class, because it’s an Integer Class.” How cool is that?
  • Leading Zeroes: An Integer Class matches leading zeros in the candidate, but only if you specify it. How? Well, by writing a zero in front of the lower limit. For example: [00..255] means that even sequences like 012, 0004 and 00255 are appropriate integers.
  • Negative integers: Of course, negative integers are permitted. For example, [-1..1] matches -1, 0 or 1. Sidenote: a leading dash in a Character Class means that dashes are allowed. It may sound trivial, but then you should know that in a Character Class [A-Z], the dash means from/to – the range A to Z. A dash in a Character Class has a different meaning depending of if it’s in the beginning or the middle of an expression. Anyway, if there is a double point, it is an Integer Class and then dash means minus.
  • Greedy: Like many other regex operators – such as repetition – the Integer Class is greedy of type longest-leftmost-match, but charity obedient. This means that [0..255] rather match 255 than 2 in candidate 255. But the regex engine is prepared to release the fives if it means the whole expression match. This differs from the super chubby alternation above. At least a Traditional NFA engine often matches the first one it finds, i.e. 2 rather than 255.
  • Meta Characters inside Integer class: There’s no need for rules to escape characters since only digits, double dots and dashes are permitted in an Integer class. Backslash is not allowed to reside in an Integer Class.
  • Negated Integer Class: A caret ^ after the right square bracket will negate the Integer Class. Any integer except those stated in the Integer Class will be matched. E.g. [^5..7] match any integer except 5, 6 and 7. Note that a negated Integer Class still must match an integer. Match exactly one integer, but not 5, 6 or 7.
  • Not accepting nothing: A Character Class doesn’t match nothing – the empty string. The same is true for an Integer Class. There must be an integer matched in the range. The exception is, of course, when an Integer Class is followed by * or ? — for example: [0..255]*

With the suggested Integer Class, an IP address can be matched with this regex:

  • ([0..255]\.){3}[0..255]

Relevant questions:

  • Is this syntax possible or would it be ambiguous?2
  • There are many possible operators to add to the regex syntax – is Integer Class the most needed?
  • Should the Integer Class be even more capable, e.g. be able to match floats?

Regex process: Copy-Paste-Generalize

Regular Expressions is a flexible tool for matching strings of text. They can be really crisp and elegant, but how can you design them? Below is a design process.

Name

Copy-Paste-Generalize

Intent

Go from an idea to a flexible and generalized regular expression.

Applicability

You have a text example and you know what you want to extract. But, of course, you want your regular expression to be generalized enough to match other candidates.

Consequences

The Copy-Paste-Generalize pattern is easy to get you started and then you can develop your regular expression in an iterative and structured way.

Mechanics

  1. Copy a text example
  2. Paste it as your initial regular expression
  3. Generelaize the expression step by step until it matches any possible candidate

Other Names

This process has many names. E.g. Mehran Habibi call something similar “The Pull Technique” in his book Java Regular Expressions.

Example

Pomodoro Technique Illustrated info page at Amazon.com

Pomodoro Technique Illustrated info page at Amazon.com

Amazon.com presents a sale rank of all books. The list is updated frequently and every book’s current rank can be found at the book’s info page. Suppose I want to match the current rank for my book Pomodoro Technique Illustrated. First I download an example text: the current page at Amazon:

  • lynx -dump http://www.amazon.com/Pomodoro-Technique-Illustrated-Minutes-Pragmatic/dp/1934356506

I use Lynx (btw: by far the greatest web browser the world has ever seen) in CLI mode. The result is a rain of lines from Pomodoro Technique Illustrated‘s info page at Amazon. I better grep something to make my example text smaller:

  • lynx -dump http://www.amazon.com/Pomodoro-Technique-Illustrated-Minutes-Pragmatic/dp/1934356506|grep -i rank

The result is:

  • * Amazon Bestsellers Rank: #23,032 in Books ([89]See Top 100 in

Great! This includes what I want to match. What I’ve done so far is the Copy part of this process. Next goes the Paste. I add an extremely simple Regular Expression and put it in a Ruby one-liner:

  • lynx -dump http://www.amazon.com/Pomodoro-Technique-Illustrated-Minutes-Pragmatic/dp/1934356506 | ruby -pe 'puts $1 if $_[/Bestsellers Rank: #(23,032) in Books/]; next'

As a matter of fact, the expression that resides between the dashes is just a Paste of what I got from the Lynx dump. And the result of this line is:

  • 23,032

…and that’s because of the parenthesis.

Copy done. Paste Done. Let’s start to generalize. Next time I run this, I might get another rank than 23,032. Digits can be captured with the meta sequence \d. This implies the next iteration:

  • lynx -dump http://www.amazon.com/Pomodoro-Technique-Illustrated-Minutes-Pragmatic/dp/1934356506 | ruby -pe 'puts $1 if $_[/Bestsellers Rank: #(\d\d,\d\d\d) in Books/]; next'

Instead of cascading the \d, I can use the limiting repetition operator:

  • lynx -dump http://www.amazon.com/Pomodoro-Technique-Illustrated-Minutes-Pragmatic/dp/1934356506 | ruby -pe 'puts $1 if $_[/Bestsellers Rank: #(\d{1,3},\d{3}) in Books/]; next'

The text in between “Rank” and the number may change. It would be more robust to describe it as non-digits:

  • lynx -dump http://www.amazon.com/Pomodoro-Technique-Illustrated-Minutes-Pragmatic/dp/1934356506 | ruby -pe 'puts $1 if $_[/Bestsellers Rank[^\d]*(\d{1,3},\d{3}) in Books/]; next'

This expression will only work when the rank is between 1,000 and 999,999. Just in case this book gets extremely popular, let’s generalize the number part:

  • lynx -dump http://www.amazon.com/Pomodoro-Technique-Illustrated-Minutes-Pragmatic/dp/1934356506 | ruby -pe 'puts $1 if $_[/Bestsellers Rank[^\d]*([\d,]*)/]; next'

The expression has become pretty compact and robust. I stop here:

  • Bestsellers Rank[^\d]*([\d,]*)

Challenge

Even though it’s only a example above, you may know how to make the Regular Expression or the Ruby/Bash code even more crisp. If you do, feel free to append a comment below.

Pomodoro Technique Illustrated -- New book from The Pragmatic Programmers, LLC

« Previous Page



Follow

Get every new post delivered to your Inbox.