18

Remember that away message on aim that said how:

Aoccdrnig to a rscheearch at Cmabrigde Uinervtisy, it deosn't mttaer in waht oredr the ltteers in a wrod are, the olny iprmoetnt tihng is taht the frist and lsat ltteer be at the rghit pclae. The rset can be a toatl mses and you can sitll raed it wouthit porbelm. Tihs is bcuseae the huamn mnid deos not raed ervey lteter by istlef, but the wrod as a wlohe.

Anyway I'm trying to make a function that would do that to an entire page. There are a few rules for this function.

  1. less then 4 characters leave alone.
  2. non-alphanumeric characters don't count as part of the word.
  3. hyphenated words are really two words
  4. words must get garbled if length >= 4 (can't be like the original)
  5. The first and last chars stay the same and only the middle chars get garbled (Thanks Hersheezy)
  6. the text should always be random and produce unique garbling on each run
  7. Pure javascript and iterates on all text nodes
  8. Shortest sweetest code wins.

Anyway it seems simple enough to implement, how's about starting a contest to see who could make the cleanest clearest code to accomplish this task. Feel free to borrow without recognition from my code (I def have)

If i missed anything add it in the comments. Anyway I worked on it very hackishly and here's me showing my less than par work

DEMO

var i, j, words, textNodes, punct = /[^a-zA-Z0-9]/;

Array.prototype.shuffle = function() {
    for (var i = 0; i < this.length; i++) {
        var j = i;
        while (j == i) {
            j = Math.floor(Math.random() * this.length);
        }
        var tmp = this[i];
        this[i] = this[j];
        this[j] = tmp;
    }
    return this;
};

String.prototype.shuffle = function() {
    return this.split('').shuffle().join('');
};

function transverse(element, array) {
    if (!array) array = [];
    if (element.nodeType === 3) {
        array.push(element);
    } else {
        for (var i = 0; i < element.childNodes.length; i++) {
            transverse(element.childNodes[i], array);
        }
    }
    return array;
}

function garble(str) {
    if (!str) return '';
    str = str.trim();
    if (/-/.test(str)) {
        str = str.split('-');
        for (var i = 0; i < str.length; i++) {
            str[i] = garble(str[i]);
        }
        return str.join('-')
    }
    if (punct.test(str.charAt(0))) {
        return str.charAt(0) + garble(str.slice(1));
    }
    if (punct.test(str.charAt(str.length - 1))) {
        return garble(str.slice(0, -1)) + str.charAt(str.length - 1);
    }
    if (str.length < 4) return str;
    if (str.length === 4) return str.charAt(0) + str.charAt(2) + str.charAt(1) + str.charAt(3)
    return str.charAt(0) + str.substr(1, str.length - 2).shuffle() +
        str.charAt(str.length - 1);
}


window.onload = function() {
    textNodes = transverse(document.documentElement);
    for (i = 0; i < textNodes.length; i++) {
        words = textNodes[i].data.split(' ');
        for (j = 0; j < words.length; j++) {
            words[j] = garble(words[j]);
        }
        textNodes[i].data = words.join(' ');
    }
};
rekire
  • 47,260
  • 30
  • 167
  • 264
qwertymk
  • 34,200
  • 28
  • 121
  • 184
  • 1
    it looks like the first and character of each word are left intact also. – Hersheezy Feb 11 '11 at 01:35
  • I'm a little confused. Did you have a specific issue you need help with? – user113716 Feb 11 '11 at 01:41
  • yeah I want it to be better implemented and see how others would go about accomplishing this. Also it gives me weird results on the demo page if you can replicate my issues – qwertymk Feb 11 '11 at 01:48
  • Have you checked out http://codereview.stackexchange.com/ ? It may be a good place for this type of question. – user113716 Feb 11 '11 at 04:04
  • your script garbles up punctuation in the middle http://jsfiddle.net/35AVA/1/ – Amjad Masad Feb 11 '11 at 19:39
  • The array shuffle function can be replace with a single `Array.sort` with random numbers fed into the sorting function. – Yi Jiang Feb 13 '11 at 23:13
  • Be sure your code does not hang or crash when it is given a word like "doom". – Thomas Eding Feb 21 '11 at 03:13
  • @trinithis I already handled that in my code, and @brad followed me to it, but what I don't understand is why @qwertymk restarted the bounty? man keep ur 50 rep, nobody wants it! just don't give any promises u can't live up too, cheerz! – Amjad Masad Feb 21 '11 at 11:52
  • @Amjad I was a little busy the past couple of days and missed giving the rep points. Anyway a promise is a promise even if it costs me another 50 points – qwertymk Feb 21 '11 at 15:44
  • @AmjadMasad: Good job, although `non-alphanumeric characters don't count as part of the word.` and the use of `\w` are a tad conflicting. ;p Great job though, kudos for the patience. – Brad Christie Feb 25 '11 at 01:18
  • @brad the "numeric" in the word alphanumeric means numbers and alpha means alphabets and the "non" in the non-alphanumeric means anything other than those. So your REGEX: /\b[a-z]{4,}\b/ would match only alphabets, opposed to \w which is exactly equivalent to writing [A-Za-z0-9_] . So I guess what u said goes right back at u :) – Amjad Masad Feb 25 '11 at 01:53
  • @Amjad: as I mentioned earlier, I stopped working on it. Lost interest. ;-) – Brad Christie Feb 25 '11 at 01:59
  • Stopped working on what exactly? learning basic REGEX? or basic english? – Amjad Masad Feb 25 '11 at 04:19

