Конкурс конкурсных функций
Помните это пропущенное сообщение о цели, в котором сказано, как:
Aoccdrnig to rscheearch в Cmabrigde Uinervtisy, он не имеет никакого отношения к тому, чтобы быть втянутыми в ловушку: olny iprmoetnt tihng - это frist и lsat ltteer на rghit pclae. Rset может быть тотальным mses, и вы можете сидеть сломать его в wouthit porbelm. Тих - это bcuseae huamn mnid deos, не растерзанный ervey leter by istlef, но wrod как wlohe.
В любом случае я пытаюсь сделать функцию, которая будет делать это на всей странице. Для этой функции существует несколько правил.
- осталось менее 4 символов.
- не буквенно-цифровые символы не считаются частью слова.
- переносимые слова - это действительно два слова
- слова должны быть искажены, если length >= 4 (не может быть как оригинал)
- Первый и последний символы остаются неизменными, и только средние символы становятся искаженными (спасибо Hersheezy)
- текст всегда должен быть случайным и создавать уникальные искажения при каждом прогоне
- Чистый javascript и итерации на всех текстовых узлах
- Побеждает самый короткий сладкий код.
В любом случае, это кажется достаточно простым для реализации, как насчет начала конкурса, чтобы узнать, кто может сделать самый чистый код для выполнения этой задачи. Не стесняйтесь брать в долг без признания моего кода (у меня есть)
Если я пропустил что-нибудь, добавьте его в комментарии. В любом случае я работал над этим очень халявно, и здесь я показываю свою менее чем парную работу.
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(' ');
}
};
Ответы
Ответ 1
ОБНОВЛЕНИЕ (ПОСЛЕДНИЕ). Не думайте, что это может быть меньше. DEMO
Последний сжатый (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});
код:
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 даже.. меньше..
Даже меньшая версия
Я не знаю, как использовать minifier, но это должно быть как минимум ( EDIT 108).
сжатая версия (365 байт):
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})}
Код:
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;
});
}
ИЗМЕНИТЬ
НОВЫЕ ПРАВИЛА ДЕМО
КОД:
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;
});
}
Это должно уважать все правила и сохранять формат и пунктуацию. 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 хороший выбор статьи, WWiWieikikb!!
Ответ 2
Обновление
Поэтому я не мог не поиграть с этой штукой и посмотреть, какие другие способы я могу манипулировать документом, используя как можно меньше кода. Достаточно сказать, что он может быть сокращен для работы в любом случае или в сценарии, но мне нравится делать вещи с настройками для пользователя, с которыми можно играть.
Сказав это, рассмотрим некоторые варианты вышеизложенного и преимущества/разочарования:
Официальное представление (473 байт)
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;});}
Неограниченная версия: (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;
});
}
- Не использовать jQuery ( "чистый javascript" )
- Добавьте
<script src="garble.js"></script>
чуть выше </body>
или оберните код в событие onload.
1 повторное размещение объявлений var делает его короче, см. 479bytes vs 473 byes)
Дополнительные версии
Основные (демонстрация)
// 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);
Доводы
- Очень тонкий и аккуратный
- Параметры, которые позволяют изменять измененное слово (обертывает каждое изменение в диапазоне с измененным по умолчанию классом или выбранным вами классом).
против
- Не будет работать с вложенными элементами (это означает, что вам нужно выбрать самый низкий возможный элемент в дереве DOM. Итак, если вы собираетесь делать абзацы без гиперссылок или интервалов внутри, это ваш победитель)
- Если в селекторе используются элементы с дочерними элементами, их форматирование html (например, ссылка на абзац) будет удалено.
Slim и Trim (демонстрация)
$(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.
});
});
});
Полнофункциональный (демонстрация)
// 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 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 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);
Доводы
- Гибкая. Это будет работать практически во всех сценариях, хотя бы быстро обработать элемент "нижняя часть пищевой цепи" или вызвать его во всем
<body>
- это может справиться с этим.
- Очень настраиваемый:
- Может указывать на выполнение изменений нижнего уровня (каждый элемент) или highLevel (только элементы без детей)
- Можно указать, хотите ли вы отображать изменения и какой класс применять (изменения обернуты в диапазон с указанным именем класса)
- Можно указать функцию, используемую для скремблирования (возможно, вы хотите просто отменить слова или использовать лучший метод)
против
- Немного больше накладных расходов, хотя и по-прежнему быстро.
- Возможно, слишком много вариантов или просто слишком раздутых.
- Я уверен, что кто-то найдет больше cons
Ответ 3
Здесь я беру на себя функцию. См. Демонстрацию здесь.
- Он использует отличный jquery.ba-replacetext плагин для обработки только текстовых узлов
- Обрабатывает произвольные слова длины, где все внутренние символы равны
- Легко понять с описательными именами
Полная версия (1187 байт):
/* 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 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;
}
Минимальная версия (348 байт):
$(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}
Ответ 4
Примечание. Я запускал это через JSLint, что, возможно, было плохой идеей. Во всяком случае, демо здесь.
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;