Scrambled Words

October 8, 2019

We convert the input to a list, save capitalization, and remove it. The main loop collects the letters of a word, scrambles the letters at the end of the word, and copies everything else. When the input is exhausted, the accumulating output is reversed into its proper order, capitalization is restored, and the output is converted back to a string:

(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))))
            ((char-alphabetic? (car cs)) ; in a word
              (loop (cdr cs) (cons (car cs) word) zs))
            ((pair? word) ; end of word
              (loop cs (list) (append (shuffle word) zs)))
            (else ; not in a word
              (loop (cdr cs) word (cons (car cs) zs)))))))

We use shuffle from the Standard Prelude. Here are some examples:

> (scramble "Programming Praxis is fun!")
"Grprnimaogm Srxpia is unf!"
> (scramble "Programming Praxis is fun!")
"Gnagmrroimp Rxsiap si nfu!"
> (scramble "Programming Praxis is fun!")
"Angmrrimgpo Ixaprs si fun!"

 

You can run the program at https://ideone.com/0r5Ckb.

Advertisements

Pages: 1 2

9 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 […]

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: