Scrambled Words

October 8, 2019

Given a string, scramble the letters of each word individually, where a word is a maximal sequence of letters, maintaining the original capitalization and punctuation. For instance, the string “Programming Praxis is fun!” might be scrambled as “Grprnimaogm Srxpia is unf!”

Your task is to write a program to scramble the letters of each word in a string. 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

10 Responses to “Scrambled Words”

  1. Zack said

    Nice little drill. Here is my take on it using Julia 1.1.1: https://pastebin.com/WsrMBkKD. Cheers!

  2. James Smith said

    Favourite language again – this time a bit golfish – so I apologies in advance

    > echo Programming Praxis is fun! | perl -pe '$_=join"",map{m{[a-z]}i?$_^uc|join"",map{$_->[0]}sort{$a->[1]<=>$b->[1]}map{[$_,rand]}split//,uc:$_}split/([^a-z]+)/i'
    Igoapmrrngm Xsriap si fnu!
    

    Expanding this out a bit – with comments…

    join "",
      map{
        m{[a-z]}i  ## First part (?) is for the words
      ? $_ ^ uc |  ## Contains " " if the letter is lower case and NULL (chr 0)
                   ## if upper case
                   ## Note uc with no parameter actions on the variable $_
                   ## "OR"ing the string (with |) with the spaces and nulls
                   ## makes the appropriate letters lower case!
        join "",                ## Join back into a word
        map{$_->[0]}            ## Pull out the letters
        sort{$a->[1]<=>$b->[1]} ## Schwartzian transform to sort letters
        map{[$_,rand]}          ## Add a random index - which we will sort!
        split//, uc             ## Split into individual characters
      : $_         ## Second part (:) if for the non-word parts of the sentance
        }
        split/([^a-z]+)/i ## Split into chunks containing words and gaps!
                          ## as using perl -pe - runs on the rows of the
                          ## file passed into the script!
    Igoapmrrngm Xsriap si fnu!
    
  3. James Smith said

    Just tweaked it – that was a bit too long…

    > echo Programming Praxis is fun! | perl -pe '$_=join"",map{/[a-z]/i?$_^uc|join"",map{$_->[0]}sort{$a->[1]<=>$b->[1]}map{[$_,rand]}split//,uc:$_}split/([a-z]+)/i'
    Igoapmrrngm Xsriap si fnu!
    
  4. chaw said

    Here is a solution in R7RS Scheme, emphasizing clarity over length or
    efficiency. It is quite similar to @programmingpraxis’s solution, but
    has no external dependencies (other than a couple of procedures from
    the popular SRFIs 1 and 27).

    (import (scheme base)
            (scheme write)
            (scheme char)
            (only (srfi 1)
                  list-tabulate)
            (only (srfi 27)
                  random-integer))
    
    (define (vector-swap! v i j)
      (let ((tmp (vector-ref v i)))
        (vector-set! v i (vector-ref v j))
        (vector-set! v j tmp)))
    
    ;; A random permutation of [0,n-1]
    (define (random-permutation n)
      (let ((v (make-vector n)))
        (do ((i (- n 1) (- i 1)))
            ((< i 0))
          (vector-set! v i i))
        (do ((i (- n 1) (- i 1)))
            ((< i 0) v)
          (vector-swap! v i (random-integer (+ i 1))))))
    
    (define (scramble-chars/case-by-position lst)
      (let* ((vec (list->vector lst))
             (n (vector-length vec))
             (rvec (random-permutation n)))
        (list-tabulate n
                       (lambda (i)
                         ((if (char-upper-case? (vector-ref vec i))
                              char-upcase
                              char-downcase)
                          (vector-ref vec (vector-ref rvec i)))))))
    
    (define (scramble-words str)
      (let loop ((ichars (string->list str))
                 (ochars '())
                 (wchars '()))
        (if (null? ichars)
            (list->string (reverse (append (scramble-chars/case-by-position wchars)
                                           ochars)))
            (if (char-alphabetic? (car ichars))
                (loop (cdr ichars)
                      ochars
                      (cons (car ichars) wchars))
                (loop (cdr ichars)
                      (cons (car ichars)
                            (append (scramble-chars/case-by-position wchars)
                                    ochars))
                      '())))))
    
    (display (scramble-words "Programming Praxis is fun!"))
    (newline)
    

    Output:

    Gomrganpmri Paxris si fnu!
    

  5. matthew said

    A straightforward C++ solution, using library functions for scanning strings and shuffling – I’d originally used a FSA, but using find_if etc. is quite neat:

    #include <algorithm>
    #include <iostream>
    #include <cctype>
    
    int main() {
      std::string t = "Programming Praxis is fun!";
      for (int i = 0; i < 10; i++) {
        auto s = t;
        for (auto p = s.begin(), q = s.end(); ; p++) {
          auto r = find_if(p, q, isalpha);
          if (r == q) break;
          p = find_if_not(r+1, q, isalpha);
          std::random_shuffle(r, p);
          if (p == q) break;
        }
        for (auto i = 0U; i < s.size(); i++) {
          s[i] = (isupper(t[i]) ? toupper : tolower)(s[i]);
        }
        std::cout << s << "\n";
      }
    }
    
    Rgmipaormng Raxpis si nfu!
    Ronagrmpimg Sxiarp si nuf!
    Poianmrgrmg Ipaxrs si fun!
    Imggnorpram Rsxaip is nfu!
    Mgamgionrrp Xpasir is unf!
    Immrnparggo Paxirs is fun!
    Gimornmparg Xpasir is fun!
    Pmriorggman Rpaxsi is ufn!
    Prgigonamrm Rsxpai is fnu!
    Rampngmogir Piaxrs is fnu!
    
  6. matthew said

    I probably should have included the string header there and I’m not sure why the compiler didn’t complain about missing the std:: for find_if and find_if_not.

  7. 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)+', s):
            i, j = word.span()
            chars[i:j] = random.sample(chars[i:j], j-i)
        chars = [a.upper() if b.isupper() else a for a, b in zip(chars, s)]
        return ''.join(chars)
    
    s = 'Programming Praxis is fun!'
    print(scramble(s))
    

    Output:

    Rgimpomagrn Xapris si nuf!
    
  8. Globules said

    Here’s a Haskell version.

    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 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
    
    main :: IO ()
    main = do
      args <- getArgs
      forM_ args $ scramble >=> putStrLn
    
    $ ./scramble "Programming Praxis, ç'est ben l'fun!"
    Gigprammnro Arixps, ç'ets ebn l'ufn!
    
  9. […] the previous exercise, we wrote a program to scramble the letters in each word of a string, retaining capitalization and […]

  10. […] program to scramble the letters of each word in a string. 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 […]

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: