Scramble words while preserving their outlines

43

7

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:

  1. Scrambling must be (pseudo) random.

  2. A word is a sequence of the Latin characters, A through Z.

  3. Only initial letters will ever be uppercase.

  4. The first and last letters must stay untouched.

  5. When scrambling, only letters within one of the following groups may exchange places:

    1. acemnorsuvwxz

    2. bdfhkl

    3. gpqy

    4. it

    5. 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:

  1. Smncrbliag must be (pusedo) rondam.

  2. A wrod is a seqencue of the Latin chreratacs, A thurogh Z.

  3. Only iniital lrttees will eevr be uppcsaere.

  4. The fisrt and lsat lettres must stay uctoenhud.

  5. When sarnclbimg, only letters wihtin one of the fwllnoiog guorps may ecxhange plaecs:

    1. aneusvrowxmcz

    2. bhkfdl

    3. gqpy

    4. it

    5. j (stays in plcae)

Emxaple

Adám

Posted 2017-05-10T05:49:03.050

Reputation: 37 779

t is supposed to be shorter than h although many people do not write it so. – Leaky Nun – 2017-05-10T06:00:52.990

@LeakyNun I know, but are you suggest removing t from group 2? Or maybe putting t in a group 4 with i? – Adám – 2017-05-10T06:02:16.470

The latter would be fine. – Leaky Nun – 2017-05-10T06:08:53.117

may the runtime be theoretically unbounded? (like random tries until something is right) – Display Name – 2017-05-10T09:40:45.090

@SargeBorsch Probably ok if done per word (as it is extremely likely to terminate fast), but not if done on the entire text (as that will probably never terminate). However, I'm not entirely sure I should allow this, as I don't want to trivialize the problem. I'd rather see clever solutions than brute ones. It isn't necessary in this case, as I've written a solution that doesn't. – Adám – 2017-05-10T09:57:13.327

1printable/patnirlbe isn't quite readable. I think the i/t swap is to blame. Hmm... paintrlbe No that didn't help either. It's probably the pr/pa swap, then. The outline maintains, but I think that I read "pr" and "pa" as being semantically(?) 1 letter. prtnialbe Ah yes. That did it. Not sure I can offer a fix to the algorithm though. – Draco18s no longer trusts SE – 2017-05-10T18:20:37.303

@Draco18s Well, really there should be more groups, r has a unique lower-right vacuum, and there is also the amount of "ink" per character and its width… – Adám – 2017-05-10T18:22:24.373

@Adám Too many groups and there wouldn't be valid swaps. :P Character width can be mitigated by only using a monospaced font to read the results in. But yes. – Draco18s no longer trusts SE – 2017-05-10T18:30:07.023

Answers

9

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>⁵
-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'] -> ["a",0] and [] -> [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

Jonathan Allan

Posted 2017-05-10T05:49:03.050

Reputation: 67 804

It's actually harder than I thought. – Leaky Nun – 2017-05-10T11:54:28.737

@LeakyNun welp it took me much longer than 10 minutes to grapple with it. – Jonathan Allan – 2017-05-10T11:54:44.423

Wow, congrats on being the first. Longest Jelly program I've ever seen. – Adám – 2017-05-10T12:02:08.130

You're right, this would probably be a breeze in Retina... if it had any sort of randomness... – Martin Ender – 2017-05-10T12:11:43.207

@Adám - There is this and the entry it cracked, although that was code-bowling - as for golf I do ... have ... a few ... longer Jelly submissions.

– Jonathan Allan – 2017-05-10T12:12:05.190

@JonathanAllan Cool. It is way up there though. – Adám – 2017-05-10T12:13:34.477

@MartinEnder Ah, that's a shame - one for the TODO list. – Jonathan Allan – 2017-05-10T12:13:41.693

1@JonathanAllan Yeah, it's been on there for ages, and is probably going to be part of the next release, because this has been bugging me many times. – Martin Ender – 2017-05-10T12:18:19.527

1czar + vex + mow + sun – Adám – 2017-05-10T12:59:12.147

3@Adám dictionary lookups to form acemnorsuvwxz. I will write up commented code at some point too. – Jonathan Allan – 2017-05-10T12:59:51.217

@MartinEnder Random regex... How about ?? which might or might not match that character/group. Would be really fun with backtracking involved. I've always wanted something like that for log parsing. – Robert Fraser – 2017-05-10T18:04:32.433

