¿Qué hay en mi salsa para pasta?

37

Fondo

En Francia, y probablemente en el resto de la Unión Europea, cualquier alimento disponible para la venta debe incluir los ingredientes que lo componen en su envase, en orden descendente porcentual . Sin embargo, no es necesario indicar el porcentaje exacto , a menos que el ingrediente o la imagen en la cubierta resalten el ingrediente.

Por ejemplo, mi salsa de tomate con albahaca, que muestra solo algunos tomates rojos grandes y hermosas hojas de albahaca en su empaque, tiene las siguientes indicaciones:

Ingredientes: Tomates 80%, cebollas en trozos, albahaca 1.4%, sal marina, puré de ajo, azúcar de caña cruda, aceite de oliva virgen extra, pimienta negra.

Suena sabroso, pero ... ¿cuántas cebollas voy a comer exactamente?

Reto

Dada una lista de porcentajes de peso en orden descendente, eventualmente incompleta, genera una lista completa de los porcentajes de peso mínimo y máximo que posiblemente se pueden encontrar en la receta.

  • Puede escribir una función o un programa completo.
  • La entrada puede estar en cualquier forma razonable (matriz de números o lista de cadenas, por ejemplo). Los valores fraccionales deben ser compatibles al menos con un decimal. Un porcentaje en peso que faltan se puede representar de cualquier manera coherente e inequívoca ( 0, '?'o null, por ejemplo). Puede suponer que la entrada siempre estará asociada a una receta válida ( [70]y [∅, ∅, 50]no es válida, por ejemplo).
  • El resultado puede tener cualquier forma razonable (una matriz para los porcentajes de peso mínimo y máximo, o una lista única de dobletes, por ejemplo). Los porcentajes mínimos y máximos pueden estar en cualquier orden ( [min, max]y [max, min]ambos son aceptables). Los porcentajes de peso exactos no necesitan ser procesados ​​de manera diferente a otros porcentajes y pueden representarse con valores mínimos y máximos iguales.

Se aplican reglas estándar para el : mientras escribe su código, mi plato de pasta se enfría, por lo que gana la presentación más corta.

Ejemplos

Dado que este problema es más difícil de lo que parece a primera vista, aquí hay una resolución paso a paso de algunos casos.

[40, ∅, ∅]

Llamemos respectivamente xy ylos dos porcentajes faltantes.

  • Debido a que viene después del primer ingrediente al 40%, xno puede ser superior al 40% en sí mismo.
    [40, [?, 40], [?, ?]]
  • La suma de los dos porcentajes que faltan es siempre del 60%. En consecuencia:
    • Si xtoma su valor máximo , ytoma su valor mínimo , que es, por lo tanto, 60% - 40% = 20%.
      [40, [?, 40], [20, ?]]
    • Si xtoma su valor mínimo , ytoma su valor máximo . Pero xno puede ser inferior a y, por lo tanto, en este caso, x= y= 60% / 2 = 30%.
      [40, [30, 40], [20, 30]]

[70, ∅, ∅, 5, ∅]

Llamemos respectivamente x, yy zlos tres porcentajes faltantes.

  • Los porcentajes mínimos y máximos para zson necesariamente entre 0% y 5%. Supongamos z= 0% por un momento. La suma de los dos porcentajes que faltan es siempre del 25%. En consecuencia:
    [70, [?, ?], [?, ?], 5, [0, 5]]
    • Si ytoma su valor mínimo , 5%, xtoma su valor máximo , que es, por lo tanto, 25% - 5% = 20%.
      [70, [?, 20], [5, ?], 5, [0, 5]]
    • Si ytoma su máximo valor , xtoma su valor mínimo . Pero xno puede ser inferior a y, por lo tanto, en este caso, x= y= 25% / 2 = 12.5%.
      [70, [12.5, 20], [5, 12.5], 5, [0, 5]]
  • Verifiquemos que todo esté bien si asumimos ahora que z= 5%. La suma de los dos porcentajes que faltan es siempre del 20%. En consecuencia:
    • Si ytoma su valor mínimo , 5%, xtoma su valor máximo , que es por lo tanto 20% - 5% = 15%. Este caso ya está incluido en los rangos calculados previamente.
    • Si ytoma su valor máximo , xtoma su valor mínimo . Pero xno puede ser inferior a y, así que en este caso,x = y= 20% / 2 = 10%. Este caso ya está incluido en el rango previamente calculado para y, pero no para x.
      [70, [10, 20], [5, 12.5], 5, [0, 5]]

Casos de prueba

Input:  [∅]
Output: [100]

Input:  [70, 30]
Output: [70, 30]

Input:  [70, ∅, ∅]
Output: [70, [15, 30], [0, 15]]

Input:  [40, ∅, ∅]
Output: [40, [30, 40], [20, 30]]

Input:  [∅, ∅, 10]
Output: [[45, 80], [10, 45], 10]

Input:  [70, ∅, ∅, ∅]
Output: [70, [10, 30], [0, 15], [0, 10]]

Input:  [70, ∅, ∅, 5, ∅]
Output: [70, [10, 20], [5, 12.5], 5, [0, 5]]

