
JavaScript中异常编程的另一项人为任务。 这次是在即将到来的2019年新年之际。 我希望决定对我来说有多有趣也会同样有趣。 我问好奇下猫。 所有的香槟和所有的快乐!
先前的任务:
在过去的一年中,圣诞老人收集了一份不错的普通开发人员名单,现在计划编写一个祝贺程序。 格式为: happy new year, ${username}!
。 但是,这是不幸的事情:键盘崩溃,并且不允许您输入许多拉丁字符。 在检查了缺陷之后,精灵们得出了一个有趣的发现,从其他方面来看,您可以Snowing day
。 您可以自行选择输出源。
因此,在输入处-一组非空字符串(名称不能为空)。 要求仅使用拉丁字符S
, n
, o
, w
, i
, g
, d
, a
, y
(总共9个字符,其中一个为大写)编写程序。 程序应遍历传递的数组,并为每个名称输出短语happy new year, ${username}!
使用某种输出源: alert , console.log或任何想到的东西。 好吧,最好不要污染全球环境。
习惯性解决方案
如果您什么都不做,那么一切都很简单:
function happy(users) { for (let i = 0; i !== users.length; i += 1) { console.log(`happy new year, ${users[i]}!`); } }
或更好的是:
function happy(users) { users.forEach(user => console.log(`happy new year, ${user}!`)); }
我们使用我们的数组,让它成为用户 :
let users = ['John', 'Jack', 'James']; happy(users);
但是这是牵强的:在拉丁语实现中仅使用允许的字符。 尝试自己应对,然后加入讨论。
有趣的决定
不耐烦的人现在可以在JSFiddle中看到下面的解决方案。
要解决此问题,您需要在以下方面消除多余的拉丁语:
- 函数声明中的关键字函数 。
- let (或var )关键字,用于声明变量。
- 组织循环以迭代传递的数组时的关键字。
- 消息文本的形成。
- 调用一些函数以输出结果。
箭头功能可以轻松帮助我们解决第一个问题:
(arr => el.forEach())(users);
我们现在不会关注变量名,因为我们可以很容易地在最后对它们进行重命名。
在需要功能或其立即产生结果的地方,我们在IIFE中使用“箭头”。 另外,函数允许您通过两种方式摆脱let和var指令:
(param => )(value); ((param = value) => )();
在这两种情况下,我们都在函数参数中声明一个变量。 仅在第一种情况下,我们在调用函数时传递值,而在第二种情况下,我们使用默认函数参数。
确实,问题从第三点开始。 我们没有足够的字符来表示经典的for , do , while循环,也没有足够的字符通过for..in和for..of进行遍历 ,也没有足够的数组方法forEach , map , filter (可以在其中传递回调)。 但是我们可以在数组上实现迭代功能:
function iterate(arr, consume) { function iter(i) { if (arr[i]) { consume(arr[i]); iter(++i); } } iter(0); }
我们递归地遍历元素,直到当前条件的验证失败为止。 为什么在这里我们可以依靠逻辑转换? 因为数组的元素不是一个空字符串(只包装了false ),但是当我们通过索引的增量退出数组时,我们得到的是不确定的 (传递给false )。
我们使用箭头表达式重写该函数:
let iterate = (arr, consume) => ( (iter = i => { if (arr[i]) { consume(arr[i]); iter(++i); } }) => iter(0) )();
但是我们不能使用if语句 ,因为我们没有字符f
。 为了使我们的功能满足条件,我们需要摆脱它:
let iterate = (arr, consume) => ( (iter = i => arr[i] ? (consume(arr[i]), iter(++i)) : 0) => iter(0) )();
三元运算符以及通过逗号运算符将两个表达式组合为一个表达式的能力帮助了我们。 我们稍后将在解决方案的布局中使用此功能。
第四个问题是,无论如何我们都需要获得一个字符串,该字符串缺少字符。 显然,我们将使用数字来表示字符。 有几种选择:
- 一个String.fromCharCode函数,期望返回整数并返回从指定Unicode序列创建的字符串。
\uhhhh
序列\uhhhh
允许\uhhhh
使用指定的十六进制代码输出任何Unicode字符。- 格式
&#dddd;
for html-characters允许您使用指定的十进制代码在页面文档中显示符号。 - Number原型对象的toString函数具有一个附加的基数参数-数字系统的基础。
- 也许还有别的东西...
您可以按照前三个选项的方向进行研究,现在考虑执行此任务的最简单的选择: Number.prototype.toString 。 基数参数的最大值为36(10位数字+ 26个小写拉丁字符):
let symb = sn => (sn + 9).toString(36);
因此,我们可以通过字母中的数字(从1开始)获取任何拉丁字符。唯一的限制是所有字符均小写。 是的,这足以让我们在消息中显示文本,但是我们无法添加一些方法和功能( forEach相同)。
但是现在还为时过早,首先您需要在函数条目中删除toString 。 首先,我们转向如下方法:
let symb = sn => (sn + 9)['toString'](36);
如果仔细看,对于字符串toString
我们只需要两个字符: t
和r
:其余所有字符都在Snowing
一词中。 获取它们非常简单,因为它们的顺序已经暗示了true
。 使用隐式类型转换,我们可以获取此字符串和所需的字符,如下所示:
!0+'';
我们实现了获取任何拉丁字母的功能:
let symb = sn => (sn + 9)[(!0+'')[0] + 'oS' + (!0+'')[0] + (!0+'')[1] + 'ing'](36);
要使用symb从字母序列号数组中获取单词,我们使用标准的Array.prototype.reduce函数:
[1,2,3].reduce((res, sn) => res += symb(sn), '');
是的,这不适合我们。 但是在我们的解决方案中,我们可以使用迭代函数执行类似的操作:
let word = chars => (res => (iterate(chars, ch => res += symb(ch)), res))(''); word([1,2,3]);
细心的人会注意到,我们为字符串数组开发了迭代函数,但此处将其与数字一起使用。 这就是为什么字母表的初始索引为1而不是0的原因。否则,即兴循环将在遇到0时结束(字母a
)。
为了便于将字符映射到其序列号,您可以获取一个词典:
[...Array(26).keys()].reduce((map, i) => (map[symb(i + 1)] = i + 1, map), {});
但是,更简单地编写单词整体逆变换的功能是更明智的:
let reword = str => str.split('').map(s => parseInt(s, 36) - 9); reword('happy');
我们完成了形成消息本身的功能:
let message = name => word([8,1,16,16,25]) + ' ' + word([14,5,23]) + ' ' + word([25,5,1,18]) + ', ' + name + '!';
关于第五个问题的结论,剩下的很少。 想到控制台 , 警报 , 确认 , 提示 , innerHTML , document.write 。 但是列出的所有选项都无法直接实现。
我们也有机会使用word函数获取任何单词。 这意味着我们可以通过使用方括号访问对象来调用对象的许多函数,就像toString一样 。
假定我们使用箭头函数,则此上下文保持全局(并且无需转发它)。 在任何地方,我们都可以通过一行访问其许多功能:
this[word([1,12,5,18,20])]('hello');
但是,为了“绘制” this
我们再次缺少字符。 我们可以用Window.self替换它,但是就可用字母而言,它甚至更糟。 但是,值得注意的是窗口对象本身,其“轮廓”对于我们来说是相当令人满意的,即使它本来可以是山羊,也要更长!
顺便说一句,在该任务的第一个版本中,关键短语仅是Snowing
单词,并且无法折叠window
(由于缺少d
字符)。 对上下文的访问基于jsfuck技巧之一:
(_ => 0)['constructor']('return this')()['alert']('hello');
或者,您也可以直接在全局上下文中访问某些内容:
(_ => 0)['constructor']('return alert')()('hello');
如您所见,在示例中,整个拉丁语排成一行。 在这里,我们从字符串创建函数,然后从浪费的箭头函数访问函数 (构造函数)。 但这太多了! 也许其他人知道如何在我们的条件下访问上下文?
最后,我们将所有内容放在一起! 我们的“ main”函数的主体将为传递的数组调用iterate ,并且使用者将输出已经内置的消息编译函数的结果。 对于消息和命令的文本,使用了一个单词函数,该函数也需要迭代 ,我们将在默认参数中确定它。 像这样:
(users => ( (( // firstly we need the iterating function iterate = (array, consume) => ((iter = i => array[i] ? (consume(array[i]), iter(++i)) : 0) => iter(0))(),
使用允许的字母重命名变量:
(_10 => ( (( _123 = (ann, snow) => ((_12 = i => ann[i] ? (snow(ann[i]), _12(++i)) : 0) => _12(0))(), wo = ann => (w => (_123(ann, an => w += (an + 9)[(!0+'')[0] + 'oS' + (!0+'')[0] + (!0+'')[1] + 'ing'](36) ), w) )('') ) => _123(_10, _1 => window[wo([3,15,14,19,15,12,5])][wo([12,15,7])]( wo([8,1,16,16,25]) + ' ' + wo([14,5,23]) + ' ' + wo([25,5,1,18]) + ', ' + _1 + '!' ) ))() ))(users);
可变的 | 内容描述 |
---|
_123 {function} | 迭代函数可迭代数组的元素。 |
_12 {function} | 迭代调用的本地iter函数。 |
雪 {function} | 使用函数作为iterate的回调。 |
安 {Array<Any>} | 数组参数。 |
{Any} | 数组元素参数。 |
wo {function} | 单词功能形成单词。 |
w {string} | 局部变量,用于累积单词中的字符串。 |
_10 {Array<string>} | 原始的用户数组。 |
_1 {string} | 源数组中的用户,他的名字。 |
仅此而已。 写出您对此的想法和想法,因为有很多选择可以做一些不同的事情或根本不做。
结论
有趣的是,针对问题的条件提出一个单词或短语是一项真正的考验。 我希望她简短而不是很有启发性,并且适合或多或少的简洁解决方案。
JavaScript的功能和许多人所熟知的6个字符的同义性为这项任务提供了灵感。 如先前讨论的任务,这可能在主题上有多种变体,而不是唯一的解决方案。 只需简单的措辞和关键词即可。 新的一年见!