27 September 2024 | Challenge 288 |

# Contiguous Palindromes

## Task 1: Closest Palindrome

**Submitted by: Mohammad Sajid Anwar**

You are given a string, `$str`

, which is an integer.

Write a script to find out the closest palindrome, not including itself. If there are more than one then return the smallest.

The closest is defined as the absolute difference minimized between two integers.

### Example 1

```
Input: $str = "123"
Output: "121"
```

### Example 2

```
Input: $str = "2"
Output: "1"
There are two closest palindrome "1" and "3". Therefore we return the smallest "1".
```

### Example 3

```
Input: $str = "1400"
Output: "1441"
```

### Example 4

```
Input: $str = "1001"
Output: "999"
```

### Solution

In the following we’ll consider the *head* of a number as the number’s first half if it has an even length and the first half including the middle digit if it has an odd length.

For every positive number \(n\) there are three palindromic candidates around it:

- The palindrome \(p_0\) build from the mirrored head of the number.
- The palindrome \(p_+\) build from the mirrored increment of the head.
- The palindrome \(p_-\) build from the mirrored decrement of the head.

As we have \(p_- \lt p_0 \lt p_+\) and \(p_- \lt n \lt p_+\), depending on the relation between \(n\) and \(p_0\), one of \(p_-\), \(p_0\) or \(p_+\) can be neglected. Let \(p^*\) be the solution. Then we have:

- \(p^* \in \{p_-, p_0\}\) if \(n < p_0\)
- \(p^* \in \{p_0, p_+\}\) if \(n > p_0\)
- \(p^* \in \{p_-, p_+\}\) if \(n = p_0\) (i.e. \(n\) is itself a palindrome)

Comparing the arithmetic mean of the two candidates against \(n\) reveals the solution.

Special cases are \(n \le 0\).
As there are no negative palindromes, here the solution is `0`

or `1`

.

There remains the task to find \(p_+\) and \(p_-\).
In the general case, we just need to increment/decrement \(n\)’s head and build a new palindrome thereof.
The length of \(n\) determines the length of the palindrome, where odd and even lengths need to be treated differently, e.g. the head `123`

results in `12321`

for an odd and `123321`

for an even length.

Here special cases arise when the head of \(p_-\) or \(p_+\) crosses a power of `10`

compared to the head of \(p_0\):

- a
`9`

has to be appended to the head after decrementing for even lengths - a
`0`

has to be removed from the end of the head after incrementing for odd lengths - odd and even lengths are exchanged

Some special provisions are required for a `zero`

head:

- it always “crosses a power of
`10`

” - it must be used to produce an odd length

The sub `near_palindrome`

is used to build \(p_-\), \(p_0\) and \(p_+\) depending on the second argument \(d \in \{-1, 0, 1\}\), while the sub `closest_palindrome`

provides the solution to the task.

```
use strict;
use warnings;
use experimental 'signatures';
sub near_palindrome ($n, $d) {
my $odd = length($n) % 2;
my $head = substr $n, 0, (length($n) + $odd) / 2;
my $l0 = length $head;
$head += $d;
if (!$head || $l0 != length $head) {
$head = $head * 10 + 9 if $d < 0 && !$odd;
$head /= 10 if $d > 0 && $odd;
$odd ^= !!$head;
}
$head . reverse($odd ? substr $head, 0, -1 : $head);
}
sub closest_palindrome ($n) {
return 0 + !$n if ($n += 0) < 1;
my $p0 = near_palindrome($n, 0);
my $lo = $p0 < $n ? $p0 : near_palindrome($n, -1);
my $hi = $p0 > $n ? $p0 : near_palindrome($n, 1);
$lo + $hi >= 2 * $n ? $lo : $hi;
}
```

See the full solution to task 1.

## Task 2: Contiguous Block

**Submitted by: Peter Campbell Smith**

You are given a rectangular matrix where all the cells contain either `x`

or `o`

.

Write a script to determine the size of the largest contiguous block.

A contiguous block consists of elements containing the same symbol which share an edge (not just a corner) with other elements in the block, and where there is a path between any two of these elements that crosses only those shared edges.

### Example 1

```
Input: $matrix = [
['x', 'x', 'x', 'x', 'o'],
['x', 'o', 'o', 'o', 'o'],
['x', 'o', 'o', 'o', 'o'],
['x', 'x', 'x', 'o', 'o'],
]
Ouput: 11
There is a block of 9 contiguous cells containing 'x'.
There is a block of 11 contiguous cells containing 'o'.
```

### Example 2

```
Input: $matrix = [
['x', 'x', 'x', 'x', 'x'],
['x', 'o', 'o', 'o', 'o'],
['x', 'x', 'x', 'x', 'o'],
['x', 'o', 'o', 'o', 'o'],
]
Ouput: 11
There is a block of 11 contiguous cells containing 'x'.
There is a block of 9 contiguous cells containing 'o'.
```

### Example 3

```
Input: $matrix = [
['x', 'x', 'x', 'o', 'o'],
['o', 'o', 'o', 'x', 'x'],
['o', 'x', 'x', 'o', 'o'],
['o', 'o', 'o', 'x', 'x'],
]
Ouput: 7
There is a block of 7 contiguous cells containing 'o'.
There are two other 2-cell blocks of 'o'.
There are three 2-cell blocks of 'x' and one 3-cell.
```

### Solution

For this task two of my favored CPAN modules may be combined: `PDL`

and `Graph`

.

We take the matrix’ elements as vertices of an undirected graph, where two vertices are connected with an edge if they are horizontally or vertically adjacent and contain the same value.
(This approach generalizes the task to matrices containing characters not limited to `x`

and `o`

.)
The task may be reformulated as finding the largest connected component in the resulting graph.

Using `PDL`

to identify the graph’s edges:
Comparing the matrix with itself shifted by one row or column, where the first or last row or column is omitted.
Collecting the matching indices and use
the stringified coordinate ndarrays as vertices.

Here is a sample session in the pdl shell explaining the procedure for the matrix from example 1 on the “row edges”.
The matrix is created as a `PDL::Char`

object that is capable of handling the task’s data format.
It gets converted into a numeric ndarray containing the ASCII codes of the first character of each cell on the fly.

```
pdl> require PDL::Char
pdl> $m = PDL::Char->new([
['x', 'x', 'x', 'x', 'o'],
['x', 'o', 'o', 'o', 'o'],
['x', 'o', 'o', 'o', 'o'],
['x', 'x', 'x', 'o', 'o'],
])->((0))->long
pdl> $r = whichND $m(0:-2) == $m(1:-1)
pdl> p $r
[
[0 0]
[1 0]
[2 0]
[1 1]
[2 1]
[3 1]
[1 2]
[2 2]
[3 2]
[0 3]
[1 3]
[3 3]
]
```

Now duplicating the coordinate entries and adding a shift to the duplicates to convert these into the neighbor vertices:

```
pdl> $e = $r->dummy(1, 2) + indx([0, 0], [1, 0])
pdl> p $e
[
[
[0 0]
[1 0]
]
[
[1 0]
[2 0]
]
[
[2 0]
[3 0]
]
[
[1 1]
[2 1]
]
[
[2 1]
[3 1]
]
[
[3 1]
[4 1]
]
[
[1 2]
[2 2]
]
[
[2 2]
[3 2]
]
[
[3 2]
[4 2]
]
[
[0 3]
[1 3]
]
[
[1 3]
[2 3]
]
[
[3 3]
[4 3]
]
]
```

The “inner matrices” represent the horizontal edges that will be used to build the graph. Here is a visualization of the resulting edges. For the vertical edges the procedure is analogous with the role of rows and columns exchanged.

```
pdl> p join " <-> ", map "$_", $_->dog for $e->dog
[0 0] <-> [1 0]
[1 0] <-> [2 0]
[2 0] <-> [3 0]
[1 1] <-> [2 1]
[2 1] <-> [3 1]
[3 1] <-> [4 1]
[1 2] <-> [2 2]
[2 2] <-> [3 2]
[3 2] <-> [4 2]
[0 3] <-> [1 3]
[1 3] <-> [2 3]
[3 3] <-> [4 3]
```

`Graph`

’s `connected_components`

method returns an AoA of all connected components with their corresponding vertices.
Here we are interested in the sizes only, especially the maximum thereof.
Using `PDL`

’s `max`

to avoid requiring another module.

```
use v5.12;
use warnings;
use PDL;
use PDL::Char;
use PDL::NiceSlice;
use Graph::Undirected;
sub max_cont_block {
state $r = indx [0, 0], [1, 0];
state $c = indx [0, 0], [0, 1];
my $m = PDL::Char->new(@_)->((0))->long;
my $g = Graph::Undirected->new;
$g->add_edge(map "$_", $_->dog) for
(whichND($m(0:-2,) == $m(1:-1,))->dummy(1, 2) + $r)->dog,
(whichND($m(,0:-2) == $m(,1:-1))->dummy(1, 2) + $c)->dog;
max long map scalar @$_, $g->connected_components;
}
```

See the full solution to task 2.

If you have a question about this post or if you like to comment on it, feel free to open an issue in my github repository.