12
\$\begingroup\$

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

\$\endgroup\$
17
\$\begingroup\$

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.

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

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)
\$\endgroup\$
  • \$\begingroup\$ I've not used regex before. Could you explain what is being passed to garble_word()? \$\endgroup\$ – Joe Oct 16 '17 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 '17 at 19:22
  • 2
    \$\begingroup\$ The regex is for word extraction (not for garbling), and should be named appropriately. \$\endgroup\$ – n0rd Oct 17 '17 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 '17 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 '17 at 0:59
3
\$\begingroup\$

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)
\$\endgroup\$
  • \$\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 '17 at 13:48
  • \$\begingroup\$ yeah, I just kept the 1 to keep consistency with the original \$\endgroup\$ – Oscar Smith Oct 17 '17 at 14:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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