Советы по игре в гольф в ECMAScript 6 и выше

88

Это похоже на другие «Советы по игре в гольф в <...>», но специально нацелено на новые функции в JavaScript, появившиеся в ECMAScript 6 и выше.

JavaScript по своей сути является очень многословным язык, function(){}, .forEach(), преобразование строки в массив, массив типа объекта в массив, и т.д., и т.д., супер раздувается и не здоровые для игры в гольф.

ES6 +, с другой стороны, имеет некоторые удобные функции и уменьшенную площадь. x=>y, [...x]И т.д. лишь некоторые из примеров.

Пожалуйста, опубликуйте несколько полезных трюков, которые помогут избавиться от этих нескольких лишних байтов из вашего кода.

ПРИМЕЧАНИЕ: трюки для ES5 уже доступны в разделе Советы по игре в гольф на JavaScript ; ответы на эту тему должны быть сосредоточены на уловках, доступных только в ES6 и других будущих версиях ES.

Тем не менее, эта тема также для пользователей, которые в настоящее время играют в гольф, используя функции ES5. Ответы могут также содержать советы, которые помогут им понять и сопоставить функции ES6 с их стилем кодирования ES5.

оптимизатор
источник

Ответы:

42

Оператор спреда ...

Оператор распространения преобразует значение массива в список через запятую.

Вариант использования 1:

Непосредственно используйте массив, где функция ожидает список

list=[1,2,3]
x=Math.min(...list)
list=[10,20], a.push(...list) // similar to concat()

Вариант использования 2:

Создать литерал массива из итерируемого (обычно строки)

[...'buzzfizz'] // -> same as .split('')

Вариант использования 3:

Объявите переменное число аргументов для функции

F=(...x) => x.map(v => v+1)
// example: F(1,2,3) == [2,3,4]

Посмотреть документ Mozilla

edc65
источник
3
Теперь у меня есть отрицательный голос здесь. Очевидно, что кто-то заметил что-то ужасно неправильное в этом совете, будучи слишком стеснительным, чтобы оставить комментарий и объяснить, что ...
edc65
Это выглядит хорошо. Может быть, это было отсутствие точки с запятой? ;) (кстати, вы также можете использовать его в качестве параметров покоя, например, знаки в Ruby)
gcampbell
Можно добавить, что в сигнатурах функций также есть вариант использования :)
Феликс Домбек
Мисклик не хотел понизить голос
Стэн Струм
@StanStrum это происходит. Я сделаю небольшое обновление к этому сообщению, чтобы вы могли в конечном итоге изменить свой голос (или вы уже сделали?)
edc65
21

Трюки узнали здесь, так как я присоединился

Мой основной язык программирования - JS и в основном ES6. С тех пор, как я присоединился к этому сайту неделю назад, я научился многим полезным трюкам от других участников. Я объединяю некоторые из них здесь. Все кредиты сообществу.

Функции стрелок и петли

Все мы знаем, что функции стрелок экономят много байтов

function A(){do something} // from this
A=a=>do something // to this

Но вы должны иметь в виду несколько вещей

  • Попробуйте объединить несколько операторов, используя ,то есть (a=b,a.map(d))- Здесь возвращаемое значение является последним выражениемa.map(d)
  • если ваша do somethingчасть состоит из более чем одного утверждения, то вам нужно добавить окружающие {}скобки.
  • Если есть окружающие {}скобки, вам нужно добавить явный оператор возврата.

Вышеприведенное справедливо много раз, когда задействованы циклы. Так что-то вроде:

u=n=>{for(s=[,1,1],r=[i=1,l=2];c=l<n;i+=!c?s[r[l++]=i]=1:1)for(j of r)c-=j<i/2&s[i-j];return n>1?r:[1]}

Здесь я трачу как минимум 9 символов из-за возврата. Это может быть оптимизировано.

  • Старайтесь избегать петель. Используйте .mapили .everyили .someвместо. Обратите внимание, что если вы хотите изменить тот же массив, который вы отображаете, он потерпит неудачу.
  • Оберните цикл в функцию стрелки закрытия, преобразовав функцию главной стрелки в один оператор.

Таким образом, выше становится:

u=n=>(s=>{for(r=[i=1,l=2];c=l<n;i+=!c?s[r[l++]=i]=1:1)for(j of r)c-=j<i/2&s[i-j]})([,1,1])|n>1?r:[1]

удаленные символы: {}return

добавленные персонажи: (){}>|

Обратите внимание, как я вызываю метод замыкания, который правильно заполняет переменную, nа затем, поскольку метод замыкания ничего не возвращает (т. Е. Возвращает undefined), я бита или его и возвращаю массив nвсе в одном выражении внешней функции стрелкиu

Запятые и точки с запятой

Избегайте их, что бы ни случилось,

Если вы объявляете переменные в цикле или как уже упоминалось в предыдущем разделе, используя ,отдельные операторы, чтобы иметь функции стрелок с одним оператором, то вы можете использовать несколько довольно изящных приемов, чтобы избежать их ,или ;сбрить последние несколько байтов.

Рассмотрим этот код:

r=v=>Math.random()*100|0;n=r();m=r();D=v=>A(n-x)+A(m-y);d=0;do{g();l=d;d=D();....

Здесь я вызываю много методов для инициализации многих переменных. Каждая инициализация использует ,или ;. Это может быть переписано как:

r=v=>Math.random()*100|0;n=r(m=r(d=0));D=v=>A(n-x)+A(m-y);do{d=D(l=d,g());....

Обратите внимание, как я использую тот факт, что метод не беспокоит передаваемую ему переменную, и использую этот факт, чтобы сбрить 3 байта.

Разное

.search вместо .indexOf

Оба дают одинаковый результат, но searchкороче. Хотя поиск ожидает регулярное выражение, поэтому используйте его с умом.

`Строки шаблона`

Это очень удобно, когда вам нужно объединить одну или несколько струнных частей на основе определенных условий.

Возьмите следующий пример для вывода квин в JS

(f=x=>alert("(f="+f+")()"))()

против

(f=x=>alert(`(f=${f})()`))()

В строке шаблона, которая является строкой внутри двух обратных кавычек (`), все, что внутри a ${ }, обрабатывается как код и оценивается для вставки полученного ответа в строку.

Я опубликую еще несколько трюков позже. Удачного игры в гольф!

оптимизатор
источник
1
.search короче, используйте его, когда это возможно! но это не то же самое .indexOf. .search хочет, а regexpне строка. Попробуйте'abc'.search('.')
edc65
@ edc65 Обновлено!
Оптимизатор
Вы можете изменить исходный массив с помощью методов экземпляра. Второй - текущий индекс, а третий - итеративный массив.
Isiah Meadows
8
«Зашел на сайт неделю назад» - 21,4 тыс. Респ ...
GamrCorps
2
Кроме того .map, рекурсия - это еще один метод, который иногда может помочь вам превратить forцикл в выражение.
Нил
20

Использование сокращений свойств

Сокращения свойств позволяют устанавливать переменные для значений массивов:

a=r[0];b=r[1] // ES5
[a,b]=r       // ES6 - 6 bytes saved

Это также может быть использовано как:

a=r[0],b=r[2] // ES5
[a,,b]=r      // ES6 - 5 bytes saved

Вы даже можете использовать это для обращения переменных:

c=a,a=b,b=c // ES5 - uses extra variable
[b,a]=[a,b] // ES6 - not shorter, but more flexible

Вы также можете использовать это для сокращения slice()функций.

z = [1, 2, 3, 4, 5];

a=z.slice(1) // a = [2,3,4,5]; ES5
[,...a]=z    // a = [2,3,4,5]; ES6

Базовые преобразования

ES6 предоставляет гораздо более короткий способ преобразования форм Base-2 (двоичный) и Base-8 (восьмеричный) в десятичный:

0b111110111 // == 503
0o767       // == 503

+может использоваться для преобразования двоичной, восьмеричной или шестнадцатеричной строки в десятичное число. Вы можете использовать 0b, 0oи 0x, для двоичного, восьмеричного и шестнадцатеричного соответственно:

parseInt(v,2) // ES5
+('0b'+v)     // ES6 - 4 bytes saved; use '0o' for octal and '0x' for hex
'0b'+v-0      // Shorter, but may not work in all cases
              // You can adapt this your case for better results

Если вы используете это> 7 раз, то использовать parseIntи переименовать его будет короче :

(p=parseInt)(v,2)

Теперь pможно использовать для parseIntэкономии многих байтов в долгосрочной перспективе.

Downgoat
источник
Трюк с базовым преобразованием хорош, но более вероятно, что число преобразования будет в форме переменной, а не литерала, и в этом случае оно становится намного длиннее.
Оптимизатор
1
'0x'+v-0еще короче, но может не работать так же хорошо в некоторых сценариях.
ETHproductions
1
Кстати, 0767(ES5) короче, чем 0o767(ES6) обозначения.
Камило Мартин
@CamiloMartin 0767- это нестандартное расширение, и оно строго запрещено в строгом режиме.
Oriol
1
Строгий режим @Oriol был плохим мемом. Это не помогло повысить производительность, на самом деле не заставило вас писать хороший код и никогда не стало бы по умолчанию. 0восьмеричные литералы с префиксом никуда не денутся и так же корректны, как и экмаскрипт 0o.
Камило Мартин
19

Использование строковых шаблонов с функциями

Когда у вас есть функция с одной строкой в ​​качестве аргументов. Вы можете опустить, ()если у вас нет выражений:

join`` // Works
join`foobar` // Works
join`${5}` // Doesn't work 
Downgoat
источник
9
Будьте предупреждены, это фактически передает массив. fun`string` так же, как fun(["string"])нет fun("string"). Это хорошо для функций, которые преобразуются в строку, например alert, но для других это может вызвать проблемы. Для получения дополнительной информации см. Статью MDN
Cyoce
5
Краткая справка: fun`foo${1}bar${2}bazэквивалентно fun(["foo","bar","baz"],1,2)
звонку
14

Массивы (Firefox 30-57)

Примечание: понимание массива никогда не было стандартизировано и устарело в Firefox 58. Используйте на свой страх и риск.


Первоначально спецификация ECMAScript 7 содержала множество новых функций на основе массива. Хотя большинство из них не вошли в финальную версию, поддержка Firefox (ed), возможно, самая большая из этих функций: новый модный синтаксис, который можно заменить, .filterи .mapс for(a of b)синтаксисом. Вот пример:

b.filter(a=>/\s/.test(a)).map(a=>a.length)
[for(a of b)if(/\s/.test(a))a.length]

Как видите, две строки не так уж отличаются, кроме второй, не содержащей громоздких ключевых слов и функций стрелок. Но это только объясняет порядок .filter().map(); что произойдет, если у вас есть .map().filter()вместо этого? Это действительно зависит от ситуации:

b.map(a=>a[0]).filter(a=>a<'['&&a>'@')
[for(a of b)if(a<'['&&a>'@')a[0]]

b.map(a=>c.indexOf(a)).filter(a=>a>-1)
[for(a of b)if((d=c.indexOf(a))>-1)d]

b.map(a=>a.toString(2)).filter(a=>/01/.test(a))
[for(a of b)if(/01/.test(c=a.toString(2)))c]

Или что, если ты хочешь или .map или .filter ? Ну, обычно получается меньше, хорошо

b.map(a=>a.toString(2))
[for(a of b)a.toString(2)]

b.filter(a=>a%3&&a%5)
[for(a of b)if(a%3&&a%5)a]

Так что мой совет использовать постижения массива , где вы обычно используете .map и .filter , но не только один или другие.

Строковые Понимания

Хорошая вещь в понимании ES7 состоит в том, что, в отличие от функций, специфичных для массива, таких как .mapи .filter, они могут использоваться с любым итерируемым объектом, а не только с массивами. Это особенно полезно при работе со строками. Например, если вы хотите запустить каждый символ cв строке через c.charCodeAt():

x=>[...x].map(c=>c.charCodeAt())
x=>[for(c of x)c.charCodeAt()]

Это два байта, сохраненные в довольно небольшом масштабе. А что если вы хотите отфильтровать определенные символы в строке? Например, этот хранит только заглавные буквы:

x=>[...x].filter(c=>c<'['&&c>'@')
x=>[for(c of x)if(c<'['&&c>'@')c]

Хм, это не короче. Но если мы объединим два:

x=>[...x].filter(c=>c<'['&&c>'@').map(c=>c.charCodeAt())
x=>[for(c of x)if(c<'['&&c>'@')c.charCodeAt()]

Вау, целые 10 байтов сохранены!

Еще одним преимуществом понимания строк является то, что жестко закодированные строки сохраняют дополнительный байт, так как вы можете опустить пробел после of:

x=>[...'[](){}<>'].map(c=>x.split(c).length-1)
x=>[for(c of'[](){}<>')x.split(c).length-1]

x=>[...'[](){}<>'].filter(c=>x.split(c).length>3)
x=>[for(c of'[](){}<>')if(x.split(c).length>3)c]

индексирование

Понимание массива затрудняет получение текущего индекса в строке / массиве, но это можно сделать:

a.map((x,i)=>x+i).filter ((x,i)=>~i%2)
[for(x of(i=0,a))if(++i%2)x+i-1]

Главное, о чем следует помнить, это убедиться, что индекс увеличивается каждый раз, а не только при выполнении условия.

Генератор понимания

Генераторные понимания имеют в основном тот же синтаксис, что и массивы; просто замените скобки на круглые скобки:

x=>(for(c of x)if(c<'['&&c>'@')c.charCodeAt())

Это создает генератор, который работает почти так же, как массив, но это история для другого ответа.

Резюме

В принципе, хотя понимание обычно короче .map().filter(), все сводится к специфике ситуации. Лучше всего попробовать оба способа и посмотреть, что получится лучше.

PS Не стесняйтесь предложить другой связанный с пониманием совет или способ улучшить этот ответ!

ETHproductions
источник
Вот трюк для диапазонов, которые сохранят еще пару символов:(x,y)=>[...Array(y-x)].map(a=>x++)
Mwr247
2
Вы можете отрезать еще 11 байтов, чтобы получить диапазон от 0 до x:x=>[...Array(x).keys()]
Mwr247
Последний для понимания: n=>[for(x of Array(n).keys())if(/1/.test(x))x](сохраняет 7 байтов)
Mwr247
@ Mwr247 На самом деле, теперь я вижу, что диапазоны обычно не так коротки с пониманием, как с другими приятными функциями ES6. Вместо этого я добавлю в раздел, посвященный строкам, и позволю вам обрабатывать диапазоны.
ETHproductions
Стоит отметить, что Array Compreatsions устарели и удалены из всех последних версий javascript. Смотрите MDN документы по этому вопросу.
Кифер Рурк
13

Выражения функций в ES6 используют обозначение стрелки, и это помогает значительно экономить байты по сравнению с версией ES5:

f=function(x,y){return x+y}
f=(x,y)=>x+y

Если ваша функция имеет только один параметр, вы можете опустить скобки, чтобы сохранить два байта:

f=x=>x+1

Если ваша функция вообще не имеет параметров, объявите ее так, как если бы она имела один для сохранения одного байта:

f=()=>"something"
f=x=>"something"

Осторожно: функции стрелок не совсем такие, как function () {}. Правила thisразные (и лучше ИМО). См документы

Уильям Барбоза
источник
2
Но когда вы играете в гольф, вам, как правило, плевать на это thisи т. Д.
Оптимизатор
1
Обычно нет, но это предостережение, о котором вы, возможно, никогда не узнаете, когда придет время. Кроме того, лямбдам чаще всего не требуется локально привязывать эту функцию в производственной среде.
Isiah Meadows
Кроме того, если вы хотите получить все свои аргументы, вы можете использовать функцию аргумента rest, например, f=(...x)=>x иметь ее f(1,2,3) => [1,2,3].
Конор О'Брайен
1
Вот совет, характерный для этого сайта: если вы отвечаете с помощью функции, которая принимает форму, (x,y)=>...вы можете сохранить байт с каррированием , заменив его наx=>y=>...
Cyoce
12

Использование evalдля функций со стрелками с несколькими операторами иreturn

Один из самых нелепых трюков, с которыми я столкнулся ...

Представьте себе простую функцию со стрелкой, которая требует нескольких операторов и a return.

a=>{for(o="",i=0;i<a;i++)o+=i;return o}

Простая функция, принимающая один параметр a, который перебирает все целые числа [0, a)и прикрепляет их к концу выходной строки o, которая возвращается. Например, вызов этого 4параметра в качестве параметра даст результат 0123.

Обратите внимание, что эта функция стрелки должна быть заключена в фигурные скобки {}, и иметьreturn o в конце.

Эта первая попытка весит 39 байт .

Неплохо, но с помощью evalмы можем улучшить это.

a=>eval('for(o="",i=0;i<a;i++)o+=i;o')

Эта функция удалила фигурные скобки и оператор return, обернув код в evalи просто сделав последний оператор в evalоценке to o. Это приводит evalк возврату o, что, в свою очередь, приводит к возврату функции o, так как теперь это один оператор.

Эта улучшенная попытка весит 38 байтов , сохраняя один байт от оригинала.

Но подождите, это еще не все! Eval-операторы возвращают все, что их последний оператор оценил. В этом случае o+=iоценивает o, поэтому нам не нужно ;o! (Спасибо, edc65!)

a=>eval('for(o="",i=0;i<a;i++)o+=i')

Эта последняя попытка весит всего 36 байтов - 3 байта экономии по сравнению с оригиналом!


Этот метод может быть распространен на любой общий случай, когда функция со стрелкой должна возвращать значение и иметь несколько операторов (которые не могут быть объединены другими средствами)

b=>{statement1;statement2;return v}

становится

b=>eval('statement1;statement2;v')

сохранение байта.

Если statement2оценивать v, это может быть

b=>eval('statement1;statement2')

сохраняя в общей сложности 3 байта.

jrich
источник
1
Я думаю, что написание анонимной функции может быть еще короче
Downgoat
@vihan да, обе эти функции можно сделать анонимными, чтобы сэкономить 2 байта каждая. Однобайтовая экономия все еще остается, хотя.
jrich
1
Но даже лучше: eval возвращает последнее вычисленное выражение, поэтому вам не нужно ;o- попробуйте:a=>eval('for(o="",i=0;i<a;i++)o+=i')
edc65
4
Но шаблонные строки!
Конор О'Брайен
1
@ CᴏɴᴏʀO'Bʀɪᴇɴ Не хочешь объяснить, как здесь будут работать строки шаблона, используя пример функции в качестве контекста?
WallyWest
10

Предпочитать строки шаблона новые строки над "\ n"

Это начнет окупаться даже при появлении одного символа новой строки в вашем коде. Один вариант использования может быть:

(16 байт)

array.join("\n")

(15 байт)

array.join(`
`)

Обновление: Вы можете даже оставить фигурные скобки из-за помеченных строк шаблона (спасибо, edc65!):

(13 байт)

array.join`
`
Chiru
источник
5
Но даже лучше, вы можете избежать скобок. Прочитайте документы ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), чтобы узнать, почему
edc65
Ах, верно. Спасибо, я добавил это.
Чиру
9

Заполнение массивов - статические значения и динамические диапазоны

Первоначально я оставил их в качестве комментариев при понимании, но так как этот пост был в основном сфокусирован на понимании, я подумал, что было бы хорошо дать этому свое место.

ES6 дал нам возможность заполнять массивы статическими значениями без использования циклов:

// ES5
function(x){for(i=0,a=[];i<x;i++)a[i]=0;return a}

// ES6
x=>Array(x).fill(0)

Оба возвращают массив длины x, заполненный значением 0.

Если вы хотите заполнить массивы динамическими значениями (например, диапазон от 0 до x), результат будет немного длиннее (хотя все еще короче, чем старый):

// ES5
function(x){for(i=0,a=[];i<x;i++)a[i]=i;return a}

// ES6
x=>Array(x).fill().map((a,i)=>i)

Оба возвращают массив длины x, начиная со значения 0 и заканчивая x-1.

Причина, по которой вам нужно, .fill()заключается в том, что простая инициализация массива не позволит вам отобразить его. То есть выполнение x=>Array(x).map((a,i)=>i)вернет пустой массив. Вы также можете обойти необходимость заполнения (и, следовательно, сделать его еще короче), используя оператор распространения, например, так:

x=>[...Array(x)]

Используя оператор и .keys()функцию распространения, теперь вы можете сделать короткий диапазон 0 ... x:

x=>[...Array(x).keys()]

Если вам нужен пользовательский диапазон от x ... y или специальный диапазон (например, четные числа), вы можете избавиться от него .keys()и просто использовать .map()или использовать .filter()с помощью оператора распространения:

// Custom range from x...y
(x,y)=>[...Array(y-x)].map(a=>x++)

// Even numbers (using map)
x=>[...Array(x/2)].map((a,i)=>i*2)

// Even numbers (using filter)
x=>[...Array(x).keys()].filter(a=>~a%2)
Mwr247
источник
Вот предложение для второго примера: x=>Array(x).fill(i=0).map(a=>i++)Кроме того, я не уверен, что 0 в .fill(0)необходим. Вы пробовали это без?
ETHproductions
@ETHproductions Вы правы, я забыл, что 0 не требуется в заливке перед картой. Это делает его на 1 символ короче, чем предложенный вами, поэтому я буду держать его таким. Спасибо!
Mwr247
Также, для последнего примера, a=>a%2-1работает нормально, как и a=>a%2<1.
ETHproductions
1
Новый трюк, который я выучил: [...Array(x)]работает так же хорошо Array(x).fill(), и на 2 байта короче. x=>[...Array(x)].map((a,i)=>i)
ETHproductions
1
@yonatanmn Очень мило! Только комментарии будут: 1) 1/4пример будет написан короче [0,0,0,0], и 2) строковые функции зависят от реализации, поэтому не будут возвращать надежную длину ( Map32 байта в Chrome, но 36 байтов в Firefox).
Mwr247
9

Возвращение значений в функциях со стрелками

Общеизвестно, что если после объявления функции arrow один оператор возвращает результат этого оператора:

a=>{return a+3}
a=>a+3

-7 байт

Поэтому, когда это возможно, объедините несколько утверждений в одно. Это проще всего сделать, заключив операторы в круглые скобки и разделив их запятыми:

a=>{r=0;a.map(n=>r+=n);return r}
a=>(r=0,a.map(n=>r+=n),r)

-8 байт

Но если есть только два утверждения, обычно возможно (и короче) объединить их с &&или ||:

a=>{r=0;a.map(n=>r+=n);return r}

// - Use && because map always returns an array (true)
// - declaration of r moved into unused map argument to make it only 2 statements
a=>a.map(n=>r+=n,r=0)&&r

-9 байт

Наконец, если вы используете карту (или аналогичную) и вам нужно вернуть число, и вы можете гарантировать, что карта никогда не вернет массив длиной 1 с числом, вы можете вернуть число с помощью |:

a=>{a=b=0;a.map(n=>(a+=n,b-=n));return a/b}

// - {} in map ensures it returns an array of undefined, so the | will make the returned
//   array cast from [ undefined, undefined, undefined ] to ",," to NaN to 0 and 0|n = n,
//   if the map returned [ 4 ] it would cast from [ 4 ] to "4" to 4 and make it 4|n
a=>a.map(n=>{a+=n,b-=n},a=b=0)|a/b
user81655
источник
В последнем примере вы также должны быть уверены, что число всегда будет целым числом.
ETHproductions
8

