Поради щодо гольфу в Р


58

Я шукаю поради щодо гольфу на статистичній мові R. R - це, мабуть, нетрадиційний вибір для Гольфу. Однак він робить деякі речі дуже компактно (послідовності, випадковість, вектори та списки), багато вбудованих функцій мають дуже короткі назви , і він має додатковий термінатор рядка (;). Які поради та підказки ви можете дати, щоб допомогти вирішити проблеми з кодовим гольфом в R?


14
Відповіді на це питання можуть подвоїтись як антистиль для R, враховуючи, що кодовий гольф - це єдиний раз, коли ви повинні багато чого зробити :-)
Ендрю Бреза

Відповіді:


44

Деякі поради:

  1. У R рекомендується використовувати <-над =. Для гольфу - навпаки, оскільки =це коротше ...
  2. Якщо ви викликаєте функцію не один раз, часто корисно визначити короткий псевдонім для неї:

    as.numeric(x)+as.numeric(y)
    
    a=as.numeric;a(x)+a(y)
    
  3. Часткове узгодження може бути вашим другом, особливо коли функції повертають списки, для яких вам потрібен лише один елемент. Порівняйте rle(x)$lengthsзrle(x)$l

  4. Багато проблем вимагає, щоб ви прочитали дані. scanчасто підходить для цього (користувач закінчує введення, вводячи порожній рядок).

    scan()    # reads numbers into a vector
    scan(,'') # reads strings into a vector
    
  5. Примус може бути корисним. t=1набагато коротше, ніж t=TRUE. Крім того, switchви можете заощадити і дорогоцінні символи, але ви хочете використовувати 1,2, а не 0,1.

    if(length(x)) {} # TRUE if length != 0
    sum(x<3)         # Adds all the TRUE:s (count TRUE)
    
  6. Якщо функція обчислює щось складне, і вам потрібні різні інші типи обчислень на основі одного і того ж основного значення, це часто вигідно: а) розділити її на більш дрібні функції; в) чи повертає їй різні типи значень залежно від аргументу функції.

  7. Як і в будь-якій мові, знайте це добре - R має тисячі функцій, мабуть, є деякі, які можуть вирішити проблему в дуже мало символів - хитрість полягає в тому, щоб знати, які з них!

Деякі незрозумілі, але корисні функції:

sequence
diff
rle
embed
gl # Like rep(seq(),each=...) but returns a factor

Деякі вбудовані набори даних та символів:

letters     # 'a','b','c'...
LETTERS     # 'A','B','C'...
month.abb   # 'Jan','Feb'...
month.name  # 'January','Feburary'...
T           # TRUE
F           # FALSE
pi          # 3.14...

22
  1. Замість того, щоб імпортувати пакет із library, захопіть змінну з пакета, використовуючи ::. Порівняйте наступні дії:

    library(splancs);inout(...)
    splancs::inout(...)
    

    Звичайно, це справедливо лише в тому випадку, якщо з пакету використовується одна функція.

  2. Це тривіально, але головне правило, коли потрібно використовувати трюк @ Tommy про згладжування функції: якщо ім’я функції має тривалість mі використовується nраз, то псевдонім лише якщо m*n > m+n+3(тому що, коли ви визначаєте псевдонім, який ви витрачаєте, m+3а потім все одно витрачаєте 1 кожен раз, коли використовується псевдонім). Приклад:

    nrow(a)+nrow(b)     # 4*2 < 4+3+2
    n=nrow;n(a)+n(b)
    length(a)+length(b) # 6*2 > 6+3+2
    l=length;l(a)+l(b)
    
  3. Примус як побічний ефект функцій:

    • замість використання as.integerсимвольні рядки можна примусити до цілого числа, використовуючи ::

      as.integer("19")
      ("19":1)[1] #Shorter version using force coercion.
      
    • цілі числа, числові тощо можуть бути аналогічно примушені до символів, використовуючи pasteзамість as.character:

      as.character(19)
      paste(19) #Shorter version using force coercion.
      

6
Re: 3-й наконечник, el("19":1)ще коротший на один байт.
JayCe

19