4 Answers4

9

UPDATE( LATEST ): Don't think it can get any smaller.. DEMO
Latest compressed (332):

var e=document.body.getElementsByTagName('*'),j,i,l,x,t,b;for(i=0;e[i];i++)for(j=0;b=e[i].childNodes[j];j++)if(b.nodeType==3)b.data=b.data.replace(/\w{4,}/g,function(w){if(/(^.)(\1)+$/.test(x=w.substring(1,l=w.length-1)))return w;t=w;while(t==w)t=w[0]+x.split('').sort(function(){return 0.5-Math.random()}).join('')+w[l];return t}); 

code:

var e = document.body.getElementsByTagName('*'),
    j, i, l, x, t, b;
for (i = 0; e[i]; i++)
for (j = 0; b = e[i].childNodes[j]; j++)
if (b.nodeType == 3) b.data = b.data.replace(/\w{4,}/g, function(w) {
    if (/(^.)(\1)+$/.test(x = w.substring(1, l = w.length - 1))) return w;
    t = w;
    while (t == w)
    t = w[0] + x.split('').sort(function() {
        return 0.5 - Math.random();
    }).join('') + w[l];
    return t;
});

UPDATE even.. smaller..

Even smaller version
I dont know the minifier your using, but this must be at least (EDIT 108) bytes smaller.
compressed version (365 bytes):

var e=document.body.getElementsByTagName('*'),a=[],c,j,i,l,x,t,b;for(i=0;c=e[i];i++)for(j=0;b=c.childNodes[j];j++)if(b.nodeType==3){b.data=b.data.replace(/\b[a-z0-9]{4,}\b/gi,function(w){if(/(^.)(\1)+$/.test(x=w.substring(1,l=w.length-1)))return w;t=w;while(t==w)t=w[0]+x.split('').sort(function(){return Math.floor(Math.random()*2)?1:-1}).join('')+w[l];return t})}  

Code:

var e = document.body.getElementsByTagName('*'),
    a = [],
    c, j, i, l, x, t, b;
