A Scrambled Words Variant

October 11, 2019

In the previous exercise, we wrote a program to scramble the letters in each word of a string, retaining capitalization and punctuation: “Programming Praxis is fun!” became something like “Grprnimaogm Srxpia is unf!” Today’s exercise is the same, but with a simple variation: the first and last letters of each word must remain unchanged, so we might see something like “Prgrnimaomg Prxias is fun!”

Your task is to modify your previous program in the minimal possible way into a program that scrambles the letters in the interior of the words in a string, retaining capitalization and punctuation. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Advertisement

Pages: 1 2

4 Responses to “A Scrambled Words Variant”

  1. James Smith said

    Again I’ll perl this – it just needs a tweak to the regular expression.

    [soucecode lang=”bash”]
    echo ‘Programming Praxis is a FUN thing to do!’ |\
    perl -pe ‘s{(?<=[a-z])([a-z]+)(?=[a-z])}{$1^uc$1|join””,map{$->[0]}sort{$a->[1]<=>$b->[1]}map{[$,rand]}split//,uc$1}ieg’
    [/sourcecode]

    Actually realised I didn’t need to do the split last time and so my previous answer can be shortened.

    [soucecode lang=”bash”]
    echo ‘Programming Praxis is a FUN thing to do!’ |\
    perl -pe ‘s{([a-z]+)}{$1^uc$1|join””,map{$->[0]}sort{$a->[1]<=>$b->[1]}map{[$,rand]}split//,uc$1}ieg’
    [/sourcecode]

    The only difference between these is the regular expression needed to find interior words … In the former I’m using non-negative-lookbehind “(?<= )” and non-negative-lookahead “(?= )” to strip the first and last letters off the word strings….

  2. Daniel said

    Here’s a solution in Python.

    import random
    import re
    
    def scramble(s):
        chars = list(s.lower())
        for word in re.finditer(r'\w(\w+)\w', s):
            i, j = word.span(1)
            chars[i:j] = random.sample(chars[i:j], j-i)
        chars = [c.upper() if s[idx].isupper() else c for idx, c in enumerate(chars)]
        return ''.join(chars)
    
    s = 'Programming Praxis is fun!'
    print(scramble(s))
    

    Output:

    Pgmrrmonaig Parixs is fun!
    
  3. matthew said

    C++ as before. This one uses a better RNG and reads input from std::cin.

    #include <random>
    #include <array>
    #include <algorithm>
    #include <functional>
    #include <iostream>
    #include <cctype>
    
    int main() {
      std::array<int, std::mt19937::state_size> seed_data;
      std::random_device r;
      std::generate_n(seed_data.data(), seed_data.size(), std::ref(r));
      std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
      auto rng = std::mt19937(seq);
    
      std::istreambuf_iterator<char> begin(std::cin), end;
      std::string t(begin, end);
      auto s = t;
      for (auto p = s.begin(), q = s.end(); ; p++) {
        auto r = std::find_if(p, q, isalpha);
        if (r == q) break;
        p = std::find_if_not(r+1, q, isalpha);
        if (p > r+3) std::shuffle(r+1, p-1, rng);
        if (p == q) break;
      }
      for (auto i = 0UL; i < s.size(); i++) {
        s[i] = (isupper(t[i]) ? toupper : tolower)(s[i]);
      }
      std::cout << s;
    }
    
  4. Globules said

    A Haskell version. The change is the addition of interiorA and its helper function, surround.

    import Control.Monad ((>=>), forM_)
    import qualified Control.Monad.Random as R
    import Data.Char (isLetter, isUpper, toLower, toUpper)
    import Data.List.Split (condense, split, whenElt)
    import System.Environment (getArgs)
    import System.Random.Shuffle (shuffleM)
    
    -- Transform a string by scrambling the order of each sequence of letters, but
    -- otherwise retain the overall order of characters.
    scramble :: R.MonadRandom m => String -> m String
    scramble xs = fmap (recap xs . concat) . mapM (interiorA shufLets) . spl $ xs
    
    -- Split a string into sequences of letters and non-letters.
    spl :: String -> [String]
    spl = split (condense $ whenElt $ not . isLetter)
    
    -- Shuffle the string only if it begins with a letter.
    shufLets :: R.MonadRandom m => String -> m String
    shufLets xxs@(x:_) = if isLetter x then shuffleM xxs else pure xxs
    shufLets xxs       = return xxs
    
    -- Capitalize the the second argument based on the case of the first.
    recap :: String -> String -> String
    recap = zipWith step
      where step x y = if isUpper x then toUpper y else toLower y
    
    -- Apply an applicative functor to the "interior" of a list.
    interiorA :: Applicative f => ([a] -> f [a]) -> [a] -> f [a]
    interiorA f (x:xs@(_:_)) = surround x (last xs) <$> f (init xs)
    interiorA _ xs           = pure xs
    
    surround :: a -> a -> [a] -> [a]
    surround x y zs = [x] ++ zs ++ [y]
    
    main :: IO ()
    main = do
      args <- getArgs
      forM_ args $ scramble >=> putStrLn
    
    $ ./scramble2 "Programming Praxis, ç'est ben l'fun!"
    Ponmiargmrg Prxais, ç'est ben l'fun!
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: