Wskazówki do gry w golfa w R.

58

Szukam wskazówek dotyczących gry w golfa w języku statystycznym R. R jest być może niekonwencjonalnym wyborem dla golfa. Jednak robi pewne rzeczy bardzo zwięźle (sekwencje, losowość, wektory i listy), wiele wbudowanych funkcji ma bardzo krótkie nazwy i ma opcjonalny terminator linii (;). Jakie wskazówki i porady możesz pomóc rozwiązać problemy z kodem do golfa w R?

Ari B. Friedman
źródło
14
Odpowiedzi na to pytanie może się podwoić jako anty-styleguide na badania, biorąc pod uwagę, że kod golf jest naprawdę jedyny czas trzeba zrobić wiele z tych rzeczy :-)
Andrzej Breza

Odpowiedzi:

44

Kilka porad:

  1. W R zaleca się użycie <-ponad =. W przypadku golfa sytuacja jest odwrotna, ponieważ =jest krótszy ...
  2. Jeśli wywołujesz funkcję więcej niż raz, często korzystne jest zdefiniowanie krótkiego aliasu:

    as.numeric(x)+as.numeric(y)
    
    a=as.numeric;a(x)+a(y)
    
  3. Częściowe dopasowanie może być twoim przyjacielem, szczególnie gdy funkcje zwracają listy, których potrzebujesz tylko jednego elementu. Porównaj rle(x)$lengthszrle(x)$l

  4. Wiele wyzwań wymaga przeczytania danych wejściowych. scanczęsto jest do tego odpowiedni (użytkownik kończy wprowadzanie danych przez wprowadzenie pustej linii).

    scan()    # reads numbers into a vector
    scan(,'') # reads strings into a vector
    
  5. Przymus może być przydatny. t=1jest znacznie krótszy niż t=TRUE. Alternatywnie, switchmożesz również zaoszczędzić cenne postacie, ale będziesz chciał użyć 1,2 zamiast 0,1.

    if(length(x)) {} # TRUE if length != 0
    sum(x<3)         # Adds all the TRUE:s (count TRUE)
    
  6. Jeśli funkcja oblicza coś skomplikowanego i potrzebujesz różnych innych rodzajów obliczeń opartych na tej samej wartości podstawowej, często korzystne jest: a) rozbicie jej na mniejsze funkcje, b) zwrócenie wszystkich potrzebnych wyników w postaci listy lub c) niech zwraca różne typy wartości w zależności od argumentu funkcji.

  7. Jak w każdym języku, dobrze go znam - R ma tysiące funkcji, prawdopodobnie jest kilka, które mogą rozwiązać problem za pomocą bardzo niewielu znaków - sztuką jest wiedzieć, które z nich!

Niektóre niejasne, ale przydatne funkcje:

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

Niektóre wbudowane zestawy danych i symbole:

letters     # 'a','b','c'...
LETTERS     # 'A','B','C'...
month.abb   # 'Jan','Feb'...
month.name  # 'January','Feburary'...
T           # TRUE
F           # FALSE
pi          # 3.14...
Tommy
źródło
22
  1. Zamiast importować pakiet za pomocą library, pobierz zmienną z pakietu za pomocą ::. Porównaj następujące:

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

    Oczywiście jest to poprawne tylko wtedy, gdy z pakietu używana jest jedna funkcja.

  2. Jest to trywialna, ale ogólna zasada, kiedy należy użyć sztuczki @ Tommy polegającej na aliasingu funkcji: jeśli nazwa funkcji ma długość mi jest używana nrazy, to alias tylko wtedy, gdy m*n > m+n+3(ponieważ podczas definiowania aliasu wydajesz, m+3a następnie nadal wydajesz 1 za każdym razem, gdy używany jest alias). Przykład:

    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. Przymus jako efekt uboczny funkcji:

    • zamiast używać as.integerciąg znaków można przekonwertować na liczbę całkowitą, używając ::

      as.integer("19")
      ("19":1)[1] #Shorter version using force coercion.
      
    • liczba całkowita, numeryczna itp. może być podobnie wymuszona na znak, używając pastezamiast as.character:

      as.character(19)
      paste(19) #Shorter version using force coercion.
      
plannapus
źródło
6
Re: 3. wskazówka, el("19":1)jest jeszcze krótsza o jeden bajt.
JayCe
19