Деякі дуже конкретні поради з гольфу:

  • якщо вам потрібно , щоб витягти довжину вектора, sum(x|1)коротше length(x), поки xчисловий, ціле, складний або логічний.
  • якщо вам потрібно витягнути останній елемент вектора, може бути дешевше (якщо можливо) ініціалізувати вектор назад за допомогою, rev()а потім викликати, x[1]а не x[length(x)](або використовувати вищезгадану підказку x[sum(x|1)]) (або tail(x,1)--- дякую Джузеппе!). Невелика зміна на цьому (де бажано другий останній елемент) можна побачити тут . Навіть якщо ви не можете ініціалізувати вектор назад, rev(x)[1]він все одно коротший x[sum(x|1)](і він працює і для векторів символів). Іноді навіть не потрібно rev, наприклад використовувати n:1замість 1:n.
  • (Як видно тут ). Якщо ви хочете примусити кадр даних до матриці, не використовуйте as.matrix(x). Візьміть транспонування транспонування, t(t(x)).

  • ifє формальною функцією. Наприклад, "if"(x<y,2,3)він коротший if(x<y)2 else 3(хоча, звичайно, 3-(x<y)коротший). Це зберігає символи лише в тому випадку, якщо вам не потрібна додаткова пара дужок, щоб сформулювати це таким чином, що часто робите.

  • Для перевірки нерівності числових об'єктів if(x-y)коротше, ніж if(x!=y). Будь-який ненульовий числовий вважається як TRUE. Якщо ви тестуєте рівність, скажімо, if(x==y)a else bтоді спробуйте if(x-y)b else a. Також дивіться попередній пункт.

  • Функція elкорисна, коли вам потрібно витягти елемент зі списку. Мабуть, найпоширеніший приклад strsplit: el(strsplit(x,""))на один байт менше, ніж strsplit(x,"")[[1]].

  • (Як тут використовується ) Векторне розширення може зберегти вас символів: якщо вектор vмає довжину, nви можете призначити їх v[n+1]без помилок. Наприклад, якщо ви хотіли надрукувати перші десять фабрикантів, які ви могли б зробити: v=1;for(i in 2:10)v[i]=v[i-1]*iа не v=1:10:for(...)(хоча, як завжди, є ще один, кращий спосіб cumprod(1:10):)

  • Іноді для текстових викликів (особливо двовимірних) plotтекст легше , ніж catвін. аргумент pch=для plotуправління , які нанесені символи. Це можна скоротити до pc=(що також буде попереджати), щоб зберегти байт. Приклад тут .

  • Щоб взяти слово під номером, не використовуйте floor(x). Використовуйте x%/%1замість цього.

  • Щоб перевірити, чи всі елементи числового чи цілого вектора рівні, ви можете часто використовувати sd, ніж щось багатослівне, наприклад all.equal. Якщо всі елементи однакові, їх стандартне відхилення дорівнює нулю ( FALSE), інакше стандартне відхилення є позитивним ( TRUE). Приклад тут .

  • Деякі функції, які, як ви очікуєте, вимагати введення цілого числа, насправді не мають. Наприклад, seq(3.5)повернеться 1 2 3(те саме стосується :оператора). Це дозволяє уникнути дзвінків floorі іноді означає, що ви можете використовувати /замість них %/%.


1
tail(v,1)така ж довжина, як і rev(v)[1]для гольфу для "останнього елемента масиву".
Джузеппе

read.csv(t="a,b,c",,F)коротше, ніж el(strsplit("a,b,c",",")).
J.Doe

18
  1. Зловживайте вбудованими Tта F. За замовчуванням, вони оцінюють , щоб TRUEі FALSE, які можуть бути автоматично перетворені в числові значення 1і 0, і вони можуть бути перевизначені за бажанням. Це означає, що вам не потрібно ініціалізувати лічильник (наприклад, i=0... i=i+1), ви можете просто використовувати Tабо Fза потреби (і перейти прямо до F=F+1пізніше).
  2. Пам'ятайте, що функції повертають останній викликаний об'єкт і не потребують явного return()виклику.
  3. Визначення коротких псевдонімів для часто використовуваних функцій чудово, таких як p=paste. Якщо ви багато використовуєте функцію і з точно двома аргументами, можливо, псевдонім інфіксації заощадить вам кілька байт. Псевдоніми для інфікування повинні бути оточені %. Наприклад:

    `%p%`=paste

    І згодом x%p%y, що на 1 байт коротше p(x,y). Визначення псевдоніму інфіксації на 4 байти довше, ніж неінфіксування p=paste, тому ви повинні бути впевнені, що воно того варте.


9
Ви можете використовувати примітивні функції і економити багато байтів:`+`=paste; x+y
Masclins

14

Використовуючи if, ifelseі`if`

Існує декілька способів зробити, якщо твердження в оптимальних рішеннях R. Golf можуть різнитися.

