Поради щодо гольфу в 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]

Див. Мозіла док


3
Тепер у мене тут є голова. Очевидно, хтось зазначив щось жахливо неправильне в цій підказці, будучи занадто сором'язливим, щоб залишити коментар і пояснити, що ...
— edc65

Це виглядає гаразд. Може, це була відсутність крапки з комою? ;) (btw, ви також можете використовувати його як параметри спокою, як splats у Ruby)
— gcampbell

Ви можете додати, що він також має випадок використання у функціях підписів :)
— Фелікс Домбек

Misclick не мав на увазі анонсації
— Stan Strum

@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+")()"))()

vs.

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

У рядку шаблону, який представляє собою рядок всередині двох зворотних лапок (`), все, що знаходиться всередині a ${ }, трактується як код і оцінюється, щоб вставити отриману відповідь у рядок.

Я опублікую ще кілька трюків пізніше. Щасливого гольфу!


1
.search коротше, використовуйте його коли це можливо! але це не те саме .indexOf. .search хоче, а regexpне рядок. Спробуйте'abc'.search('.')
— edc65

@ edc65 Оновлено!
— Оптимізатор

Ви можете змінити оригінальний масив методами екземпляра. Другий - поточний індекс, а третій - ітераційний масив.
— Ісія Медоуз

8
"Приєднався до сайту тиждень тому" - 21.4k представник ...
— 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, заощаджуючи багато байтів у довгостроковій перспективі.


Базовий трюк конверсії приємний, але більш імовірно, що число перетворення буде у формі змінної замість буквальної; у такому випадку воно стає набагато довшим.
— Оптимізатор

1
'0x'+v-0ще коротше, але може не працювати так добре в деяких сценаріях.
— ETHproductions

1
До речі, 0767(ES5) коротше позначення 0o767(ES6).
— Каміло Мартін

@CamiloMartin 0767- це нестандартне розширення, і це в явному режимі заборонено.
— Оріол

1
@Oriol суворий режим був поганим мемом. Це не допомогло в продуктивності, насправді не змусило вас написати хороший код і ніколи не стане за замовчуванням. 0- попередньо встановлені восьмеричні літерали нікуди не поділяються, і є настільки ж дійсними шпальтами 0o.
— Каміло Мартін

19

Використання шаблонів рядків з функціями

Коли в якості аргументів у вас функція з одним рядком. Ви можете пропустити це, ()якщо у вас немає жодних виразів:

join`` // Works
join`foobar` // Works
join`${5}` // Doesn't work 

9
Будьте попереджені, це фактично передає масив. fun`string` те саме, що fun(["string"]), ні fun("string"). Це добре для функцій, які передаються на рядок, як alert, але для інших це може спричинити проблеми. Більше інформації дивіться у статті MDN
— Cyoce

5
Короткий довідник: fun`foo${1}bar${2}bazрівнозначний дзвінкуfun(["foo","bar","baz"],1,2)
— Cyoce

14

Поняття масиву (Firefox 30-57)

Примітка: розуміння масиву ніколи не були стандартизовані, і вони застаріли за допомогою Firefox 58. Використовуйте на свій страх.


Спочатку специфікація ECMAScript 7 містила купу нових функцій на основі масиву. Хоча більшість з них не перетворити його в доопрацьованій версії, підтримка Firefox (редактор) , можливо , найбільший з цих функцій: фантазії новий синтаксис , який може замінити .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 Не соромтеся запропонувати ще одну пораду щодо розуміння чи спосіб покращити цю відповідь!


Ось хитрість для діапазонів, які врятують ще пару символів:(x,y)=>[...Array(y-x)].map(a=>x++)
— Mwr247

2
Ви можете відрізати ще 11 байт, щоб зробити діапазон від 0 до х:x=>[...Array(x).keys()]
— Mwr247

Останній для розуміння там: n=>[for(x of Array(n).keys())if(/1/.test(x))x](економить 7 байтів)
— Mwr247

@ Mwr247 Насправді я зараз бачу, що діапазони, як правило, не такі короткі з розумінням, як з іншими приємними особливостями ES6. Я замість цього додаю в розділ про рядки і дозволю вам обробляти діапазони.
— ETHproductions

Варто зазначити, що Поняття масиву застаріли та видалені з усіх останніх версій 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
Але коли ви гольф-ING, ви взагалі не хвилює і thisт.д.
— оптимізатор

1
Як правило, ні, але це застереження, про яке ви ніколи не дізнаєтесь, коли з’являться. Ламбдам також частіше не потрібно функціонувати місцевим зв'язком у виробництві.
— Isiah Meadows

Крім того, якщо ви хочете взяти всі свої аргументи, ви можете скористатися функцією аргументу "відпочинок", наприклад, f=(...x)=>x матиме це f(1,2,3) => [1,2,3].
— Conor O'Brien

1
Ось підказка, характерна для цього сайту: якщо ви відповідаєте за допомогою функції, яка приймає форму, (x,y)=>...ви можете зберегти байт із заправкою , замінивши його наx=>y=>...
— Cyoce

12

Використання evalдля стрілочних функцій з декількома операторами та areturn

Один із більш смішних витівків, на які я натрапив ...

Уявіть просту функцію стрілки, яка потребує декількох висловлювань та 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оцінці 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 байти.


1
Я думаю, що просто написати анонімну функцію може бути ще коротше
— Downgoat

@vihan Так, обидві ці функції можна зробити анонімними, щоб зберегти по 2 байти. Економія на один байт все ще залишається.
— jrich

1
Але ще краще: eval повертає останнє оцінене вираження, тож вам не потрібно ;o- спробуйте:a=>eval('for(o="",i=0;i<a;i++)o+=i')
— edc65

4
Але рядки шаблону!
— Conor O'Brien

1
@ CᴏɴᴏʀO'Bʀɪᴇɴ Потрібно пояснити, як тут будуть працювати рядки шаблонів, використовуючи функцію прикладу як контекст?
— WallyWest

10

Віддайте перевагу рядкам шаблону нових рядків над "\ n"

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

(16 байт)

array.join("\n")

(15 байт)

array.join(`
`)

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

(13 байт)

array.join`
`

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)

