Dividiendo la factura

12

Tarea

Supongamos que ppepole tiene que dividir un billete; cada uno de ellos está identificado por un triple (Name, n, k)compuesto por:

  • Name: el nombre ;
  • n: la cantidad que tiene que pagar ;
  • k: la cantidad que realmente pagó .

El desafío aquí es descubrir cuánto le debe a quién.

Supuestos

  • La entrada y salida pueden estar en cualquier formato conveniente.

  • p N,n N+, k N.

  • p >1.

  • Los nombres son cadenas únicas de longitud arbitraria, compuestas de letras minúsculas del alfabeto.

Solución

La solución está representada por el conjunto mínimo de transacciones entre las ppersonas; en particular son triples(from, to, amount)

  • from: namede la persona que da dinero;
  • to: namede la persona que recibe dinero;
  • amount: cantidad de dinero de la transacción.

NOTA : La suma de todas las deudas ( n) puede diferir de la suma de todas las cantidades ya pagadas ( k). En este caso, debe agregar la salida ('owner', Name, amount)o (Name, 'owner', amount)el formato que haya elegido. Cualquier nombre nunca será owner. La cadena 'propietario' es flexible.

Si existen varios conjuntos mínimos, seleccione el que tenga la suma mínima de todos los importes de las transacciones (valores absolutos); En caso de empate, elija uno de ellos.

Casos de prueba:

inputs(Name,n,k):
[('a',30,40),('b',40,50),('c',30,15)]
[('a',30,30),('b',20,20)]
[('a',30,100),('b',30,2),('c',40,0)]
[('a',344,333),('b',344,200),('c',2,2)]
[('a',450,400),('b',300,300),('c',35,55)]

outputs(from, to, amount):
[('c','a',10),('c','b',5),('owner','b',5)] or [('c','b',10),('c','a',5),('owner','a',5)]
[]
[('owner','a',2),('b','a',28),('c','a',40)] PS: [('owner','a',2),('b','a',68),('c','b',40)] has the same number of transactions, but it is not a valid answer, because the total amount of its transaction is greater than that of the proposed solution.
[('a','owner',11),('b','owner',144)]
[('a','owner',30),('a','c',20)]

Este es el código de golf: el código más corto gana .

Vedant Kandoi
fuente
1
Creo que debería cambiar "Debe generar el escenario más simplificado que requiere el menor número de transacciones". a "Debe generar el escenario que requiere el menor número de transacciones. Si existen varios de estos escenarios, elija uno con el menor total de transacciones nocionales". Ya que creo que es más claro.
Jonathan Allan
1
Buen desafío Pero probablemente requerirá casos de prueba más complejos para asegurarse de que las soluciones realmente siempre sean óptimas.
Arnauld
3
¿Qué es "nocional menos total"?
lirtosiast el
1
@JonathanAllan sigue confundido por la palabra "nocional". ¿De dónde viene eso? basado en el caso de prueba 3, parece que uno debería preferir respuestas donde la misma persona no da y recibe. ¿Es eso correcto? ¿También por qué se describe eso como nocional?
Jonás
1
@Jonah Usé "nocional total" ya que las direcciones de las transacciones no deberían considerarse (solo su tamaño absoluto), aunque ahora me doy cuenta de que es algo redundante (ya que tener dos transacciones que se contrarresten entre sí no se consideraría una vez que se rompe el empate) !). [Nocional es solo el término utilizado en finanzas.]
Jonathan Allan

Respuestas:

2

JavaScript (ES6),  252 227 223 222  215 bytes

Toma entrada como [[n0, k0, name0], [n1, k1, name1], ...].

Las transacciones en la solución pueden ser positivas o negativas. El propietario se llama indefinido .

a=>(m=g=(B,t,s)=>B.some(x=>x)?B.map((b,x)=>B.map((c,y)=>b*c<0&b*b<=c*c&&g(C=[...B],[...t,[a[x][2],b,a[y][2]]],s+a.length-1/b/b,C[C[y]+=b,x]=0))):m<s||(r=t,m=s))([...a.map(([x,y])=>t-(t+=y-x),t=0),t],[],a.push(g))&&r

Pruébalo en línea!

Comentado

a => (                              // a[] = input array
  m =                               // initialize m to a non-numeric value
  g = (B, t, s) =>                  // g = function taking: B = balances, t = transactions,
                                    //     s = score of the current solution
    B.some(x => x) ?                // if at least one balance is not equal to 0:
      B.map((b, x) =>               //   for each balance b at position x:
        B.map((c, y) =>             //     for each balance c at position y:
          b * c < 0 &               //       if b and c are of opposite sign
          b * b <= c * c &&         //       and |b| <= |c|,
          g(                        //       do a recursive call to g:
            C = [...B],             //         - with a copy C of B
            [ ...t,                 //         - append the new transaction to t[]
              [a[x][2], b, a[y][2]] //           in [from_name, amount, to_name] format
            ],                      //
            s + a.length - 1/b/b,   //         - add (a.length - 1/b²) to s
            C[C[y] += b, x] = 0     //         - update C[y] and clear C[x]
          )                         //       end of recursive call
        )                           //     end of inner map()
      )                             //   end of outer map()
    :                               // else:
      m < s ||                      //   if the score of this solution is lower than m,
      (r = t, m = s)                //   update r to t and m to s
)(                                  // initial call to g:
  [                                 //   build the list of balances:
    ...a.map(([x, y]) =>            //     each balance is equal to:
      t - (t += y - x),             //     due_value - paid_value
      t = 0                         //     keep track of the total t ...
    ),                              //
    t                               //   ... which is appended at the end of this array
  ],                                //   (this is the balance of the owner)
  [],                               //   start with t = []
  a.push(g)                         //   append a dummy owner to a[]; start with s = 1
) && r                              // return r
Arnauld
fuente