Kilka bardzo specyficznych wskazówek golfowych:

  • jeśli chcesz wyodrębnić długość wektora, sum(x|1)jest on krótszy niż length(x)tak długi, jak xnumeryczny, całkowity, złożony lub logiczny.
  • jeśli chcesz wyodrębnić ostatni element wektora, może być tańsze (jeśli to możliwe) zainicjowanie wektora wstecz przy użyciu, rev()a następnie wywołanie x[1]zamiast x[length(x)](lub przy użyciu powyższej wskazówki x[sum(x|1)]) (lub tail(x,1)--- dzięki Giuseppe!). Nieznaczna odmiana tej sprawie (gdzie przedostatni element został potrzeby) widać tu . Nawet jeśli nie można zainicjować wektora od tyłu, rev(x)[1]jest on nadal krótszy niż x[sum(x|1)](i działa również w przypadku wektorów znaków). Czasami nawet nie potrzebujesz rev, na przykład używania n:1zamiast 1:n.
  • (Jak widać tutaj ). Jeśli chcesz przymusić ramkę danych do macierzy, nie używaj as.matrix(x). Weź transpozycję do transpozycji t(t(x)).

  • ifjest funkcją formalną. Na przykład "if"(x<y,2,3)jest krótszy niż if(x<y)2 else 3(choć oczywiście 3-(x<y)krótszy niż którykolwiek z nich). Zapisuje to tylko postacie, jeśli nie potrzebujesz dodatkowej pary nawiasów klamrowych, aby sformułować to w ten sposób, co często robisz.

  • Do testowania nierówności obiektów numerycznych if(x-y)jest krótszy niż if(x!=y). Każda niezerowa liczba jest traktowana jako TRUE. Jeśli testujesz równość, powiedzmy, if(x==y)a else bspróbuj if(x-y)b else azamiast tego. Zobacz także poprzedni punkt.

  • Ta funkcja eljest przydatna, gdy musisz wyodrębnić element z listy. Najczęstszym przykładem jest prawdopodobnie strsplit: el(strsplit(x,""))jest o jeden mniej bajtów niż strsplit(x,"")[[1]].

  • (Jak tutaj użyto ) Rozszerzenie wektora może oszczędzić ci znaków: jeśli wektor vma długość n, możesz przypisać go v[n+1]bezbłędnie. Na przykład, jeśli chcesz wydrukować pierwsze dziesięć silni, które możesz zrobić: v=1;for(i in 2:10)v[i]=v[i-1]*izamiast v=1:10:for(...)(choć jak zawsze istnieje inny, lepszy sposób cumprod(1:10):)

  • Czasami w przypadku wyzwań opartych na tekście (szczególnie w 2D) łatwiej jest plottekstowi niż catgo. argument pch=do plotkontroli, które znaki są wykreślone. Można to skrócić do pc=(co da również ostrzeżenie), aby zapisać bajt. Przykład tutaj .

  • Aby zabrać głos pewnej liczbie, nie używaj floor(x). Użyj x%/%1zamiast tego.

  • Aby sprawdzić, czy wszystkie elementy wektora liczbowego lub liczb całkowitych są równe, często można użyć sdzamiast czegoś pełnego, takiego jak all.equal. Jeśli wszystkie elementy są takie same, ich odchylenie standardowe wynosi zero ( FALSE), w przeciwnym razie odchylenie standardowe jest dodatnie ( TRUE). Przykład tutaj .

  • Niektóre funkcje, których należy oczekiwać od liczb całkowitych, w rzeczywistości tego nie robią. Na przykład seq(3.5)zwróci 1 2 3(to samo dotyczy :operatora). Pozwala to uniknąć połączeń z, floora czasem oznacza, że ​​możesz użyć /zamiast %/%.

JDL
źródło
1
tail(v,1)ma również taką samą długość jak rev(v)[1]końcówka golfowa „ostatni element tablicy”.
Giuseppe
read.csv(t="a,b,c",,F)jest krótszy niż el(strsplit("a,b,c",",")).
J.Doe
18
  1. Nadużywaj wbudowanych Ti F. Domyślnie oceniają na TRUEi FALSE, które można automatycznie przekonwertować na wartości liczbowe 1i 0, i można je ponownie zdefiniować do woli. Oznacza to, że nie musisz inicjować licznika (np. i=0... i=i+1), możesz po prostu użyć Tlub Fw razie potrzeby (i przejść od razu do F=F+1później).
  2. Pamiętaj, że funkcje zwracają ostatnio wywoływany obiekt i nie wymagają jawnego return()wywołania.
  3. Świetne jest definiowanie krótkich aliasów dla często używanych funkcji, takich jak p=paste. Jeśli często używasz funkcji i przy dokładnie dwóch argumentach, możliwe, że alias naprawczy zaoszczędzi ci trochę bajtów. Naprzemienne aliasy muszą być otoczone %. Na przykład:

    `%p%`=paste

    A następnie x%p%y, który jest o 1 bajt krótszy niż p(x,y). Definicja aliasu naprawczego jest jednak o 4 bajty dłuższa niż niefiksowanie p=paste, więc musisz być pewien, że warto.

rturnbull
źródło
9
Możesz korzystać z prymitywnych funkcji i oszczędzać wiele bajtów:`+`=paste; x+y
Masclins
14

Korzystanie if, ifelseoraz`if`

Istnieje kilka sposobów wykonywania instrukcji if w R. Rozwiązania optymalne dla golfa mogą się bardzo różnić.

Podstawy

  1. ifsłuży do sterowania przepływem. Nie jest wektoryzowany, tzn. Może oceniać tylko warunki długości 1. Wymaga else(opcjonalnie) zwrócenia innej wartości.
  2. ifelsejest funkcją. Jest wektoryzowany i może zwracać wartości o dowolnej długości. Trzeci argument (wartość else) jest obowiązkowy. *
  3. `if`jest funkcją o takiej samej składni jak ifelse. Nie jest wektoryzowany, żaden z argumentów zwrotnych nie jest obowiązkowy.

* Nie jest to technicznie obowiązkowe; ifelse(TRUE,x)działa dobrze, ale generuje błąd, jeśli trzeci argument jest pusty, a warunek zostanie oceniony FALSE. Można go bezpiecznie używać tylko wtedy, gdy masz pewność, że warunek jest zawsze TRUE, a jeśli tak, to dlaczego w ogóle zawracasz sobie głowę instrukcją if?

Przykłady

Wszystkie są równoważne:

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

Zauważ, że spacje wokół elsenie są wymagane, jeśli używasz ciągów bezpośrednio w kodzie:

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

