11. Symmetric
function symmetric(x, y) {
return x == y && y != x;
}
Задача, в которой явно нужно перечитать алгоритм сравнения двух значений. Правда, в нём вроде бы нет каких-то логических багов, и приведение типов от перестановки мест не меняется. То есть, вот эти две операции дадут одинаковое значение:
> 1 == '1'
true
> '1' == 1
true
И так со всеми типами. А потому единственное, что нас может заинтересовать, это алгоритм приведения объекта к примитиву.
При приведении объекта к примитиву, первым делом проверяется наличие у него метода @@toPrimitive
. Если он есть, то используется возвращаемое им значение. А раз так, то можем в качестве решения использовать объект и число, причём объект будет возвращать разные значения при первом и втором вызове.
Например, объект может выглядеть так:
{
[Symbol.toPrimitive]: () => {
if (window.x) {
return 2;
} else {
window.x = 1;
return 1;
}
}
}
Сожмём, и вот, решение в 49 символов готово:
symmetric({[Symbol.toPrimitive]:_=>window.x?2:window.x=1},1)
Можем воспользоваться хаком из пятой задачи и сократить ещё пару символов:
symmetric({[Symbol.toPrimitive]:_=>window.x=-~window.x},1)
Однако, Symbol.toPritimive
слишком длинный и всё рушит. А потому стоит пойти дальше по алгоритму приведения объекта к примитиву, и взять вместо @@toPrimitive
свойство valueOf
. Для нас оно будет работать один-в-один так же:
symmetric({valueOf:()=>window.x=-~window.x},1)
И вот, уже 35 символов. Правда, в топе вон у всех 20:
Значит надо искать, что ещё сократить. Очевидный кандидат на эту должность — window
. Но мы не можем просто убрать его:
> symmetric({valueOf:_=>x=-~x},1)
ReferenceError: x is not defined
Такое решение не работает, потому что x
не объявлен внутри функции. Но нам ведь ничего не мешает объявить x
!
> symmetric({valueOf:_=>x=-~x},x=1)
false
Теперь всё ломает хак с -~
, но нам это даже на руку, потому что вместо него можно использовать обычный постфиксный инкремент:
> symmetric({valueOf:_=>x++},x=1)
true
Вуа-ля! Решение в 20 символов готово.