Ось пропозиція для другого прикладу: 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

Повернення значень у функціях стрілки

Загальновідомо, що якщо одне твердження слідує за оголошенням функції стрілки, воно повертає результат цього оператора:

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

У цьому останньому прикладі ви також повинні бути впевнені, що число завжди буде цілим числом.
— 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 .


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.

Тепер розглянемо різні типи рекурсії:

Попередня рекурсія

Зазвичай це найкоротший тип рекурсії. Вхід розділений на дві частини aі b, і функція обчислює щось з aі f(b). Повернувшись до нашого факторіального прикладу:

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

У цьому випадку aє n , bє n-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

Однак навіть після цього пост - не завжди коротше, ніж використання попередньої рекурсії в межах іншої функції:

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

(програми, взяті з цієї відповіді )

Як знайти найкоротше рішення

Зазвичай єдиний спосіб знайти найкоротший метод - спробувати їх усі. Це включає:

І це лише найпоширеніші рішення; найкращим рішенням може бути поєднання цих, а то і зовсім зовсім інших . Найкращий спосіб знайти найкоротший спосіб - спробувати все .


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

В останньому випадку, якщо /\w/gзацікавлені лише певні символи, що відповідають рівню (наприклад ), то використовувати заміну буде набагато краще, як у цій демонстрації .
— Шиєру Асакото

6

Написання літералів RegEx за допомогою eval

Конструктор регулярних виразів може бути дуже об'ємним через його довгу назву. Замість цього напишіть літерал з eval та backticks:

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буде більше.


Вам не потрібно newтам, тому використання конструктора фактично коротше для другого прикладу
— Optimizer

5

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

Завжди віддайте перевагу .mapбудь-якому для циклу. Легка, миттєва економія.


