Golf Un almuerzo gratis

26

Encuentre una secuencia de intercambios máximamente rentable dada una tabla de tipos de cambio.


Como ejemplo, considere las monedas A riary (su moneda local), B aht, C edi y D enar, donde la tasa de una a otra (después de que se haya aplicado cualquier tasa de transacción) viene dada por la entrada (fila, columna) en la tabla de tipo de cambio a continuación:

                       TO
       A          B          C          D

   A   0.9999     1.719828   4.509549   0.709929

F  B   0.579942   0.9999     2.619738   0.409959
R
O  C   0.219978   0.379962   0.9999     0.149985
M
   D   1.39986    2.429757   6.409359   0.9999

Obviamente, intercambiar A por A no es una gran idea, ya que este escritorio le cobrará felizmente por no hacer nada.

Menos obvio, pero cierto con esta tabla, el intercambio de A por cualquier otra moneda y luego el intercambio de nuevo es un factor de pérdida:

via B: 1.719828 × 0.579942 = 0.997400489976
via C: 4.509549 × 0.219978 = 0.992001569922
via D: 0.709929 × 1.39986  = 0.99380120994

Sin embargo, intercambiando A por D, luego D por B y luego B de regreso a A genera ganancias (dado el capital suficiente para no sucumbir al redondeo)

0.709929 × 2.429757 × 0.579942 = 1.0003738278192194

Uno podría tomar repetidamente este "almuerzo gratis" mientras exista la oportunidad.

Pero aquí existe una cadena aún más atractiva, a saber, de A a D , de D a C , de C a B y, finalmente, de B a A :

0.709929 × 6.409359 × 0.379962 × 0.579942 = 1.0026612752037345

Detalles del desafío

Dada una tabla de tipo de cambio en cualquier formato razonable que fije el significado de la moneda local (por ejemplo, la fila y la columna son siempre la moneda local)
(o dada una tabla y un índice de la moneda local)
encuentre un * secuencia de arbitraje máxima de intercambios que comienzan y terminan con la moneda local como índices en la lista de divisas sin repetir el uso de ningún intercambio (es decir, un intercambio Y-> X puede seguir a uno X-> Y, pero un X-> Y no puede siga una X-> Y).

Si no existe tal oportunidad rentable, proporcione una lista vacía o algún otro resultado que no sea confuso con una oportunidad identificada.
- por ejemplo, para el ejemplo anterior ( A-> D, D-> C, C-> B, B-> A ):

  • usando indexación 0, uno podría regresar [[0,3],[3,2],[2,1],[1,0]]o[0,3,2,1,0]
  • usando 1 indexación, uno podría regresar [[1,4],[4,3],[3,2],[2,1]]o[1,4,3,2,1]

Otros formatos están bien siempre que no haya ambigüedad.
- Una cosa a tener en cuenta es que es posible que la mejor oportunidad sea una sola transacción desde casa-> casa (un escritorio tonto). Si decide excluir el índice de moneda local de ambos extremos de la opción plana anterior (es decir, [3,2,1]o [4,3,2]) y una lista vacía para "ninguna oportunidad", entonces asegúrese de que home-> home no sea también una lista vacía.

* Si existen múltiples oportunidades válidas igualmente rentables, devuelva alguna de ellas, algunas o todas.

El algoritmo Bellman-Ford es una forma de abordar esto, pero probablemente no sea el más adecuado para el golf.

Casos de prueba

Las entradas que se muestran están en la disposición utilizada en el ejemplo, y los resultados que se muestran usan la indexación 0 para enumerar los índices de moneda (cuando existe una oportunidad, la moneda local está solo en el extremo final; ninguna oportunidad es una lista vacía).

[[0.999900, 1.719828, 4.509549, 0.709929],
 [0.579942, 0.999900, 2.619738, 0.409959],
 [0.219978, 0.379962, 0.999900, 0.149985],
 [1.399860, 2.429757, 6.409359, 0.999900]]  ->  [3, 2, 1, 0]

[[0.9999, 1.5645, 0.9048, 1.0929],
 [0.6382, 0.9999, 0.5790, 0.6998],
 [1.1051, 1.7269, 0.9999, 1.2087],
 [0.9131, 1.4288, 0.8262, 0.9999]]  ->  [1, 2, 0]

[[0.9999, 1.4288, 0.8262, 0.9131],
 [0.6998, 0.9999, 0.5790, 0.6382],
 [1.2087, 1.7269, 0.9999, 1.1051],
 [1.0929, 1.5645, 0.9048, 0.9999]]  ->  [1, 2, 3, 1, 0]

[[1.002662, 1.719828, 4.509549, 0.709929],
 [0.579942, 0.999900, 2.619738, 0.409959],
 [0.219978, 0.379962, 0.999900, 0.149985],
 [1.399860, 2.429757, 6.409359, 0.999900]]  ->  [3, 2, 1, 0, 0]