for (i = 0; c = e[i]; i++)
for (j = 0; b = c.childNodes[j]; j++)
if (b.nodeType == 3) {
    b.data = b.data.replace(/\b[a-z0-9]{4,}\b/gi, function(w) {
        if (/(^.)(\1)+$/.test(x = w.substring(1, l = w.length - 1))) return w;
        t = w;
        while (t == w)
        t = w[0] + x.split('').sort(function() {
            return Math.floor(Math.random() * 2) ? 1 : -1;
        }).join('') + w[l];
        return t;
    });
}

EDIT
NEW RULES DEMO
CODE:

var fn = function(e) {
    var ret = [],c;
    for (var i = 0; i < e.length; i++) {
        c = e[i].childNodes;
        for (var j = 0; j < c.length; j++)
            if (c[j].nodeType === 3) ret.push(c[j]);
    }
    return ret;
};
var es = fn(document.body.getElementsByTagName('*'));
for (var i = 0; i < es.length; i++) {
    var e = es[i],len,x;
    e.data = e.data.replace(/\b[a-z0-9]{4,}\b/gi, function(w) {
        if (/(^.)(\1)+$/.test(x = w.substring(1, len = w.length - 1))) return w;
        var tmp = w;
        while (tmp === w) {
            tmp = w[0] + x.split('').sort(function() {
                return Math.floor(Math.random() * 2) ? 1 : -1;
            }).join('') + w[len];
        }
        return tmp;
    });
}

This should respect all the rules, and keep format and punctuation. DEMO

//select all nodes in document and perform map on it to filter out
//non text node types, then each one of those elements is processed.
$('*').contents().map(function(i, elem) {
    if (elem.nodeType !== 3) return null;
    else return elem;
}).each(function(i, elem) {
 //call strip funciton defined down to get an object, with a word array, and
 //charecters which was stripped along with there index in the orginal string
    var str1 = '',
        tmp = strip(elem.data),
        words = tmp.words,
        sentence;
    // shuffle all words
    words = $.map(words, function(x, i) {
        return shuffle(x);
    });
    //construct raw sentence (non alphanumeric charecters)
    sentence = words.join('');
    //reinsert spaces and punctiouation 
    $.each(tmp.chars, function(i, elem) {
        sentence = sentence.substring(0, elem.index) + elem.char + sentence.substring(elem.index - 1 + elem.char.length);
    });
    //set the element text
    elem.data = sentence;
});

//shuffle funciton takes a word and shuffle the charecters between the last and the firt
function shuffle(txt) {
    //if the word is smaller than 4 charecters or it has repeated charecters in
    //its middle (i.e. loop, 'oo' cannot be shuffled!) then return it;
    if (txt.length < 4 || /(^.)(\1)+$/.test(txt.substring(1, txt.length - 1)))
        return txt;
    var str = txt.split(''),
        ret = [],
        rand, x = 0,
        tmp = txt;
    //while the txt hasn't changed in the first randomization cycle then repeated
    while (txt === tmp) {
        ret = [];
        $.each(str, function(i, c) {
            if (i === str.length - 1 || i === 0) {
                ret[i] = c;
                return;
            }
            while (true) {
                rand = Math.floor(Math.random() * (str.length - 2) + 1);
                if (!ret[rand]) {
                    ret[rand] = c;
                    break;
                }
            }
        });
        tmp = ret.join('');
    }
    return ret.join('');
}

function strip(txt) {
    var punc = /[^A-Za-z0-9]/g,
        res, nonAlphaNum = [],
        arr;
    //punc regex is all non-alphanumeric charecters which will act on the string
    //to point out unwanted charecters and store them in an array along with
    //their index
    while ((res = punc.exec(txt)) != null) {
        nonAlphaNum.push({
            index: res.index,
            char: res[0]
        });
    }
    //split into words
    arr = txt.split(/\s/);
    //remove punctiuation and other unwanted chars
    arr = $.map(arr, function(x, i) {
        return x.replace(punc, '');
    });
    return {
        words: arr,  //words array
        chars: nonAlphaNum //array of stripped charecter objects (chars, index in orginal)
    };
} 