Случайные взломы шаблонов

Эта функция перезаписывает две строки (т.е. превращается "abc","de"в "adbec"):

f=(x,y)=>String.raw({raw:x},...y)

Обратите внимание, что это работает только тогда, когда xбольше, чем y. Вы спрашиваете, как это работает? String.rawпредназначен для шаблона тега, вот так:

String.raw`x: ${x}\ny: ${y}\nx + y: ${x + y}`

Это в основном называет String.raw(["x: ", "\ny: ", "\nx + y: ", ""], x, y, x + y), хотя это не так просто. Массив шаблона также имеет специальное rawсвойство, которое в основном является копией массива, но с необработанными строками. String.raw(x, ...args)в основном возвращается x.raw[0] + args[0] + x.raw[1] + args[1] + x.raw[2] + ...и так до тех пор, пока не xкончится товар.

Итак, теперь, когда мы знаем, как String.rawработает, мы можем использовать это в наших интересах:

f=(x,y)=>String.raw({raw:x},...y)                   // f("abc", "de") => "adbec"
f=x=>String.raw({raw:x},...[...x].keys())           // f("abc") => "a0b1c"
f=(x,y)=>String.raw({raw:x},...[...x].fill(y))      // f("abc", " ") => "a b c"

Конечно, для последнего, f=(x,y)=>x.split``.join(y)это намного короче, но вы поняли идею.

Вот пара функций риффлинга, которые также работают, если xи yимеют одинаковую длину:

f=(x,y)=>String.raw({raw:x.match(/.?/g)},...y)
f=(x,y)=>String.raw({raw:x},...y)+y.slice(-1)  // Only works if x.length == y.length

Вы можете узнать больше о String.raw MDN .

ETHproductions
источник
7

Как играть в гольф с рекурсией

Рекурсия, хотя и не самый быстрый вариант, очень часто самый короткий. Как правило, рекурсия является самой короткой, если решение может быть упрощено до решения для меньшей части задачи, особенно если ввод представляет собой число или строку. Например, еслиf("abcd") можно рассчитать из "a"и f("bcd"), обычно лучше использовать рекурсию.

Взять, к примеру, факториал:

n=>[...Array(n).keys()].reduce((x,y)=>x*++y,1)
n=>[...Array(n)].reduce((x,_,i)=>x*++i,1)
n=>[...Array(n)].reduce(x=>x*n--,1)
n=>{for(t=1;n;)t*=n--;return t}
n=>eval("for(t=1;n;)t*=n--")
f=n=>n?n*f(n-1):1

В этом примере рекурсия явно короче любого другого варианта.

Как насчет суммы кодов:

s=>[...s].map(x=>t+=x.charCodeAt(),t=0)|t
s=>[...s].reduce((t,x)=>t+x.charCodeAt())
s=>[for(x of(t=0,s))t+=x.charCodeAt()]|t  // Firefox 30+ only
f=s=>s?s.charCodeAt()+f(s.slice(1)):0

Это сложнее, но мы видим, что при правильной реализации рекурсия экономит 4 байта .map .

Теперь давайте посмотрим на различные типы рекурсии:

Pre-рекурсия

Обычно это самый короткий тип рекурсии. Ввод разделен на две части aи b, и функция вычисляет что-то с помощью aи f(b). Возвращаясь к нашему факториальному примеру:

f=n=>n?n*f(n-1):1

В этом случае, aэто п , bявляется п-1 , а возвращаемое значение a*f(b).

Важное примечание: у всех рекурсивных функций должен быть способ остановить рекурсию, когда входной сигнал достаточно мал. В факториальной функции это управляется с помощью n? :1, т. Е. Если вход равен 0 , вернуть 1 без fповторного вызова .

Сообщение рекурсии

Пострекурсия похожа на предрекурсию, но немного отличается. Ввод делится на две части aи b, и функция вычисляет что-то с помощью a, а затем вызывает f(b,a). Второй аргумент обычно имеет значение по умолчанию (т.е. f(a,b=1)).

Предварительная рекурсия хороша, когда вам нужно сделать что-то особенное с конечным результатом. Например, если вы хотите факториал числа плюс 1:

f=(n,p=1)=>n?f(n-1,n*p):p+1

Однако даже в этом случае post- не всегда короче, чем использование предварительной рекурсии в другой функции:

n=>(f=n=>n?n*f(n-1):1)(n)+1

Так, когда это короче? Вы можете заметить, что пострекурсия в этом примере требует скобок вокруг аргументов функции, в то время как предварительная рекурсия этого не делает. Как правило, если оба решения нуждаются в круглых скобках вокруг аргументов, пострекурсия будет примерно на 2 байта короче:

n=>!(g=([x,...a])=>a[0]?x-a.pop()+g(a):0)(n)
f=([x,...a],n=0)=>a[0]?f(a,x-a.pop()+n):!n

