Давайте побудуємо гоночну доріжку!


19

Вступ

Моя племінниця хоче зробити гоночний трек. У неї дерев’яні частини, які підходять один до одного, утворюючи доріжку. Кожна частина має квадратну форму і містить різну форму. Я буду використовувати символи малювання труби для ілюстрації:

  • : дорога, яка йде вертикально
  • : дорога, яка йде горизонтально
  • : дороги, що повертають у напрямку
  • : Міст із підземним ходом

Цікаво, що т-перехрестя немає.

Ось приклад можливої ​​гоночної траси:

┌─┐
│ │┌─┐
│ └┼─┘
└──┘

Правила дійсної траси для гоночних автомобілів є такими:

  • Не може бути жодної дороги, яка б нікуди не йшла.
  • Він повинен утворювати петлю (і всі шматочки повинні бути частиною однієї петлі).
  • На мостах / проїздах ви не можете повернути (тому вам доведеться йти прямо через них).

На жаль, трек гоночного автомобіля шматки моєї племінниці, і я обмежений. Але ми, безумовно, хочемо використовувати їх усіх у треку. Напишіть програму, яка, враховуючи перелік тих частин, які є в нашому інвентарі, виводить доріжку гоночного автомобіля, яка використовує всі ці частини.

Опис вводу

Ми хотіли б, щоб вхід надходив за допомогою STDIN, аргументів командного рядка, читання файлів або функції введення користувача (наприклад, raw_inputабо prompt). Вхідні дані у формі виділені комами з натуральними числами

│,─,┌,┐,└,┘,┼

де кожен із них являє собою ту частину, яку ми маємо. Так, наприклад, вхід:

1,1,1,1,1,1,1

означало б, що у нас було по одному твору.

Опис виходу

Виведіть доріжку гоночного автомобіля, використовуючи перелічені вище символи для малювання труб. Доріжка гоночного автомобіля повинна використовувати саме таку кількість кожної деталі, яка вказана у вводі - не більше і не менше. Буде принаймні одна дійсна гоночна доріжка для кожного входу.

Приклад Входи та Виходи

Вхід: 3,5,2,2,2,2,1

Можливий вихід:

┌─┐
│ │┌─┐
│ └┼─┘
└──┘

Вхід: 0,0,1,4,4,1,3

Можливий вихід:

 ┌┐
 └┼┐
  └┼┐
   └┼┐
    └┘

Чи потрібно давати вихід? Або потрібно лише теоретично дати вихід?
Сумурай8,

@ Sumurai8 Що ви маєте на увазі під "теоретично" дати результат? Ви маєте на увазі програму, яка не припиняється протягом надзвичайно довгого часу, але врешті дасть результат?
абсент

1
Можливо, можна було б створити поле з nxn квадратів, заповнених гоночними шматками та порожніми квадратами, де ви можете генерувати перестановки, поки не знайдете щось, що є гоночною доріжкою. Це займе назавжди що-небудь більше, ніж кілька плиток.
Сумурай8,

4
@ Sumurai8 Ага гаразд, зараз я розумію. Я вважаю за краще, щоб програми давали вихід перед тепловою смертю Всесвіту для малих значень входів, які я показав у виклику.
абсент

4
Ваша племінниця недостатньо терпляча! : P
Сумурай8,

Відповіді:


4

Ruby 664 671 677 687 701 (678 bytes)

_={│:[1,4],─:[2,8],┌:[4,8],┐:[4,2],└:[1,8],┘:[1,2],┼:[1,4,2,8]}
s=->a,l,b{l==[]&&a==[]?b:(l.product(l).any?{|q,r|q,r=q[0],r[0];(q[0]-r[0])**2+(q[1]-r[1])**2>a.size**2}?!0:(w,f=l.pop
w&&v=!a.size.times{|i|y=_[x=a[i]]
f&&y&[f]==[]||(k=l.select{|p,d|w!=p||y&[d]==[]}
(y-[f]).map{|d|z=[w[0]+(d<2?-1:(d&4)/4),w[1]+(d==2?-1:d>7?1:0)]
g=d<3?d*4:d/4
b[z]?_[b[z]]&[g]!=[]||v=0:k<<[z,g]}
v||r=s[a[0...i]+a[i+1..-1],k,b.merge({w=>x})]
return r if r)}))}
c=eval"[#{gets}]"
r=s[6.downto(0).map{|i|[_.keys[i]]*c[i]}.flatten,[[[0,0],nil]],{}]
h=j=k=l=0
r.map{|w,_|y,x=w
h>x&&h=x
j>y&&j=y
k<x&&k=x
l<y&&l=y}
s=(j..l).map{|_|' '*(k-h+1)}
r.map{|w,p|y,x=w
s[y-j][x-h]=p.to_s}
puts s

