So you have a lot of list manipulation, I believe you can reduce that to work with strings and use re.sub
to fill in the gaps.
Let's start by analyzing the answer:
I see that the result is basically symmetrical vertically and horizontally. Since lines are printed left-to-right, top-to-bottom, we can easily split it into top and bottom halves which are mirrored. This allows us to break apart our approach in only doing half the work, and copying a reflection onto itself.
Once res
is populated with the top half of the answer, we want to extend it with the reversed copy of it. A common shorthand for a reversed list by using extended slices.
res.extend(res[::-1])
But doing this would copy the middle line twice! Since [::-1]
is the same as [n:-1:-1]
, removing the last row would result in [n-1:-1:-1]
, or simply [n-1::-1]
.
res.extend(res[n-1::-1])
Now the top half of the solution can be broken down as repeated numbers, gradually substituting in decreased values for each row. The key word here is substitution. Creating a list and appending both sides is a way to handle this, but requires multiple modifications to it as you presented:
for x in range(1, n + 1):
str_x = str(x)
l = len(res)
res.insert(0, [str_x] * l)
res.append([str_x] * l)
Your approach appends the number to the beginning and end of the string. If you were working with strings, you could essentially create it all in one declaration. We can determine that the number of times n
appears in the first line is 2n
(since it's left-right mirrored) + 1
to account for the 0
that would be in the center. The starting line is very simple:
res = [str(n) * (n * 2 + 1)]
As mentioned earlier, the subsequent lines build on the previous line by replacing all but the first and last n
-values with n-1
. I really enjoy utilizing re.sub
, but you can use string concatenation as well.
Using re
, we could build a pattern as /n+/
where n
is the number to be replaced. Once we find it, we need to replace it with n-1
using the same formula described above. But since we need to leave an n
on each side, we'll have to subtract 2 from this formula to become 2 * n + 1 - 2
or simply 2 * n - 1
.
n_minus_1_str = str(x-1) * (x * 2 - 1)
With this, we can replace all the middle n
values with n_minus_1_str
and attach n
on both sides to preserve it.
str_x + n_minus_1_str + str_x
Putting this new line together for re.sub
, we get:
new_line = re.sub(str_x + "+", str_x + (str(x-1) * (x * 2 - 1)) + str_x, res[-1])
You could also implement the above without re.sub
, and use regular string concatenation instead to build the string. The slicing gets a bit tricky, but can be accomplished with:
new_line = res[-1][:n-x+1] + str(x-1) * (x * 2 - 1) + res[-1][x-n-1:]
Finally, we just need a for
loop to run backwards from n
down to 1
(not including 0
, since we are adding n-1
in the loop.
for x in xrange(n, 0, -1):
Using xrange
, since the list generated by range
is unnecessary.
Putting this all together with '\n'.join(res)
we get the following:
from re import sub
def palindrome(n):
res = [str(n) * (n * 2 + 1)] # First line
for x in xrange(n, 0, -1): # Append remaining until 0 is in the center
str_x = str(x)
res.append(sub(str_x + "+", str_x + (str(x-1) * (x * 2 - 1)) + str_x, res[-1]))
res.extend(res[n-1::-1]) # Add the flipped version without the last row
return '\n'.join(res)
print palindrome(8)
I chose to replace the entire string of x
instead of matching only the middle values to save on regex computation.
Edit: Forgive me, first time posting in Code Review.
n > 9
. Or if you even want to allow it. \$\endgroup\$n > 9
generates are still valid palindromes, but there is no way to show them nice \$\endgroup\$docstring
, disallow it with an explicit check, generate different first and last lines, ... \$\endgroup\$