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 $oct four 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!