Це не найкоротша програма, яку я міг придумати, але я жертвував деякою стислістю для швидкості виконання.

Ви можете експериментувати з програмою тут . Зауважте, що ideone має обмеження на час виконання, тому для входів, що складаються з приблизно 12 частин, програма, ймовірно, вичерпається.

Також є тестовий набір програми. Зауважте, що останні два тести вимкнено на ideone через обмеження, згадане вище. Щоб увімкнути ці тести, видаліть x_префікс із їх назв.

Програма знаходить рішення за допомогою глибинного пошуку; він розміщує шматки по одному і відстежує вільні кінці. Пошук припиняється, коли немає більше вільних (непоєднаних) кінців і розміщені всі шматки.

Це непрограшна програма:

N, W, S, E = 1, 2, 4, 8

# given a direction, find the opposite
def opposite (dir)
  dir < 3 ? dir * 4 : dir / 4
end

# given a set of coordinates and a direction,
# find the neighbor cell in that direction
def goto(from, dir)
  y, x = from

  dx = case dir
  when W then -1
  when E then 1
  else 0
  end

  dy = case dir
  when N then -1
  when S then 1
  else 0
  end

  [y+dy, x+dx]
end

CONNECTIONS = {
  ?│ => [N, S],
  ?─ => [W, E],
  ?┌ => [S, E],
  ?┐ => [S, W],
  ?└ => [N, E],
  ?┘ => [N, W],
  ?┼ => [N, S, W, E], 
}

BuildTrack =-> { 
  piece_types = CONNECTIONS.keys
  piece_counts = gets.split(?,).map &:to_i

  pieces = 6.downto(0).map{|i|piece_types[i]*piece_counts[i]}.join.chars

  def solve (available_pieces, loose_ends=[[[0,0],nil]], board={})

    return board if loose_ends==[] and available_pieces==[]

    # optimization to avoid pursuing expensive paths
    # which cannot yield a result.
    # This prunes about 90% of the search space
    c = loose_ends.map{ |c, _| c }
    not_enough_pieces = c.product(c).any? { |q, r| 
      ((q[0]-r[0])**2+(q[1]-r[1])**2) > available_pieces.size**2
    }
    return if not_enough_pieces

    position, connect_from = loose_ends.pop

    return unless position

    available_pieces.size.times do |i|
      piece = available_pieces[i]

      remaining_pieces = available_pieces[0...i] + available_pieces[i+1..-1]

      piece_not_connected_ok = connect_from && CONNECTIONS[piece] & [connect_from] == []
      next if piece_not_connected_ok

      new_loose_ends = loose_ends.select  { |pos, dir| 
        # remove loose ends that may have been 
        # fixed, now that we placed this piece
        position != pos || CONNECTIONS[piece] & [dir] == []
      }

      invalid_placement = false

      (CONNECTIONS[piece]-[connect_from]).map do |dir|
        new_pos = goto(position, dir)
        new_dir = opposite(dir)

        if board[new_pos]
          if CONNECTIONS[board[new_pos]] & [new_dir] != []
            # do nothing; already connected
          else
            # going towards an existing piece
            # which has no suitable connection
            invalid_placement = true
          end
        else
          new_loose_ends << [new_pos, new_dir]
        end
      end

      next if invalid_placement

      new_board = board.merge({position => piece})

      result = solve(remaining_pieces, new_loose_ends, new_board)
      return result if result
    end
    nil
  end

  def print_board board
    min_x = min_y = max_x = max_y = 0

    board.each do |position, _|
      y, x = position
      min_x = [min_x, x].min
      min_y = [min_y, y].min
      max_x = [max_x, x].max
      max_y = [max_y, y].max
    end

    str = (min_y..max_y).map{|_|
      ' ' * (max_x - min_x + 1)
    }

    board.each do |position, piece|
      y, x = position
      str[y-min_y][x-min_x] = piece
    end
    puts str
  end

  print_board(solve(pieces))
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.