Mari kita membangun trek mobil balap!

19

pengantar

Keponakan saya ingin membuat lintasan balap mobil. Dia memiliki bagian kayu yang pas untuk membentuk lintasan. Setiap bagian berbentuk persegi dan berisi bentuk yang berbeda. Saya akan menggunakan karakter gambar pipa untuk menggambarkan:

  • : jalan yang menuju vertikal
  • : jalan yang menuju horizontal
  • : jalan yang berbelok ke suatu arah
  • : Jembatan dengan underpass

Anehnya, tidak ada potongan persimpangan.

Berikut adalah contoh kemungkinan trek balap mobil:

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

Aturan untuk lintasan mobil balap yang valid adalah sebagai berikut:

  • Tidak mungkin ada jalan yang menuju ke mana-mana.
  • Itu harus membentuk loop (dan semua bagian harus menjadi bagian dari loop yang sama).
  • Di jembatan / underpass, Anda tidak dapat berbelok (jadi Anda harus langsung melewatinya).

Sayangnya, trek balap mobil potongan keponakan saya dan saya terbatas. Tapi kami pasti ingin menggunakan semuanya di trek. Tulis sebuah program yang, berisi daftar barang apa saja yang ada di inventaris kami, menampilkan trek mobil balap yang menggunakan semua bagian itu.

Deskripsi Input

Kami ingin input masuk melalui STDIN, argumen baris perintah, membaca file, atau fungsi input pengguna (seperti raw_inputatau prompt). Input adalah bilangan bulat positif koma yang dipisahkan dalam formulir

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

di mana masing-masing mewakili jumlah bagian tertentu yang kita miliki. Jadi misalnya input:

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

akan berarti bahwa kami memiliki satu dari setiap bagian.

Deskripsi Output

Keluarkan trek mobil balap menggunakan karakter gambar pipa yang tercantum di atas. Lintasan mobil balap harus menggunakan persis jumlah setiap bagian yang ditentukan dalam input - tidak lebih, dan tidak kurang. Akan ada setidaknya satu lintasan mobil balap yang valid untuk setiap input.

Contoh Input dan Output

Memasukkan: 3,5,2,2,2,2,1

Output yang mungkin:

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

Memasukkan: 0,0,1,4,4,1,3

Output yang mungkin:

 ┌┐
 └┼┐
  └┼┐
   └┼┐
    └┘
Absinth
sumber
Apakah perlu memberikan output? Atau apakah secara teoritis hanya perlu memberikan output?
Sumurai8
@ Sumurai8 Apa yang Anda maksud dengan "secara teoritis" memberikan output? Apakah maksud Anda sebuah program yang tidak akan berakhir untuk waktu yang sangat lama tetapi pada akhirnya akan memberikan hasil?
absinthe
1
Seseorang mungkin akan dapat membuat bidang kotak nxn diisi dengan potongan ras dan kotak kosong, di mana Anda dapat menghasilkan permutasi sampai Anda menemukan sesuatu yang merupakan trek balap. Itu akan membutuhkan selamanya untuk apa pun selain beberapa ubin.
Sumurai8
4
@ Sumurai8 Ah oke, saya mengerti sekarang. Saya lebih suka bahwa program akan memberikan output sebelum kematian panas alam semesta untuk input nilai kecil yang saya tunjukkan dalam tantangan.
absinthe
4
Keponakanmu tidak cukup sabar! : P
Sumurai8

Jawaban:

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

Ini bukan program terpendek yang bisa saya buat, tapi saya mengorbankan beberapa kecepatan untuk eksekusi.

Anda dapat bereksperimen dengan program di sini . Perhatikan bahwa ideone memiliki batas waktu eksekusi, jadi untuk input yang terdiri lebih dari sekitar 12 buah, program mungkin akan kehabisan waktu.

Ada juga test suite untuk program ini. Perhatikan bahwa dua tes terakhir dinonaktifkan pada ideone, karena batas waktu yang disebutkan di atas. Untuk mengaktifkan tes ini, hapus x_awalan dari namanya.

Program menemukan solusi menggunakan pencarian Kedalaman-pertama; itu menempatkan potongan satu per satu dan menyimpan jejak yang longgar. Pencarian berhenti ketika tidak ada lagi ujung yang longgar (tidak terhubung) dan semua bagian telah ditempatkan.

Ini adalah program yang tidak dipisahkan:

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))
}
Cristian Lupascu
sumber