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.
Nice little drill. Here is my take on it using Julia 1.1.1: https://pastebin.com/WsrMBkKD. Cheers!
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!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!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:
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"; } }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.
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:
Here’s a Haskell version.
[…] the previous exercise, we wrote a program to scramble the letters in each word of a string, retaining capitalization and […]
[…] 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 […]