Search code examples
javascriptcode-golfscramble

garble function contest


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(' ');
    }
};

Solution

  • 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!!