(программы здесь взяты из этого ответа )

Как найти кратчайшее решение

Обычно единственный способ найти самый короткий метод - это попробовать все из них. Это включает в себя:

  • Loops
  • .map(для строк, либо [...s].mapили s.replace, для чисел, вы можете создать диапазон )
  • Массив пониманий
  • Предварительная рекурсия (иногда в рамках другого варианта)
  • Сообщение рекурсии

И это только самые распространенные решения; лучшее решение может быть их комбинацией или даже чем-то совершенно другим . Лучший способ найти самое короткое решение - попробовать все .

ETHproductions
источник
1
+1 для его значения, и я хотел бы добавить еще +1 для Zootopia
edc65
7

Более короткие способы сделать .replace


Если вы хотите заменить все экземпляры одной точной подстроки на другой в строке, очевидным способом будет:

f=s=>s.replace(/l/g,"y") // 24 bytes
f("Hello, World!")       // -> "Heyyo, Woryd!"

Тем не менее, вы можете сделать на 1 байт короче:

f=s=>s.split`l`.join`y`  // 23 bytes
f("Hello, World!")       // -> "Heyyo, Woryd!"

Обратите внимание, что это больше не короче, если вы хотите использовать какие-либо функции регулярных выражений, кроме gфлага. Однако, если вы заменяете все экземпляры переменной, она обычно намного короче:

f=(s,c)=>s.replace(RegExp(c,"g"),"") // 36 bytes
f=(s,c)=>s.split(c).join``           // 26 bytes
f("Hello, World!","l") // -> "Heo, Word!"

Иногда вы захотите отобразить каждый символ в строке, заменив каждый чем-то другим. Я часто ловлю себя на этом:

f=s=>s.split``.map(x=>x+x).join`` // 33 bytes
f=s=>[...s].map(x=>x+x).join``    // 30 bytes
f("abc") // -> "aabbcc"

Однако .replaceпочти всегда короче

f=s=>s.replace(/./g,x=>x+x)  // 27 bytes
f=s=>s.replace(/./g,"$&$&")  // Also works in this particular case

Теперь, если вы хотите отобразить каждый символ в строке, но не заботитесь о результирующей строке, .mapобычно лучше, потому что вы можете избавиться от .join``:

f=s=>s.replace(/./g,x=>t+=+x,t=0)&&t // 36 bytes
f=s=>[...s].map(x=>t+=+x,t=0)&&t     // 32 bytes
f("12345")  // -> 15
ETHproductions
источник
В последнем случае, если /\w/gзаинтересованы только определенные символы, соответствующие регулярному выражению (например ), использование замены будет намного лучше, чем в этой демонстрации .
Шиеру Асакото
6

Написание литералов RegEx с eval

Конструктор регулярных выражений может быть очень громоздким из-за его длинного имени. Вместо этого напишите литерал с eval и обратными галочками:

eval(`/<${i} [^>]+/g`)

Если переменная iравна foo, это сгенерирует:

/<foo [^>]+/g

Это равно:

new RegExp("<"+i+" [^>]+","g")

Вы также можете использовать, String.rawчтобы избежать необходимости повторять обратную косую черту\

eval(String.raw`/\(?:\d{4})?\d{3}\d{3}\d{3}\d{3}\d{3}\d{3}\d{4}/g`)

Это выведет:

/(?:\d{4})?\d{3}\d{3}\d{3}/g

Что равно:

RegExp("\\(?:\\d{4})?\\d{3}\\d{3}\\d{3}\\d{3}\\d{3}\\d{3}\\d{4}","g")

Иметь ввиду!

String.rawзанимает много байтов, и если у вас есть по крайней мере девять обратных слэшей, String.rawбудет больше.

Downgoat
источник
Вам там не нужно new, поэтому использование конструктора на самом деле короче для второго примера
Оптимизатор
5

.forEachпротив forпетель

Всегда предпочитаю .mapлюбой цикл. Простая, мгновенная экономия.


a.map(f)
for(x of a)f(x);
for(i=0;i<a.length;)f(a[i++]);
  • Всего 8 байт для оригинала
  • 8 байтов сохранено против for-of ( сокращение на 50% )
  • 22 байта сохранено против C-стиля для цикла ( сокращение на 73% )

a.map(x=>f(x,0))
for(x of a)f(x,0);
for(i=0;i<a.length;)f(a[i++],0);
  • Всего 16 байт для оригинала
  • 2 байта сохранено против for-of ( сокращение на 11% )
  • 16 байтов сохранено против C-стиля для цикла ( сокращение на 50% )

a.map((x,i)=>f(x,i,0))
for(i in a)f(a[i],i,0);
for(i=0;i<a.length;)f(a[i],i++,0);
  • Всего 22 байта для оригинала
  • 1 байт сохранен против for-in ( 4% сокращение на )
  • 11 байт в сравнении с C-стилем для цикла ( сокращение на 33% )

a.map(x=>f(x)&g(x))
for(x of a)f(x),g(x);
for(i=0;i<a.length;)f(x=a[i++]),g(x);
  • Всего 19 байт для оригинала
  • 2 байта сохранено против for-of ( сокращение на 10% )
  • Сохранено 18 байт по сравнению с C-стилем для цикла ( сокращение на 49% )
Isiah Meadows
источник
5

Использование неинициализированных счетчиков в рекурсии

Примечание . Строго говоря, это не относится к ES6. Однако более разумно использовать и злоупотреблять рекурсией в ES6 из-за краткого характера функций стрелок.


Довольно часто встречается рекурсивная функция, которая использует счетчик, kизначально установленный в ноль и увеличивающийся на каждой итерации:

f = (…, k=0) => [do a recursive call with f(…, k+1)]

При определенных обстоятельствах можно пропустить инициализацию такого счетчика и заменить k+1на -~k:

f = (…, k) => [do a recursive call with f(…, -~k)]

Этот трюк обычно экономит 2 байта .

Почему и когда это работает?

Формула , которая позволяет это ~undefined === -1. Итак, на первой итерации, -~kбудет оцениваться до 1. На следующих итерациях, -~kпо существу, эквивалентно тому, -(-k-1)что равно k+1, по крайней мере, для целых чисел в диапазоне [0… 2 31 -1].

Однако вы должны убедиться, что k = undefinedвыполнение первой итерации не нарушит поведение функции. Вы должны особенно помнить, что большинство арифметических операций, связанных undefinedсNaN .

Пример № 1

Учитывая положительное целое число n, эта функция ищет наименьшее целое число, kкоторое не делится n:

f=(n,k=0)=>n%k?k:f(n,k+1)   // 25 bytes

Его можно сократить до:

f=(n,k)=>n%k?k:f(n,-~k)     // 23 bytes

Это работает , потому что n % undefinedэто NaN, что falsy. Это ожидаемый результат на первой итерации.

[Ссылка на оригинальный ответ]

Пример № 2

Учитывая положительное целое число n, эта функция ищет целое число pтакое, что (3**p) - 1 == n:

f=(n,p=0,k=1)=>n<k?n>k-2&&p:f(n,p+1,k*3)  // 40 bytes