5

PHP, 278 Bytes

<?=preg_replace_callback("#\pL\K(\pL+)(?=\pL)#",function($t){preg_match_all("#([^bdf-lpqty])|([bdfhkl])|([gpqy])|([it])|(j)#",$t[0],$p);foreach($p as$v){$k++?$c=array_keys($n=array_filter($v)):$o=[];!$n?:shuffle($n)&&$o+=array_combine($c,$n);}ksort($o);return join($o);},$argn);

Try it online!

Expanded

echo preg_replace_callback("#\pL\K(\pL+)(?=\pL)#" # patter \pL is shorter as [a-z]
,function($t){  # replacement function beginning
  preg_match_all("#([^bdf-lpqty])|([bdfhkl])|([gpqy])|([it])|(j)#",$t[0],$p); # makes groups with the regex. group 0 is the whole substring
  foreach($p as$v){ # loop through groups
    $k++?$c=array_keys($n=array_filter($v)):$o=[]; # group 0 make new empty replacement array in the other case filter the group remove empty values. 
    #You gain an array with the keys as position in the substring and the values
    #store the key array and the values array
    !$n?:shuffle($n)&&$o+=array_combine($c,$n); 
    #if values shuffle the values and make a new array with the keys and the shuffled values and merge the new array to the replacement array
  }
  ksort($o); # sort the replacement array ascending positions 
  return join($o); # return the replacement as string
},$argn);

functions

array_combine

array_filter

array_keys

ksort

preg_replace_callback

shuffle

Jörg Hülsermann

Posted 2017-05-10T05:49:03.050

Reputation: 13 026

Tip: You could use the "disable output cache" setting on TIO, rather than running the code a few times. I just ran it with the example - All good! – Jonathan Allan – 2017-05-10T12:35:16.797

@JonathanAllan Thank You for the tip with the cache. It was hard enough to find a way to solve this – Jörg Hülsermann – 2017-05-10T12:41:28.337

5

Pyth, 79 bytes

sm?td++hduuXNhTeTC,f@@GTHUG.S@HGG+-GJ."by❤jã~léܺ"cJ\jPtdedd:jb.z"([A-Za-z]+)"3

where is U+0018.

Try it online!

Sample

It is well knwon that a text can still be raed while the irnands of its wrods have been seraclbmd, as long as their first and last lettres plus their oaervll ontliues rmeain conntsat. Given a text, sacmrble each wrod acrncdiog to thsee relus:

  1. Scamrlbing must be (puesdo) rnadom.

  2. A word is a suqencee of the Latin chraectars, A thuorgh Z.

  3. Only iaitinl lettres will eevr be uppaersce.

  4. The first and last lettres msut stay uotcnuhed.

  5. When srancblimg, only lettres wiihtn one of the follnwiog guorps may enxhcage plecas:

    1. amsuvrcnoxewz

    2. bhfkdl

    3. gpqy

    4. it

    5. j (stays in place)

Leaky Nun

Posted 2017-05-10T05:49:03.050

Reputation: 45 011

Can't you save with \pL instead of [A-Za-z]? – Adám – 2017-05-10T13:36:26.883

@Adám What is \pL? – Leaky Nun – 2017-05-10T13:38:01.553

Any character with the property of being a Letter. – Adám – 2017-05-10T13:38:47.087

I don't think it works here... – Leaky Nun – 2017-05-10T13:41:33.507

wouldn't \w be enough? – Display Name – 2017-05-10T13:41:39.617

@SargeBorsch that would match digits also – Leaky Nun – 2017-05-10T13:41:56.210

