Expandir uma expansão de bash brace

20

Por razões principalmente históricas, o bash é uma mistura de paradigmas de sintaxe e programação - isso pode torná-lo estranho e às vezes frustrante para o golfe. No entanto, existem alguns truques na manga, que muitas vezes podem torná-lo competitivo com outros scripts tradicionais. línguas. Uma delas é a expansão de chaves .

Existem dois tipos básicos de expansão de chaves:

  • Os colchetes de lista podem conter listas separadas por vírgula de cadeias arbitrárias (incluindo duplicatas e a cadeia vazia). Por exemplo {a,b,c,,pp,cg,pp,}, expandirá para a b c pp cg pp(observe os espaços ao redor das cadeias vazias).
  • Os colchetes de sequência podem conter pontos finais de sequência separados por ... Opcionalmente, outro ..pode ser seguido, seguido por um tamanho de etapa. Os pontos finais da sequência podem ser números inteiros ou caracteres. A sequência sobe ou desce automaticamente de acordo com o ponto final maior. Por exemplo:
    • {0..15} vai expandir para 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    • {-10..-5} vai expandir para -10 -9 -8 -7 -6 -5
    • {3..-6..2} vai expandir para 3 1 -1 -3 -5
    • {a..f} vai expandir para a b c d e f
    • {Z..P..3} vai expandir para Z W T Q

Além disso, chaves de sequência e lista podem existir com chaves de lista:

  • {a,b,{f..k},p} vai expandir para a b f g h i j k p
  • {a,{b,c}} vai expandir para a b c

O aparelho se expande com cadeias que não sejam espaços em branco em ambos os lados. Por exemplo:

  • c{a,o,ha,}t vai expandir para cat cot chat ct

Isso também funciona para várias chaves concatenadas juntas:

  • {ab,fg}{1..3} vai expandir para ab1 ab2 ab3 fg1 fg2 fg3

Isso pode ficar bastante complexo. Por exemplo:

  • {A..C}{x,{ab,fg}{1..3},y,} vai expandir para Ax Aab1 Aab2 Aab3 Afg1 Afg2 Afg3 Ay A Bx Bab1 Bab2 Bab3 Bfg1 Bfg2 Bfg3 By B Cx Cab1 Cab2 Cab3 Cfg1 Cfg2 Cfg3 Cy C

No entanto, se houver espaço em branco entre as expansões, elas simplesmente se expandirão como expansões separadas. Por exemplo:

  • {a..c} {1..5} vai expandir para a b c 1 2 3 4 5

Observe como a ordem é sempre preservada.


As inscrições para esse desafio expandirão as expansões de chaves de bash, conforme descrito acima. Em particular:

  • avaliação por bash(ou outras conchas que executam expansão semelhante) não é permitida
  • chaves entre sequências sempre serão de número para número, minúsculas para minúsculas ou maiúsculas para maiúsculas sem mistura. Os números serão números inteiros no intervalo assinado de 32 bits. Se fornecido, o tamanho opcional da etapa sempre será um número inteiro positivo. (Observe que o bash também se expandirá {A..z}, mas isso pode ser ignorado para esse desafio)
  • itens individuais entre chaves sempre serão compostos apenas por caracteres alfanuméricos em maiúsculas e minúsculas (string vazia incluída)
  • chaves de lista podem conter aninhamentos arbitrários de outras expansões de chave
  • chaves podem ser concatenadas números arbitrários de vezes. Isso será limitado pela memória do seu idioma; portanto, a expectativa é que você possa teoricamente fazer números arbitrários de concatenações, mas se / quando ficar sem memória, isso não será considerado.

Os exemplos no texto acima servem como casos de teste. Resumidos, com cada linha de entrada correspondente à mesma linha de saída, são eles:

Entrada

{0..15}
{-10..-5}
{3..-6..2}
{a..f}
{Z..P..3}
{a,b,{f..k},p}
{a,{b,c}}
c{a,o,ha,}t
{ab,fg}{1..3}
{A..C}{x,{ab,fg}{1..3},y,}
{a..c} {1..5}
{a{0..100..10},200}r

Resultado

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
-10 -9 -8 -7 -6 -5
3 1 -1 -3 -5
a b c d e f
Z W T Q
a b f g h i j k p
a b c
cat cot chat ct
ab1 ab2 ab3 fg1 fg2 fg3
Ax Aab1 Aab2 Aab3 Afg1 Afg2 Afg3 Ay A Bx Bab1 Bab2 Bab3 Bfg1 Bfg2 Bfg3 By B Cx Cab1 Cab2 Cab3 Cfg1 Cfg2 Cfg3 Cy C
a b c 1 2 3 4 5
a0r a10r a20r a30r a40r a50r a60r a70r a80r a90r a100r 200r
Trauma Digital
fonte
3
Olhei para isso e é uma dor simplesmente para analisar causa de todos os casos de ponta :-(
Neil

Respostas:

3

Ruby, 405 403 401 400 bytes

Um homem sábio (Jamie Zawinski) disse uma vez: "Algumas pessoas, quando confrontadas com um problema, pensam 'eu sei, vou usar expressões regulares'. Agora eles tem dois problemas."

Acho que não apreciei totalmente essa citação até tentar resolver esse problema com regex recursivo. Inicialmente, os casos de regex pareciam simples, até que eu tive que lidar com casos extremos envolvendo letras adjacentes a colchetes, e então eu sabia que estava no inferno.

De qualquer forma, execute-o online aqui com casos de teste

->s{s.gsub!(/{(-?\w+)..(-?\w+)(..(\d+))?}/){x,y=$1,$2;a,b,c=[x,y,$4].map &:to_i
$1[/\d/]?0:(a,b=x,y)
k=a<b ?[*a..b]:[*b..a].reverse
?{+0.step(k.size-1,$4?c:1).map{|i|k[i]}*?,+?}}
r=1
t=->x{x[0].gsub(/^{(.*)}$/){$1}.scan(/(({(\g<1>|,)*}|[^,{}]|(?<=,|^)(?=,|$))+)/).map{|i|i=i[0];i[?{]?r[i]:i}.flatten}
r=->x{i=x.scan(/({(\g<1>)*}|[^{} ]+)/).map(&t)
i.shift.product(*i).map &:join}
s.split.map(&r)*' '}

Ungolfed:

->s{
  s.gsub!(/{(-?\w+)..(-?\w+)(..(\d+))?}/){  # Replace all range-type brackets {a..b..c}
    x,y=$1,$2;a,b,c=[x,y,$4].map &:to_i     # Set up int variables
    $1[/\d/]?0:(a,b=x,y)                    # Use int variables for a,b if they're numbers
    k=a<b ?[*a..b]:[*b..a].reverse          # Create an array for the range in the correct direction
    '{'+                                    # Return the next bit surrounded by brackets
      0.step(k.size-1,$4?c:1).map{|i|k[i]   # If c exists, use it as the step size for the array
      }*','                                 # Join with commas
      +'}'
  }
  r=1                                       # Dummy value to forward-declare the parse function `r`
  t=->x{                                    # Function to parse a bracket block
    x=x[0].gsub(/^{(.*)}$/){$1}             # Remove outer brackets if both are present
                                            # x[0] is required because of quirks in the `scan` function
    x=x.scan(/(({(\g<1>|,)*}|[^,{}]|(?<=,|^)(?=,|$))+)/)
                                            # Regex black magic: collect elements of outer bracket
    x.map{|i|i=i[0];i[?{]?r[i]:i}.flatten   # For each element with brackets, run parse function
  }
  r=->x{                                    # Function to parse bracket expansions a{b,c}{d,e}
    i=x.scan(/({(\g<1>)*}|[^{} ]+)/)        # Regex black magic: scan for adjacent sets of brackets
    i=i.map(&t)                             # Map all elements against the bracket parser function `t`
    i.shift.product(*i).map &:join          # Combine the adjacent sets with cartesian product and join them together
  }
  s.split.map(&r)*' '                       # Split on whitespace, parse each bracket collection
                                            #   and re-join with spaces
}
Value Ink
fonte
2

Python 2.7, 752728 bytes

Uau, isso é como um monte de código de golfe em um desafio!

Graças a @ Neil por encurtar um lambda

def b(s,o,p):
 t,f=s>':'and(ord,chr)or(int,str);s,o=t(s),t(o);d=cmp(o,s)
 return list(map(f,range(s,o+d,int(p)*d)))
def e(s):
 c=1;i=d=0
 while c:d+=-~'{}}'.count(s[i])%3-1;i+=1;c=i<len(s)and 0<d
 return i
def m(s):
 if len(s)<1:return[]
 if','==s[-1]:return m(s[:-1])+['']
 i=0
 while i<len(s)and','!=s[i]:i+=e(s[i:])
 return[s[:i]]+m(s[i+1:])
n=lambda a,b:[c+d for c in a for d in b]or a or b
def p(s):
 h=s.count
 if h('{')<1:return[s]
 f,l=s.index('{'),e(s)
 if h('{')<2and h('..')>0and f<1:s=s[1:-1].split('..');return b(s[0],s[1],s[2])if len(s)>2 else b(s[0],s[1],1)
 if f>0 or l<len(s):return n(p(s[:f]),n(p(s[f:l]),p(s[l:])))
 return sum(map(list,map(p,m(s[1:-1]))),[])
o=lambda s:' '.join(p('{'+s.replace(' ',',')+'}'))

Explicação

  • b: calcula o intervalo de acordo com as especificações.
  • e: retorna a posição do primeiro colchete externo mais externo. Iterativo.
  • m: divide elementos mais externos em vírgulas. Recursivo.
  • n: combina matrizes durante a verificação de vazios. Não consegui and/ortrabalhar.
  • p: Onde a maior parte do trabalho é realizada. Verifica todos os casos (intervalo, apenas lista, precisa ser combinado). Recursivo.
  • o: O que deve receber entrada. Formata entrada / saída para p.

Sinto que posso melhorar em alguns lugares, por isso tentarei jogar mais. Também devo colocar mais detalhes na explicação.

Azul
fonte
Eu esperava [c+d for c in a for d in b] or a or btrabalhar.
Neil
2

JavaScript (Firefox 30-57), 465 427 425 bytes

f=s=>/\{/.test(s)?f(s.replace(/([^,{}]*\{[^{}]*\})+[^,{}]*/,t=>t.split(/[{}]+/).map(u=>u.split`,`).reduce((a,b)=>[for(c of a)for(d of b)c+d]))):s.split`,`.join` `
s=>f(`{${s.split` `}}`.replace(/\{(-?\w+)\.\.(-?\w+)(\.\.(\d+))?\}/g,(m,a,o,_,e)=>{m=(a>'@')+(a>'_');a=parseInt(a,m?36:10);o=parseInt(o,m?36:10);e=+e||1;if(o<a)e=-e;for(r=[];e<0?o<=a:a<=o;a+=e)r.push(m?a.toString(36):a);r=`{${r}}`;return m-1?r:r.toUpperCase()}))

A versão ES6 fpesa 10 bytes extras:

f=s=>/\{/.test(s)?f(s.replace(/([^,{}]*\{[^{}]*\})+[^,{}]*/,t=>t.split(/[{}]+/).map(u=>u.split`,`).reduce((a,b)=>[].concat(...a.map(c=>b.map(d=>c+d)))))):s.split`,`.join` `
g=s=>f(`{${s.split` `}}`.replace(/\{(-?\w+)\.\.(-?\w+)(\.\.(\d+))?\}/g,(m,a,o,_,e)=>{m=(a>'@')+(a>'_');a=parseInt(a,m?36:10);o=parseInt(o,m?36:10);e=+e||1;if(o<a)e=-e;for(r=[];e<0?o<=a:a<=o;a+=e)r.push(m?a.toString(36):a);r=`{${r}}`;return m-1?r:r.toUpperCase()}))
h=(s,t=s.replace(/\{[^{}]*\}/,""))=>s!=t?h(t):!/[{}]/.test(s)
<input oninput="o.textContent=h(this.value)?g(this.value):'{Invalid}'"><div id=o>

Explicação: Começa alterando espaços em vírgulas e agrupando toda a sequência {}para obter consistência (graças a @Blue pela ideia). Em seguida, procura por todas as {..}construções e as expande em {,}construções. Em seguida, usa a recursão para expandir repetidamente todas as {,}construções de dentro para fora. Finalmente, substitui todas as vírgulas por espaços.

f=s=>/\{/.test(s)?                  while there are still {}s
 f(s.replace(                       recursive replacement
  /([^,{}]*\{[^{}]*\})+[^,{}]*/,    match the deepest group of {}s
  t=>t.match(/[^{}]+/g              split into {} terms and/or barewords
   ).map(u=>u.split`,`              turn each term into an array
   ).reduce((a,b)=>                 loop over all the arrays
    [for(c of a)for(d of b)c+d]))   cartesian product
  ):s.split`,`.join` `              finally replace commas with spaces
s=>f(                               change spaces into commas and wrap
 `{${s.split` `}}`.replace(         match all {..} seqences
   /\{([-\w]+)\.\.([-\w]+)(\.\.(\d+))?\}/g,(m,a,o,_,e)=>{
    m=(a>'@')+(a>'_');              sequence type 0=int 1=A-Z 2=a-z
    a=parseInt(a,m?36:10);          convert start to number
    o=parseInt(o,m?36:10);          convert stop to number
    e=+e||1;                        convert step to number (default 1)
    if(o<a)e=-e;                    check if stepping back
    for(r=[];e<0?o<=a:a<=o;a+=e)    loop over each value
     r.push(m?a.toString(36):a);    convert back to string
    r=`{${r}}`;                     join together and wrap in {}
    return m-1?r:r.toUpperCase()})) convert type 1 back to upper case
Neil
fonte