Vamos escrever um Minifier

14

fundo

Minificadores são usados, geralmente, ao veicular JavaScript no seu navegador da Web. É comumente usado para reduzir o número de bytes que precisam ser enviados. Economizar largura de banda é útil por razões óbvias. Algumas pessoas usam ofuscadores (que intencionalmente tornam o código mais difícil de ler), não estou falando sobre eles.

Minimizaremos o Python 2

Eu estava debatendo se deveria usar JavaScript ou Python para a experiência de minificação e decidi por Python por duas razões: o espaço em branco é importante e acho que isso adicionará uma dinâmica interessante ao problema. Além disso, o uso do Python 2.7 fornecerá outra dinâmica, como remover supérfluos ()durante uma impressão (ou seja, print("Hello world")vs. print"Hello world"). Pessoalmente, eu preferiria abri-lo para qualquer idioma, mas para alguns idiomas esse processo não fará muito sentido. E qual idioma você decide reduzir afetará diretamente sua pontuação (e se o idioma ainda pode ser reduzido).

Especificações

Seu objetivo é modificar apenas o código de uma maneira que não altere sua funcionalidade. É claro que você pode alterar os nomes das variáveis ​​(dentro do seu programa de minificação), desde que isso não afete a saída (mantenha o controle do escopo ). Embora eu esteja oferecendo um programa específico, não otimize para o caso de teste, pois todas as brechas padrão são proibidas.

Pontuação : duração do programa após você o ter minificado.

Entrada : qualquer programa Python 2.7 (que não contém erros)

Saída : uma versão minificada.

Embora seu código possa acomodar todas as entradas válidas do Python 2.7, é necessário testar seu script em relação a algo para provar sua eficácia.

Clique aqui para ver o programa de exemplo.

Tornando o problema mais acessível

Sinta-se livre para usar ou modificar qualquer código encontrado dentro da minha solução (listado abaixo). Eu fiz isso para você começar com o tratamento básico de cotações; no entanto, você pode expandi-lo para recuo e etc.

Exemplos de maneiras de reduzir o Python

Todo o espaço em branco pode ser substituído pela quantidade mínima possível (reconheço que no Python você pode fazer algumas coisas complicadas com guias , mas deixarei isso para você decidir se deve ou não implementá-lo).

Exemplo

Os seguintes:

def print_a_range(a):
    for i in range(a):
        print(i)

Poderia ser:

def print_a_range(a):
 for i in range(a):
  print(i)

Tecnicamente, se houver apenas uma linha dentro de um loop, você poderá compactá-lo ainda mais:

def print_a_range(a):
 for i in range(a):print(i)  #Note, you can also remove the `()` here.

No entanto, há outra maneira de minimizar o espaço em branco no Python:

Os seguintes:

print ([a * 2 for a in range(20) if a % 2 == 0])

Poderia ser:

print([a*2for a in range(20)if a%2==0])

Observe que não há necessidade de um espaço entre 2e for. Variável, funções e palavras-chave não podem começar com um número. Portanto, o intérprete Python não tem problema <num><keyword>. Você também deve observar que não há um espaço entre )e if.

Note que você não deve alterar a saída do programa! Então:

print"f(x)=x*2 is a great equation!"

A declaração de impressão acima deve permanecer a mesma porque remover o espaço entre 2e ismodificar a saída.

Neil
fonte
Sidenote: não há programa que saída pode o mais curto equivalente de qualquer programa de entrada arbitrária, por essa discussão
Leaky Nun
Não se deixe algumas ferramentas python Minifier . Não acho que essa pergunta possa receber uma solução melhor do que as ferramentas já existentes.
tsh
É permitido mudar '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'para '1'*100? Exigir fazer como o comportamento é o mesmo?
L4m2 20/0318

Respostas:

2

Pontuação do Python 2.7, 2013

Este programa pode ser usado como referência e você pode pegar o código a seguir e modificá-lo e publicá-lo em suas próprias soluções.

Em retrospectiva, talvez eu devesse ter usado regex também para o tratamento de cotações, mas acho que, no estado atual, pode ser suficiente para levar as pessoas ao problema.

Por que escolhi o Python 2.7: Pensei que seria mais fácil testar para ver se fiz o travamento do programa por meio da execpalavra - chave.

Este código recebe o programa como in.txt.

Imaginei que deveria pelo menos fazer a bola rolar para quem quiser participar escrevendo um analisador de cotações (que também manipula comentários) e um breve exemplo de como o regex, quando combinado com o analisador de cotações, pode realmente mudar o jogo em termos de complexidade deste problema.

Nota: ainda há muito espaço para melhorias neste minificador. Como se você pudesse brincar com indentação, nomes de variáveis ​​e remover os parênteses quando eles estão sendo usados, minhas palavras-chave, como printou yield.

import re

with open("in.txt","r") as fi:
    code = fi.read()

class QuoteHandler():
    def __init__(self):
        pass
    def loadCode(self,code):
        quoteFlag = False
        currentQuoteChar = ""
        ignoreNext = False
        inEndLineComment=False
        startLocation = 0

        self.reAddStrings = []

        outStr = ""

        for i, character in enumerate(code):
            if ignoreNext:
                ignoreNext = False
            elif inEndLineComment:
                if character in "\r\n":
                    inEndLineComment=False
            elif character == "#" and not quoteFlag:
                inEndLineComment = True
            elif character in "'\"" and (currentQuoteChar == character or not quoteFlag):
                if quoteFlag:
                    self.reAddStrings.append(code[startLocation+1:i])
                else:
                    currentQuoteChar = character
                    startLocation = i
                quoteFlag = not quoteFlag
            elif character == "\\":
                ignoreNext = True

            if not inEndLineComment and not quoteFlag:
                outStr+=character                
        return outStr

    def find_all_locations(self,substr,code):
        return [m.start() for m in re.finditer(substr, code)]

    def unloadCode(self,code):
        temp = self.reAddStrings[::-1]
        for i, location in enumerate(list(self.find_all_locations('"',code))[::-1]):
            code = code[:location] + "\"" + temp[i] + code[location:]
        return code

def applyRegexes(code):#\w here?
    operatorRegexCleaner = ["([\d\/*\-\"=,'+{}:[\](\)])","[ \t]+","(\w)"]
    regexes = [
        [''.join(operatorRegexCleaner),r"\1\2"],
        [''.join(operatorRegexCleaner[::-1]),r"\1\2"],#removes whitespace between operators
        ["\n\s*\n","\n"]#removes empty lines
    ]
    for regex in regexes:
        code = re.sub(regex[0],regex[1],code)
    return code

qh = QuoteHandler()
code = qh.loadCode(code)
code = applyRegexes(code)
code = qh.unloadCode(code)
print(code)
exec(code)

Saída do programa:

def factor(factor_number):
    for n in range(2,factor_number):
        if factor_number % n==0:    
            yield(n)
def gcd(a,b):
    """Calculate the Greatest Common Divisor of a and b.

    Unless b==0, the result will have the same sign as b (so that when
    b is divided by it, the result comes out positive).
    """
    while b:
         a,b=b,a%b 
    return a
class Apricot:
    def __init__(self):
        self.mold=False
    def get(self):
        return self.mold
    def update(self):
        self.mold=not self.mold
    def blue(self):return5
def tell_me_about_these_numbers(*a):
    print("%d is the first number!" % a[0])
    print("{} / 3 is {}".format(a[0],a[0]/3.))
    myFavorate=Apricot()
    for number in a:
        print list(factor(number))
        myFavorate.update()
    print[gcd(a,b)for a,b in zip(a[:-1],a[1:])]
    print(myFavorate.get())
tell_me_about_these_numbers(5,6,9,45,200)
print"Let's play with scope!"
a,b=10,9
def randomFunction(a):
    print(a)
randomFunction(b)
print(a)
for a in range(100):
    b+=a
print(a)
print(b)
li=[]
for i in range(10):
 li.append(i*2)
print(li)
print([i*2for i in range(10)])
a=c=b=d=e=f=g=h=i=j=k=l=m=n=o=p=q=r=s=t=u=v=w=x=y=z=5
print(a)
a-=1
print(a)
g=10
print(str(10**g+5)[::-1])
def blue_fish(a):
    def blue_fish(a):
        def blue_fish(a):
            return a
        a+=1
        return blue_fish(a)
    a-=1
    return blue_fish(a)
print(blue_fish(10))
def blue_fish(a):
    if a==0:
        return"0"
    return"1" +blue_fish(a-1)
print(blue_fish(5))
blue_fish=lambda a,b,c:a*b*c
print(blue_fish(1,2,3))
blue_fish=lambda*a:reduce(lambda a,b:a*b,a)
print(blue_fish(1,2,3))
print(max([[6,1],[5,2],[4,3],[3,4],[2,5],[1,6]],key=lambda a:a[1]))
print(zip(*[[1],[2],[3],[4],[5]]))
print"Now let's test to see if you handle quotes correctly:"
print"test \'many diffent\' \"types of \" quotes, even with \' \" trailing quotes"
print"""

Multi line quotes are great too!

"""
a=""" ::
one more multi-line quote won't hurt
"""
print a
Neil
fonte