[[1.002662, 1.719828, 4.509549, 0.709929],
 [0.579942, 1.002604, 2.619738, 0.409959],
 [0.219978, 0.379962, 1.003000, 0.149985],
 [1.399860, 2.429757, 6.409359, 1.002244]]  ->  [3, 3, 2, 2, 1, 1, 0, 0]

[[0.9999, 1.4288, 0.8262, 0.9131],
 [0.6998, 0.9999, 0.5790, 0.6382],
 [1.2087, 1.7269, 1.0001, 1.1051],
 [1.0929, 1.4974, 0.9048, 0.9999]]  ->  [1, 2, 2, 0]

[[0.9999, 1.3262, 0.7262, 0.9131],
 [0.6998, 0.9999, 0.5490, 0.6382],
 [1.2087, 1.7269, 0.9999, 1.2051],
 [1.0929, 1.5645, 0.9048, 0.9999]]  ->  [3, 2, 3, 1, 0]

[[0.9999, 1.5645, 0.9048, 0.5790],
 [0.6382, 0.9999, 0.5790, 0.3585],
 [1.1051, 1.7269, 0.9999, 0.6391],
 [1.7271, 2.6992, 1.5645, 0.9999]]  ->  [1, 2, 0]  and/or  [3, 2, 0]

[[0.9999, 1.2645, 0.7048, 0.3790],
 [0.4382, 0.9999, 0.3790, 0.1585],
 [1.0001, 1.5269, 1.0001, 0.4391],
 [1.5271, 2.4992, 1.3645, 0.9999]]  ->  []

[[0.9999, 1.2645, 0.7048, 0.3790],
 [0.4382, 0.9999, 0.3790, 0.1585],
 [0.9999, 1.5269, 1.4190, 0.4391],
 [1.5271, 2.4992, 1.3645, 0.9999]]  ->  [2, 2, 0]

Este es el por lo que la solución más corta en bytes gana, pero la competencia también debe hacerse dentro del lenguaje, ¡así que no dejes que los lenguajes de código de golf te impidan enviar tu favorito!

Jonathan Allan
fuente

Respuestas:

8

JavaScript (ES6), 122 113 103 bytes

Toma datos como una matriz transpuesta con respecto al formato descrito en el desafío. Devuelve una cadena que describe los intercambios en (from,to)formato.

a=>(g=(s,x=b=0,h='')=>a.map((r,y)=>~h.search(k=`(${x},${y})`)||g(s*r[x],y,h+k),x|s<b||(b=s,p=h)))(1)&&p

Primer caso de prueba: ¡ Pruébelo en línea!

Más casos de prueba: ¡ Pruébelo en línea!

Comentado

a => (                  // given the exchange rate matrix a[][]
  g = (                 // g = recursive function taking:
    s,                  //   s = current amount of money
    x = b = 0,          //   x = ID of current currency, b = best result so far
    h = ''              //   h = exchange history, as a string
  ) =>                  //  
  a.map((r, y) =>       // for each row at position y in a[]:
    ~h.search(          //   if we can't find in h ...
      k = `(${x},${y})` //     ... the exchange key k from currency x to currency y
    ) ||                //   then:
    g(                  //   do a recursive call to g() with:
      s * r[x],         //     s = new amount obtained by applying the exchange rate
      y,                //     x = y
      h + k             //     h = h + k
    ),                  //   end of recursive call
    x | s < b ||        //   if x is our home currency and s is greater than or equal to b
    (b = s, p = h)      //   then set b to s and set p to h
  )                     // end of map()
)(1)                    // initial call to g() with s = 1
&& p                    // return p
Arnauld
fuente
4

Python 2 , 143 125 124 bytes

lambda M:g(M)[1]
g=lambda M,s=[],p=1,x=0:max([(p,s)]*-~-x+[g(M,s+[(x,y)],p*M[x][y],y)for y in range(len(M))if(x,y)not in s])

Pruébalo en línea!

Utiliza indexación basada en 0 (0 es la moneda local); devuelve una lista de tuplas de los intercambios que producen el pago máximo.

El enfoque es la fuerza bruta: a través de la recursión, terminamos visitando cada ruta que no se repite desde el borde comenzando en 0(por nser el número de monedas, esto da una profundidad máxima de n^2). Para el subconjunto de estas rutas que también terminan con '0', maximizamos la recompensa.

Chas Brown
fuente
1

Haskell, 175 bytes

e?l|e`elem`l=0|2>1=1
w[]=[]
w l=[maximum l];0!q=[q]
n!c@(v,i,(h,l))=do{j<-[0..3];c:((n-1)!(v*l!!i!!j*(i,j)?h,j,((i,j):h,l)))}
z l=w$filter(\(v,e,_)->v>1&&e==0)$12!(1,0,([],l))

Pruébalo aquí

Radek
fuente