#!/usr/bin/ruby # ## https://rosettacode.org/wiki/Free_polyominoes_enumeration # func translate2origin(poly) { # Finds the min x and y coordiate of a Polyomino. var minx = poly.map(:head).min var miny = poly.map(:tail).min poly.map_2d {|x,y| [x-minx, y-miny] }.sort } func rotate90(x,y) { [y, -x] } func reflect(x,y) { [-x, y] } # All the plane symmetries of a rectangular region. func rotations_and_reflections(poly) { gather { take(poly) take(poly.map_2d!{|x,y| rotate90(x,y) }) take(poly.map_2d!{|x,y| rotate90(x,y) }) take(poly.map_2d!{|x,y| rotate90(x,y) }) take(poly.map_2d!{|x,y| reflect(x,y) }) take(poly.map_2d!{|x,y| rotate90(x,y) }) take(poly.map_2d!{|x,y| rotate90(x,y) }) take(poly.map_2d!{|x,y| rotate90(x,y) }) } } func canonical(poly) { rotations_and_reflections(poly).map(translate2origin) } # All four points in Von Neumann neighborhood. func contiguous(x, y) { [[x-1, y], [x+1, y], [x, y-1], [x, y+1]] } # Finds all distinct points that can be added to a Polyomino. func new_points(poly) { var points = Set() poly.each_2d {|x,y| points << contiguous(x,y)... } points - poly } func new_polys(polys) { var pattern = Bag() polys.map { |poly| gather { new_points(poly).each_2d { |x,y| var pl = translate2origin(poly + [[x,y]]) next if pattern.has(pl) take(var t = canonical(pl) -> min) pattern << t... } }... } } # Generates polyominoes of rank n recursively. func rank(n) { given (n) { when (0) { [[]] } when (1) { [[[0,0]]] } else { new_polys(rank(n-1)) } } } # Generates a textual representation of a Polyomino. func text_representation(poly) { var table = Hash() poly.each_2d {|x,y| table{[x,y]} = '#' } var maxx = poly.map(:head).max var maxy = poly.map(:tail).max (0..maxx).map{|x| (0..maxy).map{|y| table{[x,y]} \\ ' ' }.join } } var arr = 5.of { rank(_).len } say arr assert_eq(arr, [1, 1, 1, 2, 5]) var n = (ARGV[0] ? ARGV[0].to_i : 4) say ("\nAll free polyominoes of rank %d:" % n) rank(n).sort.each{|poly| say text_representation(poly).join("\n")+"\n" }