btw nice choice of the article, WWiWieikikb!!

Amjad Masad
  • 4,035
  • 1
  • 21
  • 20
  • 1
    @qwertymk please check out the new version, and what minifier are you using? – Amjad Masad Feb 20 '11 at 17:19
  • 1
    congrats on winning the contest – qwertymk Feb 22 '11 at 14:17
  • @Amjad: Oh, and it can get shorter: var a=document.body.getElementsByTagName('*'),b=0,c,d,e,k;for(;c=a[b++];)for(d=0;e=c.childNodes[d++];)if(e.nodeType==3)e.data=e.data.replace(/(?!\w(\w)\1+\w)(\w{4,})/ig,function(f){return f.replace(/(.)(.*)(.)/,function(g,h,i,j){while((k=h+i.split('').sort(function(){return Math.random()-.5}).join('')+j)==g);return k})}); **323** – Brad Christie Feb 25 '11 at 02:53
  • @brad I highly doubt that would work from the first look at that empty tag selector, but there is a couple of cool points to consider. – Amjad Masad Feb 25 '11 at 04:22
6

Updated

So I couldn't help but play around a bit with this thing and see what other ways I could manipulate the document with as little code as possible. Suffice it to say that it can be shortened to work in an either/or scenario, but I like to make things with options for the user to play with.

Having said that, here are some variations on the above and benefits/disappointments:


Official Submission (473bytes)

Minified (473bytes) 1

function t(e){var r=[],w;for(w=0;w<e.length;w++){if(e[w].nodeType===3)r.push(e[w]);else if(e[w].childNodes)r=r.concat(t(e[w].childNodes));}return r;}var e,x=t(document.body.childNodes),y,z;for(y=0;y<x.length;y++){x[y].data=x[y].data.replace(/\b[a-z]{4,}\b/ig,function(w){if(w.length==4&&(/^.([a-z])\1./i).test(w))return w;e=w;while(e==w){z=w.split('');e=z[0]+(z.slice(1,z.length-1).sort(function(a,b){return(Math.random()*2)>1?1:-1;}).join(''))+z[z.length-1];}return e;});}

Un-minified version: (479bytes) 1

function t(e){
  var r=[],w;
  for(w=0;w<e.length;w++){
    if(e[w].nodeType===3)r.push(e[w]);
    else if(e[w].childNodes)r=r.concat(t(e[w].childNodes));
  }
  return r;
}
var x=t(document.body.childNodes);
for(var y=0;y<x.length;y++){
  x[y].data=x[y].data.replace(/\b[a-z]{4,}\b/ig,function(w){
    if(w.length==4&&(/^.([a-z])\1./i).test(w))
      return w;
    var e=w;
    while (e==w){
      var x=w.split('');
      e=x[0]+(x.slice(1,x.length-1).sort(function(a,b){
        return(Math.random()*2)>1?1:-1;
      }).join(''))+x[x.length-1];
    }
    return e;
  });
}
  • No use of jQuery ("pure javascript")
  • Add <script src="garble.js"></script> just above </body> or wrap the code in an onload event.

1 re-placement of var declarations makes it shorter, see 479bytes vs 473 byes)


Additional Versions

Basic (demo)

// jQuery Garble
// "Basic" version
//
// Requirements:
// 1. Find all words 4+ letters long (exclude hyphens, punctuation or numbers from
//    the classification)
// 2. The words being garbled must follow:
//    a. They can not remain the same as the previous state
//    b. The first and last character must remain in-tact
// 3. The garbling must be random and produce a new result each iteration.
//
// Usage:
// $(selector).garble(options);
//
(function($){
    $.fn.extend({
        garble: function(options){
            // basic options
            var o = $.extend({
                flagChanges: false,
                changeClass: 'modified'
            },options);
            // iterate over elements
            return this.each(function(i,e){
                var txt = $(e).text();
                // locate words with 4+ letters
                $(e).html(txt.replace(/\b[a-z]{4,}\b/ig,function(w){
                    var e = w;
                    // make sure we get an altered word back
                    while (e==w){
                        var x = w.split('');
                        e = x[0]+(x.slice(1,x.length-1).sort(function(y,z){
                            return (Math.random()*2)>1?1:-1; // randomize
                        }).join(''))+x[x.length-1];
                    }
                    return (o.flagChanges?'<span class="'+o.changeClass+'">'+e+'</span>':e);
                }));
            });
        }
    });
})(jQuery);

