Part One
第一关是找出导致镜像的镜子位置,下面的例子,镜子出现在5/6列之间。
#.##..##.
..#.##.#.
##......#
分两种情况考虑,行镜像以及列镜像
列镜像
先分别求出镜像的左侧字符串与右侧字符串,再对比reverse转换之后的字符串是否相等即可
SELECT blank._row, v.id,
case when v.id <= length(line) / 2 then substring(line from 1 for v.id) else substring(line from 2 * v.id - length(line) + 1 for length(line) - v.id) end as l_str,
case when v.id <= length(line) / 2 then substring(line from v.id + 1 for v.id) else substring(line from v.id + 1 for length(line)) end as r_str
FROM origin, blank,
(select id from seq limit (select max(length(line)) from origin)) v
WHERE origin._row > blank.pre_row
AND origin._row < blank._row
AND v.id < length(line)
) t
GROUP BY _row, id
HAVING COUNT(case when l_str = reverse(r_str) then 1 end) = COUNT(*)
行镜像
行镜像比较简单,仅需直接对比字符串即可,需要自关联找出镜像的那一行字符串。
SELECT blank._row, h.id - blank.pre_row as id, origin.line as up, mirror.line as down
FROM origin
JOIN blank
ON origin._row > blank.pre_row
AND origin._row < blank._row
JOIN (select id from seq limit (select max(_row) from origin)) h
ON h.id > blank.pre_row
AND h.id < blank._row - 1
AND origin._row <= h.id
LEFT JOIN origin as mirror
ON mirror._row = (case when 2 * h.id - origin._row + 1 < blank._row then 2 * h.id - origin._row + 1 end)
) t
GROUP BY _row, id
HAVING COUNT(case when up = down OR down is null then 1 end) = COUNT(*)
最终SQL
WITH RECURSIVE origin AS (
SELECT row_number() over () as _row, line
FROM (
SELECT line
FROM lance_input
UNION ALL
SELECT null
) t
), blank AS (
SELECT case when pre_row is null then 0 else pre_row end as pre_row, _row
FROM (
SELECT lag(_row) over () as pre_row, _row
FROM origin
WHERE line is null
) t
), seq AS (
SELECT 1 as id
UNION ALL
SELECT id + 1 as id
FROM seq
)
SELECT SUM(id)
FROM (
SELECT _row, id
FROM (
SELECT blank._row, v.id,
case when v.id <= length(line) / 2 then substring(line from 1 for v.id) else substring(line from 2 * v.id - length(line) + 1 for length(line) - v.id) end as l_str,
case when v.id <= length(line) / 2 then substring(line from v.id + 1 for v.id) else substring(line from v.id + 1 for length(line)) end as r_str
FROM origin, blank,
(select id from seq limit (select max(length(line)) from origin)) v
WHERE origin._row > blank.pre_row
AND origin._row < blank._row
AND v.id < length(line)
) t
GROUP BY _row, id
HAVING COUNT(case when l_str = reverse(r_str) then 1 end) = COUNT(*)
UNION ALL
SELECT _row, id * 100
FROM (
SELECT blank._row, h.id - blank.pre_row as id, origin.line as up, mirror.line as down
FROM origin
JOIN blank
ON origin._row > blank.pre_row
AND origin._row < blank._row
JOIN (select id from seq limit (select max(_row) from origin)) h
ON h.id > blank.pre_row
AND h.id < blank._row - 1
AND origin._row <= h.id
LEFT JOIN origin as mirror
ON mirror._row = (case when 2 * h.id - origin._row + 1 < blank._row then 2 * h.id - origin._row + 1 end)
) t
GROUP BY _row, id
HAVING COUNT(case when up = down OR down is null then 1 end) = COUNT(*)
) s
Part Two
第二关,通过修改一个字符,从而找出另一个潜在的镜子。如下面的例子,之前的镜子出现在2/3列之间。如果修改了5行第一个字符,则镜子出现在1/2列之间。
....##.
#####..
.....#.
....###
#..####
####...
....###
过滤条件
之前的条件,是镜像的列或者行全部相同,因此过滤条件是
# 列镜像
HAVING COUNT(case when l_str = reverse(r_str) then 1 end) = COUNT(*)
# 行镜像
HAVING COUNT(case when up = down OR down is null then 1 end) = COUNT(*)
现在的条件,是修改一个字符后,同样实现镜像,那么过滤条件应该修改为如下,即只差一行/列就完全一致
# 列镜像
HAVING COUNT(case when l_str = reverse(r_str) then 1 end) + 1 = COUNT(*)
# 行镜像
HAVING COUNT(case when up = down OR down is null then 1 end) + 1 = COUNT(*)
Levenshtein Distance
但是仅仅这个条件是不够的,不匹配的那一行/列,应该只差一个字符。因此需要使用到levenshtein distance,求出两个字符串的差异度。
test=# SELECT levenshtein('GUMBO', 'GAMBOL');
levenshtein
-------------
2
(1 row)
最终SQL
WITH RECURSIVE origin AS (
SELECT row_number() over () as _row, line
FROM (
SELECT line
FROM lance_input
UNION ALL
SELECT null
) t
), blank AS (
SELECT case when pre_row is null then 0 else pre_row end as pre_row, _row
FROM (
SELECT lag(_row) over () as pre_row, _row
FROM origin
WHERE line is null
) t
), seq AS (
SELECT 1 as id
UNION ALL
SELECT id + 1 as id
FROM seq
)
SELECT sum(id) from (
SELECT _row, id, SUM(levenshtein(l_str, reverse(r_str))) as levenshtein_distance
FROM (
SELECT blank._row, v.id, length(line) as len ,
case when v.id <= length(line) / 2 then substring(line from 1 for v.id) else substring(line from 2 * v.id - length(line) + 1 for length(line) - v.id) end as l_str,
case when v.id <= length(line) / 2 then substring(line from v.id + 1 for v.id) else substring(line from v.id + 1 for length(line)) end as r_str
FROM origin, blank,
(select id from seq limit (select max(length(line)) from origin)) v
WHERE origin._row > blank.pre_row
AND origin._row < blank._row
AND v.id < length(line)
) t
GROUP BY _row, len, id
HAVING COUNT(case when l_str = reverse(r_str) then 1 end) + 1 = COUNT(*)
UNION ALL
SELECT _row, id * 100, SUM(levenshtein(up, down)) as levenshtein_distance
FROM (
SELECT blank._row, blank._row - blank.pre_row - 1 as lines, h.id - blank.pre_row as id, origin.line as up, mirror.line as down
FROM origin
JOIN blank
ON origin._row > blank.pre_row
AND origin._row < blank._row
JOIN (select id from seq limit (select max(_row) from origin)) h
ON h.id > blank.pre_row
AND h.id < blank._row - 1
AND origin._row <= h.id
LEFT JOIN origin as mirror
ON mirror._row = (case when 2 * h.id - origin._row + 1 < blank._row then 2 * h.id - origin._row + 1 end)
) t
GROUP BY _row, lines, id
HAVING COUNT(case when up = down OR down is null then 1 end) + 1 = COUNT(*)
) t where levenshtein_distance = 1