algorithm - regresivas - minuto para ganar cronometro
Código de golf: número de cuenta atrás del juego (4)
Reto
Aquí está la tarea, inspirada en el conocido juego de televisión británico Countdown . El desafío debe ser bastante claro incluso sin conocimiento del juego, pero no dude en pedir aclaraciones.
Y si te apetece ver un video de este juego en acción, echa un vistazo a este clip de YouTube . Cuenta con el maravilloso Richard Whitely en 1997.
Le dan 6 números, elegidos al azar del conjunto {1, 2, 3, 4, 5, 6, 8, 9, 10, 25, 50, 75, 100}, y un número objetivo aleatorio entre 100 y 999. El objetivo es usar los seis números dados y las cuatro operaciones aritméticas comunes (suma, resta, multiplicación, división, en todos los números racionales) para generar el objetivo, o lo más cerca posible de cada lado. Cada número solo se puede utilizar una vez como máximo, mientras que cada operador aritmético se puede usar cualquier número de veces (incluido el cero). Tenga en cuenta que no importa cuántos números se usen.
Escriba una función que tome el número objetivo y el conjunto de 6 números (puede representarse como lista / colección / matriz / secuencia) y devuelve la solución en cualquier notación numérica estándar (por ejemplo, infijo, prefijo, postfijo). La función siempre debe devolver el resultado más cercano posible al objetivo , y debe ejecutarse como máximo 1 minuto en una PC estándar. Tenga en cuenta que, en caso de que exista más de una solución, cualquier solución individual es suficiente.
Ejemplos:
{50, 100, 4, 2, 2, 4}, objetivo 203
por ejemplo, 100 * 2 + 2 + (4/4) (exacto)
ej. (100 + 50) * 4 * 2 / (4 + 2) (exacto){25, 4, 9, 2, 3, 10}, objetivo 465
ej. (25 + 10 - 4) * (9 * 2 - 3) (exacto){9, 8, 10, 5, 9, 7}, objetivo 241
ej. ((10 + 9) * 9 * 7) + 8) / 5 (exacto){3, 7, 6, 2, 1, 7}, objetivo 824
ej. ((7 * 3) - 1) * 6 - 2) * 7 (= 826; desactivado por 2)
Reglas
Aparte de lo mencionado en la declaración del problema, no hay más restricciones. Puede escribir la función en cualquier idioma estándar (la E / S estándar no es necesaria). El objetivo, como siempre, es resolver la tarea con el menor número de caracteres de código.
Diciendo eso, no puedo simplemente aceptar la respuesta con el código más corto. ¡También miraré la elegancia del código y la complejidad del tiempo del algoritmo!
Mi solución
Estoy intentando una solución F # cuando encuentro el tiempo libre. ¡Lo publicaré aquí cuando tenga algo!
Formato
Por favor, publique todas las respuestas en el siguiente formato para facilitar la comparación:
Idioma
Número de caracteres: ???
Función completamente ofuscada:
(code here)
Función clara (idealmente comentada):
(code here)
Cualquier nota sobre el algoritmo / atajos inteligentes que se necesita.
Ruby 1.9.2
Número de caracteres: 404
Me rindo por el momento, funciona mientras haya una respuesta exacta. Si no es así, lleva mucho tiempo enumerar todas las posibilidades.
Totalmente ofuscado
def b a,o,c,p,r
o+c==2*p ?r<<a :o<p ?b(a+[''(''],o+1,c,p,r):0;c<o ?b(a+['')''],o,c+1,p,r):0
end
w=a=%w{+ - * /}
4.times{w=w.product a}
b [],0,0,3,g=[]
*n,l=$<.read.split.map(&:to_f)
h={}
catch(0){w.product(g).each{|c,f|k=f.zip(c.flatten).each{|o|o.reverse! if o[0]==''(''};n.permutation{|m|h[x=eval(d=m.zip(k)*'''')]=d;throw 0 if x==l}}}
c=h[k=h.keys.min_by{|i|(i-l).abs}]
puts c.gsub(/(/d*)/./d*/,''/1'')+"=#{k}"
Descifrado
Coming soon
Script de prueba
#!/usr/bin/env ruby
[
[[50,100,4,2,2,4],203],
[[25,4,9,2,3,10],465],
[[9,8,10,5,9,7],241],
[[3,7,6,2,1,7],824]
].each do |b|
start = Time.now
puts "{[#{b[0]*'', ''}] #{b[1]}} gives #{`echo "#{b[0]*'' ''} #{b[1]}" | ruby count-golf.rb`.strip} in #{Time.now-start}"
end
Salida
→ ./test.rb
{[50, 100, 4, 2, 2, 4] 203} gives 100+(4+(50-(2)/4)*2)=203.0 in 3.968534736
{[25, 4, 9, 2, 3, 10] 465} gives 2+(3+(25+(9)*10)*4)=465.0 in 1.430715549
{[9, 8, 10, 5, 9, 7] 241} gives 5+(9+(8)+10)*9-(7)=241.0 in 1.20045702
{[3, 7, 6, 2, 1, 7] 824} gives 7*(6*(7*(3)-1)-2)=826.0 in 193.040054095
Detalles
La función utilizada para generar los pares de corchetes ( b
) se basa en esta: encontrar todas las combinaciones de corchetes bien formados
Ruby 1.9.2 segundo intento
Número de caracteres: 492 440 (426)
De nuevo, hay un problema con la respuesta no exacta. Esta vez, esto es bastante rápido pero, por alguna razón, lo más cerca que llega a 824 es 819 en lugar de 826.
Decidí poner esto en una nueva respuesta ya que está usando un método muy diferente a mi último intento.
La eliminación del total de la salida (ya que no es requerida por la especificación) es de -14 caracteres.
Totalmente ofuscado
def r d,c;d>4?[0]:(k=c.pop;a=[];r(d+1,c).each{|b|a<<[b,k,nil];a<<[nil,k,b]};a)end
def f t,n;[0,2].each{|a|Array===t[a] ?f(t[a],n): t[a]=n.pop}end
def d t;Float===t ?t:d(t[0]).send(t[1],d(t[2]))end
def o c;Float===c ?c.round: "(#{o c[0]}#{c[1]}#{o c[2]})"end
w=a=%w{+ - * /}
4.times{w=w.product a}
*n,l=$<.each('' '').map(&:to_f)
h={}
w.each{|y|r(0,y.flatten).each{|t|f t,n.dup;h[d t]=o t}}
puts h[k=h.keys.min_by{|i|(l-i).abs}]+"=#{k.round}"
Descifrado
Coming soon
Script de prueba
#!/usr/bin/env ruby
[
[[50,100,4,2,2,4],203],
[[25,4,9,2,3,10],465],
[[9,8,10,5,9,7],241],
[[3,7,6,2,1,7],824]
].each do |b|
start = Time.now
puts "{[#{b[0]*'', ''}] #{b[1]}} gives #{`echo "#{b[0]*'' ''} #{b[1]}" | ruby count-golf.rb`.strip} in #{Time.now-start}"
end
Salida
→ ./test.rb
{[50, 100, 4, 2, 2, 4] 203} gives ((4-((2-(2*4))/100))*50)=203 in 1.089726252
{[25, 4, 9, 2, 3, 10] 465} gives ((10*(((3+2)*9)+4))-25)=465 in 1.039455671
{[9, 8, 10, 5, 9, 7] 241} gives (7+(((9/(5/10))+8)*9))=241 in 1.045774539
{[3, 7, 6, 2, 1, 7] 824} gives ((((7-(1/2))*6)*7)*3)=819 in 1.012330419
Detalles
Esto construye el conjunto de árboles ternarios que representan todas las combinaciones posibles de 5 operadores. Luego pasa e inserta todas las permutaciones de los números ingresados en las hojas de estos árboles. Finalmente, simplemente itera a través de estas posibles ecuaciones almacenándolas en un hash con el resultado como índice. Entonces es bastante fácil elegir el valor más cercano a la respuesta requerida del hash y mostrarlo.
Haskell
Número de caracteres: 361 350 338 322
Función completamente ofuscada:
m=map
f=toRational
a%w=m(/(b,v)->(b,a:v))w
p[]=[];p(a:w)=(a,w):a%p w
q[]=[];q(a:w)=[((a,b),v)|(b,v)<-p w]++a%q w
z(o,p)(a,w)(b,v)=[(a`o`b,''('':w++p:v++")")|b/=0]
y=m z(zip[(-),(/),(+),(*)]"-/+*")++m flip(take 2 y)
r w=do{((a,b),v)<-q w;o<-y;c<-o a b;c:r(c:v)}
c t=snd.minimum.m(/a->(abs(fst a-f t),a)).r.m(/a->(f a,show a))
Función clara:
-- | add an element on to the front of the remainder list
onRemainder :: a -> [(b,[a])] -> [(b,[a])]
a`onRemainder`w = map (/(b,as)->(b,a:as)) w
-- | all ways to pick one item from a list, returns item and remainder of list
pick :: [a] -> [(a,[a])]
pick [] = []
pick (a:as) = (a,as) : a `onRemainder` (pick as)
-- | all ways to pick two items from a list, returns items and remainder of list
pick2 :: [a] -> [((a,a),[a])]
pick2 [] = []
pick2 (a:as) = [((a,b),cs) | (b,cs) <- pick as] ++ a `onRemainder` (pick2 as)
-- | a value, and how it was computed
type Item = (Rational, String)
-- | a specification of a binary operation
type OpSpec = (Rational -> Rational -> Rational, String)
-- | a binary operation on Items
type Op = Item -> Item -> Maybe Item
-- | turn an OpSpec into a operation
-- applies the operator to the values, and builds up an expression string
-- in this context there is no point to doing +0, -0, *0, or /0
combine :: OpSpec -> Op
combine (op,os) (ar,as) (br,bs)
| br == 0 = Nothing
| otherwise = Just (ar`op`br,"("++as++os++bs++")")
-- | the operators we can use
ops :: [Op]
ops = map combine [ ((+),"+"), ((-), "-"), ((*), "*"), ((/), "/") ]
++ map (flip . combine) [((-), "-"), ((/), "/")]
-- | recursive reduction of a list of items to a list of all possible values
-- includes values that don''t use all the items, includes multiple copies of
-- some results
reduce :: [Item] -> [Item]
reduce is = do
((a,b),js) <- pick2 is
op <- ops
c <- maybe [] (:[]) $ op a b
c : reduce (c : js)
-- | convert a list of real numbers to a list of items
items :: (Real a, Show a) => [a] -> [Item]
items = map (/a -> (toRational a, show a))
-- | return the first reduction of a list of real numbers closest to some target
countDown:: (Real a, Show a) => a -> [a] -> Item
countDown t is = snd $ minimum $ map dist $ reduce $ items is
where dist is = (abs . subtract t'' . fst $ is, is)
t'' = toRational t
Cualquier nota sobre el algoritmo / atajos inteligentes que se necesita:
- En la versión de golf,
z
vuelve en la lista de la mónada, en lugar deMaybe
comoops
. - Mientras que el algoritmo aquí es la fuerza bruta, opera en un espacio pequeño, fijo y lineal debido a la pereza de Haskell. Codifiqué el maravilloso algoritmo de @ keith-randall, pero funcionó aproximadamente al mismo tiempo y tomó más de 1.5G de memoria en Haskell.
-
reduce
genera algunas respuestas varias veces, para incluir fácilmente soluciones con menos términos. - En la versión de golf,
y
se define parcialmente en términos de sí mismo. - Los resultados se calculan con valores
Rational
. El código de Golf sería de 17 caracteres más corto y más rápido si se calcula conDouble
. - Observe cómo la función en
onRemainder
cuenta la similitud estructural entrepick
ypick2
.
Controlador para la versión de golf:
main = do
print $ c 203 [50, 100, 4, 2, 2, 4]
print $ c 465 [25, 4, 9, 2, 3, 10]
print $ c 241 [9, 8, 10, 5, 9, 7]
print $ c 824 [3, 7, 6, 2, 1, 7]
Ejecutar, con el tiempo (aún por debajo de un minuto por resultado):
[1076] : time ./Countdown
(203 % 1,"(((((2*4)-2)/100)+4)*50)")
(465 % 1,"(((((10-4)*25)+2)*3)+9)")
(241 % 1,"(((((10*9)/5)+8)*9)+7)")
(826 % 1,"(((((3*7)-1)*6)-2)*7)")
real 2m24.213s
user 2m22.063s
sys 0m 0.913s
Pitón
Número de caracteres: 548 482 425 421 416 413 408
from operator import *
n=len
def C(N,T):
R=range(1<<n(N));M=[{}for i in R];p=1
for i in range(n(N)):M[1<<i][1.*N[i]]="%d"%N[i]
while p:
p=0
for i in R:
for j in R:
m=M[i|j];l=n(m)
if not i&j:m.update((f(x,y),"("+s+o+t+")")for(y,t)in M[j].items()if y for(x,s)in M[i].items() for(o,f)in zip(''+-*/'',(add,sub,mul,div)))
p|=l<n(m)
return min((abs(x-T),e)for t in M for(x,e)in t.items())[1]
puedes llamarlo así:
>>> print C([50, 100, 4, 2, 2, 4], 203)
((((4+2)*(2+100))/4)+50)
Toma aproximadamente medio minuto en los ejemplos dados en una PC antigua.
Aquí está la versión comentada:
def countdown(N,T):
# M is a map: (bitmask of used input numbers -> (expression value -> expression text))
M=[{} for i in range(1<<len(N))]
# initialize M with single-number expressions
for i in range(len(N)):
M[1<<i][1.0*N[i]] = "%d" % N[i]
# allowed operators
ops = (("+",lambda x,y:x+y),("-",lambda x,y:x-y),("*",lambda x,y:x*y),("/",lambda x,y:x/y))
# enumerate all expressions
n=0
while 1:
# test to see if we''re done (last iteration didn''t change anything)
c=0
for x in M: c +=len(x)
if c==n: break
n=c
# loop over all values we have so far, indexed by bitmask of used input numbers
for i in range(len(M)):
for j in range(len(M)):
if i & j: continue # skip if both expressions used the same input number
for (x,s) in M[i].items():
for (y,t) in M[j].items():
if y: # avoid /0 (and +0,-0,*0 while we''re at it)
for (o,f) in ops:
M[i|j][f(x,y)]="(%s%s%s)"%(s,o,t)
# pick best expression
L=[]
for t in M:
for(x,e) in t.items():
L+=[(abs(x-T),e)]
L.sort();return L[0][1]
Funciona mediante una enumeración exhaustiva de todas las posibilidades. Es un poco inteligente en el sentido de que si hay dos expresiones con el mismo valor que usan los mismos números de entrada, descarta una de ellas. También es inteligente en cuanto a cómo considera nuevas combinaciones, usando el índice en M para reducir rápidamente todas las combinaciones potenciales que comparten números de entrada.