Code Golf:倒数游戏

时间:2011-01-03 17:42:30

标签: algorithm math permutation code-golf

挑战

这项任务的灵感来自着名的英国电视游戏节目 Countdown 。即使不了解游戏,挑战也应该非常明确,但可以随意要求澄清。

如果你想看到这个游戏的片段,请查看this YouTube clip。它拥有1997年美妙的已故理查德怀特利。

  

您将获得6个数字,从{1,2,3,4,5,6,8,9,10,25,50,75,100}中随机选择,随机目标数在100之间目的是使用六个给定的数字和四个常用的算术运算(加法,减法,乘法,除法;所有有理数)来生成目标 - 或尽可能接近任何一方。每个数字最多只能使用一次,而每个算术运算符可以使用任意次数(包括零)。请注意,使用的数字并不重要。

     

编写一个函数,该函数接受目标数和6个数字的集合(可以表示为列表/集合/数组/序列),并以任何标准数字表示法(例如,中缀,前缀,后缀)返回解决方案。该函数必须始终将最接近的结果返回到目标,并且必须在标准PC上运行最多1分钟。请注意,在存在多个解决方案的情况下,任何一个解决方案就足够了。

示例:

  • {50,100,4,2,2,4},目标203
    例如100 * 2 + 2 +(4/4)(确切)
    例如(100 + 50)* 4 * 2 /(4 + 2)(确切)

  • {25,4,9,2,3,10},目标465
    例如(25 + 10 - 4)*(9 * 2 - 3)(确切)

  • {9,8,10,5,9,7},目标241
    例如((10 + 9)* 9 * 7)+ 8)/ 5 (确切)

  • {3,7,6,2,1,7},目标824
    例如((7 * 3) - 1)* 6 - 2)* 7 (= 826;关闭2)

规则

除了问题陈述中提到的,没有进一步的限制。您可以使用任何标准语言编写函数(不需要标准I / O)。一如既往的目标是用最少数量的代码来解决任务。

说,我可能不会简单地用最短的代码接受答案。我还将关注代码的优雅和算法的时间复杂性!

我的解决方案

当我找到空闲时间时,我正在尝试使用F#解决方案 - 当我有东西时会将它发布在这里!


格式

请以下列格式发布所有答案,以便于比较:

  

语言

     

字符数:???

     

完全混淆的功能:

(code here)
     

清除(理想评论)功能:

(code here)
     

关于算法/聪明的快捷方式的任何注释。


4 个答案:

答案 0 :(得分:11)

的Python

字符数: 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]
你可以这样称呼它:

>>> print C([50, 100, 4, 2, 2, 4], 203)
((((4+2)*(2+100))/4)+50)

在旧PC上给出的示例大约需要半分钟。

以下是评论版:

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]

通过详尽列举所有可能性来实现。它有点聪明,如果有两个具有相同值的表达式使用相同的输入数字,它会丢弃其中一个。它在考虑新组合方面也很聪明,使用M中的索引快速修剪共享输入数字的所有潜在组合。

答案 1 :(得分:9)

的Haskell

字符数: 361 350 338 322

完全混淆的功能:

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))

清除功能:

-- | 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

关于算法/聪明捷径的任何注释:

  • 在高尔夫版本中,z会在列表monad中返回,而不是Maybe作为ops返回。
  • 虽然这里的算法是强力的,但由于Haskell的懒惰,它在小而固定的线性空间中运行。我编写了精彩的@ keith-randall算法,但它大约在同一时间运行并在Haskell中占用了1.5G的内存。
  • reduce多次生成一些答案,以便轻松包含较少字词的解决方案。
  • 在高尔夫版本中,y部分是根据自身定义的。
  • 使用Rational值计算结果。 Golf'd代码将缩短17个字符,如果使用Double进行计算,则会更快。
  • 请注意函数onRemainder如何排除pickpick2之间的结构相似性。

高尔夫版本的驱动程序:

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]

按时间运行(每个结果仍然不到一分钟):

[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

答案 2 :(得分:1)

Ruby 1.9.2

字符数:404

我现在放弃,只要有确切的答案,它就会起作用。如果没有,则需要花费太长时间来列举所有可能性。

完全混淆

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}"

解码

Coming soon

测试脚本

#!/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

输出

→ ./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

详细信息

用于生成括号对(b)的函数基于以下函数:Finding all combinations of well-formed brackets

答案 3 :(得分:0)

Ruby 1.9.2第二次尝试

字符数: 492 440(426)

同样,非确切答案存在问题。这次这很容易,但是出于某种原因,最接近824的是819而不是826.

我决定把它放在一个新的答案中,因为它使用的方法与我上一次的尝试完全不同。

删除输出的总数(因为规范不要求)是-14个字符。

完全混淆

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}"

解码

Coming soon

测试脚本

#!/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

输出

→ ./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

详细信息

这构造了一组三元树,代表了5个运算符的所有可能组合。然后它通过并将输入数字的所有排列插入到这些树的叶子中。最后,它简单地遍历这些可能的等式,将它们存储为散列,结果为索引。然后,很容易从哈希中选择最接近所需答案的值并显示它。