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


3 Answers 3


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)
    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.

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

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

def garble(sentence):
    return RE_GARBLE.sub(garble_word, sentence)
  • \$\begingroup\$ I've not used regex before. Could you explain what is being passed to garble_word()? \$\endgroup\$
    – Joe
    Oct 16, 2017 at 18:59
  • 1
    \$\begingroup\$ @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. \$\endgroup\$
    – alecxe
    Oct 16, 2017 at 19:22
  • 2
    \$\begingroup\$ The regex is for word extraction (not for garbling), and should be named appropriately. \$\endgroup\$
    – n0rd
    Oct 17, 2017 at 0:44
  • 1
    \$\begingroup\$ I would also expect garble_word function to take a word string as an input, not some regex match object. \$\endgroup\$
    – n0rd
    Oct 17, 2017 at 0:46
  • 1
    \$\begingroup\$ @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. \$\endgroup\$
    – alecxe
    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:
                       + ''.join(random.sample(
                         [char for char in word[1:-1]], len(word) - 2))
                       + word[-1])
    return ' '.join(words)
  • \$\begingroup\$ you can probably make this a little faster by using if len(word) > 3, since one, two, and three letter words are all unchanged. \$\endgroup\$
    – MMAdams
    Oct 17, 2017 at 13:48
  • \$\begingroup\$ yeah, I just kept the 1 to keep consistency with the original \$\endgroup\$ Oct 17, 2017 at 14:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.