Jak dotąd `if`wydaje się być zwycięzcą, o ile nie mamy wektoryzacji danych wejściowych. Ale co z przypadkami, w których nie dbamy o inny stan? Powiedzmy, że chcemy wykonać część kodu tylko wtedy, gdy jest to warunek TRUE. ifZwykle najlepszy jest tylko jeden wiersz kodu :

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

W przypadku wielu wierszy kodu ifnadal wygrywa:

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

Istnieje również możliwość, gdzie należy dbać o stan innego, i gdzie chcemy wykonać dowolny kod zamiast zwracać wartość. W takich przypadkach ifi `if`są równoważne w liczbie bajtów.

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

Podsumowanie

  1. Użyj, ifelsegdy wprowadzisz długość> 1.

  2. Jeśli zwracasz prostą wartość zamiast wykonywania wielu wierszy kodu, użycie `if`funkcji jest prawdopodobnie krótsze niż pełna if... elseinstrukcja.

  3. Jeśli chcesz tylko jednej wartości TRUE, użyj if.

  4. Do wykonania dowolnego kodu `if`i ifzwykle są takie same pod względem liczby bajtów; Polecam ifgłównie dlatego, że jest łatwiejszy do odczytania.

rturnbull
źródło
1
Miły! Bardzo dobre porównania, +1!
Billywob
13
  1. Możesz przypisać zmienną do bieżącego środowiska, jednocześnie podając ją jako argument do funkcji:

    sum(x <- 4, y <- 5)
    x
    y
  2. Jeśli wpisujesz a, data.framea twój stan zależy od kilku jego kolumn, możesz uniknąć powtarzania data.framenazwy za pomocą with(lub 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,])

    zamiast

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

    Oczywiście zapisuje to znaki tylko wtedy, gdy długość twoich referencji data.frameprzekracza długośćwith(,)

  3. if...elsebloki mogą zwracać wartość instrukcji końcowej, w której wykonywana jest kiedykolwiek część bloku. Na przykład zamiast

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

    Możesz pisać

    y <- if (a==1) 1 else 
         if (a==2) 2 else 3
Matthew Plourde
źródło
4
Jedyną przestrogą dotyczącą (1) jest to, że kiedy to robisz, przekazujesz je w kolejności, a nie według nazwanych argumentów. Jeśli f <- function(a,b) cat(a,b), f(a <- 'A', b <- 'B')to nie jest to samo co f(b <- 'B', a <- 'A').
Ari B. Friedman
11

Niejawna konwersja typu

Funkcje as.character, as.numerici as.logicalsą zbyt ciężkie bajt. Przytnijmy je.

Konwersja na logiczny z numerycznego (4 bajty)

Załóżmy, że xjest wektorem numerycznym. Użycie operatora logicznego not !niejawnie przekształca wartość liczbową w wektor logiczny, gdzie 0jest FALSEi są wartości niezerowe TRUE. !następnie odwraca to.

x=!x

x=0:3;x=!xzwraca TRUE FALSE FALSE FALSE.

Konwersja znaków na postać numeryczną lub logiczną (7 bajtów)

To jest zabawne. (Z tego tweeta .)

x[0]=''

R widzi, że jesteś aktualizowanie wektor xz '', czyli klasy character. Rzuca więc xna klasę, characterdzięki czemu jest kompatybilny z nowym punktem danych. Następnie przechodzi się umieścić ''w odpowiednim miejscu ... ale indeks 0nie istnieje (ten trik działa również z Inf, NaN, NA, NULL, i tak dalej). W rezultacie xjest modyfikowany tylko w klasie.

x=1:3;x[0]=''zwraca "1" "2" "3"i x=c(TRUE,FALSE);x[0]=''zwraca "TRUE" "FALSE".

Jeśli masz już znak obiektu w swoim obszarze roboczym, możesz go użyć zamiast ''zapisać bajt. Np. x[0]=y!

Konwersja znaków na postać liczbową lub logiczną pod pewnymi warunkami (6 bajtów)

J.Doe wskazał w komentarzach sześciobajtowe rozwiązanie:

c(x,"")

Działa xto, jeśli jest atomowy i jeśli zamierzasz przekazać go do funkcji, która wymaga wektora atomowego. (Funkcja może generować ostrzeżenie o ignorowaniu elementów argumentu.)

Konwersja na numeryczną z logicznej (4 bajty)

Możesz użyć funky sztuczki indeksowania z góry (np. x[0]=3), Ale w rzeczywistości jest szybszy sposób:

x=+x

Operator dodatni domyślnie przekształca wektor jako wektor numeryczny, tak się TRUE FALSEdzieje 1 0.

rturnbull
źródło
Twoja ostatnia sztuczka może być x=+x, aby utrzymać TRUEjak 1.
Giuseppe,
@Giuseppe Oh, duh, oczywiście! Dzięki, zaktualizowano teraz.
rturnbull,
Konwersja z numerycznej lub logicznej na znak. Możesz użyć c(x,"")if xjest atomowa, pod warunkiem, że będziesz używał xfunkcji, która dba tylko o pierwszy element (może narzekać). Jest to 1 bajt tańszy niż x[0]="";.
J.Doe
10

Pętle do-while w R.

Czasami żałuję, że R nie ma do-whilepętli, ponieważ:

 some_code
while(condition){
 some_code # repeated
}

