Solutions de contournement en python pour l'affectation dans lambda

34

Ceci est une question de astuce pour jouer au golf en Python.

En golf Python, il est courant qu'une soumission soit une fonction définie comme un lambda. Par exemple,

f=lambda x:0**x or x*f(x-1)

calcule la factorielle de x.

Le format lambda présente deux grands avantages :

  • Le passe-partout de f=lambda x:...ou lambda x:...est plus court que le def f(x):...return...oux=input()...print...
  • Un appel récursif peut être utilisé pour effectuer une boucle avec une légère surcharge d'octet.

Cependant, les lambdas ont le gros inconvénient de ne permettre qu'une seule expression, aucune déclaration. En particulier, cela ne signifie aucune tâche comme c=chr(x+65). C'est problématique quand on a une expression longue dont la valeur doit être référencée deux fois (ou plus).

Des assignations comme E=enumeratesont possibles en dehors de la fonction ou en tant qu'argument optionnel, mais uniquement si elles ne dépendent pas des entrées de la fonction. Des arguments facultatifs tels que f=lambda n,k=min(n,0):...fail, car l’entrée nn’a pas été définie lorsqu’elle kest évaluée au moment de la définition.

Le résultat est que, parfois, vous aspirez à répéter une longue expression dans un lambda parce que l'alternative est un long non-lambda.

lambda s:s.strip()+s.strip()[::-1]
def f(s):t=s.strip();print t+t[::-1]

Le seuil de rentabilité est d'environ 11 caractères ( détails ), au-delà duquel vous passez en a defou program. Comparez ceci au seuil de rentabilité habituel de longueur 5 pour une expression répétée:

range(a)+range(b)
r=range;r(a)+r(b)

print s[1:],s[1:]*2
r=s[1:];print r,r*2

D'autres langues ont des solutions de contournement, Octave par exemple . Il existe des astuces connues pour Python, mais elles sont longues, maladroites et / ou à usage limité. Une méthode courte et polyvalente pour simuler une assignation dans un lambda révolutionnerait le golf Python.


Quels sont les moyens pour un golfeur Python de surmonter ou de contourner cette limitation? Quelles idées potentielles devraient avoir à l'esprit lorsqu'ils voient une longue expression répétée deux fois dans un lambda?

Mon objectif avec cette question de conseils est de plonger profondément dans ce problème et:

  • Cataloguez et analysez les solutions de contournement au golf pour créer une fausse affectation dans un lambda
  • Explorez de nouvelles pistes pour de meilleures méthodes

Chaque réponse doit expliquer une solution de contournement ou une piste potentielle.

Xnor
la source
J'imagine que c'est l'une de ces choses qui ne peuvent pas être bien faites en Python. JavaScript a une longueur d'avance sur celui-ci.
mbomb007
Tout comme pour la réponse de orlp, la suggestion (supprimée) de Neil d'utiliser des lambdas imbriqués n'est pas forcément plus longue que la valeur de def dans les cas où vous avez de toute façon besoin d'un lambda imbriqué. Je pense que cela mérite une analyse plus approfondie.
Martin Ender
2
Pour l'exemple exact donné avec la concaténation de chaîne en minuscule inversée, on pourrait aller de l'avant lambda s:(s+s[::-1]).lower(). Bien sûr, cela ne répond pas à la question.
Jonathan Allan
@ JonathanAllan Bon point, changé en strip.
xnor

Réponses:

6

eval

Cela n’est pas si important en soi, mais si votre solution utilise déjà evald’une manière ou d’une autre, vous pouvez généralement utiliser cette technique.

eval("%f*%f+%f"%((5**.5,)*3))
orlp
la source
Wow, c'est intelligent! Pourquoi je ne vois jamais ce genre de code?
Le
6
@ z0rberg's Probablement parce que eval est diabolique.
HyperNeutrino
Je ne souscris pas à ce codeisme. #EvalLivesMatter ... mais sérieusement, comment est-ce pire qu'une simple injection de DLL?
Le
6

Expressions d'affectation dans Python 3.8

Python 3.8 ( TIO ) introduit les expressions d’affectation , qui permettent :=d’affecter une variable en ligne à une expression.

>>> (n:=2, n+1)
(2, 3)

Ceci peut être utilisé dans un lambdaenvironnement où les tâches ne sont généralement pas autorisées. Comparer:

lambda s:(t:=s.strip())+t[::-1]
lambda s:s.strip()+s.strip()[::-1]
def f(s):t=s.strip();return t+t[::-1]

Voir cette astuce pour plus.

Xnor
la source
2

Lambdas intérieurs

Celles-ci vous permettent de définir plusieurs variables à la fois.

lambda s:s.strip()+s.strip()[::-1]

contre.

lambda s:(lambda t:t+t[::-1])(s.strip())

est beaucoup plus long, mais si vous avez plusieurs variables, ou des variables plus longues, qui se répètent plusieurs fois:

lambda a,b,c:a.upper()*int(c)+b.lower()*int(c)+a.upper()[::-1]+b.lower()[::-1]+a.upper()*int(c)+a.lower()*int(c)

contre.

lambda a,B,c:(lambda A,b,n:A*n+b*n+A[::-1]+b[::-1]+A*n+b*c)(a.upper(),B.lower(),int(c))

Nombre de caractères