Его можно сократить до:

f=(n,p,k=1)=>n<k?n>k-2&&p:f(n,-~p,k*3)    // 38 bytes

Это работает, потому что pне используется вообще на первой итерации ( n<kбудучи ложным).

[Ссылка на оригинальный ответ]

Arnauld
источник
5

ES6 функции

математический

Math.cbrt(x)сохраняет символы, чем Math.pow(x,1/3).

Math.cbrt(x)
Math.pow(x,1/3)

3 символа сохранены

Math.hypot(...args)полезно, когда вам нужен квадратный корень из суммы квадратов аргов. Заставить код ES5 сделать это гораздо сложнее, чем использовать встроенный.

Функция Math.trunc(x)не будет полезна, так как x|0она короче. (Спасибо Mwr247!)

Есть много свойств, которые требуют много кода для ES5, но проще в ES6:

  • Math.acosh, asinh, atanh, cosh, sinh, tanh. Вычисляет гиперболический эквивалент тригонометрических функций.
  • Math.clz32, Возможно, это можно сделать в ES5, но теперь это стало проще. Считает лидирующие нули в 32-битном представлении числа.

Есть намного больше, так что я просто буду перечислять некоторые из них :
Math.sign, Math.fround, Math.imul, Math.log10, Math.log2, Math.log1p.

ev3commander
источник
Math.trunc(x)в четыре раза длиннее x|0.
Mwr247
@ mwr247: Хорошо, обновлю.
ev3commander
Вот самые короткие эквиваленты ES5, о которых я знаю, для пары из этих функций: Math.hypot(a,b) => Math.sqrt(a*a+b*b)(на 3 байта длиннее; с большим количеством аргументов становится еще длиннее), Math.sign(a) => (a>0)-(a<0)(короче на 1 байт, но в некоторых случаях требуются круглые скобки; может не работать NaN)
ETHproductions
@ETHproductions Вам нужен массив arguments для (обходного пути es5) гипотезы. И вы уверены, что обходной путь для Math.sign работает для -0? (Должен вернуть -0)
ev3commander
1
@ ev3commander Они предназначены только для оперативной замены соответствующих им эквивалентов ES6, поэтому они сокращены для 99% использования. Чтобы действительно воссоздать эти функции, потребовалось бы гораздо больше кода. Кроме того, я не вижу причин для необходимости иметь специальный случай для -0, так как (AFAIK) нет способа получить -0, кроме как путем его ручного указания, и практически бесполезен для него в code-golf. Но спасибо за указание на эти вещи.
ETHproductions
5

Оптимизация небольших постоянных диапазонов для map()

контекст

map()for[0 ..N-1]

for(i = 0; i < 10; i++) {
  do_something_with(i);
}

может быть заменен либо:

[...Array(10).keys()].map(i => do_something_with(i))

или чаще:

[...Array(10)].map((_, i) => do_something_with(i))

Array(N)N

[0 ..N-1]

я

N           | Method                               | Example                         | Length
------------+--------------------------------------+---------------------------------+-------
N ≤ 6       | use a raw array of integers          | [0,1,2,3].map(i=>F(i))          | 2N+10
N = 7       | use either a raw array of integers   | [0,1,2,3,4,5,6].map(i=>F(i))    | 24
            | or a string if your code can operate | [...'0123456'].map(i=>F(i))     | 23
            | with characters rather than integers |                                 |
8 ≤ N ≤ 9   | use scientific notation 1e[N-1]      | [...1e7+''].map((_,i)=>F(i))    | 24
N = 10      | use scientific notation 1e9          | [...1e9+''].map((_,i)=>F(i))    | 24
            | or the ES7 expression 2**29+'4' if   | [...2**29+'4'].map(i=>F(i))     | 23
            | the order doesn't matter and your    |                                 |
            | code can operate with characters     |  (order: 5,3,6,8,7,0,9,1,2,4)   |
            | rather than integers                 |                                 |
11 ≤ N ≤ 17 | use scientific notation 1e[N-1]      | [...1e12+''].map((_,i)=>F(i))   | 25
N = 18      | use the fraction 1/3                 | [...1/3+''].map((_,i)=>F(i))    | 24
N = 19      | use the fraction 1/6                 | [...1/6+''].map((_,i)=>F(i))    | 24
20 ≤ N ≤ 21 | use scientific notation 1e[N-1]      | [...1e20+''].map((_,i)=>F(i))   | 25
N = 22      | use scientific notation -1e20        | [...-1e20+''].map((_,i)=>F(i))  | 26
23 ≤ N ≤ 99 | use Array(N)                         | [...Array(23)].map((_,i)=>F(i)) | 27

NB . Длина кода обратного вызова F(i)не учитывается.

[1..9]

[1..9]

[...17**6+'8'].map(i=>F(i))  // order: 2,4,1,3,7,5,6,9,8; length: 23

Оптимизации без счетчика

N

N           | Method                               | Example                         | Length
------------+--------------------------------------+---------------------------------+-------
N ≤ 5       | use a raw array of integers          | [0,0,0,0].map(_=>F())           | 2N+10
6 ≤ N ≤ 10  | use scientific notation 1e[N-1]      | [...1e7+''].map(_=>F())         | 20
11 ≤ N ≤ 17 | use scientific notation 1e[N-1]      | [...1e12+''].map(_=>F())        | 21
N = 18      | use the fraction 1/3                 | [...1/3+''].map(_=>F())         | 20
N = 19      | use the fraction 1/6                 | [...1/6+''].map(_=>F())         | 20
20 ≤ N ≤ 21 | use scientific notation 1e[N-1]      | [...1e20+''].map(_=>F())        | 21
N = 22      | use scientific notation -1e20        | [...-1e20+''].map(_=>F())       | 22
23 ≤ N ≤ 99 | use Array(N)                         | [...Array(23)].map(_=>F())      | 23

NB . Длина кода обратного вызова F()не учитывается.

Arnauld
источник
Не должно 2**26быть 2**29?
Лохматый
@ Шэгги Хек. Хороший улов!
Арно
Не хотел редактировать в себе, потому что у меня слепота кода! : D
Лохматый
Использование .keys(), вам не нужна лямбда:[...Array(10).keys()].map(do_something_with)
long-lazuli
@ long-lazuli Если вам не нужна лямбда и вы просто хотите диапазон, то вам, вероятно, карта тоже не нужна ...
Арнаулд
4

Разрушающие задания

ES6 вводит новый синтаксис для деструктурирующих назначений, то есть разделение значения на части и назначение каждого фрагмента другой переменной. Вот несколько примеров:

Строки и массивы

a=s[0];b=s[1];       // 14 bytes
[a,b]=s;             //  8 bytes

a=s[0];s=s.slice(1); // 20 bytes
a=s.shift();         // 12 bytes, only works if s is an array
[a,...s]=s;          // 11 bytes, converts s to an array

Объекты

a=o.asdf;b=o.bye;c=o.length; // 28 bytes
{asdf:a,bye:b,length:c}=o;   // 26 bytes

a=o.a;b=o.b;c=o.c; // 18 bytes
{a,b,c}=o;         // 10 bytes