jest zdecydowanie za długi i bardzo nie golfowy. Możemy jednak przywrócić to zachowanie i zgolić niektóre bajty dzięki sile {funkcji.

{i (każda z .Primitivefunkcji jest w R.

Dokumentacja dla nich brzmi:

W rzeczywistości (jest semantycznie równoważny z tożsamością function(x) x, podczas gdy {jest nieco bardziej interesujący, patrz przykłady.

i poniżej wartości,

Na (wynik z oceny argumentu. Ma to ustawioną widoczność, więc będzie drukowane automatycznie, jeśli zostanie użyte na najwyższym poziomie.

Dla {, wynik ostatniego wyrażenia oceniana . Ma to widoczność ostatniej oceny.

(podkreślenie dodane)

Co to znaczy? Oznacza to, że pętla „do-while” jest tak prosta jak

while({some_code;condition})0

ponieważ wyrażenia wewnątrz {}każdorazowo oceniane i tylko ostatnia jest zwracany przez {, co pozwala nam ocenić some_codeprzed wejściem do pętli, i to działa za każdym razem conditionjest TRUE(lub truthy). Jest 0to jedno z wielu wyrażeń 1-bajtowych, które tworzą „prawdziwe” ciało whilepętli.

Giuseppe
źródło
10
  1. Nadużycie w outercelu zastosowania dowolnej funkcji do wszystkich kombinacji dwóch list. Wyobraź sobie macierz z indeksami i, j indeksowanymi przez pierwsze argumenty, a następnie możesz zdefiniować dowolną funkcję (i, j) dla każdej pary.

  2. Użyj Mapjako skrótu do mapply. Uważam, że mapplyjest tańszy niż pętla for w sytuacjach, w których musisz uzyskać dostęp do indeksu. Nadużywanie struktury listy w R. unlistjest drogie. methods::elpozwala tanio usunąć pierwszy element z listy. Spróbuj użyć funkcji z obsługą listy natywnie.

  3. Służy do.calldo uogólnienia wywołań funkcji z dowolnymi danymi wejściowymi.

  4. Gromadzenie argumentów Reducejest niezwykle pomocne dla golfa kodowego.

  5. Pisanie do konsoli linia po linii cat(blah, "\n")jest tańsze write(blah, 1). Ciągi zakodowane na stałe z „\ n” mogą być tańsze w niektórych sytuacjach.

  6. Jeśli funkcja ma domyślne argumenty, możesz użyć funkcji (,, n-arg), aby bezpośrednio określić n-ty argument. Przykład: seq(1, 10, , 101)W niektórych funkcjach obsługiwane jest częściowe dopasowanie argumentów. Przykład: seq(1, 10, l = 101).

  7. Jeśli zobaczysz wyzwanie polegające na manipulacji ciągiem, po prostu naciśnij przycisk Wstecz i przeczytaj następne pytanie. strsplitjest samotnym, odpowiedzialnym za zrujnowanie R golfa.

Teraz kilka nowych odkryć z 2018 roku

  1. A[cbind(i,j)] = zmoże być dobrym sposobem na manipulowanie macierzami. Ta operacja jest bardzo wydajna bajtowo przy założeniu, że projektujesz i, j, zjako wektory o prawidłowych długościach. Możesz zaoszczędzić jeszcze więcej, wywołując rzeczywistą funkcję indeksu / przypisania "[<-"(cbind(i,j), z). Ten sposób wywoływania zwraca zmodyfikowaną macierz.

  2. Użyj nowej linii zamiast \npodziałów linii.

  3. Zmniejszenie liczby wierszy może zaoszczędzić bajty. Przypisywanie w linii lapply(A<-1:10,function(y) blah)i przypisywanie argumentów funkcji function(X, U = X^2, V = X^3)są na to sposobem.

  4. Podobnie "[<-"jest funkcja w R (i jest związana z moim starożytnym pytaniem na SO )! Jest to podstawowa funkcja odpowiedzialna za operacje takie jak x[1:5] = rnorm(5). Dobra właściwość wywołania funkcji według nazwy umożliwia zwrócenie zmodyfikowanego wektora. W kolejności słowa "[<-"(x, 1:5, normr(5))robi prawie to samo, co powyższy kod, z tym wyjątkiem, że zwraca zmodyfikowane x. Powiązane „długość <-”, „nazwy <-”, „cokolwiek <-” zwracają zmodyfikowane dane wyjściowe

Vlo
źródło
1
Myślę, że użycie "[<-"jest warte własnej odpowiedzi „Wskazówki”, ponieważ zwróci zmodyfikowaną tablicę / macierz / cokolwiek.
Giuseppe,
10
  1. Zapisz wartości w linii : inni wspominali, że możesz przekazywać wartości w kolejności i przypisywać je do użycia w innym miejscu, np

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

    Możesz jednak również zapisać wartości bezpośrednio w celu użycia w tym samym wierszu !

    Na przykład

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

    czyta z stdin, tworzy zmienną x=1:n, a następnie indeksuje do xużycia tej wartości x. Może to czasem zaoszczędzić bajty.

  2. Alias ​​dla pustego wektora Można użyć {}jako pustego wektora, c()gdy oba się wrócą NULL.

  3. Konwersja bazy Dla cyfr całkowitych z nbazy 10, użyj n%/%10^(0:nchar(n))%%10. To pozostawi końcowe zero, więc jeśli jest to dla ciebie ważne, użyj, n%/%10^(1:nchar(n)-1)%%10ponieważ jest krótszy niż indeksowanie tablicy. Można to dostosować do innych baz, używając floor(log(n,b))+1zamiastnchar(n)

  4. Używanie seqi: : Zamiast używania 1:length(l)(lub 1:sum(x|1)), możesz użyć seq(l)tak długo, jak ljest a listlub vectoro długości większej niż 1, jak domyślnie seq_along(l). Jeśli lpotencjalnie może być długość 1, seq(a=l)załatwi sprawę.

    Dodatkowo :użyje (z ostrzeżeniem) pierwszego elementu swoich argumentów.

  5. Usuwanie atrybutów Korzystanie c()z array(lub matrix) spowoduje to samo, co as.vector; ogólnie usuwa atrybuty inne niż nazwy.

  6. Używanie czynnikowegamma(n+1) jest krótsze niż używanie factorial(n)i i tak factorialjest zdefiniowane gamma(n+1).

  7. Rzut monetą Gdy potrzebujesz wykonać losowe zadanie w 50% przypadków, użycie rt(1,1)<0jest krótsze niż runif(1)<0.5o trzy bajty.

  8. Wyodrębnianie / wykluczanie elementów head i tailczęsto są przydatne do wyodrębnienia pierwszych / ostatnich kilku elementów tablicy; head(x,-1)wyodrębnia wszystkie oprócz ostatniego elementu i jest krótszy niż przy użyciu indeksowania ujemnego, jeśli nie znasz jeszcze długości:

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

Giuseppe
źródło
@ J.Doe godny własnego postu, tak myślę! Być może z tytułem „alternatywy dla rep”. Inne pytania ze wskazówkami mają jedną wskazówkę na odpowiedź, którą z całego serca popieram również w przypadku tego pytania! Jest także 1:n*0krótszy niż Im(1:n)dwa bajty, co oznacza, że ​​twoja druga sztuczka może być x+0*-n:nrównież :-)
Giuseppe
1
@ J.Doe Lub jeszcze lepiej, !1:nto także tablica nzer w zależności od przypadku użycia; zasługuje na to pytanie MATL / MATLAB (prawdopodobnie Luis Mendo).
Giuseppe,
Dzięki, @Giuseppe! Czy mogę zasugerować utworzenie tego postu, ponieważ nie chcę czerpać reputacji z dobrych pomysłów.
J.Doe
@ J.Doe och, nie mam nic przeciwko. Zawsze dobrze jest mieć widoczność innych golfistów R; Myślę, że w tej chwili mogę powiedzieć, że jestem bardzo znaną istotą! Chodzisz po okolicy, sugerując całkiem imponujące ulepszenia, więc weź powtórzenie (gra słów nie przeznaczona) i kontynuuj dobrą pracę w golfa :-)
Giuseppe
1
nie (log(i,b)%/%1):0)zamiast floor(log(n,b))+1?
Tylko ASCII
8

Niektóre podstawowe pojęcia, ale powinny być nieco przydatne:

  1. W instrukcjach przepływu sterowania można nadużyć, że dowolna liczba nie równa zero będzie oceniana jako TRUE, np.: if(x)Jest równa if(x!=0). I odwrotnie, if(!x)jest równoważne z if(x==0).

  2. Podczas generowania sekwencji przy użyciu :(np. 1:5) Można nadużyć faktu, że operator potęgowania ^jest jedynym operatorem, który ma pierwszeństwo przed operatorem :(w przeciwieństwie do +-*/).

    1:2^2 => 1 2 3 4 

    co pozwala zaoszczędzić dwa bajty w nawiasach, których normalnie musiałbyś użyć, gdybyś chciał np. zapętlić elementy n x nmacierzy ( 1:n^2) lub innej liczby całkowitej, którą można wyrazić w krótszy sposób za pomocą notacji wykładniczej ( 1:10^6).

  3. +-*/Pokrewną sztuczkę można oczywiście również zastosować w operacjach wektoryzowanych , chociaż najczęściej stosuje się ją do +-:

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

    Działa +1to, ponieważ jest wektoryzowane i dodaje 1do każdego elementu 0:nwynikającego z wektora 1 2 ... n+1. Podobnie 0:(n+1) == -1:n+1oszczędza również jeden bajt.

  4. Pisząc krótkie funkcje (które można wyrazić w jednym wierszu), można nadużyć przypisania zmiennej, aby zapisać dwa bajty w otaczających nawiasach klamrowych {...}:

    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")}

    Pamiętaj, że nie zawsze może to być zgodne z zasadami określonych wyzwań.

Billywob
źródło
Tylko mała korekta: ^jest wektoryzowana, po prostu ma pierwszeństwo przed :(tzn. Jest wykonywana wcześniej, :chyba że nawiasy wyraźnie wskazują na coś przeciwnego, zobacz ?Syntaxdokładną kolejność pierwszeństwa operatorów binarnych i jednoargumentowych). To samo dotyczy plików binarnych, +-/*które mają niższy priorytet niż :stąd twoja sztuczka nr 3.
plannapus
@plannapus Dzięki za wyjaśnienie. Zaktualizowano sformułowanie.
Billywob
7

Zmień znaczenie operatorów

Operatory R są tylko funkcjami, które są specjalnie traktowane przez analizator składni. Na przykład <jest w rzeczywistości funkcją dwóch zmiennych. Te dwa wiersze kodu robią to samo:

x < 3
`<`(x, 3) 

Możesz ponownie przypisać inną funkcję do operatora, a analizator składni nadal to zrobi, w tym przestrzegając pierwszeństwa operatora, ale końcowe wywołanie funkcji będzie nowe, a nie oryginalne. Na przykład:

`<`=rep

teraz oznacza, że ​​te dwa wiersze kodu robią to samo:

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

i pierwszeństwo jest przestrzegane, czego skutkiem są rzeczy takie jak

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

Zobacz na przykład tę odpowiedź , a także stronę pierwszeństwa operatora . Jako efekt uboczny, twój kod stanie się tak tajemniczy, jak kod napisany w języku golfowym.

Niektórzy operatorzy lubią +i -akceptują jeden lub dwa parametry, więc możesz nawet wykonywać następujące czynności:

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

Zobacz na przykład tę odpowiedź .

Zobacz także tę odpowiedź dotyczącą używania [jako dwubajtowego, trzyargumentowego operatora.

JayCe
źródło
2
To jest komentarz do wskazówek rturnbull, ale myślę, że musimy zacząć egzekwować zasadę „jedna wskazówka na odpowiedź”, ponieważ tak cholernie trudno jest znaleźć tę, której potrzebuję, kiedy tu przychodzę.
Giuseppe
1
także w zależności od pierwszeństwa operatorów, możesz zrobić fajne rzeczy, które mogą pomóc; jak <ma niższy priorytet niż +, ale *ma wyższy priorytet niż, +więc możesz potencjalnie połączyć je razem!
Giuseppe
1
@Giuseppe wiesz, co próbowałem znaleźć przed opublikowaniem i nie mogłeś tego znaleźć. Dzięki za zwrócenie na to uwagi. Planuję dodawać więcej szczegółów na temat pierwszeństwa operatorów z przykładami, gdy zacznę coraz częściej korzystać z tej sztuczki.
JayCe
2
Oto zabawna: jeśli łączysz się ?z pastejakąś inną funkcją, która może przyjąć dwa argumenty, kolejność pierwszeństwa oznacza, że ​​nadal możesz używać przypisań wbudowanych za pośrednictwem a<-b?d<-e.
J.Doe,
1
Powinieneś dodać [jako trzyelementowy alias (to dwa bajty); Często uważam, że jest to pomocne w przypadku takich rzeczy outer(i konsekwentnie o tym zapominam!), Chociaż oczywiście musisz upewnić się, że tak naprawdę nie musisz używać [. Przydałby się również link do strony z pierwszeństwem operatora, aby pomóc w wyborze aliasu.
Giuseppe
5

Scenariusze, w których można uniknąć paste(...,collapse="")istrsplit

Są to trudności w zwykłych wyzwaniach strunowych. Istnieje kilka obejść.

  • Reduce(paste0,letters) dla -5 bajtów od paste0(letters,collapse="")

  • 2-bajtowy golf gdzie masz listę zawierającą dwa wektory c(1,2,3)a c(4,5,6)i chcesz złączyć je elementem mądry na łańcuch "142536". Wykorzystanie przez operatora daje ci p=paste0;"^"=Reduce;p^p^roszczędność dwóch bajtów podczas zwykłego paste0połączenia.

  • Zamiast paste0("(.{",n,"})")konstruować (np.) Wyrażenie regularne dla 20 bajtów, rozważ wyrażenie regularne w wyrażeniu regularnym: sub(0,"(.{0})",n)dla 17 bajtów.

Czasami (w rzeczywistości dość często) trzeba iterować wektor znaków lub ciągów znaków lub dzielić słowo na litery. Istnieją dwa typowe przypadki użycia: jeden, w którym trzeba wziąć wektor znaków jako dane wejściowe do funkcji lub programu, i drugi, w którym znasz wektor wcześniej i musisz go gdzieś zapisać w kodzie.

za. Gdzie trzeba wziąć ciąg wejściowy i podzielić go na słowa lub znaki .

  1. Jeśli potrzebujesz słów (w tym znaków w specjalnym przypadku):

    • Jeśli nowa linia 0x10(ASCII 16) oddzielająca słowa jest w porządku, x=scan(,"")zaleca się zawijanie kodu function(s,x=el(strsplit(s," "))).

    • Jeśli słowa mogą być oddzielone od jakiejkolwiek innej białych znaków , w tym wielokrotnych spacji, tabulacji, znaki nowej linii itp, można użyć @ NGM za podwójną sztuczki skanowania : x=scan(,"",t=scan(,"")). Daje to zeskanowany ciąg znaków scanjako textargument i oddziela go spacjami.

    • Drugim argumentem scanmoże być dowolny ciąg, więc jeśli go utworzyłeś, możesz go przetworzyć, aby zapisać bajt.

  2. Jeśli chcesz zmienić ciąg wejściowy na wektor znaków :

    • x=el(strsplit(s,""))jest najkrótszym ogólnym rozwiązaniem. splitArgumentem działa na niczym długości zerowej tym c(), {}etc więc jeśli zdarzy się, że stworzył zmiennej długości zero, można użyć go zapisać bajt.

    • Jeśli możesz pracować z kodami znaków ASCII, zastanów się utf8ToInt, ponieważ utf8ToInt(x)jest on krótszy niż strsplitpołączenie. Wklejenie ich z powrotem intToutf8(utf8ToInt(x))jest krótsze niż Reduce(paste0,el(strsplit(x,""))).

    • Jeśli chcesz podzielić dowolne ciągi liczb, takie "31415926535"jak dane wejściowe, możesz użyć, utf8ToInt(s)-48aby zapisać 3 bajty el(strsplit(s,"")), pod warunkiem, że możesz użyć cyfr całkowitych zamiast znaków, jak to często bywa. Jest to również krótszy niż zwykły przepis na dzielenie liczb na cyfry dziesiętne.

b. Gdzie potrzebujesz wcześniej ustalonego wektora słów lub znaków.

  • Jeśli potrzebujesz wektora pojedynczych znaków, które mają jakiś regularny wzór lub są w kolejności alfabetycznej, spójrz na użycie intToUtf8lub chartrzastosowanie do sekwencji za pomocą a:blub na wbudowanych zestawach liter letterslub LETTERS. Wbudowany język wzorców chartrjest szczególnie wydajny .

  • Dla 1 do 3 znaków lub słów , c("a","b","c")to tylko ogólne rozwiązanie najkrótsza.

  • Jeśli potrzebujesz stałej wektor między 4 a 10 Brak białych znaków lub słów , należy scanz stdinjak filearg:

f(x=scan(,""))
q
w
e
r
t
y
u
  • Jeżeli scanz stdinnie jest to możliwe, na 6 lub więcej nie białych znaków lub słów użyć scanz textargumentem scan(,"",t="a b c d e f").

  • Jeśli potrzebujesz wektora (a) 6 lub więcej znaków dowolnego typu lub (b) 10 lub więcej znaków innych niż spacje , prawdopodobnie jest to metoda strsplitvia x=el(strsplit("qwertyuiop","")).

  • Państwo może być w stanie uciec z poniższym cytatem sztuczki : quote(Q(W,E,R,T,Y)), co stwarza, że wypowiedzi. Niektóre funkcje lubią strrepi grepbędą zmuszać to do wektora ciągów! Jeśli to zrobisz, jest to dobre dla dowolnej długości słowa lub wektora znaków od 3 do 11.

  • Nie ma dobrego powodu, aby używać strsplitsłów przez x=el(strsplit("q w e r t y"," ")). Zawsze przegrywa scan(,"",t="q w e r t y"))przez stały narzut 5 bajtów.

Oto tabela liczb bajtów używanych przez każde podejście do odczytu w wektorze pojedynczych znaków długości n. Względne uporządkowanie w każdym wierszu obowiązuje dla znaków lub słów, z wyjątkiem tych, strsplitw ""których działa tylko na znaki.

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

do. Jeśli potrzebujesz wprowadzić tekst jako matrycę znaków , kilka przepisów, które wydają się krótkie

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")),"")))
J.Doe
źródło
1
scanma textargument, który jest bardziej konkurencyjny niż el(strsplit(x," "))wtedy, gdy potrzebujesz tylko łańcuchów! Wypróbuj online! W przeciwieństwie do twojej ostatniej sugestii read.csv.
Giuseppe,
Jeśli chcesz tylko postaci, twoje wezwanie scanjest lepsze do 5 znaków, el(strsplit(x,""))jest bardziej konkurencyjne niż scandla 6 lub więcej. Wypróbuj online! Nie znalazłem jeszcze dobrego zastosowania read.csv, ale może byłoby przydatne, gdybyś z jakiegoś powodu potrzebował tabeli danych?
J.Doe
Nigdy nie znalazłem zastosowania dla, data.frameale może musimy znaleźć / stworzyć wyzwanie, w którym byłoby to pomocne! Może dplyrstyl group_by()i summarize()rodzaj manipulacji? NIE WIEM.
Giuseppe,
A czytanie ciągów scan(,"")wydaje się jeszcze lepsze? Wypróbuj online!
J.Doe
Tak, na pewno, chociaż jeśli interpretujesz format wejściowy ściśle tak, jak robi to tutaj ngm, to podwójne scanjest przydatne.
Giuseppe,
4

Niektóre sposoby znalezienia pierwszego niezerowego elementu tablicy.

Jeśli ma nazwę x:

x[!!x][1]

Zwraca, NAjeśli nie ma niezerowych elementów (w tym kiedy xjest pusty, ale nie NULLktóre błędy).

Anonimowo:

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

Zwraca, NULLjeśli nie ma niezerowych elementów lub jest pusty lub NULL.

ngm
źródło
Zwróci to, NAjeśli wszystkie elementy xsą zerowe, więc uważam, więc używaj go ostrożnie!
Giuseppe
Find(c,x)to ta sama bajtowość: korzyść, której nie trzeba powtarzać (zdefiniować) x, i inne zachowanie, jeśli nie pasuje. TIO
JayCe
Findjest również trochę bezpieczniejszy, ponieważ działa NULL, o ile nic więcej nie musi się wydarzyć w wyniku, w którym to przypadku nie jestem pewien, czy powrót NAlub NULLjest bezpieczniejszy.
ngm
och to prawda. problemem ze zwróceniem NULL są błędy ... w pytaniu o porównanie wersji, które najpierw wypróbowałem, sign(Find(c,w))co spowodowało błędy - musiałem zrobić, Find(c,sign(w))aby nie popełnić błędu. Myślę, że oba sposoby mają swoje zastosowania.
JayCe
4

Alternatywy dla rep()

Czasami rep()można tego uniknąć za pomocą operatora jelita grubego :i recyklingu wektora R.

  • Do powtarzania nwartości zerowe, w którym n>0, 0*1:njest krótszy niż 3 bajty rep(0,n)i !1:n, tablica FALSEjest 4 bajty krótsze, jeśli przypadek zastosowania pozwala na to.

  • Aby powtórzyć x nczasy, x+!1:njest o 2 bajty krótszy niż rep(x,n). Dla nnich użyj, !!1:njeśli możesz użyć tablicy TRUE.

  • Aby powtórzyć x 2n+1razy, gdzie n>=0, x+0*-n:njest krótszy niż 4 bajty rep(x,2*n+1).

  • Oświadczenie !-n:nda TRUEflanki po obu stronach n FALSE. To może być używany do generowania nawet liczbę znaków w wywołań intToUtf8()jeśli pamiętać, że zero jest ignorowany.

Modułowa arytmetyka może być użyteczna. repinstrukcji z eachargumentem można czasem uniknąć za pomocą dzielenia liczb całkowitych.

  • Aby wygenerować wektor c(-1,-1,-1,0,0,0,1,1,1), -3:5%/%3jest o 5 bajtów krótszy niż rep(-1:1,e=3).

  • Aby wygenerować wektor c(0,1,2,0,1,2,0,1,2), 0:8%%3zapisuje 4 bajty rep(0:2,3).

  • Czasami transformacje nieliniowe mogą skrócić arytmetykę sekwencji. Mapować i in 1:15do c(1,1,3,1,1,3,1,1,3,1,1,3,1,1,3)wewnątrz instrukcji złożonej, oczywista odpowiedź Golfy jest 1+2*(!i%%3)do 11 bajtów. Jednak 3/(i%%3+1)ma 10 bajtów i będzie piętroć w tej samej sekwencji, więc można go użyć, jeśli potrzebujesz sekwencji do indeksowania tablicy.

J.Doe
źródło
3

Jeśli potrzebujesz użyć funkcji, użyj pryr::f()zamiast function().

Przykład:

function(x,y){x+y}

jest równa

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

lub nawet lepiej

pryr::f(x+y)

Ponieważ jeśli jest tylko jeden argument, formals są zgadywane na podstawie kodu .

BLT
źródło
O ile nie da się tego sprowadzić do jednego argumentu (jak w trzecim przykładzie), nie jest to golf, ponieważ function(x,y){x+y}można go zapisać jak function(x,y)x+ydla tej samej liczby bajtów, pryr::f(x,y,x+y)ale z większą czytelnością.
Khuldraeseth na'Barya
3

Przetrwanie wyzwań dotyczących sznurków

Jak wspomniano w innej odpowiedzi unlist(strsplit(x,split="")i paste(...,collapse="")może być przygnębiające. Ale nie odchodź od nich, są obejścia!

  • utf8ToIntkonwertuje ciąg do wektora, intToUtf8wykonuje operację odwrotną. Otrzymujesz wektor int, a nie wektor, charale czasem tego właśnie szukasz. Na przykład, aby wygenerować listę -, lepiej użyć intToUtf8(rep(45,34))niżpaste(rep("-",34),collapse="")
  • gsubjest bardziej przydatny niż inna funkcja greprodziny, gdy działa na jednym łańcuchu. Dwa powyższe podejścia można połączyć, jak w tej odpowiedzi, która skorzystała z porad ovs , Giuseppe i ngm .
  • Wybierz wygodny format we / wy, jak w tej odpowiedzi, przyjmując dane wejściowe jako wiersze tekstu (bez cudzysłowów) lub ten przyjmujący wektor znaków. W razie wątpliwości skontaktuj się z OP.
  • Jak wskazano w komentarzach, <porównuje ciągi leksykograficzne, jak można by się spodziewać.
JayCe
źródło
intToUtf8ma także drugi argument, multiple = FALSEktóry konwertuje z ints na pojedyncze znaki (łańcuchy o długości jeden), a nie pojedynczy łańcuch, jeśli jest ustawiony na TRUE.
Giuseppe
Począwszy od wersji 3.5.0 pojawia się trzeci argument allow_surrogate_pairs = FALSE, ale nie wiem, co on robi; doktorzy mówią coś o czytaniu dwóch bajtów jako, UTF-16ale ledwo wiem, co to UTF-8jest, więc zignoruję to, dopóki ktoś inny nie znajdzie sposobu na grę w golfa.
Giuseppe
2

Wskazówki dotyczące ograniczonych problemów ze źródłami:

  1. Znaki w stałych literału R można zastąpić kodami szesnastkowymi, kodami ósemkowymi i kodami Unicode.

    np. ciąg "abcd"można zapisać:

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

    Możemy również mieszać znaki z ósemkowym / szesnastkowym / Unicode i używać razem niektórych kodów ósemkowych i niektórych kodów szesnastkowych, o ile znaki Unicode nie są mieszane ze znakami ósemkowym / szesnastkowym, np .:

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

    Zobaczyć koniec tej sekcji dla dalszych szczegółów.

  2. Ponieważ funkcje można pisać za pomocą literałów łańcuchowych, np. cat()Można pisać alternatywnie:

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

    możemy również używać kodów ósemkowych, kodów szesnastkowych i Unicode do nazw funkcji:

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

    z jedynym wyjątkiem, że sekwencje Unicode nie są obsługiwane w backtickach ''

  3. Za pomocą okrągłych nawiasów można uniknąć nadużywania operatorów, np .:

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

Zastosowanie wszystkich trzech lew można znaleźć w tej odpowiedzi

digEmAll
źródło
Liczby można także zapisywać w systemie szesnastkowym: 0xBi 0xbzwracać 11(nie trzeba wstawiać cudzysłowów ani cudzysłowów).
Robin Ryder