Input:  [30, ∅, ∅, ∅, 10, ∅, ∅, 5, ∅, ∅]
Output: [30, [10, 25], [10, 17.5], [10, 15], 10, [5, 10], [5, 10], 5, [0, 5], [0, 5]]
Agujero negro
fuente
55
Completamente no relacionado
Urna mágica del pulpo
3
Yo añadiría una explicación paso a paso de la entrada-a-salida para [40, ∅, ∅]y [70, ∅, ∅, 5, ∅]con las cosas tienen un poco más claramente. Un desafío debe ser claro sin mirar los casos de prueba, que no es el caso en este momento. Si lo entiendo correctamente para [40, ∅, ∅]: 60 más es necesario para el 100%, dividido entre estos dos . El primero tiene que ser 30 o superior (de lo contrario, el segundo estará por encima, lo que no debería ser posible cuando estén en orden). Además, no puede estar arriba 40, por lo que el primero se convierte [30,40]y el segundo se convierte [(100-40-40=)20, (100-40-30=)30].
Kevin Cruijssen
¿Consistente [min,max]/ [max,min]mixto permitido?
l4m2
@ L4m2 Mezclado [min,max]y [max,min]es casi aceptable, pero como no puede conducir a resultados ambiguos, diría que está bien.
Blackhole
Tal vez me estoy perdiendo algo, pero ¿por qué [70, 12, 11, 5, 2]no funciona para su segundo ejemplo? Si funciona, el mínimo para xsería menor que 12.5.
DLosc

Respuestas:

11

JavaScript (ES6), 252 bytes

Espera 0porcentajes faltantes. Devuelve un par de valores mínimos y máximos para todas las entradas.

a=>(g=a=>(h=(M,I,J=I^1)=>a.some((x,i)=>a.map((y,j)=>s-=j-i?M(j,i)-i?y[I]:M(w=y[I],z=x[J])-z||w==z?w:++k&&z:y[J],s=100,k=1,X=x)&&(I?-s:s)<0)?X[J]=M(X[I],X[J]+s/k):0)(Math.max,0)+h(Math.min,1)?g(a):a)(a.map((n,i)=>[n?p=n:a.find(n=>i--<0&&n)||0,p],p=100))

Pruébalo en línea!

¿Cómo?

Inicialización

Primero reemplazamos cada valor en la matriz de entrada a [] con el rango más grande posible.

a.map((n, i) =>       // for each value n at position i in a[]:
  [                   //   generate a [min, max] array:
    n ?               //     if n is not 0:
      p = n           //       use n as the minimum and save it in p
    :                 //     else:
      a.find(n =>     //       find the first value n
        i-- < 0 &&    //         which is beyond the current value
        n             //         and is not equal to 0
      ) || 0,         //       or use 0 as a default value
    p                 //     use p as the maximum
  ],                  //   end of array declaration
  p = 100             //   start with p = 100
)                     // end of map()

Ejemplos:

[ 0 ] --> [ [ 0, 100 ] ]
[ 30, 0, 5, 0 ] --> [ [ 30, 30 ], [ 5, 30 ], [ 5, 5 ], [ 0, 5 ] ]

Función principal

La función principal es h () . Busca la primera entrada que parece ser inconsistente cuando tratamos de minimizarla o maximizarla. Si encuentra uno, lo actualiza a un valor que es al menos temporalmente aceptable, dados los otros rangos.

Toma como entrada M = Math.max / I = 0 o M = Math.min / I = 1 y define J como I XOR 1 .

Debido a que h () se escribió para admitir los pases de minimización y maximización, el código es un poco difícil de comentar. Es por eso que nos enfocaremos solo en el pase de maximización, para lo cual tenemos M = Math.max , I = 0 y J = 1 . Con estos parámetros, el código dice lo siguiente:

a.some((x, i) =>              // for each range x at position i in a[] (tested range):
  a.map((y, j) =>             //   for each range y at position j in a[] (reference range):
    s -=                      //     update s:
      j - i ?                 //       if i is not equal to j:
        Math.max(j, i) - i ?  //         if j > i:
          y[0]                //           the reference range is beyond the tested range
                              //           so we just use the minimum value of the y range
        :                     //         else:
          Math.max(           //           take the maximum of:
            w = y[0],         //             w = minimum value of the y range
            z = x[1]          //             z = maximum value of the x range
          ) - z ||            //           if it's not equal to z
          w == z ?            //           or they are equal (i.e. if w <= z):
            w                 //             use w
          :                   //           else:
            ++k && z          //             increment the counter k and use z
      :                       //       else:
        y[1],                 //         use the maximum value of the y range
    s = 100,                  //     start with s = 100
    k = 1,                    //     start with k = 1
    X = x                     //     save the range x in X
  ) &&                        //   end of map()
  (0 ? -s : s) < 0            //   abort if s < 0 (i.e. if we've subtracted more than 100)
) ?                           // end of some(); if truthy:
  X[1] = Math.max(            //   update the maximum value of the faulty range to:
    X[0],                     //     either the minimum value
    X[1] + s / k              //     or the maximum value, less the correction
  )                           //   whichever is greater
:                             // else:
  0                           //   do nothing

Recursividad

La función recursiva g () sigue llamando a h () hasta que ni el pase de minimización ni el de maximización conducen a una nueva corrección, y finalmente devuelve el resultado final.

g = a => h(Math.max, 0) + h(Math.min, 1) ? g(a) : a
Arnauld
fuente
Bien hecho :-) !
Blackhole
44
@Blackhole ¡Gracias! Y por cierto: mi propia salsa para pasta se lee [38,0,10,0,0,0,0,0,0,0].
Arnauld