import random as r
g=[(y,x)for y in a for x in a]
p=[(y,x)for y,x in g if abs(x+y*1j)<=l and x|y]
for c in g:
o+=m if c==(0,0)else d.pop()if c in p else" "
Trying it online is an option.
Whoops, stumbled onto this due to recent activity, didn't notice it was over two years old somehow until I spent a good while on this. Well, two answers is kind of sad, so this is probably a good idea to post anyway. I'm sure there are dozens of ways to improve on this—didn't notice until just now that input is always odd, which would've been helpful to know.
i=input() is input, of course,
l=len(i) is saving the length of the string because it's used quite a few times.
a=range(-l,l+1) — a quick tool to create an iterator ranging the available distances away from the origin both ways along one dimension.
g=[(y,x)for y in a for x in a] builds a list of tuple coordinates that makes up the entire final grid.
p=[(y,x)for y,x in g if abs(x+y*1j)<=l and x|y] creates a subset of the list containing only the coordinates that non-center letters can possibly land on.
m=i[l//2] establishes the center character.
d=[*i.replace(m,"",1).center(len(p))] — the center character's taken out, leaving us with the other debris. The
center() function is very nice here, because it allows us to pad out the line (with by default a space) until it's a certain number of characters long. Here, that's the number of spaces the letters can land on, thus mirroring the distribution we need.
r.shuffle(d) naturally shuffles said distribution to be actually... distributed.
for c in g: o+=m if c==(0,0)else d.pop()if c in p else" ", looks over the entire square of feasible tiles, regardless of anything possibly landing on it or not, and as necessary, adds a character to our output string
o. Characters are popped out of our sample of debris so that they only appear once.
if c==l:o+="\n" — Adds line breaks as well. Returns.