Costruiamo una pista per auto da corsa!


19

introduzione

Mia nipote vuole fare una pista da corsa. Ha parti in legno che si incastrano per formare la pista. Ogni parte ha una forma quadrata e contiene una forma diversa. Userò i caratteri del disegno della pipa per illustrare:

  • : la strada che va in verticale
  • : la strada che va in orizzontale
  • : le strade che girano in una direzione
  • : Un ponte con un sottopasso

Curiosamente, non ci sono pezzi di giunzione a T.

Ecco un esempio di una possibile pista per auto da corsa:

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

Le regole per una pista di gara valida sono le seguenti:

  • Non ci possono essere strade che non vanno verso il nulla.
  • Deve formare un anello (e tutti i pezzi devono far parte dello stesso anello).
  • Ai ponti / sottopassi, non puoi girare (quindi devi attraversarli).

Sfortunatamente, la pista della macchina da corsa collega me e mia nipote sono limitate. Ma vogliamo sicuramente usarli tutti in pista. Scrivi un programma che, dato un elenco di quali pezzi sono presenti nel nostro inventario, produce una pista per auto da corsa che utilizza tutti quei pezzi.

Descrizione dell'input

Vorremmo che l'input arrivasse tramite STDIN, argomenti della riga di comando, lettura dei file o una funzione di input dell'utente (come raw_inputo prompt). L'input è numeri interi positivi separati da virgola nel modulo

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

dove ognuno di questi rappresenta la quantità di quel particolare pezzo che abbiamo. Quindi ad esempio l'input:

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

significherebbe che ne avevamo uno per ogni pezzo.

Descrizione dell'uscita

Emetti una pista per auto da corsa usando i caratteri del disegno della tubazione sopra elencati. La pista della macchina da corsa dovrebbe usare esattamente il numero di ogni pezzo specificato nell'input - né più né meno. Ci sarà almeno una pista di gara valida per ogni ingresso.

Esempi di ingressi e uscite

Ingresso: 3,5,2,2,2,2,1

Una possibile uscita:

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

Ingresso: 0,0,1,4,4,1,3

Una possibile uscita:

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

Deve dare l'output? O ha solo teoricamente bisogno di dare l'output?
Sumurai8,

@ Sumurai8 Cosa intendi per "teoricamente" dare l'output? Intendi un programma che non terminerà per un tempo estremamente lungo ma che alla fine darà l'output?
assenzio

1
Probabilmente si sarebbe in grado di creare un campo di nxn quadrati pieni di pezzi di gara e quadrati vuoti, dove è possibile generare permutazioni fino a trovare qualcosa che sia una pista. Ci vorrebbe un'eternità per qualcosa di più di qualche tessera.
Sumurai8,

4
@ Sumurai8 Ah ok, capisco adesso. Preferirei che i programmi fornissero un output prima della morte termica dell'universo per i piccoli input di valore che ho mostrato nella sfida.
assenzio

4
Tua nipote non è abbastanza paziente! : P
Sumurai8,

Risposte:


4

Rubino 664 671 677 687 701 (678 byte)

_={│:[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

Questo non è il programma più breve che ho potuto inventare, ma ho sacrificato un po 'di brevità per la velocità di esecuzione.

Puoi provare il programma qui . Si noti che ideone ha un limite di tempo di esecuzione, quindi per input composti da più di circa 12 pezzi, probabilmente il programma scadrà.

C'è anche una suite di test per il programma. Si noti che gli ultimi due test sono disabilitati su ideone, a causa del limite di tempo sopra menzionato. Per abilitare questi test, eliminare ilx_ prefisso dai loro nomi.

Il programma trova una soluzione usando la ricerca Depth-first; posiziona i pezzi uno alla volta e tiene traccia delle estremità libere. La ricerca si interrompe quando non ci sono più estremità libere (non collegate) e tutti i pezzi sono stati posizionati.

Questo è il programma non golfato:

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))
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.