A Scrambled Words Variant

October 11, 2019

Much of our solution is unchanged from the previous exercise; we use the same method to handle capitalization, and the main processing loop is a simple scan through the string from left to right. But the two middle cond clauses are more complicated:

(define (scramble str)
  (let* ((cs (string->list str))
         (upper (map char-upper-case? cs))
         (cs (map char-downcase cs)))
    (let loop ((cs cs) (word (list)) (zs (list)))
      (cond ((null? cs) ; end of input
              (list->string
                (map (lambda (u? c)
                       (if u? (char-upcase c) c))
                     upper (reverse zs))))
            ((and ; collect letter into accumulator
                  (pair? zs)
                  (char-alphabetic? (car zs))
                  (char-alphabetic? (car cs))
                  (pair? (cdr cs))
                  (char-alphabetic? (cadr cs)))
              (loop (cdr cs) (cons (car cs) word) zs))
            ((pair? word) ; end of word interior
              (loop (cddr cs) (list)
                    (append (list (cadr cs))
                            (list (car cs))
                            (shuffle word) zs)))
            (else ; not in a word
              (loop (cdr cs) word (cons (car cs) zs)))))))

The hardest part was accumulating zs in reverse; it took three tries to get the append in the right order. Here’s an example:

> (scramble "Programming Praxis is fun!")
"Parnmmrgiog Prixas is fun!"

You can run the program at https://ideone.com/9mJTqV. It is amazing that keeping the first and last letters of a word makes the whole word nearly readable.

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 )

Google photo

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

Twitter picture

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

Facebook photo

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

Connecting to %s

%d bloggers like this: