Suggerimenti per sorgenti limitate in Python
Proprio come il code-golf , il codice sorgente limitato spinge a sfruttare stranezze e funzionalità nascoste del linguaggio Python. Abbiamo già un posto dove raccogliere tutti questi suggerimenti per il golf in codice , quelli per i sorgenti limitati rimangono trasmessi con il passaparola o nascosti in profondità nella documentazione di Python.
Quindi oggi vorrei chiederti quali sono alcuni suggerimenti per risolvere le sfide con fonti limitate in Python?
Si prega di includere solo 1 suggerimento per risposta.
Cosa rende un buon suggerimento qui?
Ci sono un paio di criteri che penso dovrebbe avere un buon suggerimento:
Dovrebbe essere (in qualche modo) non ovvio.
Simile ai suggerimenti per il golf in codice , dovrebbe essere qualcosa a cui qualcuno che ha giocato un po 'a golf in Python e ha letto la pagina dei suggerimenti non avrebbe immediatamente pensato. Ad esempio "Sostituisci
a + b
cona+b
per evitare di utilizzare spazi", è ovvio per qualsiasi giocatore di golf poiché è già un modo per rendere il tuo codice più breve e quindi non è un buon consiglio.Non dovrebbe essere troppo specifico.
Poiché ci sono molti diversi tipi di restrizioni sulla fonte, le risposte qui dovrebbero essere almeno in qualche modo applicabili a più restrizioni alla fonte, o una limitazione alla fonte comune. Ad esempio, i suggerimenti del modulo Come fare X senza utilizzare i caratteri Y sono generalmente utili poiché i caratteri vietati sono una restrizione comune alla fonte. Anche la cosa che ti aiuta a fare dovrebbe essere un po 'generica. Ad esempio, i suggerimenti del modulo Come creare numeri con restrizione X sono utili poiché molti programmi utilizzano numeri indipendentemente dalla sfida. Suggerimenti del modulo Come implementare l'algoritmo di Shor con la restrizione X sono fondamentalmente solo risposte a una sfida che hai appena inventato e non molto utili per le persone che risolvono altre sfide.
Risposte
Evita le lettere "normali"
Gli identificatori vengono normalizzati dal parser Python 3. Ciò implica che le lettere corsive (Unicode) come 𝓪𝓫𝓬𝓓𝓔𝓕
vengono interpretate come i loro equivalenti conformi ASCII abcDEF
. Quindi il codice seguente funziona (come è stato sfruttato qui ):
𝓝=123
𝓹𝓻𝓲𝓷𝓽(𝓝)
Versioni di Python in cui questo comportamento è confermato:
- Lavori: 3.4, 3.5, 3.6, 3.7, 3.8
- Non funziona: 2.7
Esempio di restrizione della fonte:
- Utilizzare nessun carattere
abc···xyz
,ABC···XYZ
.
Evita i numeri con valori booleani
Quando si eseguono operazioni aritmetiche sui booleani, Python li tratta come se fossero i numeri 1 e 0. Quindi, ad esempio
>>> True+False
1
Puoi creare tutti i numeri positivi semplicemente aggiungendo booleani l'uno all'altro.
Puoi anche sostituire i valori booleani con valori booleani, ad esempio []>[]
è False
ed [[]]>[]
è True
così
>>> ([]>[])+([[]]>[])
1
In alcuni casi, i booleani possono anche essere usati al posto di un numero senza dover usare l'aritmetica per lanciarlo. Ad esempio, puoi indicizzare in liste / tuple / stringhe con valori booleani, quindi:
>>> ['a','b'][True]
'b'
Esempi di limitazioni della fonte:
Non utilizzare cifre (
0123456789
)Non utilizzare caratteri alfanumerici
Non utilizzare
if
condizioni
Evita le parentesi con l'indicizzazione degli elenchi
Le parentesi sono molto utili per creare la corretta precedenza dell'operatore, quindi è un peccato quando vengono bandite. Tuttavia, se []
sono ancora disponibili, possiamo usarli invece. Basta sostituire
(...)
con
[...][0]
Questo crea un elenco e lo indicizza per ottenere il suo unico elemento. L'elenco fa sì che l'interno venga valutato prima risolvendo il tuo problema di precedenza.
L'esempio sopra usa i caratteri []0
per farlo, tuttavia ci sono altri terzi caratteri che possono essere usati in questo caso, se necessario.
- Usando caratteri
[]>
scrivi[...][[]>[]]
- Usando caratteri
[]<
scrivi[...][[]<[]]
- Usando caratteri
[]=
scrivi[...][[[]]==[]]
Esempio di restrizione della fonte:
- Non usare parentesi
Chiamate di funzioni senza parentesi
Possiamo evitare di usare le parentesi per la precedenza degli operatori usando l'indicizzazione delle liste , ma le parentesi sono ancora molto utili per chiamare le funzioni.
L'indicizzazione dell'elenco può essere utilizzata anche qui per risolvere il problema, tuttavia è molto più complessa, quindi ho creato la sua risposta.
Per chiamare una funzione, iniziamo creando una nuova classe la cui indicizzazione è definita come funzione. Quindi, se vogliamo chiamarlo, print
potrebbe sembrare
class c:__class_getitem__=print
Quindi per chiamare la funzione la indicizziamo semplicemente con l'argomento che vogliamo. Ad esempio per stampare "Hello World"
lo facciamo
c["Hello World"]
Questo ha alcuni sfortunati difetti:
- Può essere utilizzato solo per chiamare funzioni con un parametro.
- Ci sono parecchi personaggi che sono necessari per portare a termine questo trucco. (
:=[]_acegilmst
) - Utilizza molti caratteri se stai giocando a golf in codice
Ma a volte potrebbe essere la tua unica opzione.
Esempio di restrizione della fonte:
- Non usare parentesi
Ecco un esempio di come viene utilizzato.
Usa <<
e |
per generare costanti senza+
Fatto divertente: puoi ottenere qualsiasi costante positiva solo usando []<|
. La strada da percorrere è spostare a sinistra un valore booleano. []<[[]]
è 1, quindi []<[[]]<<[]<[[]]
dovresti spostare a sinistra 1 con 1, che è 2.
Funziona?
>>> []<[[]]<<[]<[[]]
Traceback (most recent call last):
File "<pyshell#29>", line 1, in <module>
[]<[[]]<<[]<[[]]
TypeError: unsupported operand type(s) for <<: 'list' and 'list'
...No.
La precedenza è sbagliata. Fortunatamente, possiamo risolvere questo problema con "Ad Hoc Garf Hunter Parenthesis (TM)":
>>> [[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]
2
Aha!
Per ottenere numeri diversi dalla potenza di due, è comunque necessario +
... o no. |
o [bitwise or][[]<[]]
* lo farà per te.
>>> [[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]|[[]<[[]]][[]<[]]<<[[]<[[]]][[]<[]]
10
Per ottenere numeri negativi senza -
, potresti voler usare ~
.
* Quella parte era racchiusa in una "parentesi Ad Hoc Garf Hunter (TM)" per indicare la precedenza.
Accedi ai metodi e alle funzioni integrate tramite __dict__
Le classi contengono un __dict__
attributo, che mappa i loro nomi di metodo ai metodi stessi. Se non puoi digitare direttamente il nome di un metodo, puoi ottenerlo da questo __dict__
.
Ad esempio, diciamo che devi aggiungere qualcosa a una lista, ma non puoi usare i caratteri p,n,+
, ecc. Poiché append()
è il 26 ° metodo in una lista __dict__
, puoi chiamare il metodo in questo modo:
a = [1,2,3]
list(a.__class__.__dict__.values())[26](a, 4)
print(a) # prints [1,2,3,4]
Può essere utilizzato anche con __builtins__
per accedere alle funzioni integrate. Anche se qualcuno vieta il carattere x
per bloccare la exec
funzione, puoi comunque chiamare in exec
questo modo:
list(__builtins__.__dict__.values())[20]("print('Hello, World!')")
Funziona meglio nelle versioni più recenti di Python che garantiscono l'ordine del dizionario, ma probabilmente ci sono altri modi per usarlo nelle versioni precedenti, come l'iterazione __dict__
di una corrispondenza con un'espressione regolare o una sottostringa.
Utilizzare ord()
o stringhe binarie per evitare le cifre
La maggior parte dei numeri interi negli intervalli [32..47]
e [58..126]
può essere facilmente ottenuta dal codice ASCII di un singolo carattere con:
x=ord('A')
# or, if parentheses are not allowed:
y=b'A'[False]
I numeri interi più grandi possono anche essere prodotti utilizzando dai loro punti Unicode:
>>>print (ord("±"))
177
>>> print (ord("π"))
960
Se puoi usare un'assegnazione o devi evitare le parentesi, puoi invece decomprimere i valori. Nota che questo non funzionerà in linea, anche con l'operatore tricheco.
x,*_=b'A'
y,_=b'A_'
Esempi di limitazioni della fonte:
- Non utilizzare cifre
- Non usare cifre / parentesi / nessuna parentesi
Utilizzare --
per evitare +
Ad esempio da fare a+b
:
a--b
Esempio di restrizione della fonte:
- Evita l'
+
operatore
Alternative a eval
eexec
Hai bisogno di trattare una stringa come codice, ma non puoi usare eval
o exec
? Esistono almeno altri tre modi per eseguire una stringa:
1) timeit.timeit
import timeit
_=timeit.timeit("print('Hello!')", number=1)
timeit
esegue i number
tempi e restituisce una media del tempo impiegato. Di default viene eseguito 1 milione di volte, quindi quasi sicuramente vorrai impostare number=1
o sollevare un'eccezione per scoppiare (ad esempio "print('hello'); 0/0"
).
Grazie a Ethan White per avermi mostrato questo approccio.
2) os.system
import os
c='echo "import math;print(math.pi)" | python3'
_=os.system(c) # Prints 3.141592653589793
os.system
esegue un comando shell arbitrario e restituisce il suo codice di uscita. Se hai solo bisogno di stampare qualcosa, puoi attenerci echo
, ma puoi anche eseguire codice arbitrario chiamando python3
se stesso.
3) code.InteractiveInterpreter (). Runcode
from code import InteractiveInterpreter as I
i = I()
i.runcode("print('Hello!')")
code
è specificamente progettato per i cicli di lettura-valutazione-stampa, e anche se è un po 'goffo, questo è il più potente dei tre. timeit
e os.system
isolare i loro processi, ma un InteractiveInterpreter
può usare lo stato globale invece del proprio:
from code import InteractiveInterpreter as I
a = 64
i = I(globals())
i.runcode("import math; a=math.log2(a)")
print(a) # a = 6.0
print(math.pi) # math is imported globally
Utilizzare *
per evitare/
x**-1
è equivalente a 1/x
. Quindi per fare y/x
puoi farlo x**-1*y
.
Se vuoi disperatamente sbarazzarti del -1
, puoi dare un'occhiata all'altro suggerimento di Ad Hoc Garf Hunter.
Esempio di restrizione della fonte:
- Evita di usare il
/
personaggio
Codifica i caratteri limitati e usa exec()
Come la maggior parte dei linguaggi interpretati, Python può eseguire una stringa come codice con eval
e exec
. eval
è più limitato, ma exec
può gestire importazioni, definizioni di funzioni, cicli, eccezioni, ecc.
Se combinato con alcuni degli altri suggerimenti sulla codifica dei caratteri, questo ti consente di scrivere il codice normalmente:
import sys
def f(i):
return 1 if i==1 else i*f(i-1)
i=int(sys.argv[1])
print(f(i))
Quindi scegli una codifica e passa la versione codificata a exec
:
exec('\x69\x6d\x70\x6f\x72\x74\x20\x73\x79\x73\x0a\x0a\x64\x65\x66\x20\x66\x28\x69\x29\x3a\x0a\x20\x72\x65\x74\x75\x72\x6e\x20\x31\x20\x69\x66\x20\x69\x3d\x3d\x31\x20\x65\x6c\x73\x65\x20\x69\x2a\x66\x28\x69\x2d\x31\x29\x0a\x0a\x69\x3d\x69\x6e\x74\x28\x73\x79\x73\x2e\x61\x72\x67\x76\x5b\x31\x5d\x29\x0a\x70\x72\x69\x6e\x74\x28\x66\x28\x69\x29\x29\x0a')
Sostituisci gli operatori con metodi stupidi
La maggior parte degli operatori Python sono zucchero sintattico per chiamate di metodi specifici (spesso chiamati "metodi magici" o "metodi dunder", con "dunder" che sta per "double underscore"). Ad esempio, +
chiamate __add__()
, ==
chiamate __eq__()
e <<
chiamate__lshift__()
Se gli operatori sono limitati, puoi chiamare direttamente questi metodi:
a = 1
print(a.__add__(1).__eq__(2)) # True
Per l'assegnazione, puoi usare __setitem__
sui dizionari locals()
o globals()
, indipendentemente dal fatto che la variabile esista o meno:
a = 1
locals().__setitem__('a',2)
locals().__setitem__('b',2)
print(a.__add__(b).__eq__(4)) # True
Nota che dovrai aggiungere parentesi attorno ai numeri per evitare un errore di sintassi. 4.__eq__(4)
non funzionerà, ma (4).__eq__(4)
funzionerà.
Come creare caratteri solo con numeri, virgolette e barre rovesciate
Le stringhe possono essere composte con \ooo
dove ooo
è il valore ottale del carattere.
Per esempio:
'\141'=='a'
È inoltre possibile utilizzare esadecimale, a scapito di un x
(e a
, b
, c
, d
, e
e / o f
se sono utilizzati):
'\x61'=='a'
E unicode a scapito di un u
(due pre-Python 3) e caratteri esadecimali se vengono utilizzati:
'\u2713'=='✓'
Usa __import__("module")
invece diimport module
Per evitare spazi bianchi in import statement
, o per costruire dinamicamente il nome del modulo da importare come stringa (ad esempio "RANDOM".lower()
se non è possibile utilizzare un minuscolo d
). Non è probabile che utile, perché non capita spesso bisogno della libreria standard, e hai ancora bisogno di essere in grado di utilizzare _
, i
, m
, p
, o
, r
, t
, (
, e )
.
Modifica: o come suggerisce Ad Hoc Garf Hunter, puoi usare import<tab>module
(con un carattere di tabulazione letterale)!
Questo probabilmente non è rilevante per la domanda, ma è molto sciocco.
Questo è (forse) un modo più portabile del metodo contorto di water_ghost per accedere ai metodi incorporati.
L'indice è 26 solo su CPython 3. Questa modifica molto piccola ed estremamente facile da capire gli consente di funzionare su CPython 2.7, CPython 3, PyPy 2.7 e PyPy 3 (testato su Debian 10 amd64)
a = [1, 2, 3]
list(a.__class__.__dict__.values())[[14,13,26,25][sum(map(ord,{__import__("sys").version[0],__import__("platform").python_implementation()[0]}))&3]](a, 4)
print(a)
Gli indici corretti (sul mio sistema) sono
CPython 2.7 13
CPython 3 26
PyPy 2.7 26
PyPy 3 25
E per una fortunata coincidenza ('C'+'2')%4 == 1
, ('C'+'3')%4 == 2
, ('P'+'2') == 2
e ('P'+'3') == 3
. Il valore 14 è lì per indurti a pensare che ci sia uno schema.
__debug__
è vero
Abbastanza autoesp ... espl ...
>>> __debug__
True