Эти назначения также могут использоваться в параметрах функции:

f=a=>a[0]+a[1]+a[2]
f=([a,b,c])=>a+b+c

f=b=>b[1]?b[0]+f(b.slice(1)):b[0]*2
f=b=>b[1]?b.shift()+f(b):b[0]*2
f=([a,...b])=>b[0]?a+f(b):a*2
ETHproductions
источник
4

Еще один способ избежать return

Вы знаете, что вы должны использовать eval для функций со стрелками с несколькими операторами и возвратом . В некоторых необычных случаях вы можете сэкономить больше, используя внутреннюю подфункцию.

Я говорю необычно, потому что

  1. Возвращаемый результат не должен быть последним выражением, вычисленным в цикле

  2. Должно быть (как минимум) 2 разных инициализации перед циклом

В этом случае вы можете использовать внутреннюю подфункцию без возврата, передав одно из начальных значений в качестве параметра.

пример Найти обратную сумму суммы функции exp для значений в диапазоне от a до b.

Долгий путь - 55 байт

(a,b)=>{for(r=0,i=a;i<=b;i++)r+=Math.exp(i);return 1/r}

С eval - 54 байта

(a,b)=>eval("for(r=0,i=a;i<=b;i++)r+=Math.exp(i);1/r")

С внутренней функцией - 53 байта

(a,b)=>(i=>{for(r=0;i<=b;i++)r+=Math.exp(i)})(a)||1/r

Обратите внимание, что без требования нижнего предела диапазона aя могу объединить инициализации i и r, и версия eval будет короче.

edc65
источник
В вашем образце нет необходимости хранитьa
l4m2
@ l4m2 Я не могу понять твою точку зрения, помогите, пожалуйста ...
edc65
(i,b)=>{for(r=0;i<=b;i++)r+=Math.exp(i);return 1/r}
14 м2,
@ l4m2 да, return a/rлучший пример
edc65
1
Eval все еще лучше, (a,b)=>1/eval("for(r=0,i=a;i<=b;i++)r+=Math.exp(i)")и в этом случае(i,b)=>1/eval("for(r=0;i<=b;)r+=Math.exp(i++)")
JayXon
4

Использование синтаксиса каррирования для двоичных и рекурсивных функций

Диадические функции

Всякий раз, когда функция принимает ровно два аргумента без значений по умолчанию, использование синтаксиса каррирования сохраняет один байт.

До

f =
(a,b)=>a+b  // 10 bytes

Вызывается с f(a,b)

После

f =
a=>b=>a+b   // 9 bytes

Вызывается с f(a)(b)

Примечание : этот пост в Meta подтверждает правильность этого синтаксиса.

Рекурсивные функции

Использование синтаксиса каррирования может также сэкономить несколько байтов, когда рекурсивная функция принимает несколько аргументов, но ей нужно только обновить некоторые из них между каждой итерацией.

пример

Следующая функция вычисляет сумму всех целых чисел в диапазоне [a,b]:

f=(a,b)=>a>b?0:b+f(a,b-1)   // 25 bytes

Поскольку aостается неизменным в течение всего процесса, мы можем сохранить 3 байта, используя:

f =                         // no need to include this assignment in the answer anymore
a=>F=b=>a>b?0:b+F(b-1)      // 22 bytes

Примечание . Как заметил Нейл в комментариях, тот факт, что аргумент явно не передается рекурсивной функции, не означает, что его следует считать неизменным. При необходимости, можно изменить aв коде функции с a++, a--или любой другой аналогичный синтаксис.

Arnauld
источник
Последний пример можно записать как a=>F=b=>a>b?0:a+++F(b), модифицируя aдля каждого рекурсивного вызова. Это не поможет в этом случае, но может сохранить байты в случаях с большим количеством аргументов.
Нил
Хех, я как раз думал о том, чтобы написать подсказку для этого :-)
ETHproductions
4

Функция тестирования на первичность

Следующая 28-байтовая функция возвращает trueпростые числа и falseне простые числа:

f=(n,x=n)=>n%--x?f(n,x):x==1

Это можно легко изменить, чтобы рассчитать другие вещи. Например, эта 39-байтовая функция считает количество простых чисел, меньших или равных числу:

f=(n,x=n)=>n?n%--x?f(n,x):!--x+f(n-1):0

Если у вас уже есть переменная n которую вы хотите проверить на простоту, функцию простоты можно немного упростить:

(f=x=>n%--x?f(x):x==1)(n)

Как это устроено

f = (         // Define a function f with these arguments:
  n,          //   n, the number to test;
  x = n       //   x, with a default value of n, the number to check for divisibility by.
) =>
  n % --x ?   //   If n is not divisible by x - 1,
  f(n, x)     //     return the result of f(n, x - 1).
              //   This loops down through all numbers between n and 0,
              //     stopping when it finds a number that divides n.
  : x == 1    //   Return x == 1; for primes only, 1 is the smallest number
              //     less than n that divides n.
              //   For 1, x == 0; for 0, x == -1.

Примечание. Это приведет к ошибке «ошибка слишком большой рекурсии» при вызове с достаточно большим вводом, например 12345. Вы можете обойти это с помощью цикла:

f=n=>eval('for(x=n;n%--x;);x==1')
ETHproductions
источник
1
Но потерпите неудачу с слишком большой рекурсией для входа всего за 12345
edc65
x==1может быть, x<2для экономии.
CalculatorFeline
@CalculatorFeline Спасибо, но тогда это не для 1или 0(потому xчто будет 0или -1, соответственно)
ETHproductions
Может быть полезно в определенных случаях. Также !~-xза -0 байт.
CalculatorFeline
3

Array#concat() и оператор распространения

Это во многом зависит от ситуации.


Объединение нескольких массивов.

Предпочитаю функцию concat, кроме клонирования.

0 байт сохранено

a.concat(b)
[...a,...b]

3 байта потрачено впустую

a.concat(b,c)
[...a,...b,...c]

3 байта сохранены

a.concat()
[...a]

6 байтов сохранено

// Concatenate array of arrays
[].concat.apply([],l)
[].concat(...l)

Предпочитаю использовать уже существующий массив Array#concat().

Простые 4 байта сохранены

[].concat(a,b)
a.concat(b)
Isiah Meadows
источник
3

Вернуть промежуточный результат

Вы знаете, что с помощью оператора запятой вы можете выполнить последовательность выражений, возвращающих последнее значение. Но злоупотребляя синтаксисом литерального массива, вы можете вернуть любое промежуточное значение. Это полезно, например, в .map ().

// capitalize words
// f is a flag indicating if prev char is space
[...x].map(c=>(f?c=c.toUpperCase():0,f=c<'!',c),f=1).join('')

// shortened to ...
[...x].map(c=>[f?c.toUpperCase():c,f=c<'!'][0],f=1).join('')
edc65
источник
3
Помните, конечно, что это .join('')может быть.join``
Cyoce
3

Установить параметры функции по умолчанию

($,a,b,_)=>_!=undefined?'asdf':_ // before
($,a,b,_)=>_!=[]._?'asdf':_ // before, but a bit golfed
($,a,b,_='asdf')=>_ // after

Это действительно полезно ...

