Enumerating IPs in a Regex
A recent weekly programming challenge involved enumerating possible IP addresses from a block of numbers. For example, if you give it 12345678, your response would be:
123.45.67.8
123.45.6.78
123.4.56.78
12.34.56.78
1.234.56.78
I was dissapointed to see that the perl answer provided in laurent_r’s blog post was so procedural compared to the raku version (in the same post). It seemed that perl’s regex engine should be up to the task of enumerating matches while checking that the IP components do not exceed 255.
So, with a little help from perldoc perlre, I present the following regex
magic:
#!/usr/bin/perl
$oct = qr!(0 | [1-9]\d{0,2}) (?(?{ $^N < 256 })|(*F))!x;
shift =~ m!^$oct $oct $oct $oct$ (?{ print "$1.$2.$3.$4\n" }) (*F)!x;
Much better (for some definitions of better)! That’s the entire program. The main ways the raku version still exceeds the perl one are:
- it’s less cryptic-looking
- I had to manually repeat
$octfour times, as quantifiers in perl forget the contents of the matches.
Postscript
I spent a few minutes staring at lua options, and it doesn’t appear their pattern matching is strong enough to help much in this case. So… this is what I came up with (~30 lines, and it could really use some comments if this weren’t a throw-away program):
#!/usr/bin/env lua
local ipstr, len = arg[1], #arg[1]+1
function octet(s,e)
if (e-1) < s then return nil end
local n = ipstr:sub(s,e-1) + 0
if (ipstr:sub(s,s) == '0' and (e-s > 1)) or (n > 255) then
return nil
end
return n
end
for e1 = 2,4 do
local n1 = octet(1,e1)
if not n1 then break end
for e2 = e1+1,e1+3 do
local n2 = octet(e1,e2)
if not n2 then break end
for e3 = e2+1,e2+3 do
local n3 = octet(e2,e3)
if not n3 then break end
local n4 = octet(e3,len)
if n4 then
print(string.format("%d.%d.%d.%d", n1, n2, n3, n4))
end
end
end
end
Which would you rather write?
Lua Golf
edit, next day: Here’s a recursive, 18-liner. The downside here is, now the number validation is mixed into the search function, but it turns out that really helps because it’s easier to arrange never asking for a substring that’s too long, and I can tell at the point I get a zero that I can break.
#!/usr/bin/env lua
function search_ips(ip, str)
if #ip == 4 then
if #str == 0 then print(table.concat(ip,'.')) end
else
local slot = #ip+1
for len = 1,math.min(3,#str) do
ip[slot] = tonumber(str:sub(1,len))
if (ip[slot] > 255) then break end
search_ips(ip, str:sub(len+1))
if ip[slot] == 0 then break end
end
ip[slot] = nil
end
end
search_ips({ }, arg[1])
Still, that’s 18 lines versus 2–and the 2-line version actually still has the octect validation split out!