Подтвердить что ты не робот

Code Golf: игра с обратным отсчетом

Вызов

Вот задача, вдохновленная известным британским ТВ-шоу Countdown. Задача должна быть довольно ясной, даже не зная об игре, но не стесняйтесь просить разъяснения.

И если вам интересно увидеть клип этой игры в действии, посмотрите этот клип YouTube. В нем есть замечательный покойный Ричард Уайти в 1997 году.

Вам предоставляется 6 чисел, выбранных случайным образом из набора {1, 2, 3, 4, 5, 6, 8, 9, 10, 25, 50, 75, 100} и случайного целевого числа между 100 и 999. Цель состоит в том, чтобы использовать шесть заданных чисел и четыре общие арифметические операции (сложение, вычитание, умножение, деление, по всем рациональным числам) для создания цели - или как можно ближе к любой из сторон. Каждый номер может использоваться только один раз максимум, в то время как каждый арифметический оператор может использоваться любое количество раз (включая ноль.) Обратите внимание, что не имеет значения, сколько чисел используется.

Напишите функцию, которая принимает целевой номер и набор из 6 чисел (может быть представлена ​​как список/коллекция/массив/последовательность) и возвращает решение в любой стандартной числовой нотации (например, infix, prefix, postfix). Функция должна всегда возвращать ближайший возможный результат к цели и должна работать не более 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)

Правила

Кроме указанных в заявлении проблемы, никаких дополнительных ограничений нет. Вы можете написать функцию на любом стандартном языке (стандартный ввод-вывод не требуется). Цель всегда состоит в том, чтобы решить задачу с наименьшим количеством символов кода.

Говоря это, я могу не просто принять ответ с кратчайшим кодом. Я также буду смотреть на изящество кода и временную сложность алгоритма!

Мое решение

Я пытаюсь найти решение F #, когда я нахожу свободное время, - опубликую его здесь, когда у меня есть что-то!


Формат

Пожалуйста, разместите все ответы в следующем формате для удобства сравнения:

Язык

Количество символов:

Полностью запутанная функция:

(code here)

Функция Clear (идеально прокомментированная):

(code here)

Любые заметки об алгоритме/умных сочетаниях, которые он принимает.


4b9b3361

Ответ 1

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

Любые заметки об алгоритме/умных сокращениях:

  • В версии golf'd, z возвращает в монаде списка, а не Maybe, как это делает ops.
  • В то время как алгоритм здесь является грубой силой, он работает в небольшом фиксированном линейном пространстве из-за ленивости Haskell. Я закодировал замечательный алгоритм @keith-randall, но он работал примерно в одно и то же время и занял 1.5G памяти в Haskell.
  • reduce генерирует несколько ответов несколько раз, чтобы легко включать решения с меньшим количеством терминов.
  • В версии golf'd y определяется частично в терминах самого себя.
  • Результаты вычисляются с помощью значений Rational. Код для гольфа будет на 17 символов короче и быстрее, если вычислено с помощью Double.
  • Обратите внимание на то, как функция onRemainder определяет структурное сходство между pick и pick2.

Драйвер для версии для гольфа:

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

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)

Занимает около полуминут на приведенных примерах на старом ПК.

Вот прокомментированная версия:

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, чтобы быстро сократить все потенциальные комбинации, которые используют входные номера.

Ответ 3

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

Тест script

#!/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), основана на следующем: Поиск всех комбинаций правильно сформированных скобок

Ответ 4

Ошибка 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

Тест script

#!/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 операторов. Затем он проходит и вставляет все перестановки входных чисел в листья этих деревьев. Наконец, он просто итерации через эти возможные уравнения хранения их в хэш с результатом в качестве индекса. Тогда достаточно легко выбрать ближайшее значение к требуемому ответу из хэша и отобразить его.