Основи

  1. ifпризначений для контролю потоку. Він не векторизований, тобто може оцінювати лише умови довжини 1. Це вимагає else(необов'язково) повернення іншого значення.
  2. ifelseє функцією. Він векторизований і може повертати значення довільної довжини. Третій аргумент (інше значення) є обов'язковим. *
  3. `if`є функцією, з тим же синтаксисом, що і ifelse. Він не векторизований, і жоден із зворотних аргументів не є обов'язковим.

* Це технічно не є обов'язковим; ifelse(TRUE,x)працює добре, але він видає помилку, якщо третій аргумент порожній і умова оцінюється на FALSE. Тож безпечно використовувати лише тоді, коли ви впевнені, що ця умова є завжди TRUE, і якщо це так, то чому ви навіть не турбуєтесь із заявою if?

Приклади

Всі вони рівноцінні:

if(x)y else z # 13 bytes
ifelse(x,y,z) # 13 bytes
`if`(x,y,z)   # 11 bytes

Зауважте, що пробіли навколо elseне потрібні, якщо ви використовуєте рядки безпосередньо в коді:

if(x)"foo"else"bar"   # 19 bytes
ifelse(x,"foo","bar") # 21 bytes
`if`(x,"foo","bar")   # 19 bytes

Поки `if`виглядає переможцем, доки ми не маємо векторизованих даних. А як щодо випадків, коли нас не хвилює інша умова? Скажімо, ми хочемо виконати якийсь код лише в тому випадку, якщо це умова TRUE. ifЗазвичай лише один рядок коду :

if(x)z=f(y)         # 11 bytes
ifelse(x,z<-f(y),0) # 19 bytes
`if`(x,z<-f(y))     # 15 bytes

Для кількох рядків коду ifвсе ще є переможцем:

if(x){z=f(y);a=g(y)}        # 20 bytes
ifelse(x,{z=f(y);a=g(y)},0) # 27 bytes
`if`(x,{z=f(y);a=g(y)})     # 23 bytes

Там також можливість того, де ми дійсно дбаємо про стан ще, і де ми хочемо , щоб виконати довільний код , а не повертати значення. У цих випадках, ifі `if`еквівалентні по лічильнику байтів.

if(x)a=b else z=b   # 17 bytes
ifelse(x,a<-b,z<-b) # 19 bytes
`if`(x,a<-b,z<-b)   # 17 bytes

if(x){z=y;a=b}else z=b   # 22 bytes
ifelse(x,{z=y;a=b},z<-b) # 24 bytes
`if`(x,{z=y;a=b},z<-b)   # 22 bytes

if(x)a=b else{z=b;a=y}   # 22 bytes
ifelse(x,a<-b,{z=b;a=y}) # 24 bytes
`if`(x,a<-b,{z=b;a=y})   # 22 bytes

if(x){z=y;a=b}else{z=b;a=y}   # 27 bytes
ifelse(x,{z=y;a=b},{z=b;a=y}) # 29 bytes
`if`(x,{z=y;a=b},{z=b;a=y})   # 27 bytes

Підсумок

  1. Використовуйте, ifelseколи вводиться довжина> 1.

  2. Якщо ви повертаєте просте значення, а не виконуєте багато рядків коду, використання `if`функції, ймовірно, коротше повного if... elseоператора.

  3. Якщо ви просто хочете отримати одне значення TRUE, використовуйте if.

  4. Для виконання довільного коду `if`і, ifяк правило, однакові за кількістю байтів; Рекомендую ifголовним чином тому, що це легше читати.


1
Приємно! Дуже хороші порівняння, +1!
Billywob

13
  1. Ви можете призначити змінну поточному середовищу, одночасно надаючи її як аргумент функції:

    sum(x <- 4, y <- 5)
    x
    y
  2. Якщо ви піддаєте підпункт a data.frameі ваш стан залежить від кількох його стовпців, ви можете уникнути повторення data.frameімені, використовуючи with(або subset).

    d <- data.frame(a=letters[1:3], b=1:3, c=4:6, e=7:9)
    with(d, d[a=='b' & b==2 & c==5 & e==8,])

    замість

    d[d$a=='b' & d$b==2 & d$c==5 & d$e==8,]

    Звичайно, це зберігає символи лише в тому випадку, якщо довжина ваших посилань data.frameперевищує довжинуwith(,)

  3. if...elseблоки можуть повернути значення підсумкового оператора, в якому коли-небудь частина блоку виконується. Наприклад, замість

    a <- 3
    if (a==1) y<-1 else
    if (a==2) y<-2 else y<-3

    можна писати

    y <- if (a==1) 1 else 
         if (a==2) 2 else 3

4
Єдиною обережністю щодо (1) є те, що коли ви робите це, ви передаєте його в порядку, а не за назвою аргументів. Якщо f <- function(a,b) cat(a,b), то f(a <- 'A', b <- 'B')не те саме, що f(b <- 'B', a <- 'A').
Арі Б. Фрідман

11

Неявне перетворення типу

Функції as.character, as.numericі as.logicalзанадто важкі для байтів. Давайте їх обріжемо.

Перетворення в логічне з числового (4 байти)

Припустимо x, це числовий вектор. Використовуючи логічний не оператор, !неявно перетворює числовий на логічний вектор, де 0є FALSEі ненульові значення TRUE. !то інвертує це.

x=!x

x=0:3;x=!xповертає TRUE FALSE FALSE FALSE.

Перетворення символу в числовий або логічний (7 байт)

Це весело. (З цього твіту .)

x[0]=''

R бачить , що ви оновлюєте вектор xз '', який класу character. Таким чином, він xвходить в клас, characterщоб він був сумісним з новою точкою даних. Потім він йде поставити ''в потрібному місці ... але індекс 0не існує (цей трюк також працює з Inf, NaN, NA, NULL, і так далі). В результаті xмодифікується лише в класі.

x=1:3;x[0]=''повертається "1" "2" "3", і x=c(TRUE,FALSE);x[0]=''повертається "TRUE" "FALSE".

Якщо у вашому робочому просторі вже визначений символьний об'єкт, ви можете використовувати його замість того, ''щоб зберегти байт. Наприклад, x[0]=y!

Перетворення символу в числовий або логічний за певних умов (6 байт)

Дж. До вказував у коментарях шестибайтове рішення:

c(x,"")

Це працює, якщо xатомний, і якщо ви маєте намір передати його функції, яка вимагає атомного вектора. (Функція може кидати попередження про ігнорування елементів аргументу.)

Перетворення в числове з логічного (4 байти)

Ви можете використовувати прикольний фокус з індексацією зверху (наприклад x[0]=3), але насправді є швидший спосіб:

x=+x

Позитивний оператор неявно перераховує вектор як числовий вектор, так і TRUE FALSEстає 1 0.


Вашим останнім трюком могло б x=+xстати TRUEяк 1.
Джузеппе

@Giuseppe О, так, звичайно! Дякуємо, оновлено зараз.
rturnbull

Перетворення від числового чи логічного до символьного. Ви можете використовувати, c(x,"")якщо xце атомний, за умови, що ви потім будете використовувати xу функції, яка піклується лише про перший елемент (він може скаржитися). Це на 1 байт дешевше x[0]="";.
J.Doe

10

Петлі до-час в R

Іноді я відчуваю, що бажаю, щоб R мав do-whileцикл, тому що:

 some_code
while(condition){
 some_code # repeated
}

є занадто довгим і дуже безголовим. Однак ми можемо відновити таку поведінку і відголити деякі байти під силу {функції.

{і (кожна .Primitiveфункція в Р.

Документація на них говорить:

Ефективно, (семантично еквівалентний ідентичності function(x) x, тоді {як трохи цікавіше, див. Приклади.

і під значенням,

Бо (результат оцінки аргументу. Для цього встановлено видимість, тому він автоматично надрукує, якщо використовувати його на найвищому рівні.

Для {, результат останнього обчисленого виразу . Це має видимість останнього оцінювання.

(наголос додано)

Отже, що це означає? Це означає, що цикл виконання часу настільки ж простий, як і

while({some_code;condition})0

тому що вирази всередині {}кожного оцінюються, і повертається лише останній{ , що дозволяє нам оцінювати some_codeперед тим, як увійти до циклу, і він виконується кожного разу conditionє TRUE(або правдою). Це 0один з багатьох 1-байтних виразів, що утворює "справжнє" тіло whileциклу.


10
  1. Зловживайте outerзастосовувати довільну функцію до всіх комбінацій двох списків. Уявіть матрицю з i, j, індексовану першими аргами, тоді ви можете визначити довільну функцію (i, j) для кожної пари.

  2. Використовувати Mapяк ярлик для mapply. Я стверджую, що mapplyце дешевше, ніж цикл for в тих ситуаціях, коли вам потрібно отримати доступ до індексу. Зловживати структурою списку в Р. unlistдорого. methods::elдозволяє дешево відстежувати перший елемент. Спробуйте використовувати функції з підтримкою списку на самому світі.

  3. Використовуйте do.callдля узагальнення функціональних викликів з довільними входами.

  4. Накопичені аргументи Reduceвкрай корисні для гольфу з кодом.

  5. Запис на консоль рядок за допомогою cat(blah, "\n")дешевше write(blah, 1). Жорстко закодовані рядки з "\ n" можуть бути дешевшими в деяких ситуаціях.

  6. Якщо функція поставляється з аргументами за замовчуванням, ви можете використовувати функцію (,, n-arg) для прямого визначення n-го аргументу. Приклад: seq(1, 10, , 101)У деяких функціях підтримується часткове узгодження аргументів. Приклад: seq(1, 10, l = 101).

  7. Якщо у вас виникло завдання, пов’язане з маніпуляцією з рядком, просто натисніть кнопку "назад" і прочитайте наступне питання. strsplitнесе відповідальність за руйнування R гольфу.

Тепер про кілька нововиявлених порад з 2018 року

  1. A[cbind(i,j)] = zможе бути хорошим способом маніпулювання матрицями. Ця операція є дуже байтною, якщо ви проектуєте i, j, zвектори з правильною довжиною. Ви можете зекономити ще більше, зателефонувавши у фактичну функцію індексу / призначення "[<-"(cbind(i,j), z). Цей спосіб виклику повертає змінену матрицю.

  2. Використовуйте новий рядок замість \nрозривів рядків.

  3. Стиснення підрахунків рядків може заощадити ваші байти. lapply(A<-1:10,function(y) blah)Лінійне призначення та призначення функцій аргументів - function(X, U = X^2, V = X^3)це способи цього зробити.

  4. Так "[<-"це функція в R (і пов'язана з моїм стародавним питанням про SO )! Це основна функція, відповідальна за такі операції, як x[1:5] = rnorm(5). Акуратне властивість виклику функції по імені дозволяє повернути змінений вектор. Для того, щоб слова "[<-"(x, 1:5, normr(5))робили майже те саме, що і код вище, за винятком того, що він повертає модифікований х. Пов'язані "довжина <-", "імена <-", "що-небудь <-" всі повертають модифікований вихід


1
Я думаю, що використання "[<-"гідне власної відповіді "Поради", оскільки воно поверне модифікований масив / матрицю / будь-що інше.
Джузеппе

10
  1. Збережіть значення в рядку : інші згадували, що ви можете передавати значення в порядку і призначити їх для використання в іншому місці, тобто

    sum(x<- 1:10, y<- seq(10,1,2))

    Однак ви також можете зберегти значення, вбудовані для використання в одному рядку !

    Наприклад

    n=scan();(x=1:n)[abs(x-n/2)<4]

    читає з stdin, створює змінну x=1:n, а потім індексує на xвикористання цього значення x. Це іноді може зберегти байти.

  2. Псевдонім для порожнього вектора Ви можете використовувати {}як порожній вектор, c()коли вони обидва повертаються NULL.

  3. Перетворення бази Для цілих цифр nбази 10 використовуйте n%/%10^(0:nchar(n))%%10. Це залишить кінцевий нуль, тому, якщо це важливо для вас, використовуйте, n%/%10^(1:nchar(n)-1)%%10оскільки він коротший, ніж індексація масиву. Це можна адаптувати до інших баз, використовуючи floor(log(n,b))+1замість нихnchar(n)

  4. Використання seqі: : Замість того щоб використовувати 1:length(l)(або 1:sum(x|1)), ви можете використовувати до seq(l)тих пір , як lце listабо vectorдовжини більше 1, а значення по замовчуванням дорівнює seq_along(l). Якщо lпотенційно може бути довжина 1, seq(a=l)зробимо трюк.

    Крім того, :буде (з попередженням) використовувати перший елемент своїх аргументів.

  5. Видалення атрибутів Використання c()для array(або matrix) буде робити те саме, що і as.vector; він, як правило, видаляє атрибути без імені.

  6. Факторне використання gamma(n+1)коротше, ніж використання, factorial(n)і factorialвизначається як gamma(n+1)інакше.

  7. Перегортання монети Коли потрібно виконати випадкове завдання 50% часу, використання rt(1,1)<0коротше, ніж runif(1)<0.5на три байти.

  8. Витягування / виключення елементів head і tailчасто корисно для вилучення перших / останніх кількох елементів масиву; head(x,-1)витягує всі, крім останнього елемента, і коротше, ніж використання негативної індексації, якщо ви ще не знаєте довжини:

    head(x,-1)
    x[-length(x)]
    x[-sum(x|1)]


@ J.Doe гідний своєї посади, я думаю! Можливо, з назвою "альтернативи rep". Інші поради щодо питань мають обмеження до однієї підказки на відповідь, яку я щиро схвалюю і для цього питання! Крім того, 1:n*0він коротший, ніж Im(1:n)на два байти, а значить, і другий трюк може бути x+0*-n:n:-)
Джузеппе

1
@ J.Doe Або ще краще, !1:nце також масив nнулів залежно від випадку використання; Хоча заслуга в питанні MATL / MATLAB підказок (можливо, Луїс Мендо) для цього, хоча.
Джузеппе

Дякую, @Giuseppe! Чи можу я запропонувати вам створити цю публікацію, оскільки я не хочу брати репутацію у ваших добрих ідей.
J.Doe

@ J.Doe о, я не проти. Завжди добре, щоб інші гольфісти R отримували більшу видимість; Я думаю, що справедливо сказати, що я досить відома сутність в цьому моменті! Ви збираєтесь запропонувати досить вражаючі вдосконалення, тому візьміть представник (каламбур не призначений) і продовжуйте добре працювати з гольфу :-)
Джузеппе

1
(log(i,b)%/%1):0)а не замість floor(log(n,b))+1?
ASCII лише

8

Деякі основні поняття, але повинні бути дещо корисними:

  1. У операторах управління потоками ви можете зловживати тим, що будь-яке число, яке не дорівнює нулю, буде оцінено як TRUE, наприклад: if(x)еквівалентне if(x!=0). І навпаки, if(!x)еквівалентно if(x==0).

  2. При генерації послідовностей, використовуючи :(наприклад, 1:5), можна зловживати тим фактом, що оператор експоненції ^є єдиним оператором, який має перевагу над :-оператором (на відміну від +-*/).

    1:2^2 => 1 2 3 4 

    що зберігає вам два байти на круглих дужках, які, як правило, доведеться використовувати, якщо ви хочете, наприклад, перевести цикл на елементи n x nматриці ( 1:n^2) або будь-яке інше ціле число, яке може бути виражено більш коротким способом, використовуючи експоненціальне позначення ( 1:10^6).

  3. +-*/Зв'язаний трюк, звичайно, може бути використаний і для векторизованих операцій , хоча найчастіше це стосується +-:

    for(i in 1:(n+1)) can instead be written as for(i in 0:n+1)

    Це працює тому +1, що векторизується і додає 1до кожного елемента, що отримує 0:nвектор 1 2 ... n+1. Аналогічно 0:(n+1) == -1:n+1економиться і один байт.

  4. Під час написання коротких функцій (які можуть бути виражені в одному рядку), можна зловживати змінним призначенням, щоб зберегти два байти на фігурних дужках, що додаються {...}:

    f=function(n,l=length(n))for(i in 1:l)cat(i*l,"\n")
    f=function(n){l=length(n);for(i in 1:l)cat(i*l,"\n")}

    Зауважте, що це не завжди може відповідати правилам певних проблем.


Лише невелика корекція: ^векторизована, вона просто має перевагу над :(тобто виконується раніше, :якщо дужки прямо не вказують протилежне, див ?Syntax. Точний порядок пріоритетності бінарних та одинарних операторів). Те ж саме стосується двійкових, +-/*які мають нижчий пріоритет, ніж :звідси ваш трюк № 3.
планнапус

@plannapus Дякую за уточнення. Оновлено формулювання.
Billywob

7

Змініть значення операторів

Оператори R - це лише функції, які отримують спеціальний режим обробки парсером. Наприклад, <це фактично функція двох змінних. Ці два рядки коду роблять те саме:

x < 3
`<`(x, 3) 

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

`<`=rep

тепер означає, що ці два рядки коду роблять те саме:

rep("a", 3)
"a"<3

і пріоритет дотримується, внаслідок чого подібні речі

"a"<3+2
#[1] "a" "a" "a" "a" "a"

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

Деякі оператори люблять +і -можуть приймати один або два параметри, тож ви навіть можете робити такі речі, як:

`-`=sample
set.seed(1)
-5  # means sample(5)
#[1] 2 5 4 3 1
5-2 # means sample(5, 2)
#[1] 5 4

Дивіться, наприклад, цю відповідь .

Дивіться також цю відповідь щодо використання [як двобайтового, триаргументативного оператора.


2
Це коментар до порад rturnbull, але я думаю, що нам потрібно почати застосовувати правило "одна порада за відповідь", тому що настільки химерно важко знайти той, який мені потрібен, коли я приїжджаю сюди.
Джузеппе

1
також, залежно від переваги операторів, ви можете робити якісь прикольні речі, які можуть допомогти; like <має нижчий пріоритет ніж +, але *має вищий пріоритет, ніж +таким чином, ви могли потенційно зв'язати їх разом!
Джузеппе

1
@Giuseppe ти знаєш, що я намагався знайти перед публікацією, і не зміг його знайти. Дякуємо, що вказали на це. Я планую додавати більше деталей щодо пріоритетності оператора з прикладами, оскільки я все більше і більше починаю використовувати цей трюк.
JayCe

2
Ось такий цікавий: якщо ви прив'язуєтесь ?до pasteякоїсь іншої функції, яка може брати два аргументи, порядок пріоритетності означає, що ви все ще можете використовувати вбудовані завдання через a<-b?d<-e.
Дж. Дое

1
Ви повинні додати [псевдонім три елемента (це два байти); Я часто вважаю корисним такі речі, як outer(і постійно про це забуваю!), Хоча, звичайно, вам потрібно переконатися, що вам насправді не потрібно використовувати [. Можливо, також буде корисним посилання на сторінку пріоритету оператора, щоб допомогти у виборі псевдоніму.
Джузеппе

5

Сценарії, де можна уникнути paste(...,collapse="")іstrsplit

Це біль у звичайних важких викликах. Є деякі шляхи вирішення.

  • Reduce(paste0,letters) за -5 байт від paste0(letters,collapse="")

  • 2-байтовое поле , де у вас є список , що містить два вектора c(1,2,3)і c(4,5,6)і хочете об'єднати їх поелементно в рядок "142536". Зловживання оператором дає змогу p=paste0;"^"=Reduce;p^p^rзаощадити два байти під час звичайного paste0дзвінка.

  • Замість paste0("(.{",n,"})")побудови (наприклад) регулярного вираження на 20 байт, розглянемо регулярний вираз у регулярному вираженні: sub(0,"(.{0})",n)для 17 байт.

Іноді (досить часто насправді) вам потрібно буде перебирати вектор символів або рядків або розділяти слово на літери. Є два поширених випадки використання: той, де вам потрібно взяти вектор символів як вхід до функції чи програми, і той, де ви знаєте вектор заздалегідь і його потрібно десь зберігати у своєму коді.

а. Де потрібно взяти рядок як вхідний і розділити його на слова або символи .

  1. Якщо вам потрібні слова (включаючи символи як окремий регістр):

    • Якщо новий рядок 0x10(ASCII 16), що розділяє слова, добре, x=scan(,"")бажано ввести код у function(s,x=el(strsplit(s," "))).

    • Якщо слова можуть бути розділені будь-яким іншим прогалиною , в тому числі кількох прогалин, табуляції, новий рядок і т.д., ви можете використовувати @ НГМИ в подвійному фокусі сканування : x=scan(,"",t=scan(,"")). Це дає сканованому рядку scanяк textаргумент і розділяє його пробілом.

    • Другим аргументом scanможе бути будь-який рядок, тому якщо ви створили його, ви можете переробити його, щоб зберегти байт.

  2. Якщо вам потрібно перетворити вхідний рядок у вектор символів :

    • x=el(strsplit(s,""))є найкоротшим загальним рішенням. splitАргумент працює на чому - або нульової довжини в тому числі c(), і {}т.д. , так що якщо ви випадково створили змінну нульової довжини, ви можете використовувати його , щоб зберегти байт.

    • Якщо ви можете працювати з кодами символів ASCII, врахуйте utf8ToInt, оскільки utf8ToInt(x)це коротше, ніж strsplitвиклик. Щоб вставити їх назад разом, intToutf8(utf8ToInt(x))коротше, ніж Reduce(paste0,el(strsplit(x,""))).

    • Якщо вам потрібно розділити довільні рядки чисел на зразок "31415926535"введення, ви можете використовувати utf8ToInt(s)-48для збереження 3 байти el(strsplit(s,"")), за умови, що ви можете використовувати цілі цифри замість символів, як це часто буває. Це також коротше, ніж звичайний рецепт ділення чисел на десяткові цифри.

б. Де потрібно заздалегідь зафіксований вектор або слів, або символів.

  • Якщо вам потрібен вектор окремих символів , які мають деяку закономірність або знаходяться в алфавітному порядку, подивитися на використанні intToUtf8або chartrзастосовуються до послідовності через a:bабо на вбудованому в листах наборів lettersабо LETTERS. Мова модель вбудовується в chartrце особливо потужним .

  • Для отримання 1 до 3 символів або слів , c("a","b","c")є єдиним загальним найкоротшим рішенням.

  • Якщо вам потрібен фіксований вектор між 4 і 10 символами або словами , які не scanмістять пробілів , використовуйте stdinяк fileаргумент:

f(x=scan(,""))
q
w
e
r
t
y
u
  • Якщо scanз " stdinнеможливо", для 6 або більше символів або слів , які не scanмістять пробілів , використовуйте textаргумент scan(,"",t="a b c d e f").

  • Якщо вам потрібен вектор (a) 6 або більше символів будь-якого типу або (b) 10 або більше символів , що не мають пробілу , strsplitчерез x=el(strsplit("qwertyuiop","")), ймовірно, шлях.

  • Можливо, вам вдасться уникнути наступного цитата :, quote(Q(W,E,R,T,Y))який створює це вираз. Деякі функції на кшталт strrep, і grepпримусять це передати вектор рядків! Якщо це зробити, це добре для будь-якої довжини слова або символу від 3 до 11.

  • Немає вагомих причин використовувати strsplitслова через x=el(strsplit("q w e r t y"," ")). Він завжди програє scan(,"",t="q w e r t y"))за допомогою фіксованого накладного набору в 5 байт.

Ось таблиця байтових підрахунків, використовуваних кожним підходом для читання у векторі одиничних символів довжини n. Відносний порядок в кожному рядку дійсний для символів або слів, за винятком strsplitна ""яких працює тільки на персонажах.

| n  | c(...) | scan | scan | strsplit | quote |
|    |        |+stdin|+text | on ""    | hack  |
|    |        |      |      | CHAR ONLY|       |
|----|--------|------|------|----------|-------|
| 1  | 3      | 11   | 15   | 20       | 8     |
| 2  | 10     | 13   | 17   | 21       | 11    |
| 3  | 14     | 15   | 19   | 22       | 13    |
| 4  | 18     | 17   | 21   | 23       | 15    |
| 5  | 22     | 19   | 23   | 24       | 17    |
| 6  | 26     | 21   | 25   | 25       | 19    |
| 7  | 30     | 23   | 27   | 26       | 21    |
| 8  | 34     | 25   | 29   | 27       | 23    |
| 9  | 38     | 27   | 31   | 28       | 25    |
| 10 | 42     | 29   | 33   | 29       | 27    |
| 11 | 46     | 31   | 35   | 30       | 29    |
| 12 | 50     | 33   | 37   | 31       | 31    |

c. Якщо вам потрібно ввести текст як матрицю символів , є кілька рецептів, які здаються короткими

s="hello\nworld\n foo"

# 43 bytes, returns "" padded data frame
# If lines > 5 are longer than lines <= 5, wraps around and causes error
read.csv(t=gsub("(?<=.)(?=.)",",",s,,T),,F)

# 54 bytes with readLines(), "" padded matrix
sapply(p<-readLines(),substring,p<-1:max(nchar(p)),p))

# plyr not available on TIO
# 58 bytes, returns NA padded matrix, all words split by whitespace
plyr::rbind.fill.matrix(Map(t,strsplit(scan(,"",t=s),"")))
# 61 bytes, returns NA padded matrix
plyr::rbind.fill.matrix(Map(t,(a=strsplit)(el(a(s,"\n")),"")))

1
scanмає textаргумент, який є більш конкурентоспроможним, ніж el(strsplit(x," "))якщо вам потрібні лише рядки! Спробуйте в Інтернеті! На відміну від вашої останньої пропозиції від read.csv.
Джузеппе

Якщо ви просто хочете символів, то ваш дзвінок scanкраще до 5 символів, el(strsplit(x,""))є більш конкурентоспроможним, ніж scanдля 6 і більше. Спробуйте в Інтернеті! Я ще не знайшов корисного для read.csv, але, можливо, це було б корисно, якщо вам потрібна таблиця даних з якихось причин?
J.Doe

Я ніколи не знайшов для себе використання, data.frameале, можливо, нам потрібно знайти / створити виклик, де це було б корисно! Може бути dplyrстиль group_by()і summarize()тип маніпуляції? ІДК.
Джузеппе

А для читання в рядках scan(,"")все ж здається краще? Спробуйте в Інтернеті!
J.Doe

Так, звичайно, хоча якщо ви інтерпретуєте вхідний формат строго так, як це робить ngm, то подвійне scanзручно.
Джузеппе

4

Деякі способи знайти перший ненульовий елемент масиву.

Якщо він має ім'я x:

x[!!x][1]

Повертає, NAякщо немає ненульових елементів (у тому числі, коли xпорожній, але які не NULLпомилки.)

Анонімно:

Find(c, c(0,0,0,1:3))

Повертає , NULLякщо немає ненульових елементів, або порожній або NULL.


Це повернеться, NAякщо всі елементи xнулю, я вважаю, тому використовуйте це з обережністю!
Джузеппе

Find(c,x)- це той самий облік рахунку: перевага, яку вам не потрібно повторювати (визначати) x, та інша поведінка, якщо не відповідає. TIO
JayCe

Findце також трохи безпечніше, як це працює NULL, до тих пір, поки з результатом нічого іншого не повинно відбутися, і тоді я не впевнений, повертається NAчи NULLбезпечніше.
ngm

о, це правильно. проблема з поверненням NULL - це помилки ... у питанні порівняння версій я спершу спробував, sign(Find(c,w))що спричинило помилки - довелося зробити, Find(c,sign(w))щоб не помилитися . Я думаю, що обидва способи мають своє використання.
JayCe

4

Альтернативи rep()

Іноді rep()можна уникнути за допомогою оператора ободової кишки :та переробки векторів R.

  • Для повторення nнулів, де n>0, 0*1:nна 3 байти коротше rep(0,n)і !1:n, а масив FALSE- на 4 байти коротший, якщо випадок використання дозволяє це.

  • Для повторення x nразів, x+!1:nце на 2 байти коротше rep(x,n). Для nних використовуйте, !!1:nякщо ви можете використовувати масив TRUE.

  • Для повторення x 2n+1разів, де n>=0, x+0*-n:nна 4 байти коротше, ніж rep(x,2*n+1).

  • Заява !-n:nнадасть TRUEпобічну сторону з обох сторін n FALSE. Це можна використовувати для отримання парних чисел символів у дзвінках, intToUtf8()якщо ви пам'ятаєте, що нуль ігнорується.

Модульна арифметика може бути корисною. repзаяви з eachаргументом іноді можна уникнути, використовуючи ціле ділення.

  • Для того, щоб згенерувати вектор c(-1,-1,-1,0,0,0,1,1,1), -3:5%/%35 байт коротше rep(-1:1,e=3).

  • Щоб генерувати вектор c(0,1,2,0,1,2,0,1,2), 0:8%%3економить 4 байти rep(0:2,3).

  • Іноді нелінійні перетворення можуть вкоротити арифметику послідовності. Щоб відобразити i in 1:15на c(1,1,3,1,1,3,1,1,3,1,1,3,1,1,3)всередині складеного оператора, очевидний golfy відповідь 1+2*(!i%%3)на 11 байт. Однак, 3/(i%%3+1)це 10 байтів , і вони відповідатимуть тій же послідовності, тому її можна використовувати, якщо вам потрібна послідовність для індексації масиву.


3

Коли вам потрібно використовувати функцію, використовуйте pryr::f()замість function().

Приклад:

function(x,y){x+y}

еквівалентно

pryr::f(x,y,x+y)

або, ще краще,

pryr::f(x+y)

Оскільки Якщо є лише один аргумент, формали відгадуються з коду .


Якщо ви не можете звести його до одного аргументу (наприклад, у третьому прикладі), це не гольф, бо function(x,y){x+y}можна записати як function(x,y)x+yдля того ж самого рахунку, що pryr::f(x,y,x+y)й з більшою читабельністю.
Khuldraeseth na'Barya

3

Переживаючі виклики, пов’язані з струнами

Як згадується в іншій відповіді, unlist(strsplit(x,split="")і це paste(...,collapse="")може пригнічувати. Але не просто йдіть від них, є обхідні шляхи!

  • utf8ToIntперетворює рядок у вектор, intToUtf8робить зворотну операцію. Ви отримуєте вектор int, а не вектор, charале іноді це те, що ви шукаєте. Наприклад, для створення списку -, кращого використання, intToUtf8(rep(45,34))ніжpaste(rep("-",34),collapse="")
  • gsubє більш корисною, ніж інша функція grepродини при роботі над однією струною. Два вище підходи можна поєднувати, як у цій відповіді, яка скористалася порадами ovs , Giuseppe та ngm .
  • Виберіть зручний формат вводу-виводу, як у цій відповіді, беручи введення як текстові рядки (без лапок), або цей, який бере вектор знаків. Якщо у вас виникли сумніви, зверніться до ОП.
  • Як зазначається в коментарях, <порівнюється рядки лексикографічно, як можна було б очікувати.

intToUtf8також є другий аргумент, multiple = FALSEякий буде конвертувати з ints в окремі символи (рядки довжиною-одна), а не в одну рядок, якщо встановлено TRUE.
Джузеппе

Також, починаючи з 3.5.0, є третій аргумент allow_surrogate_pairs = FALSE, але я не знаю, що це робить; Документи говорять щось про читання двобайтів як, UTF-16але я ледве знаю, що UTF-8це таке, я просто ігнорую це, поки хтось інший не знайде спосіб пограти в гольф.
Джузеппе

2

Поради щодо проблем із обмеженим джерелом:

  1. Символи в константах R-літералів можна замінити шістнадцятковими кодами, восьмеричними кодами та унікодами.

    наприклад, рядок "abcd"може бути записаний:

        # in octal codes
        "\141\142\143\144"
    
        # in hex codes
        "\x61\x62\x63\x64"
    
        # in unicodes
         "\u61\u62\u63\u64"
        # or
        "\U61\U62\U63\U64" 

    Ми також можемо змішувати символи з восьмигранними / шістнадцятковими / унікодними і використовувати разом декілька кодів окта та деякі шістнадцяткові коди, доки символи Unicode не змішуються з восьмеричними / шістнадцятковими, наприклад:

        # Valid
        "a\142\x63\x64"
    
        # Valid
        "ab\u63\U64"
    
        # Error: mixing Unicode and octal/hex escapes in a string is not allowed
        "\141\142\x63\u64"

    Детальнішу інформацію див. У кінці цього розділу .

  2. Оскільки функції можна записати за допомогою рядкових літералів, наприклад, cat()можна записати альтернативно:

    'cat'()
    "cat"()
    `cat`()

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

    # all equal to cat()
    "\143\141\164"()
    `\x63\x61\x74`()
    '\u63\u61\u74'()
    "ca\u74"()

    за винятком лише того, що послідовності unicode не підтримуються всередині задників ``

  3. Круглі дужки можна уникнути зловживань операторами, наприклад:

    cat('hello')
    
    # can be written as
    `+`=cat;+'hello'

Застосування всіх трьох хитрощів можна знайти в цій відповіді


Також цифри можна записати шістнадцятковим: 0xBі 0xbreturn 11(не потрібно зворотних посилань чи лапок).
Робін Райдер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.