Tipps zum Golfen in Julia

20

Welche allgemeinen Tipps haben Sie zum Golfen in Julia? Ich bin auf der Suche nach Ideen, die generell auf Code-Golf-Probleme angewendet werden können, die zumindest etwas spezifisch für Julia sind (z. B. "Kommentare entfernen" ist keine Antwort).

Jonathan Van Matre
quelle

Antworten:

19

HINWEIS: Das Folgende kann einige veraltete Tipps enthalten, da Julia strukturell noch nicht ganz stabilisiert ist.

Ein paar Tricks, um ein paar Zeichen zu speichern

  1. Überladungsoperatoren mit häufig verwendeten Binärfunktionen . Wenn Sie beispielsweise viele Ganzzahldivisionen durchführen müssen und keine inverse Division erforderlich ist, verwenden \ =divSie und Sie können dann a\banstelle von eingeben div(a,b). Beachten Sie das Leerzeichen - dies ist erforderlich, um zu vermeiden, dass es als "\ =" - Operator analysiert wird. Beachten Sie auch, dass Sie bei Überlastung auf der Ebene der REPL-Eingabeaufforderung (\)=Base.(\)oder verwenden \ =Base. \, um es zurückzusetzen. HINWEIS: Für einige Funktionen sind vorhandene UTF-8-Operatoren vordefiniert, z. B. ÷für div, wie von Alex A angegeben.
  2. Verwenden Sie ^ mit Strings für die bedingte Ausgabe . Das heißt, anstatt a>0?"Hi":"", verwendet "Hi"^(a>0)einen Byte oder für boolean a zu speichern, verwenden "Hi"^azu drei Bytes zu speichern.
  3. (Manchmal) Speichern Sie kleine Vektoren mit fester Größe als separate Variablen . Beispielsweise können a=split("Hi there"," ")Sie a[1]und a[2]durch Verwendung möglicherweise vermeiden a,b=split("Hi there"," "), was als aund bezeichnet werden bkann, wobei drei Bytes für jede Verwendung auf Kosten von nur zwei zusätzlichen Zeichen bei der Zuweisung eingespart werden. Tun Sie dies natürlich nicht, wenn Sie mit Vektoroperationen arbeiten können.
  4. Greifen Sie auf das erste Element von Array mit zu[] - für Arrays ist der Ausdruck A[]äquivalent zu A[1]. Beachten Sie, dass dies nicht für Strings funktioniert, wenn Sie das erste Zeichen erhalten möchten, oder für Tuples.
  5. Verwenden Sie isempty nicht für Arrays, Tuples oder Strings, sondern ==[]für Arrays und ==()Tupel. in ähnlicher Weise für das Negativ verwenden !=[]und !=(). Verwenden Sie ==""für Zeichenfolgen >"""" leer, aber nicht leer, da "" lexikografisch vor allen anderen Zeichenfolgen steht.
  6. Verwenden Sie den richtigen Kurzschluss-Booleschen Operator anstelle von "if" . Es mag ein bisschen weniger Julia-spezifisch sein, aber es lohnt sich daran zu denken. x<=1&&"Hi"kann so geschrieben werden x>1||"Hi", dass ein Zeichen gespeichert wird (solange die Rückkehr des Booleschen nicht wichtig ist).
  7. Verwenden Sie keine enthält, um das Vorhandensein von Zeichen in Zeichenfolgen zu überprüfen. Wenn Sie sich auf das grundlegende ASCII-Format beschränken, verwenden Sie in('^',s)statt contains(s,"^"). Wenn Sie andere Zeichen verwenden können, können Sie mit etwas mehr speichern '^'∈s, beachten Sie jedoch, dass dies in UTF-8 3 Byte sind.
  8. Suchen Sie nach minimalen / maximalen Werten in einem Array? Verwenden Sie nicht Minimum oder Maximum, sondern verwenden Sie minimum(x)oder maximum(x), verwenden Sie min(x...)oder max(x...), um ein Zeichen aus Ihrem Code zu entfernen, wenn Sie wissen x, dass mindestens zwei Elemente vorhanden sind. Wenn Sie wissen, dass alle Elemente von xnicht negativ sind, können Sie auch minabs(x)oder verwendenmaxabs(x)
  9. Verwenden Sie nach Möglichkeit und Erlaubnis der Herausforderung eine aktuelle Zeile anstelle von \ n - Beachten Sie, dass dies die Lesbarkeit Ihres Codes erschwert und dass Sie möglicherweise eine "ungolfed" -Version angeben müssen, um das tatsächliche Verständnis zu ermöglichen es.
  10. Put - Optionen nach der Regex String - wenn Sie eine Regex Zeichenfolge in mehrzeiligen Modus haben wollen, zum Beispiel, nicht eingeben r"(?m)match^ this", Typen r"match^ this"m, Speicher drei Zeichen.
  11. Reverse 1-D-Arrays mit Flipud - reverse(x)sind ein Byte länger als flipud(x)und führen dieselbe Operation aus, sodass letztere besser ist.
  12. Verwenden Sie nach Möglichkeit die Array-Verkettung anstelle von push !, unshift !, append !, oder prepend! - Bei normalen Arrays ist dies problemlos möglich. Für Arrays vom Typ Any mit Array-Elementen benötigen Sie geschweifte Klammern um die hinzugefügten Arrays ( {[1,2]}nicht {1,2}). Für Julia 0.4 benötigen Sie diese Klammern Any[[1,2]].
  13. Verwenden Sie die Array-Indizierung, um die Größe eines Arrays oder einer Zeichenfolge abzurufen. Wenn Sie die endArray-Indizierung verwenden, wird sie automatisch in die Länge des Arrays / der Zeichenfolge konvertiert. Anstatt also k=length(A), benutzen Sie A[k=end]3 Zeichen zu speichern. Beachten Sie, dass dies möglicherweise nicht vorteilhaft ist, wenn Sie k sofort verwenden möchten. Dies funktioniert auch in einem mehrdimensionalen Fall - A[k=end,l=end]wird die Größe jeder Dimension von erhalten A- (k,l)=size(A)ist in diesem Fall jedoch um ein Byte kürzer, verwenden Sie es also nur, wenn Sie sofort auf das letzte Element gleichzeitig zugreifen möchten.
  14. Abrufen eines Index-Iterators mithilfe der Array-Indizierung - Ähnlich wie bei 13 können Sie auch einen Iterator abrufen, der mit der Länge eines Arrays übereinstimmt . A[k=1:end]In diesem Fall kwird ein Iterator-Abgleich durchgeführt 1:length(A). Dies kann nützlich sein, wenn Sie gleichzeitig auch ein Array verwenden möchten A.
  15. Verwenden Sie collect nicht, um einen String in ein char-Array umzuwandeln - stattdessen collect(A)verwenden Sie [A...], was dasselbe bewirkt und 4 Bytes spart.
  16. Brauchen Sie eine Zahl in eine Zeichenfolge umgewandelt? Verwenden Sie "$(s[i])"oder dec(s[i])für Ausdrücke oder Variablen mit mehreren Zeichen und "$i"für Variablen mit einem Zeichen.
  17. Verwenden Sie ?:anstelle von &&oder ||für bedingte Zuweisung - das heißt, wenn Sie einen Auftrag ausführen soll nur auf eine Bedingung, können Sie ein Byte speichern , indem er cond?A=B:1statt cond&&(A=B)oder cond?1:A=Bnicht cond||(A=B). Beachten Sie, dass es sich 1hier um einen Dummy-Wert handelt.
  18. Verwenden Sie unionoder anstelle vonunique - union(s), um das Gleiche zu tun unique(s)und dabei ein Byte zu speichern. Wenn Sie Nicht-ASCII-Zeichen verwenden können, ∪(s)geschieht dasselbe und kostet nur 3 Bytes anstelle der 5 Bytes in union.
Glen O
quelle
2
Oh, wie würde ich diesen ersten Trick in Python lieben.
Siehe auch
Sie können Leerzeichen einfach aufteilen, split("Hi there")da das Musterargument standardmäßig ein Leerzeichen ist.
Alex A.
@AlexA. - Ich weiß, aber es ist nicht der Punkt des Trinkgeldes, und das Trinkgeld gilt auch so oder so.
Glen O
Punkt 12 hat sich in 0.5 geändert.
Lyndon White
@Oxinabox - Ich bin nicht überrascht, ich bin mir ziemlich sicher, dass einige von ihnen mittlerweile veraltet sind. Ich habe ursprünglich die meisten Tipps für 0.3 geschrieben, denke ich.
Glen O
15

Definieren Sie Operatoren neu, um Funktionen zu definieren

Durch die Neudefinition von Operatoren können viele Bytes in Klammern und Kommas gespeichert werden.

Rekursive unäre Operatoren

Vergleichen Sie für ein unäres Beispiel die folgenden rekursiven Implementierungen der Fibonacci-Sequenz:

F(n)=n>1?F(n-1)+F(n-2):n # 24 bytes
!n=n>1?!~-n+!(n-2):n     # 20 bytes
!n=n>1?!~-n+!~-~-n:n     # 20 bytes

Probieren Sie es online!

Der neu definierte Operator behält seinen ursprünglichen Vorrang.

Beachten Sie, dass wir nicht einfach !zu Gunsten von tauschen können ~, da dies ~bereits für Ganzzahlen definiert ist, während dies !nur für Boolesche Werte definiert ist.

Binäre Operatoren

Auch ohne Rekursion ist die Neudefinition eines Operators kürzer als die Definition einer Binärfunktion. Vergleichen Sie die folgenden Definitionen eines einfachen Teilbarkeitstests.

f(x,y)=x==0?y==0:y%x==0 # 23 bytes
(x,y)->x==0?y==0:y%x==0 # 23 bytes
x->y->x==0?y==0:y%x==0  # 22 bytes
x\y=x==0?y==0:y%x==0    # 20 bytes

Probieren Sie es online!

Rekursive binäre Operatoren

Im Folgenden wird veranschaulicht, wie Sie einen Binäroperator neu definieren, um die Ackermann-Funktion zu berechnen:

A(m,n)=m>0?A(m-1,n<1||A(m,n-1)):n+1    # 35 bytes
^ =(m,n)->m>0?(m-1)^(n<1||m^~-n):n+1   # 36 bytes
| =(m,n)->m>0?m-1|(n<1||m|~-n):n+1     # 34 bytes
m\n=m>0?~-m\(n<1||m\~-n):n+1           # 28 bytes

Probieren Sie es online!

Beachten Sie, dass dies ^sogar länger ist als die Verwendung eines regulären Bezeichners, da seine Priorität zu hoch ist.

Wie schon erwähnt

m|n=m>0?m-1|(n<1||m|~-n):n+1           # 28 bytes

würde nicht für ganzzahlige Argumente funktionieren, da |dies in diesem Fall bereits definiert ist. Die Definition für Ganzzahlen kann mit geändert werden

m::Int|n::Int=m>0?m-1|(n<1||m|~-n):n+1 # 38 bytes

aber das ist unerschwinglich lang. Es funktioniert jedoch, wenn wir ein float als linkes Argument und eine ganze Zahl als rechtes Argument übergeben.

Dennis
quelle
11
  1. Lassen Sie sich nicht zu leicht von Faktor (n) verführen. Die verführerische Funktion factor(n)der Basisbibliothek weist einen schwerwiegenden Fehler auf: Sie gibt die Faktorisierung Ihrer Ganzzahl in einem ungeordneten DictTyp zurück. Daher ist es kostspielig collect(keys())und collect(values())möglicherweise auch ein catund ein sort, um die gewünschten Daten daraus zu holen. In vielen Fällen kann es billiger sein, nur nach Probedivisionen zu rechnen. Traurig aber wahr.

  2. "Karte verwenden" map ist eine großartige Alternative zum Schleifen. Machen Sie sich den Unterschied zwischen mapund bewusst map!und nutzen Sie die vorhandenen Funktionen der letzteren, wenn Sie können.

  3. Die Verwendung von Mapreduce mapreduce erweitert die Funktionalität von Map noch weiter und kann zu einer erheblichen Einsparung von Bytes führen.

  4. Anonyme Funktionen sind großartig! ..besonders wenn an die oben genannten mapFunktionen übergeben.

  5. Kumulative Array-Funktionen cumprod , cumsumdie Flavorful- cumminund die anderen ähnlich benannten Funktionen ermöglichen kumulative Operationen entlang einer angegebenen Dimension eines n-dimensionalen Arrays. (Oder * un * angegeben, wenn das Array 1-d ist)

  6. Kurznotation für Alle Wenn Sie alle Dimensionen eines mehrdimensionalen Arrays (oder Dict) auswählen möchten A[Any,2], können Sie z. B. Bytes mit speichernA[:,2]

  7. Verwenden Sie die einzeilige Notation für Funktionen, anstatt function f(x) begin ... endSie oft zu vereinfachenf(x)=(...)

  8. Verwenden Sie den ternären Operator. Bei If-Then-Else-Konstruktionen mit einem Ausdruck kann dies Platz sparen. Vorsichtsmaßnahmen: Obwohl dies in einigen anderen Sprachen möglich ist, können Sie den Teil nach dem Doppelpunkt in Julia nicht auslassen. Außerdem ist der Operator in Julia Ausdrucksebene, sodass Sie ihn nicht für die bedingte Ausführung ganzer Codeblöcke verwenden können.
    if x<10 then true else false endvs
    x<10?true:false

Jonathan Van Matre
quelle
3
Wie um alles in der Welt ist "benutze den ternären Operator" etwas spezifisch für Julia? Es ist relevant für jede Sprache, die es hat.
Peter Taylor
5
Es ist wichtig, dass es es hat. Viele Sprachen haben auch Map-, anonyme oder reine Funktionen, eine Art Kurzform für alle Funktionen, kumulative Funktionen usw. Wenn wir jeden Tipp-Thread auf die Funktionen reduzieren würden, die für diese Sprache absolut einzigartig sind, gäbe es nur sehr wenige Tipps .
Jonathan Van Matre
3
Meine Güte, nur alle funktionalen für den Anfang, bei denen alles einen Wert zurückgibt, sodass eine ternäre Operation überflüssig wäre. Wenn Sie spezielle Beispiele haben müssen: Go, Haskell, Scala, Lua, VB, Prolog, PL / SQL ... selbst Python hat dies in vielen Versionen nicht getan. Gibt es unter den fast zehn Sprachen, in deren Tipps der ternäre Operator erwähnt wird, einen Grund, warum Sie sich nur dafür entschieden haben, in meiner Sprache provinziell zu sein?
Jonathan Van Matre
3
Na ja, zumindest bist du ein Curmudgeon mit Chancengleichheit. ヘ ( ̄  ̄  ̄ ヘ)
Jonathan Van Matre
1
Darf ich vorschlagen, Tipp 3 anzupassen? mapreduce ist länger als mapfoldl oder mapfoldr und kann je nach Implementierung ein unterschiedliches Verhalten aufweisen. mapfoldl und mapfoldr sind konsistent links- und rechtsassoziativ und daher eine bessere Wahl. Dies gilt auch allgemeiner für das Reduzieren (verwenden Sie foldl oder foldr).
Glen O
9

Über Funktionen iterieren

Dies ist auch in anderen Sprachen möglich, in der Regel jedoch länger als die einfache Methode. Die Fähigkeit von Julia, seine unären und binären Operatoren neu zu definieren, macht es jedoch ziemlich golfen.

Zum Erzeugen der Additions-, Subtraktions-, Multiplikations- und Divisionstabelle für die natürlichen Zahlen von 1 bis 10 könnte man beispielsweise verwenden

[x|y for x=1:10,y=1:10,| =(+,-,*,÷)]

die definieren den binären Operator |wie +, -, *und ÷, berechnet dann x|yfür jede Operation und xund yin den gewünschten Bereichen.

Dies funktioniert auch für unäre Operatoren. Um beispielsweise die komplexen Zahlen 1 + 2i , 3-4i , -5 + 6i und -7-8i , ihre Negative, ihre komplexen Konjugate und ihre multiplikativen Inversen zu berechnen , könnte man verwenden

[~x for~=(+,-,conj,inv),x=(1+2im,3-4im,-5+6im,-7-8im)]

die definiert den unären Operator ~wie +, -, conjund inv, berechnet dann ~xfür alle gewünschten komplexen Zahlen.

Beispiele in aktuellen Wettbewerben

Dennis
quelle
6
  1. Schlüsselwörter können manchmal unmittelbar auf Konstanten folgen, ohne dass ein Leerzeichen oder ein Semikolon erforderlich ist. Beispielsweise:

    n->(for i=1:n n-=1end;n)

    Beachten Sie das Fehlen eines Leerzeichens zwischen 1und end. Dies gilt auch für das endAuftreten nach einem engen Paren, dh )end.

  2. Führen Sie Integer - Division unter Verwendung ÷anstatt div()oder einen Betreiber zu überlasten. Beachten Sie, dass dies ÷in UTF-8 2 Byte wert ist.

  3. Verwenden Sie vec()oder A[:](für einige Arrays A), anstatt reshape()wann immer dies möglich ist.

  4. Erstellen Sie Funktionen anstelle vollständiger Programme, wenn dies in den Herausforderungsregeln zulässig ist. Es ist kürzer, eine Funktion zu definieren, die Eingaben akzeptiert, als Variablen durch Lesen von stdin zu definieren. Beispielsweise:

    n->(n^2-1)
    n=read(STDIN,Int);n^2-1
  5. Variablen können innerhalb des Arguments zu einer Funktion inkrementiert werden. Das Folgende ist zum Beispiel meine Antwort auf die Herausforderung " Find the Next 1-Sparse Binary Number" :

    n->(while contains(bin(n+=1),"11")end;n)

    Dies ist kürzer als das Inkrementieren ninnerhalb der Schleife.

Alex A.
quelle
6
  1. Don't typereturn f(x)=x+4 ist identisch mit, aber kürzer als f(x)=return x+4. Julia gibt immer das Ergebnis der letzten Anweisung zurück.
  2. Verwenden Sie = anstelle von in . [x for x in 1:4]ist 3 Zeichen länger als, entspricht aber[x for x=1:4]
gggg
quelle
5

Verwenden Sie Broadcasting-Funktionsaufrufe.

Eingeführt in Julia 0.5. Es ist wie eine Karte, verwendet jedoch weniger Zeichen und überträgt weniger Zeichen, sodass Sie weniger Lambdas schreiben können, um mit den Dingen umzugehen.

Eher, als:

  • map(f,x) -- 8 Charaktere.
  • f.(x) - 5 Zeichen

Besser noch:

  • map(a->g(a,y),x) - 16 Zeichen
  • g.(x,[y]) - 9 Zeichen
Lyndon White
quelle