# Python shuffle the middle letters in all the words of a sentence

The purpose of the program is to shuffle only the middle letters of each word in a sentence, leaving it still readable like this:

Aoccdrnig to a rscheearch at Cmabrigde Uinervtisy, it deosn't mttaer in waht oredr the ltteers in a wrod are, the olny iprmoetnt tihng is taht the frist and lsat ltteer be at the rghit pclae.

def garble(sentence):

return ' '.join([word[0] + ''.join(random.sample([char for char in word[1:-1]],len(word[1:-1]))) + word[-1] if len(word) > 1 else word for word in  sentence.split()])


I definitely think I have gone overboard putting it all in one line but I'm not sure where to separate it

• And if you look at this research (actually by a Cambridge University researcher) you will find that it's mostly false. Oct 17, 2017 at 8:56
• codegolf.stackexchange.com/a/9355/32352 for an extreme example of compressing everything in one line... Oct 17, 2017 at 9:11

I totally agree with @alecxe's comment on Python, but I thing regex is a little overkill for this problem. So, here's a non-regex solution:

import random

def shuffle_string(string):
chars = list(string)
random.shuffle(chars)
return ''.join(chars)

def garble_word(word):
# No operation needed on sufficiently small words
# (Also, main algorithm requires word length >= 2)
if len(word) <= 3:
return word

# Split word into first & last letter, and middle letters
first, mid, last = word[0], word[1:-1], word[-1]

return first + shuffle_string(mids) + last

def garble(sentence):
words = sentence.split(' ')
return ' '.join(map(garble_word, words))


Edit: It should be noted that while my solution is simpler than @alecxe's, it is not as robust. If you care about dealing with strange whitespace properly, or about handling punctuation (see @RolandIllig's comment), use @alecxe's solution.

• The *mids requires Python 3. Oct 17, 2017 at 0:26
• @DennisWilliamson True, but the question is tagged "python-3.x" Updated regardless. Oct 17, 2017 at 0:26
• Your code considers ear, a valid garbling for the "word" are,. Oct 17, 2017 at 5:32
• *mids already provide a list of characters, no need to chars = list(string) in shuffle_string (unless Python 2…) Oct 17, 2017 at 10:58
• I like this one slightly better because it uses a more similar technique to mine
– Joe
Oct 17, 2017 at 18:42

The fact that Python allows fitting things on a single line and pack in comprehensions, does not mean you need to try to squash the code to be shorter hurting readability. I would probably at least expand the code into multiple lines with descriptive variable names.

Here is, though, an alternative solution based on regular expression replacement function:

from random import shuffle
import re

RE_GARBLE = re.compile(r"\b(\w)(\w+)(\w)\b")

def garble_word(match):
first, middle, last = match.groups()

middle = list(middle)
shuffle(middle)

return first + ''.join(middle) + last

def garble(sentence):
return RE_GARBLE.sub(garble_word, sentence)

• I've not used regex before. Could you explain what is being passed to garble_word()?
– Joe
Oct 16, 2017 at 18:59
• @Joe sure, the match is a "Match" object, that allows us to access the captured groups via .groups(). A good place to start with regular expressions is the How To from the standard library documentation. Oct 16, 2017 at 19:22
• The regex is for word extraction (not for garbling), and should be named appropriately.
– n0rd
Oct 17, 2017 at 0:44
• I would also expect garble_word function to take a word string as an input, not some regex match object.
– n0rd
Oct 17, 2017 at 0:46
• @n0rd well, here garble_word() is a replacement function for the re.sub() which expects an argument to be a "match" object..just following the contract/rules..thanks. Oct 17, 2017 at 0:59

You definitely need to split up the line. Something like the following is way more readable.

def garble(sentence):
words = []
for word in  sentence.split():
if len(word) > 1:
words.append(word[0]
+ ''.join(random.sample(
[char for char in word[1:-1]], len(word) - 2))
+ word[-1])
else:
words.append(word)
return ' '.join(words)

• you can probably make this a little faster by using if len(word) > 3, since one, two, and three letter words are all unchanged. Oct 17, 2017 at 13:48
• yeah, I just kept the 1 to keep consistency with the original Oct 17, 2017 at 14:16