Initial: (lambda:)()(11 octets)
Première variable: [space]a(2 octets)
Variables suivantes: ,b,(3 octets)
Utilisation: a(1 octet).

(lambda* a*_,b_:)(*<value a>*_,<value b>_)

(Enregistre également sur des crochets)

Donc, cela prend des 3n + 10octets, où nest le nombre de variables. Ceci est un coût initial élevé, mais peut porter ses fruits à la fin. Il retourne même sa valeur intérieure, de sorte que vous pouvez imbriquer plusieurs (Bien que cela deviendra vite pas la peine.)

Ceci n'est utile que pour les calculs intermédiaires longs dans les interprétations de listes imbriquées, car def f():a=...;b=...;returnsa durée est généralement plus courte.

Pour 1 valeur, cela enregistre:, uses * length - length - uses - 13donc n'est utile que lorsque cette expression est positive.
Pour ndes expressions différentes ufois utilisées au total, où leur longueur combinée est l, cela économise:
l - (3 * n) - u - 10 ( + brackets removed )

Artyer
la source
1

Utiliser une liste

Déclarer une liste en paramètre et utiliser .append() orpour stocker la valeur:
lambda s:s.lower()+s.lower()[::-1]
se transforme en
lambda s,l=[]:l.append(s.lower())or l[-1]+l[-1][::-1]

Nombre de caractères:

,l=[]5 caractères
l.append()or13 caractères
l[-1]5 caractères pour chaque utilisation

Seuil de rentabilité

La quantité de caractère ajouté est la suivante:
uses*(5-length) + 18 + length
Dans l'exemple précédent, l'instruction est s.lower()composée de 9 caractères et est utilisée 2 fois, en appliquant cette technique à 19 caractères. S'il était utilisé 7 fois, il y aurait une réduction d'un caractère.
La quantité d'utilisations minimales de cette technique vaut la peine
min_uses = (18+length)/(length-5)

Upsides

  • Autoriser une nouvelle affectation à un coût légèrement réduit (la liste est déjà déclarée)
  • Est un listobjet si [0], .pop(), [x:y]et d' autres fonctions de la liste peut être utilisée pour les tricks. très situationnel

Inconvénients

  • Coût initial élevé
  • Coût d'utilisation élevé
  • Ne fonctionne que pour des utilisations de longueur supérieure à 5

Utilise un dictionnaire

thanks @Zgarb
Même idée que ci-dessus Déclarer un dictionnaire en tant que paramètre et utiliser .setdefault()pour stocker (et renvoyer) la valeur:
lambda s:s.lower()+s.lower()[::-1]
se transforme en
lambda s,d={}:d.setdefault(0,s.lower())+d[0][::-1]
Remarque: contrairement à la listcontrepartie, setdefaultrenvoie la valeur attribuée.

Nombre de caractères:

,d={}5 caractères
d.setdefault(k,)16 caractères
d[k]4 caractères pour chaque utilisation

Seuil de rentabilité

La quantité de caractère ajouté est la suivante:
(uses-1)*(4-length) + 21
Dans l'exemple précédent, l'instruction est s.lower()composée de 9 caractères et est utilisée 2 fois, en appliquant cette technique à 16 caractères. S'il était utilisé 7 fois, il y aurait une réduction d'un caractère.
La quantité d'utilisations minimales de cette technique vaut la peine
min_uses = 1-21/(4-length)

Upsides / Inconvénients

  • Fondamentalement identique à la liste
  • Ne fonctionne que pour des utilisations de longueur supérieure à 4

Autres considérations

  • Si cette technique vaut la réduction de caractère, vous lambdapouvez probablement la supprimer et réécrire la fonction avec def/ inputpour un programme plus court.
  • Comme @FlipTack l'a souligné, la liste et le dict AREZ GARDE entre les appels de fonctions, bien que cela dérange surtout, il peut être utilisé pour les appels récursifs. encore très situationnel
  • Le dictionnaire sera toujours plus court que la liste.
Barre
la source
3
Les soumissions de fonction doivent être utilisables plus d'une fois. Actuellement, cette liste utilise la même liste à chaque fois que lambda est exécuté, ce qui risquerait de gâcher les choses lors des exécutions ultérieures.
FlipTack
@ FlipTack par intérêt, cela vous dérangerait-il de lier une source telle que meta? Je pense que cette règle pourrait avoir un impact sur quelques astuces que je connais.
JAD
Je pense que vous pouvez faire mieux avec un dictionnaire: lambda s,d={}:d.setdefault(0,s.lower())+d[0][::-1]il est également réutilisable.
Zgarb
Vous pouvez utiliser list.extendpour ajouter plusieurs éléments à la fois, ce qui sera plus court que list.appendplusieurs fois.
mbomb007
0

Utilisez pour définir les variables et renvoyer les données après opération comme ceci:

add = lambda x, y: exec("x+=y")
Dylan Eliot
la source
alternativement: {add = lambda x, y: [x.append (x [0] + y), x.reverse (), x.pop ()]}
Dylan Eliot
0

Liste des compréhensions

Il s’agit plus d’un dernier recours car c’est si peu d ’égoïste, mais vous pouvez faire
[<expression> for <variable> in <value>]
pour pseudo-définir une variable dans un lambda. Fondamentalement, le seul point positif de cette méthode est que l'expression interne peut rester lisible, ce qui est évidemment le moindre de vos soucis lorsque vous jouez au golf.

ASCII seulement
la source