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.
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….
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:
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; }A Haskell version. The change is the addition of interiorA and its helper function, surround.