Тем не менее, следует понимать, что что-то вроде этого _=>_||'asdf'короче, когда вы передаете только один (полезный) аргумент функции.

Mama Fun Roll
источник
1
Я бы отметил, что _=>_||'asdf'в большинстве случаев использование OR обычно короче
Downgoat
@Downgoat Я бы отметил, что это возвращает "asdf"для ввода ""(пустая строка).
ETHproductions
2
Обратите внимание, что значение по умолчанию оценивается всякий раз, когда аргумент был бы undefined, даже если вы явно передаете это значение. Например, [...Array(n)].map((a,b,c)=>b)всегда проходит undefinedдля a, и поэтому вы можете указать для него значение по умолчанию (хотя и не в терминах b).
Нил
3

использование eval вместо фигурных скобок для функций стрелок

Функции стрелки потрясающие. Они принимают форму x=>y, где xаргумент и yвозвращаемое значение. Тем не менее, если вам нужно использовать управляющую структуру, например while, вам придется поставить фигурные скобки, например =>{while(){};return}. Тем не менее, мы можем обойти это; К счастью, evalфункция берет строку, оценивает эту строку как код JS и возвращает последнее вычисленное выражение . Например, сравните эти два:

x=>{while(foo){bar};return baz} // before
x=>eval('while(foo){bar};baz')  // after
//                            ^

Мы можем использовать расширение этой концепции для дальнейшего сокращения нашего кода: в глазах evalуправляющих структур также возвращают свое последнее вычисленное выражение. Например:

x=>{while(foo)bar++;return bar} // before
x=>eval('while(foo)++bar')      // after
//                        ^^^^^
Cyoce
источник
3

Гольф Логические операции в ES6

"GLOE (S6)"

Общая логика

Скажем, вы построили заявления sи t. Посмотрите, можете ли вы использовать любую из следующих замен:

Traditional conjuction: s&&t
Equivalent conjuction: s*t OR s&t

Traditional disjunction: s||t
Equivalent disjunction: s+t OR s|t

(Они могут не работать, если порядок неправильный; т.е. +и *имеют более низкий приоритет, чем ||и &&делают.)

Кроме того, вот несколько удобных логических выражений:

  • Либо sили tверно / XOR:s^t
  • sи tимеют одинаковую ценность истины: !s^tилиs==t

Логика массива

Все члены aудовлетворяют условию p:

a.every(p)                             // 10 bytes (11 bytes saved)
a.map(x=>c&=p(x),c=1)                  // 21 bytes (16 bytes saved)
for(i=0,c=1;i<a.length;c&=p(a[i++]));  // 37 bytes (hideously long)

По крайней мере один член aудовлетворяет условию p:

a.some(p)                            // 9  bytes (13 bytes saved)
a.map(x=>c|=p(x),c=0)                // 21 bytes (14 bytes saved)
for(i=c=0;i<a.length;c|=p(a[i++]));  // 35 bytes (just please no)

Нет члены aудовлетворяют условию p: !a.some(p).

Элемент eсуществует в массиве a:

a.includes(e)                        // 13 bytes, standard built-in
~a.indexOf(e)                        // 13 bytes, "traditional" method
a.find(x=>e==x)                      // 15 bytes, find (ES6)
a.some(x=>x==e)                      // 15 bytes, some (ES5)
(a+"").search(e)                     // 16 bytes, buggy
a.filter(t=>t==e).length             // 24 bytes, no reason to use this
for(i=c=0;i<a.length;c+=e==a[i++]);  // 35 bytes, super-traditional

Элемент eимеет не существует в массиве a:

!a.includes(e)
!~a.indexOf(e)
a.every(t=>t!=e)
!a.filter(t=>t==e).length
for(i=0,c=1;i<a.length;c*=e!=a[i++]);
Конор О'Брайен
источник
Я вообще использую &&и ||как x?y:xи x?x:yсоответственно. Но я могу видеть, как это было бы полезно в более основанных на логике программах. Одной из проблем +будет то, что, например, 3и -3оба правдивы, но 3+-3это не так.
ETHproductions
@ETHproductions Ах, ты прав; это крайний случай. -также может работать, если s != t.
Конор О'Брайен
a.filter(t=>t==e).length==a.lengthэто неверно. Это должно быть!a.filter(t=>t==e).length
ETHproductions
@ETHproductions правильно, ты!
Конор О'Брайен,
3

Сократить повторные вызовы функций

Если вы повторяли вызовы функции с длинным именем, например, манипулирование холстом:

c.lineTo(0,100);c.lineTo(100,100);c.lineTo(100,0);c.lineto(0,0);c.stroke()

Традиционный способ сократить это - псевдоним имени функции:

c[l='lineTo'](0,100);c[l](100,100);c[l](100,0);c[l](0,0);c.stroke()

Если у вас достаточно вызовов, лучше создать функцию, которая сделает эту работу за вас:

l=(x,y)=>c.lineTo(x,y);l(0,100);l(100,100);l(100,0);l(0,0);c.stroke()

Если большинство вызовов функции связаны друг с другом, вы можете сделать так, чтобы функция возвращала себя сама, что позволяет вам обрезать два байта каждого последующего вызова:

l=(x,y)=>c.lineTo(x,y)||l;l(0,100)(100,100)(100,0)(0,0);c.stroke()

Пример использования: 1 , 2

ETHproductions
источник
1
Вы можете сократить с оператором связывания :(l=::c.lineTo)(0,100)(100,100)(100,0)(0,0);c.stroke()
Downgoat
@ Downgoat Спасибо, какие браузеры это поддерживают? (Кроме того, из того, что я видел, произойдет ошибка при втором вызове, поскольку c.lineToон сам по себе не вернется)
ETHproductions
Вы должны втирать это через babel, так как это особенность
ES7
3

Оператор связывания ::

Оператор связывания может использоваться для сокращения байтов по повторяющимся функциям:

(x='abc'.search(a))+x.search(b) // Before
(x=::'abc'.search)(a)+x(b)      // 5 bytes saved

Кроме того, если вы хотите использовать функцию с другим, thisнапример:

s[r='replace'](/a/g,'b')+s[r](/c/g,'d') // Before
(r=s.replace)(/a/g,'b')+s::r(/c/g,'d')  // 1 byte saved
Downgoat
источник
3

Избегайте запятых при хранении большого количества данных

Если у вас есть много данных (т. Е. Индексов, символов и т. Д.), Которые необходимо сохранить в массиве, лучше оставить все запятые в стороне. Это работает лучше всего, если каждый фрагмент данных имеет одинаковую длину строки, 1, очевидно, является оптимальным.

43 байта (базовый уровень)

a=[[3,7,6,1,8,9,4,5,2],[5,4,3,2,7,6,5,4,3]]

34 байта (без запятых)

a=[[..."376189452"],[..."543276543"]]

Если вы готовы изменить свой доступ массиву , вы можете уменьшить его еще больше, сохранив те же значения, например:

27 байт (те же данные, только изменяет доступ к массиву)

a=[..."376189452543276543"]
Chiru
источник
Почему выделяется только последний блок?
CalculatorFeline
@CalculatorFeline Спасибо, исправлено.
Чиру