Pros

  1. Very slim and trim
  2. Options that allow you to modify the modified word (wraps each change in a span with the default "modified" class, or a class of your choosing).

Cons

  1. Won't work with nested elements (this means you have to select the lowest possible element in the DOM tree. So, if you're just going to be doing paragraphs with no hyperlinks or spans within, this is your winner)
  2. If elements that have children are used in the selector, their html formatting (such as a linkin a paragraph) will be stripped.

Slim and Trim (demo)

$(function(){                                                              // on load
  $('*','body').contents().map(function(i,e){                              // grab all elements,
    return e.nodeType !== 3 ? null : e;                                    // then filter by textual elements
  }).each(function(i,e){                                                   // amd iterate through them.
    e.data = e.data.replace(/\b[a-z]{4,}\b/ig, function(w) {               // go through and find 4+ letters words
      if (w.length==4&&w.substring(1,2)==w.substring(2,3))                 // (avoid infinite loops on words that
        return w;                                                          // can't be changed [e.g. look])
      var e = w;                                                           // Store the original word for comparison, but
      while (e==w){                                                        // keep scrambling until we have a new word.
        var x = w.split('');                                               // (do so by breaking out middle letters in to array,
        e = x[0] + (x.slice(1, x.length - 1).sort(function(a,b){           // then sort those middle letters
          return (Math.random() * 2) > 1 ? 1 : -1;                         // based on a random number)
        }).join('')) + x[x.length - 1];                                    // Now, rejoin it all back together
      }
      return e;                                                            // and finally return the modified result.
    });
  });
});

Fully Featured (demo)

// jQuery Garble
// "Feature Rich" version
//
// Requirements:
// 1. Find all words 4+ letters long (exclude hyphens, punctuation or numbers from
//    the classification)
// 2. The words being garbled must follow:
//    a. They can not remain the same as the previous state
//    b. The first and last character must remain in-tact
// 3. The garbling must be random and produce a new result each iteration.
//
// Usage:
// $(selector).garble(options);
//
(function($) {
    $.fn.extend({
        garble: function(options) {
            var o = $.extend({}, $.fn.garble.defaults, options);

            // takes in a string and performs the necessary manipulation(s) on it. Use regex
            // to only collect words greater than or equal to 4 characters long, and consider
            // punctuation not part of the word.
            var garbleStr = function(s,t){
                return s.replace(/\b[a-z]{4,}\b/ig, function(w) {
                    var e = o.algorithm(w);

                    // if we're not performing a low-level parse and they want the changes styled,
                    // return a span with either the detault class or their custom class
                    if (t && !o.lowLevel && o.highLevel.flagChanges)
                        return '<span class="'+o.highLevel.changeClass+'">'+e+'</span>';

                    // just return the new word
                    return e;
                });
            };

            // Very high-level process.
            // Will only change the lowest node's text (so a paragraph
            // with links, only the links will be altered)
            var highLevel = function(i, e) {
                // we're not at the bottom element, keep going
                if ($(e).children().length>0){
                    return $(e).children().each(highLevel);
                }

                var t = $(e).text();
                $(e).html(garbleStr(t,e.tagName!=='TEXTAREA'));
            };
            // Low level process
            // Goes down to each individual element and changes it
            var lowLevel = function(i, e) {
                var d = e.data;
                e.data = garbleStr(d);
            };

            // depending their selection, execute either or
            if (o.lowLevel){
                return this.find('*').contents().map(function(i, e) {
                    return (e.nodeType !== 3 ? null : e);
                }).each(lowLevel);
            }else{
                return this.contents().each(highLevel);
            }
        },
    });

    // Defaults
    $.fn.garble.defaults = {
        // Perform low-level modification? (Modifies all words it finds,
        // not just the one farthests down the tree)
        lowLevel: false,

        // when lowLevel is set to false, these options are available:
        highLevel: {
            // wrap changes in a <span>
            flagChanges: false,

            // the class being applied to the above <span>
            changeClass: 'modified'
        },

        // function used to garble the text. This will be passed each word
        // individually and should return the new word's value.
        algorithm: function(w) {
            // if w = "hello":
            // 1. Make an array out of the letters.
            // 2. keep the first and last in-tact, but use .slice() to extract the middle
            // 3. Perform the specified algorithm on the middle characters
            // 4. return result
            var e = w;
            while (e==w){ // secure it gets changed
                var x = w.split('');
                e = x[0] + (x.slice(1, x.length - 1).sort(function(a,b){
                    return (Math.random() * 2) > 1 ? 1 : -1;
                }).join('')) + x[x.length - 1];
            }
            return e;
        }
    };
})(jQuery);