a.map(f)
for(x of a)f(x);
for(i=0;i<a.length;)f(a[i++]);
  • 8 байт для оригіналу
  • 8 байтів, збережених проти (за 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 байти, збережені проти за (з 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 байт збережено порівняно з додатком ( зменшення на 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 байти, збережені проти за ( 10% зменшення)
  • 18 байт збережено проти стилю C для циклу ( зменшення на 49% )

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будучи помилковою).

[Посилання на оригінальну відповідь]


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.


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 Вам потрібен масив аргументів для гіпота (рішення es5). А ви впевнені, що рішення для Math.sign працює на -0? (Слід повернути -0)
— ev3commander

1
@ ev3commander Вони призначені лише як внутрішні заміни відповідних еквівалентів ES6, тому їх зменшують на 99% використання. Для справжнього відтворення цих функцій знадобиться набагато більше коду. Крім того, я не бачу причин вимагати наявності спеціального випадку для -0, оскільки (AFAIK) немає жодного способу отримати -0, окрім вручну вказуючи його, і практично не використовувати його в коді-гольф. Але дякую, що вказали на ці речі.
— 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] , з лічильником

i

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

Примітка : довжина коду зворотного дзвінка 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

Примітка : довжина коду зворотного дзвінка F()не враховується.


Не повинно 2**26бути 2**29?
— Shaggy

@Shaggy Heck. Гарний улов!
— Арнольд

Я не хотів редагувати в собі, тому що у мене сліпота коду! : D
— Shaggy

Використовуючи .keys(), вам не потрібна лямбда:[...Array(10).keys()].map(do_something_with)
— довгий лазуріт

@ long-lazuli Якщо вам не потрібна лямбда і просто хочете асортимент, то вам, мабуть, і карта не потрібна ...
— Arnauld

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

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 коротша.


У вашому зразку зберігати не потрібноa
— l4m2

@ l4m2 Я не можу зрозуміти вашу думку, допоможіть, будь ласка ...
— edc65

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

@ l4m2 uh справа, 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

Використання синтаксису каррі для діадичної та рекурсивної функцій

Діадичні функції

Кожен раз, коли функція бере рівно два аргументи без значень за замовчуванням, використання синтаксису currying зберігає один байт.

Раніше

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

Подзвонив с f(a,b)

Після

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

Подзвонив с f(a)(b)

Примітка : Ця публікація в Meta підтверджує дійсність цього синтаксису.

Рекурсивні функції

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

Приклад

Наступна функція обчислює суму всіх цілих чисел у діапазоні [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--або будь-який інший аналогічний синтаксис.


Останній приклад можна записати як 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')

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)

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

3
Згадайте, звичайно, що .join('')може бути.join``
— Cyoce

3

Встановити параметри за замовчуванням функції

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

Цей дійсно корисний ...

Однак не забудьте зрозуміти, що щось на кшталт _=>_||'asdf'коротше, коли ви передаєте лише один (корисний) аргумент функції.


1
Зауважу, що використання АБО _=>_||'asdf'зазвичай у більшості випадків коротше
— Пуховик

@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
//                        ^^^^^

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.
— Conor O'Brien

a.filter(t=>t==e).length==a.lengthневірно. Це повинно бути!a.filter(t=>t==e).length
— ETHproductions

@ETHproductions прямо ти!
— Conor O'Brien

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


1
ви можете скоротити з оператором прив’язки :(l=::c.lineTo)(0,100)(100,100)(100,0)(0,0);c.stroke()
— Пуховик

@Downgoat Спасибі, які браузери це підтримують? (Крім того, з того, що я бачив, що буде помилка під час другого дзвінка, оскільки c.lineToвона, природно, не повертається)
— ETHproductions

Ви повинні протерти це через вавило, оскільки це особливість 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

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"]

Чому виділяється лише останній блок?
— CalculatorFeline

@CalculatorFeline Дякую, виправлено.
— Чіру
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.