Expande una expansión de llaves

20

Por razones principalmente históricas, bash es una mezcla de paradigmas de sintaxis y programación; esto puede hacer que jugar golf sea incómodo y, a veces, frustrante. Sin embargo, tiene algunos trucos bajo la manga que a menudo pueden hacerlo competitivo con otros guiones convencionales. idiomas Una de ellas es la expansión de llaves .

Hay dos tipos básicos de expansión de llaves:

  • Las llaves de lista pueden contener listas de cadenas arbitrarias separadas por comas (incluidos los duplicados y la cadena vacía). Por ejemplo, {a,b,c,,pp,cg,pp,}se expandirá a a b c pp cg pp(tenga en cuenta los espacios alrededor de las cadenas vacías).
  • Las llaves de secuencia pueden contener puntos finales de secuencia separados por ... Opcionalmente, ..puede seguir otro , seguido de un tamaño de paso. Los puntos finales de secuencia pueden ser enteros o caracteres. La secuencia ascenderá o descenderá automáticamente según el punto final que sea mayor. Por ejemplo:
    • {0..15} se expandirá a 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    • {-10..-5} se expandirá a -10 -9 -8 -7 -6 -5
    • {3..-6..2} se expandirá a 3 1 -1 -3 -5
    • {a..f} se expandirá a a b c d e f
    • {Z..P..3} se expandirá a Z W T Q

Más allá de esto, la secuencia y las llaves de lista pueden existir con llaves de lista:

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

Las llaves se expanden con cadenas que no son espacios en blanco a cada lado de ellas. Por ejemplo:

  • c{a,o,ha,}t se expandirá a cat cot chat ct

Esto también funciona para múltiples llaves concatenadas juntas:

  • {ab,fg}{1..3} se expandirá a ab1 ab2 ab3 fg1 fg2 fg3

Esto puede ser bastante complejo. Por ejemplo:

  • {A..C}{x,{ab,fg}{1..3},y,} se expandirá a 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

Sin embargo, si hay espacios en blanco entre las expansiones, entonces simplemente se expanden como expansiones separadas. Por ejemplo:

  • {a..c} {1..5} se expandirá a a b c 1 2 3 4 5

Tenga en cuenta cómo se conserva siempre el orden.


Las entradas para este desafío expandirán las expansiones de llaves bash como se describió anteriormente. En particular:

  • eval by bash(u otros shells que realizan una expansión similar) no está permitido
  • las llaves de secuencia siempre serán número a número, minúsculas a minúsculas o mayúsculas a mayúsculas sin mezcla. Los números serán enteros en el rango de 32 bits con signo. Si se proporciona, el tamaño de paso opcional siempre será un número entero positivo. (Tenga en cuenta que bash también se expandirá {A..z}, pero esto puede ignorarse para este desafío)
  • los elementos individuales en llaves de lista siempre estarán compuestos solo por caracteres alfanuméricos en mayúsculas y minúsculas (cadena vacía incluida)
  • la lista de llaves puede contener anidaciones arbitrarias de otras expansiones de llaves
  • Las llaves se pueden concatenar números arbitrarios de veces. Esto estará limitado por la memoria de su idioma, por lo que la expectativa es que en teoría pueda hacer números arbitrarios de concatenaciones, pero si se queda sin memoria, eso no contará en su contra.

Los ejemplos en el texto anterior sirven como casos de prueba. Resumiendo, con cada línea de entrada correspondiente a la misma línea de salida, son:

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

Salida

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
fuente
3
Investigué esto y es un dolor simplemente analizar debido a todos los casos extremos :-(
Neil

Respuestas:

3

Ruby, 405 403 401 400 bytes

Un hombre sabio (Jamie Zawinski) dijo una vez: "Algunas personas, cuando se enfrentan a un problema, piensan 'Lo sé, usaré expresiones regulares'". Ahora ellos tienen dos problemas."

No creo que aprecie completamente esa cita hasta que traté de resolver este problema con expresiones regulares recursivas. Inicialmente, los casos de expresiones regulares parecían simples, hasta que tuve que lidiar con los casos extremos que involucraban letras adyacentes a los corchetes, y luego supe que estaba en el infierno.

De todos modos, ejecútelo en línea aquí con casos de prueba

->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)*' '}

Sin golf:

->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
}
Tinta de valor
fuente
2

Python 2.7, 752 728 bytes

¡Guau, esto es como un montón de golf de código en un desafío!

Gracias a @Neil por acortar una 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(' ',',')+'}'))

Explicación

  • b: calcula el rango según las especificaciones.
  • e: devuelve la posición de la primera llave de cierre más externa. Iterativo.
  • m: divide los elementos más externos en comas. Recursivo.
  • n: combina matrices mientras busca vacíos. No pude ir and/ora trabajar.
  • p: Donde se realiza la mayor parte del trabajo. Comprueba todos los casos (Rango, solo lista, debe combinarse). Recursivo.
  • o: Lo que debe tomar entrada. Formatos de entrada / salida a p.

Siento que puedo mejorar en algunos lugares, así que intentaré jugar más al golf. También debería poner más detalles en la explicación.

Azul
fuente
Hubiera esperado [c+d for c in a for d in b] or a or btrabajar.
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()}))

La versión ES6 de fpesa 10 bytes adicionales:

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>

Explicación: Comienza cambiando los espacios en comas y envolviendo toda la cadena {}para lograr consistencia (gracias a @Blue por la idea). Luego busca todas las {..}construcciones y las expande en {,}construcciones. Luego usa la recursión para expandir repetidamente todas las {,}construcciones de adentro hacia afuera. Finalmente reemplaza todas las comas con espacios.

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
fuente