Scramble words while preserving their outlines This is much more advanced than How to randomize letters in a word and Cambridge Transposition because of the rule about which letters may be swapped with which. A simple regex will not suffice here.



It is well known that a text can still be read while the innards of its words have been scrambled, as long as their first and last letters plus their overall outlines remain constant. Given a printable Ascii+Newline text, scramble each word according to these rules:


Scrambling must be (pseudo) random.
A word is a sequence of the Latin characters, A through Z.
Only initial letters will ever be uppercase.
The first and last letters must stay untouched.
When scrambling, only letters within one of the following groups may exchange places:


acemnorsuvwxz
bdfhkl
gpqy
it
j (stays in place)



Example


  Srcmable wrods while psrrnveieg their oeiltnus
  
  It is well known that a txet can still be read while the inrands of its wrods have been srcambled, as long as their fisrt and last letters plus their ovaerll ontliues raemin canstnot. Given a patnirlbe Acsii+Nwnliee txet, samrclbe ecah word anoccdirg to these relus:
  
  
  Smncrbliag must be (pusedo) rondam.
  A wrod is a seqencue of the Latin chreratacs, A thurogh Z.
  Only iniital lrttees will eevr be uppcsaere.
  The fisrt and lsat lettres must stay uctoenhud.
  When sarnclbimg, only letters wihtin one of the fwllnoiog guorps may ecxhange plaecs:
  
  
  aneusvrowxmcz
  bhkfdl
  gqpy
  it
  j (stays in plcae)
  
  
  
  Emxaple
Jelly, 80 74 bytes

-2 bytes by moving from czar + vex + mow + sun to czar + vexes + unmown (the repeated es and ns are not a problem)
-1 byte using Tị rather than ȦÐf
-1 byte using Œle€Øa rather than i@€ØB&gt;⁵
-2 bytes by reconfiguring the layout a little

Tị
TẊị⁹ż@œp
e€ç⁸F
W;“HọƊṘ€.`]HɲøƁḤ0ẉlfrøj⁷»Ḳ¤ç/
Ḣ,ṪjÇḟ0
Œle€Øað¬œpÇ€ÑżœpÑ¥


A full program taking a character list (or Python formatted string), which prints the result of the scrambling.

Try it online!

A huge amount of difficulty for Jelly here it seems (either that or I have missed a trick, which has been known to happen!) This will surely be beaten by languages with better string manipulation like Retina(no random functionality) or 05ab1e.

How?

Tị - Link 1, get truthy items: list a
T  - truthy indexes of a
 ị - index into a

TẊị⁹ż@œp - Link 2, selective shuffle: list a, list b
T        - truthy indexes of a (those indexes that may be shuffled in b)
 Ẋ       - random shuffle
   ⁹     - link's right argument, b
  ị      - index into (gets the shuffled values)
      œp - partition b at truthy indexes of a
    ż@   - zip with reversed @rguments (place shuffled values - yields a list of lists)

e€ç⁸F - Link 3, value selective shuffle: list a, list b
e€    - c exists in b? for €ach c in a (1s where b has shuffle-able characters, else 0s)
   ⁸  - link's left argument, a
  ç   - call the last link (2) as a dyad
    F - flatten the result (from the yielded list of lists to one list)

W;“HọƊṘ€.`]HɲøƁḤ0ẉlfrøj⁷»Ḳ¤ç/ - Link 4, perform all shuffles on a word's innards: list x
W                             - wrap x in a list
                          ¤   - nilad followed by link(s) as a nilad:
  “HọƊṘ€.`]HɲøƁḤ0ẉlfrøj⁷»     -   compression of s(bdfhkl)+d( czar)+d(vexes)+d(unmown)+s( gpqy)+d( ti)
                              -     where d() looks up a word in Jelly's dictionary and s() adds a string to the compressed output.
                         Ḳ    -   split on spaces: ["bdfhkl","czarvexesunmown","gpqy","ti"]
                           ç/ - reduce by last link (3) as a dyad (shuffles by each in turn)

Ḣ,ṪjÇḟ0 - Link 5, shuffle a word: list w
Ḣ       - head w (yields the leftmost character and modifies w)
  Ṫ     - tail w (yields the rightmost character and modifies w)
 ,      - pair
        -   Note: head and tail yield 0 when w is empty, so ['a'] -&gt; ["a",0] and [] -&gt; [0,0]
    Ç   - call the last link (4) as a monad (with the modified w)
   j    - join
     ḟ0 - filter discard zeros (thus single or zero letter words pass through unchanged)

Œle€Øað¬œpÇ€ÑżœpÑ¥ - Main link: list s
Œl                 - convert s to lowercase, say t
    Øa             - lowercase alphabet, say a
  e€               - c exists in a? for €ach c in t
      ð            - dyadic chain separation (call that u)
       ¬           - not (vectorises across u), say v
        œp         - partition s at truthy indexes of v (extract words, plus empty lists from within strings of non-alphabetic characters)
          Ç€       - call the last link (5) as a monad for €ach (shuffle their innards)
            Ñ      - call the next link (1) as a monad (only keep the actual words)
                 ¥ - last two links as a dyad:
              œp   -   partition s at truthy indexes of u (get the non-words, plus empty lists from within strings of alphabetic characters)
                Ñ  -   call the next link (1) as a monad (only keep actual non-words)
             ż     - zip together
                   - implicit print