@LeakyNun I know, I know. I never programmed in Pyth (or Python) so I have no idea which regex syntax it uses.. In java it's (i?)[a-z] which is indeed longer than [A-Za-z]. In Retina it's i`[a-z], which is shorter than [A-Za-z]. If Pyth's syntax is 1 or 2 bytes it's shorter, otherwise it's easier to just keep [A-Za-z]. Ah well, I'll just delete my comments to clean up this comment section.. It was just an idea.. Next time I'll google the regex syntax before making the comment, sorry. – Kevin Cruijssen – 2017-05-10T15:01:03.780

The try it online link says that it's 84 characters. – isaacg – 2017-05-11T02:23:27.700

@isaacg updated. – Leaky Nun – 2017-05-11T02:55:50.563

5

JavaScript 176 bytes

t.replace(/\B(\w+)\B/g,b=>{return[/[acemnorsuvwxz]/g,/[bdfhkl]/g,/[gpqy]/g,/[it]/g].forEach(d=>{g=b.match(d),b=b.replace(d,c=>{return g.splice(Math.random()*g.length,1)})}),b})

Method:

  1. RegExp iterates over the centre of each word (/\B(\w+)\B/g) using 1st replace fn.

  2. 1st replace fn iterates an array of RegExp's for each letter-group (/[bdfkhl/g, /[gqpy]/g, etc..).

  3. Each iteration builds a temp array of word-centre's characters appearing in current letter-group.

  4. Each iteration then uses current letter-group's RegExp to iterate over entire word-centre, using a 2nd replace fn.

  5. 2nd replace fn randomly splices the temp array, removing a random character and returning it.

Demo:

Run it in JSFiddle: https://jsfiddle.net/CookieJon/bnpznb7r/

Bumpy

Posted 2017-05-10T05:49:03.050

Reputation: 489

Welcome to PPCG. Amazing first answer. However, I think you need \pL(\pL+)\pL rather than \B(\w+)\B to exclude digits and underscore. – Adám – 2017-05-11T07:21:18.590

Ah, thanks! Must admit regex is not my bag (I have to look up the reference EVERY time I use it!) I can swallow the 3 extra characters... will update my answer shortly thanks again. :-) – Bumpy – 2017-05-11T07:28:49.157

1Incredible first answer! :) A few, quick improvements for you to get you down to 155 bytes, including @Adáms correction above: t=>t.replace(/\B[a-z]+\B/gi,b=>([/[acemnorsuvwxz]/g,/[bdfhkl]/‌​g,/[gpqy]/g,/[it]/g]‌​.map(d=>b=b.replace(‌​d,c=>g.splice(new Date%g.length,1),g=b.match(d))),b)) – Shaggy – 2017-05-11T11:18:57.103

@Shaggy I think b=>[...].map(...)&&b saves another byte. Also I'm not sure your i is necessary. – Neil – 2017-05-11T21:35:28.400

If @Adám is going to be strictly picky with his word definition then you'll need to use t.replace(/[A-Za-z]([a-z]+)(?=[a-z])/g,(w,b)=>...w[0]+b...) or some such. – Neil – 2017-05-11T21:39:23.680

@Neil, yeah there's definitely more room for improvement. Don't know where that i flag snuck in from, though. – Shaggy – 2017-05-11T21:57:32.080

I'm about to update my answer with these nifty tricks, but I can't grok one of them... g.splice(new Date%g.length,1),g=b.match(d) So I guess the comma there makes this assignment a single expression (hence no return), I guess assigning is RTL (hence g defined after the splice), but why will this only work in the arrow function? I.e. executing the following in console throws 'g undefined' error: g.splice(1,1),g=[1,2,3,4]? What am I missing? – Bumpy – 2017-05-12T01:04:44.043

@Neil Why? Input is printable ASCII+newlines only. What will \pL(\pL+)\pL find which isn't a word? Also remember that 2-letter-words (and 3-letter words) cannot be scrambled (as there are no additional innard-letters to exchange with), so there is no need to match them. – Adám – 2017-05-12T03:22:56.897

Sorry, what does \pL(\pL+)\pL do? http://regexr.com/ says it matches the 'p' and 'L' characters...?

– Bumpy – 2017-05-12T03:42:20.463

@Bumpy What you're actually seeing is b.replace(d,c=>g.splice(...),g=b.match(d)). So we're trying to pass three parameters to replace instead of two. The third is ignored by replace, but it still gets evaluated, which includes the side-effect of setting g. – Neil – 2017-05-12T07:47:22.773

@Adám This is JavaScript RegExp flavour, so it doesn't have all that fancy PCRE stuff like character properties or lookbehinds. – Neil – 2017-05-12T07:49:13.270

Thanks Neil, gotcha. Also while you're here, care to explain .map(...)&&b -- I'm baffled! Still not sure which regex to use for the main one, either. After this, I think I'll publish updated version and draw a line under it for now. Thanks... this is quite fun! – Bumpy – 2017-05-12T07:56:36.923

Definitely better than mine, hahah. Chapeau! :) – Hankrecords – 2017-05-12T10:34:38.567

In your version you have b=>{return[...].forEach(...),b}. Let's start with the forEach. It's 7 bytes, but we can use map to achieve the same side-effect, thus saving 4 bytes. Next, the {return...}. We can switch this to (...) to save 6 bytes... but we can go one better; since map always returns a truthy value, we can switch the , to &&, which then makes the ()s unnecessary, ending up with b=>[...].map(...)&&b. We've saved a total of 11 bytes. – Neil – 2017-05-13T23:52:37.680

Thanks again... I've been poring over this and believe I understand all the suggestions (apart from the correct regex). Will update later now, I've started another code golf challenge I have to finish first!! :-) (addictive!) – Bumpy – 2017-05-14T03:03:07.733

2

C, 453, 356 369 bytes

#define F for
#define M rand()%s+1+q
char a[256],*b=" acemnorsuvwxz\1bdfhkl\1gpqy\1it\1j";g(c,t)char*c,*t;{static int i,j,k,w,v,n,q,s,r;r=-1;if(c&&t){strcpy(c,t);if(!k)F(j=i=k=1;b[i];++i)b[i]-1?(a[b[i]]=j):++j;F(r=i=0;c[i];){F(;isspace(c[i]);++i);F(q=i;!isspace(c[i])&&c[i];++i);F(s=v=i-q-2;--v>0;)if(a[c[j=M]]==a[c[w=M]]&&a[c[j]])n=c[j],c[j]=c[w],c[w]=n;}}return r;}

ungolf with comments

// Input in the arg "t" result in the arg "c"
// NB the memory pointed from c has to be >= memory pointed from t
//    the char is 8 bit
#define F for
#define M rand()%s+1+q
char a[256], *b=" acemnorsuvwxz\1bdfhkl\1gpqy\1it\1j";
   g(c,t)char*c,*t;
   {static int i,j,k,w,v,n,q,s,r;
    r=-1;
    if(c&&t)
      {strcpy(c,t);                         // copy the string in the result space
       if(!k)
         F(j=i=k=1;b[i];++i)
             b[i]-1?(a[b[i]]=j):++j;        // ini [possible because at start k=0]
       F(r=i=0;c[i];)
         {F(;isspace(c[i]);++i);            //skip spaces
                                            // the start q the end+1 i
          F(q=i;!isspace(c[i])&&c[i];++i);  //skip word
          F(s=v=i-q-2;--v>0;)               //loop for swap letters of the same set
            if(a[c[j=M]]==a[c[w=M]]&&a[c[j]])
                n=c[j],c[j]=c[w],c[w]=n;
         }
      }
   return r;
  }


#include <stdio.h>
#define G(x,y) if(x)goto y
main()
{char a[256],r[256];
l1:
 gets(a);// i would know the string lenght<256
 g(r,a);
 printf("%s\n",r);
 G(*a,l1);
}

RosLuP

Posted 2017-05-10T05:49:03.050

Reputation: 3 036

1

JavaScript (ES6), 380 327 311 294 Bytes

(298 282 265 Bytes excluding the rules)

Thanks to @Shaggy for the useful tips!

((b,d)=>b.replace(/\B[a-z]+\B/gi,f=>(g=>(g.map(j=>(h=d.slice(0,~(rind=d.indexOf(j))?rind:-1),~rind?h.split`,`.length-1:-1)).map((j,k,l,m=[])=>{l.map((n,o)=>n==j?m.push(o):0),sub=m[new Date%(m.length-1)]||k,tmp=g[sub],g[sub]=g[k],g[k]=tmp}),g.join``))([...f])))(s,"aneusvrowxmcz,bhkfdl,gqpy,it");

var f = ((b,d)=>b.replace(/\B[a-z]+\B/gi,f=>(g=>(g.map(j=>(h=d.slice(0,~(rind=d.indexOf(j))?rind:-1),~rind?h.split`,`.length-1:-1)).map((j,k,l,m=[])=>{l.map((n,o)=>n==j?m.push(o):0),sub=m[new Date%(m.length-1)]||k,tmp=g[sub],g[sub]=g[k],g[k]=tmp}),g.join``))([...f])))

var s="Let there be scrambling";
console.log(s);
console.log(f(s,"aneusvrowxmcz,bhkfdl,gqpy,it"))

s="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";
console.log(s);
console.log(f(s,"aneusvrowxmcz,bhkfdl,gqpy,it"))

Function f takes in a string of any kind (single word, multiple words, multiple words with signs in it - which it interprets as word-breaking) and an array a string of "rules" of any length separated by commas.

That array of rules, in the case of your question, would be ["aneusvrowxmcz", "bhkfdl", "gqpy", "it"] "aneusvrowxmcz,bhkfdl,gqpy,it"

Some letters don't get mixed even though they could, since you stated in your question that letters "may exchange spaces". If I misinterpreted it, I can change the code to always scramble letters that match the rules.

I know this is an enormous amount of bytes and it won't be able to compete with golfing languages, but I wanted to try anyway, hope you like it :)

Human-readable non-uglified code:

((txt,rules)=>txt.replace(/\B[a-z]+\B/gi,wo=>((w=>(w.map(c=>(h=rules.slice(0, ~(rind=rules.indexOf(c))?rind:-1),~rind?(h.split`,`.length-1):-1)).map((e,i,arr,a=[])=>{
    arr.map((x,i)=>(x==e)?a.push(i):0),
    sub=a[new Date%(a.length-1)]||i,
    tmp=w[sub],
    w[sub]=w[i],
    w[i]=tmp
}),w.join``))([...wo]))))(str, "aneusvrowxmcz,bhkfdl,gqpy,it")

Hankrecords

Posted 2017-05-10T05:49:03.050

Reputation: 149

1The OP rules have to be included in byte count. By may, I meant have a chance to. – Adám – 2017-05-10T14:50:30.363

1Welcome to PPCG :) You can definitely golf a lot off this. – Shaggy – 2017-05-10T14:54:23.253

1

I was going to try to golf this down for you but, given how much can be done with it, I ran out of time so instead I'll point you here and here to help get you started.

– Shaggy – 2017-05-10T15:09:38.543

1

A few quick pointers, though: 01) Get rid of all the vars an lets. 02) Unless it's a recursive function, no need to include the variable declaration (f=) in your byte count. 03) Use currying when a function has 2 parameters (b=>d=> instead of (b,d)=>) and call your function with f(b)(d). 04) You have the i flag so no need to include A-Z in your regex. 05) You can use indexOf or search on a string, without splitting it into an array.

– Shaggy – 2017-05-10T15:15:16.203

1How does suggestion 03 save characters? They look the same to me. – Steve Bennett – 2017-05-12T04:21:00.313

@SteveBennett b=>d=> 6 bytes, (b,d)=> 7 bytes. – TheLethalCoder – 2017-05-12T10:27:41.450

@TheLethalCoder Yes, but with b=>d=> you have to use (b)(d), which is 1 character more than (b,d), so isn't it the same thing? – Hankrecords – 2017-05-12T10:32:53.973

Anyway, I updated the code after shoving off more than 50 bytes. Thanks everyone for the tips! – Hankrecords – 2017-05-12T10:33:29.763

@Hankrecords Only if you're recursively calling the function, if you don't call it again you don't need to use (b)(d) though I didn't properly look over your code to see if that is the case here. – TheLethalCoder – 2017-05-12T10:35:23.547

1Hmm, I don't get what you mean. If you have f=b=>d=>..., then f is a function that takes an argument, and returns a function. How could you call f in any way other than f(myb)(myd)? Genuinely curious. – Steve Bennett – 2017-05-14T22:31:48.997

1

Python 3.6, 349 340 bytes

from itertools import *
from random import *
import re
def S(s):
    C=lambda c:len(list(takewhile(lambda x:c not in x,('j','it','gqpy','bhkfdl'))));L=[];B=[[]for i in range(5)]
    for l in s:c=C(l);L+=[c];B[c]+=[l];shuffle(B[c])
    return''.join(B[n].pop()for n in L)
A=lambda t:re.sub('[A-Za-z]{3,}',lambda x:x[0][0]+S(x[0][1:][:-1])+x[0][-1],t)

Indented with tabs. The function is named A. It doesn't use brute force, the runtime is deterministic, as OP asked.

Display Name

Posted 2017-05-10T05:49:03.050

Reputation: 654

1

Mathematica 232 Bytes

StringReplace[#,x:Repeated[WordCharacter,{2,∞}]:>""<>(s=StringTake)[x,{i,i}~Table~{i,StringLength@x}/.Flatten[Thread[#->RandomSample@#]&/@(StringPosition[x~s~{2,-2},#]+1&/@Characters@{"acemnorsuvwxz","bdfhkl","gpqy","it","j"})]]]&

The basic idea is to permute the subsets corresponding to the 4 distinct character groups. Probably room for improvement.

Kelly Lowder

Posted 2017-05-10T05:49:03.050

Reputation: 3 225

1

C, 306 282 Bytes

c,o,d,e,g;l(char*f){char*s[]={"aneusvrowxmcz","bhkfdl","gqpy","it",0},**h,*i,*t;for(i=f;*i;){if(isalpha(*i)){t=i;while(*i&&isalpha(*i))i++;e=i-t-2;for(h=s;*h&&e;*h++){for(c=999;--c;){d=1+rand()%e,o=1+rand()%e;if(strchr(*h,t[d])&&strchr(*h,t[o]))g=t[d],t[d]=t[o],t[o]=g;}}}else++i;}}

Try it online

Ungolfed:

int func(char*p) 
{
    char *groups[] = {"aneusvrowxmcz","bhkfdl","gqpy","it",0}, **g, *s, *t;
    int n,r,i,l,o;

    for (s = p; *s;)
    {
        if (isalpha(*s))
        {
            t = s;
            while (*s && isalpha(*s))
                s++;
            // start scrambling
            l = s - t - 2;
            for(g=groups; *g && l; *g++)
            {
                for(n=999;--n;)
                {
                    i = 1 + rand() % l;
                    r = 1 + rand() % l;
                    if (strchr(*g, t[i]) && strchr(*g, t[r]))
                    {
                        o=t[i];
                        t[i]=t[r];
                        t[r]=o;
                    }
                }
            }
            // end scrambling
        }
        else 
            s++;
    }
}

Johan du Toit

Posted 2017-05-10T05:49:03.050

Reputation: 1 524

Why would you like to do 999 swap in a word? Do you know that one word of one char has l=-1 and this possibly means that it begin to do 999 possible swap using 1 + rand() % -1 so random write in 2 giga of memory... But possible i see it wrong.... – RosLuP – 2017-05-12T14:45:29.843

There's unfortunately no magic regarding the use of 999. It's just 1 byte less than 1000 :) – Johan du Toit – 2017-05-12T15:02:16.920

In gcc it seems rand()%(-1) return 0 the first 2 time I tried it. so possible no swap of random 2giga space...% of int is not the % of unsigned... – RosLuP – 2017-05-12T15:19:24.283

@RosLup, I'm sorry but I don't follow what you are saying.. – Johan du Toit – 2017-05-12T15:42:27.247

0

Clojure, 326 322 324 bytes

Update 1: replaced (map(fn[[k v]]...)...) with (for[[k v]...]...)

Update 2: fixed regex, using \pL instead of \w etc.

#(let[G(zipmap"bdfhklgpqyitj""0000001111223")](apply str(flatten(interleave(for[v(re-seq #"\pL+"%)w[(rest(butlast v))]W[(into{}(for[[k v](group-by G w)][k(shuffle v)]))]R[(rest(reductions(fn[r i](merge-with + r{(G i)1})){}w))]][(first v)(map(fn[c r](nth(W(G c))(-(r(G c))1)))w R)(if(second v)(last v))])(re-seq #"\PL+"%)))))

I'm looking forward to seeing something shorter. Earlier ungolfed version with a few example runs:

(def f #(let[G(zipmap"bdfhklgpqyitj""0000001111223")] ; Create groups, the longest "acemnorsuvwxz" goes to an implicit group nil
          (apply str(flatten(interleave
                              (for[v (re-seq #"\w+"%)                                          ; Iterate over words
                                   w [(rest(butlast v))]                                       ; This holds the middle part
                                   W [(into{}(map(fn[[k v]][k(shuffle v)])(group-by G w)))]    ; Create shuffled groups
                                   R [(rest(reductions(fn[r i](merge-with + r{(G i)1})){}w))]] ; Calculate cumulative sum of group items, used to look-up nth value from shuffled values
                               [(first v)                                     ; First character
                                (map(fn[g r](nth(W g)(-(r g)1)))(map G w)R)   ; Shuffled middle part
                                (if(>(count v)1)(last v))])                   ; Last character, unless the word is just a single character
                              (re-seq #"\W+"%)))))) ; Interleave with spaces, commas, newline etc.

(f "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.\n")
;  "It is well known that a txet can sitll be read wlihe the irnands of its wrods hvae been seacmlbrd, as lnog as their fisrt and lsat letters plus their oavrell ontlieus rmaein cnontast.\n"
;  "It is well kwonn that a text can sitll be raed wlihe the innards of its wrods hvae been seramlbcd, as long as their fisrt and lsat lettres plus their oravell ouiltnes rmeain cnsatont.\n"
;  "It is well konwn that a text can still be read while the iarnnds of its words have been sraemlbcd, as lnog as their first and lsat lrttees plus their oaevrll ontlieus remain canntsot.\n"

NikoNyrh

Posted 2017-05-10T05:49:03.050

Reputation: 2 361

I think you need \pL+ and \PL+ rather than \w+ and \W+ to exclude digits and underscore. – Adám – 2017-05-11T07:18:20.920

0

Perl 6, 241 195 bytes

Includes +1 byte for -p command-line switch.

s:g/(<:L>)(<:L>+)(<:L>)/{$0}{[~]
$1.comb.pairs.classify({first
.value~~*,:k,/<[bdfhkl]>/,/<[gpqy]>/,/<[it]>/,/j/,!0}).values.map({$_».key
»=>«$_».value.pick(*)})».List.flat.sort».value}$2/;

Ungolfed:

s:g/(<:L>)(<:L>+)(<:L>)/{$0}{
    [~]
    $1.comb
    .pairs
    .classify({
        first .value ~~ *, :k,
            /<[bdfhkl]>/,
            /<[gpqy]>/,
            /<[it]>/,
            /j/,
            !0
    })
    .values
    .map({ $_».key »=>« $_».value.pick(*) })
    ».List
    .flat
    .sort
    ».value
}$2/;

Sean

Posted 2017-05-10T05:49:03.050

Reputation: 4 136

I think you need (\pL)(\pL+)(\pL) rather than (\w)(\w+)(\w) to exclude digits and underscore. – Adám – 2017-05-11T07:19:29.663

Actually \pL includes a lot of characters outside of the allowed range of latin letters A-Z. I've updated my code to reflect the requirements more accurately. – Sean – 2017-05-11T21:34:57.767

Which characters? Remember that input is restricted to printable ASCII+Newlines. – Adám – 2017-05-12T03:18:22.513

Ah, I missed that. \pL is spelled <:L> in Perl 6 though. – Sean – 2017-05-12T05:42:18.657

0

C#, 438 394 380 374 bytes

namespace System.Text.RegularExpressions{using Linq;s=>Regex.Replace(s,@"\p{L}(([gpqy])|(i|t)|(j)|([bdf-l])|([a-z]))*?[a-z]?\b",m=>{var a=m.Value.ToArray();for(int i=1,j;++i<7;){var c=m.Groups[i].Captures;var n=c.Cast<Capture>().Select(p=>p.Index-m.Index).ToList();foreach(Capture p in c){a[j=n[new Random().Next(n.Count)]]=p.Value[0];n.Remove(j);}}return new string(a);});}

Save 10 bytes thanks to @MartinEnder♦.

Annoyingly, CaptureCollection doesn't implement IEnumerable<T> and that's why the .Cast<Capture>() is needed. Hopefully, I can combine the Linq query and the foreach loop though.

I'm sure there's a lot that can be golfed but it took me long enough just to get it working...

Try it online!

Formatted/Full version:

namespace System.Text.RegularExpressions
{
    using Linq;

    class P
    {
        static void Main()
        {
            Func<string, string> f = s =>
                Regex.Replace(s, @"\p{L}(([gpqy])|(i|t)|(j)|([bdf-l])|([a-z]))*?[a-z]?\b", m =>
                {
                    var a = m.Value.ToArray();

                    for (int i = 1, j; ++i < 7;)
                    {
                        var c = m.Groups[i].Captures;

                        var n = c.Cast<Capture>().Select(p => p.Index - m.Index).ToList();

                        foreach(Capture p in c)
                        {
                            a[j = n[new Random().Next(n.Count)]] = p.Value[0];
                            n.Remove(j);
                        }
                    }

                    return new string(a);
                });

            Console.WriteLine(f("Scramble words while preserving their outlines"));
            Console.ReadLine();
        }
    }
}

TheLethalCoder

Posted 2017-05-10T05:49:03.050

Reputation: 6 930