Pros

  1. Flexible. This will work in just about every scenario, albeit a quick-to process bottom-of-the-food-chain element or calling it on the entire <body>--this can handle it.
  2. Very customizable:
    1. Can specify to perform low level (every element) changes, or highLevel (only elements with no children)
    2. Can specify if you want to show the changes, and what class to apply (changes are wrapped in a span with the specified class name)
    3. Can specify a function to use for scrambling (maybe you want to just reverse the words or use a better method)

Cons

  1. Little more overhead, though it is still fast.
  2. Maybe too many options or just too bloated.
  3. I'm sure someone will find more cons
Brad Christie
  • 100,477
  • 16
  • 156
  • 200
  • for some reason it always randomizes the text exactly the same way. Is there something wrong with your sort randomizer function? See condition 6 (which wasn't there when you started but I never claimed to be fair) – qwertymk Feb 11 '11 at 03:02
  • Yeah I think you want to change `(Math.random()*2)>0` to `(Math.random()*2)>1`. – ide Feb 11 '11 at 03:15
  • @ide: indeed, changing the random makes the word scrambled better. ;-) – Brad Christie Feb 11 '11 at 04:21
  • nice.. glad to have inspired :) contents().map(function(i, e) { return (e.nodeType !== 3 ? null : e); }).each( – Amjad Masad Feb 11 '11 at 19:46
  • @Amjad: Indeed, I had gone the more extensive route and had saw your use of map--well played. I'm always looking for short-hand (and forgot jQuery had that function to be honest. I always fund myself using array_map in PHP so it only made sense). ;-) – Brad Christie Feb 11 '11 at 20:50
  • @Brad @AmjadMasad the rules have been updated and a bounty set – qwertymk Feb 14 '11 at 23:30
  • @qwertymk: So Just shortest within the confines? All i have to do is remove the options and it'll be pretty short. – Brad Christie Feb 14 '11 at 23:32
  • yeah but #7 says pure javascript – qwertymk Feb 15 '11 at 03:57
  • @qwertymk: And what's unpure about jQuery? I was unaware that used another language... – Brad Christie Feb 15 '11 at 04:23
  • pure as in no framework and no external code needed to be executed – qwertymk Feb 15 '11 at 05:25
  • @qwertymk: How's that, more your style? – Brad Christie Feb 16 '11 at 02:03
  • Returning random numbers from your comparator is bad form. Look at how it messed up the Browser Choice screen: http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html – Neil Feb 17 '11 at 20:36
  • @Neil: Not too concerned with "bad form" at this point. This went from a hey, cool project" to a PITA in a few updates. In a scenario where size wasn't a convern I would be more critical to the algorithm I used. In this case, however, not the top priority list. Function over form. – Brad Christie Feb 17 '11 at 20:42
  • WOW, seriously? Now what am I supposed to do? :) – qwertymk Feb 20 '11 at 08:54
  • @qwertymk: Well, I can change my random method and shrink it... ;p – Brad Christie Feb 20 '11 at 13:50
  • Why the stricy equality? Can't you save a character by using `==` over `===`? – Thomas Eding Feb 21 '11 at 20:33
  • @trinithis: a bunch of modifications could be made: less an =, place the code within the "find elements" loop and save the trouble of populating an array first just to dismantle, condense/re-use variable names, etc. I just lost motivation. And the bounty has already been skipped, so not really too motivated to be quite honest. As I said, this was fun but now is just an endless project. – Brad Christie Feb 21 '11 at 20:38
1

Here's my take on the function. See the demo here.

  • It uses the excellent jquery.ba-replacetext plugin to handle finding only text nodes
  • Handles arbitrary length words where all inner characters are equal
  • Easy to understand with descriptive names

Full version (1187 bytes):

  /* Called on document.ready */
  $(function () {
    $("body *").replaceText(/\b([A-z]{4,})\b/g, scramble_inner );
  });
  /* Scramble the inner characters of a word */
  function scramble_inner(word) {
    return word[0] 
      + force_shuffle(word.slice(1, word.length - 1))
      + word[word.length - 1];
  }
  /* Randomize characters in the string, but check to make sure
   * they're different from the original. Handle's the special
   * case where all inner characters are equal, for instance "moooo".
   */
  function force_shuffle(str) {
    if (all_chars_same(str)) return str;
    var result = str;
    while (str === result) {
      result = str.split('').sort(function() {
        return Math.floor(Math.random() * 2) ? 1 : -1;
      }).join('');
    }
    return result;
  }
  /* Check whether all characters in the string are equal, eg "ooo" */
  function all_chars_same(str) {
    for (i = 0; i < str.length; i++) {
      if (str[i] !== str[0]) {
        return false;
      }
    }
    return true;
  }

Minified version (348 bytes):

$(function(){$("body *").replaceText(/\b([A-z]{4,})\b/g,a)});function a(w){return w[0]+b(w.slice(1,w.length-1))+w[w.length-1]}function b(s){if(c(s))return s;var r=s;while(s===r){r=s.split('').sort(function(){return Math.floor(Math.random()*2)?1:-1}).join('')}return r}function c(s){for(i=0;i<s.length;i++){if(s[i]!==s[0]){return false}}return true}
Jack Senechal
  • 1,600
  • 2
  • 17
  • 20
0

Note: I ran this through JSLint, which may have been a bad idea. Anyway, the demo is here.

function shuffle(letters) {
    var i = letters.length - 2;
    while (i > 1) {
        var pos = Math.floor(Math.random() * i) + 1;
        var tmp = letters[i];
        letters[i] = letters[pos];
        letters[pos] = tmp;
        i--;
    }
}

function scramble(word) {
    if (word.slice(1, -2) == word.slice(2, -1)) {
        return word;
    }
    var letters = word.split('');
    var result = word;
    while (result == word) {
        shuffle(letters);
        result = letters.join('');
    }
    return result;
}

function process(node) {
    var data = node.data;
    if (/[a-z]{4}/i.test(data)) {
        node.data = data.replace(/[a-z]{4,}/gi, scramble);
    }
}

function traverse(element) {
    var node = element.firstChild;
    while (node) {
        if (node.nodeType == Node.ELEMENT_NODE) {
            traverse(node);
        } else if (node.nodeType == Node.TEXT_NODE) {
            process(node);
        }
        node = node.nextSibling;
    }
}

function garble() {
    traverse(document.body);
}

window.onload = garble;
Neil
  • 54,642
  • 8
  • 60
  • 72