Hashiwokakero: costruisci ponti!


19

Hashiwokakero ("costruisci ponti" in giapponese) è un puzzle in cui ti viene assegnato il compito di collegare un gruppo di isole con ponti. Le regole sono:

  1. I ponti devono correre verticalmente o orizzontalmente tra due isole.
  2. I ponti potrebbero non incrociarsi.
  3. Una coppia di isole può essere collegata al massimo da due ponti paralleli.
  4. Ogni isola è contrassegnata da un numero compreso tra 1 e 8, inclusi. Il numero di ponti collegati a un'isola deve corrispondere al numero su quell'isola.
  5. I ponti devono collegare le isole in un singolo gruppo collegato.

Il tuo compito è scrivere un programma che risolva i puzzle di Hashiwokakero.

Puoi presumere che ogni dato enigma sia risolvibile e che esista una sola soluzione.

Il programma dovrebbe essere ragionevolmente efficiente. Ad esempio, la risoluzione del puzzle 25x25 di seguito non dovrebbe richiedere più di 10 minuti su un PC medio e non dovrebbe usare più di un gigabyte di memoria. Risolvere enigmi più piccoli come quello 7x7 dovrebbe richiedere alcuni secondi.

Ingresso:

Il puzzle verrà fornito come una mappa 2D di personaggi, con le cifre 1che 8rappresentano le isole e gli spazi che rappresentano l'acqua. Le linee saranno riempite con spazi se necessario per renderle tutte della stessa lunghezza.

Le isole saranno sempre separate orizzontalmente e verticalmente da almeno un quadrato d'acqua in modo che ci sia spazio per posizionare il potenziale ponte tra di loro. Ci saranno sempre almeno due isole in un puzzle.

Il tuo programma dovrebbe preferibilmente leggere la mappa del puzzle dall'input standard, ma puoi specificare un metodo di input alternativo se il tuo linguaggio di programmazione lo richiede.

Produzione:

L'output dovrebbe essere come l'input, tranne che con gli spazi sostituiti da bridge se necessario. I ponti devono essere disegnati utilizzando i caratteri di disegno della casella Unicode (U + 2500), (U + 2502), (U + 2550) e (U + 2551) per rappresentare ponti singoli e doppi orizzontali e verticali.

Se i caratteri Unicode sono un problema, è possibile utilizzare i caratteri ASCII -, |, =e Hinvece.

Criteri vincenti:

Questo è il codice golf. Vince la soluzione più breve, corretta e ragionevolmente efficiente.

Esempi:

Puzzle (7x7):

2 2  1 
 1  4 3
3  2   
    4  
 3 2  3
1      
 3  4 2

Soluzione:

2─2──1
│1──4─3
3──2║ ║ 
│  │4 ║
│3─2║ 3
1║  ║ │
 3──4─2

Puzzle (25x25):

2 2 2  2  1 1 2 2  2  2 2 
           1 3 5  4  4 2  
2  2 4 5  5 4 2 2  1    3 
  2   1  1 3 3 2       2  
   3 4 4  4 4 5 4 3  2  3 
2 4 5 4            2   3  
 2 1   4 2  4 3   1  1  2 
2 1 3     1  1  6  4   2  
 3 2  4  3  6 3         2 
2 2 3  3  2     5 2  4 3  
 2 1               1    2 
  1 3 3 3 3 5 8 7 6  5 4  
2  3   1 1 2              
 1   1  5 1   4 5 6 3 1 2 
1   1  2    2        3 4  
 3 5 4  4  3  3 8 7 5 1 2 
2      3  1 2  2     1 1  
 2      2  2  2 5 7 6 3 3 
3  3 6 3  5 3  2   2 2 3  
 2            1 2 3 2   2 
3  4 6  4 5 5  3 3 5  1   
 2    1    2 2  1   1  3  
2    1    1 2 3  6 5 2  2 
 2 3  4 4  4 2         1  
2 2  2 2  2 2 2  1  1 3 2 

Soluzione:

2─2─2──2  1 1─2─2──2──2─2
│      │  │1─3═5══4══4─2│
2  2─4─5══5═4═2│2──1 │ │3
│ 2│ ║1│ 1─3═3─2│    │ 2║
│ ║3═4│4══4─4═5─4─3──2 │3
2 4═5─4│  │ │ ║ │ │2───3│
│2─1║ ║4─2│ 4─3 │ 1│ 1 │2
2│1─3 ║║ │1 ║1──6══4 │ 2│
│3─2──4║ 3══6─3 ║  │ │ │2
2│2═3 │3──2 │ ║ 5─2│ 4─3│
│2─1│ │   │ │ ║ ║ │1 ║ │2
│ 1─3─3─3─3─5═8═7═6──5═4│
2──3───1│1─2│ ║ │ ║    ││
 1 │ 1──5─1││ 4─5─6─3─1│2
1│ │1──2║  │2 │ ║ ║ │3═4│
│3─5═4 │4──3│ 3═8═7═5│1│2
2│ │ ║ 3│ 1│2──2║ │ ║1│1│
│2 │ ║ ║2 │2──2│5═7═6─3─3
3│ 3─6─3│ 5═3 │2│ ║2│2─3│
║2 │ ║  │ ║ │ 1│2─3║2│ ║2
3│ 4═6──4─5─5──3─3─5││1║│
│2 │ │1 │ │2║2──1│ ║1││3│
2│ │ 1│ │ 1║2│3══6═5─2││2
│2─3──4═4──4─2│  │    │1│
2─2──2─2──2─2─2  1  1─3─2

Puzzle aggiuntivi possono essere trovati qui .


C'è un'opzione che l'input è impossibile da risolvere?
Hauleth,

@Hauleth: puoi presumere che ogni dato enigma sia risolvibile e che esista una sola soluzione.
Hammar,

Awww, non l'ho visto. Colpa mia.
Hauleth,

C'è una dimensione minima del puzzle o un numero di nodi o dobbiamo preoccuparci di casi strani come 1 1l'inserimento?
captncraig,

@CMP: non esiste un minimo esplicito, anche se le regole implicano che non esiste un puzzle più piccolo di 1 1, che è un puzzle valido e deve essere gestito correttamente.
Hammar,

Risposte:


10

Haskell, 1074 personaggi

main=interact$unlines.(\f@(l:_)->let a=(length l,length f)in head.filter(網(0,0)a).計(0,0)a$f).lines
橋=結"─═"数;結=zip;網 置@(右,下) 域@(幅,高) 地|下>=高=實|右>=幅=網(0,下+1)域 地|目 置 地`含`島
 =折((&&).折((&&).not.(`含`島))實)實(潔 置 域 地)|實=網(右+1,下)域 地
導=[(種,動)|動<-[1,-1],種<-"─═│║"];潔 置 域 地=折(拡 置 域)(換 地 置 '0')導
拡 置 域(種,動)地|([地],置)<-続(行 置 種 動)域 種 動 種 地=潔 置 域 地|實=地
計 置@(右,下)域@(幅,高)地|下>=高=[地]|右>=幅=計(0,下+1)域 地|[価]<-目 置 地`価`島
 =見込(価-環 置 域 地)>>=折(\種->(fst.続(行 置 種 1)域 種 1' '=<<))[地]>>=計(右+1,下)域
 |實=計(右+1,下)域 地;見込 価|価<0=[]|価>4=[]|實=[[""],["─","│"],["─│","║","═"],["─║","═│"],["═║"]]!!価
続 置 域 種 動 空 地|存 置 域=建 置 域 種 動 空 地|實=([],置)
建 置 域 種 動 空 地|目 置 地`含`島=([地],置)|目 置 地==空=続(行 置 種 動)域 種 動 空(換 地 置 種)
 |實=([],置);存(右,下)(幅,高)|右>=0,幅>右,0<=下=高>下|實=not 實;環 置 域 地=折(環行 置 域 地)0導
環行 置 域 地(種,動)数|置<-行 置 種 動,存 置 域,事<-目 置 地,事==種,[価]<-事`価`(橋++桥)=数+価|實=数
行(右,下)種 数|種`含`橋=(右+数,下)|實=(右,下+数);目(右,下)地=地!!下!!右;島=結"12345678"数
換 地(右,下)事|(上に,線:下に)<-捌 下 地,(左,古:右)<-捌 右 線=上に++(左++(事:右)):下に
折=foldl.flip;捌 0覧=([],覧);捌 数(物:覧)|(一覧,他)<-捌(数-1)覧=(物:一覧,他);實=1>0;数=[1..]
価 _[]=[];価 事((物,数):覧)|事==物=[数]|實=価 事 覧;含 事 覧|[_]<-価 事 覧=實|實=1<0;桥=結"│║"数


Inizialmente, lo avevo ancora più puramente giapponese implementando anche le funzioni primitive in termini di corrispondenza di schemi semplici e combinazioni di elenchi:

Haskell, 1192

main=interact$unlines.(\f@(l:_)->let a=(length l,length f)in head.filter(網(0,0)a).計(0,0)a$f).lines
橋=結合"─═"数;結合 []_=[];結合(事:覧)(物:一覧)=(事,物):結合 覧 一覧
網 置@(右,下) 域@(幅,高) 地|下>=高=實|右>=幅=網(0,下+1)域 地|目 置 地`含`島
 =折る((&&).折る((&&).反対.(`含`島))實)實(潔 置 域 地)|實=網(右+1,下)域 地
導=[(種,動)|動<-[1,-1],種<-"─═│║"];潔 置 域 地=折る(拡 置 域)(換 地 置 '0')導
拡 置 域(種,動)地|([地],置)<-続(行 置 種 動)域 種 動 種 地=潔 置 域 地|實=地
計 置@(右,下)域@(幅,高)地|下>=高=[地]|右>=幅=計(0,下+1)域 地|[価]<-目 置 地`価`島
 =見込(価-環 置 域 地)>>=折る(\種->(一.続(行 置 種 1)域 種 1' '=<<))[地]>>=計(右+1,下)域
 |實=計(右+1,下)域 地;見込 価|価<0=[]|価>4=[]|實=[[""],["─","│"],["─│","║","═"],["─║","═│"],["═║"]]!!価
続 置 域 種 動 空 地|存 置 域=建 置 域 種 動 空 地|實=([],置)
建 置 域 種 動 空 地|目 置 地`含`島=([地],置)|目 置 地==空=続(行 置 種 動)域 種 動 空(換 地 置 種)
 |實=([],置);存(右,下)(幅,高)|右>=0,幅>右,0<=下=高>下|實=反対 實;環 置 域 地=折る(環行 置 域 地)0導
環行 置 域 地(種,動)数|置<-行 置 種 動,存 置 域,事<-目 置 地,事==種,[価]<-事`価`結 橋 桥=数+価|實=数
行(右,下)種 数|種`含`橋=(右+数,下)|實=(右,下+数);一(第,第二)=第;目(右,下)地=地!!下!!右;島=結合"12345678"数
換 地(右,下)事|(上に,線:下に)<-捌 下 地,(左,古:右)<-捌 右 線=結 上に(結 左(事:右):下に);変 関[]=[]
変 関(物:覧)=関 物:変 関 覧;折る 関 物[]=物;折る 関 物(事:覧)=折る 関(関 事 物)覧;捌 0覧=([],覧)
捌 数(物:覧)|(一覧,他)<-捌(数-1)覧=(物:一覧,他);實=1>0;反対 真|真=1<0|實=實;数=[1..];結=(++)
価 _[]=[];価 事((物,数):覧)|事==物=[数]|實=価 事 覧;含 事 覧|[_]<-価 事 覧=實|實=1<0;桥=結合"│║"数



$ make ;   def0 +RTS -M1g < test-25x25.txt
ghc -o bin/def0 golfed0.hs -rtsopts -O2
[1 of 1] Compiling Main             ( golfed0.hs, golfed0.o )
Linking bin/def0 ...
2─2─2──2  1 1─2─2──2──2─2
│      │  │1─3═5══4══4─2│
2  2─4─5══5═4═2│2──1 │ │3
│ 2│ ║1│ 1─3═3─2│    │ 2║
│ ║3═4│4══4─4═5─4─3──2 │3
...

funziona in ≈3 minuti sul mio i5 .


Versione commentata:

type Board = [[Char]]
type Location = (Int,Int)
type BoardDimensions = (Int,Int)

main=interact$unlines.(\f@(l:_)
  ->let a=(length l,length f)  -- dimensions of the field from the input
     in head.filter(網(0,0)a)   --   ↙−   determine all possible ways to build bridges
  {-                ↑      -}   .計(0,0)a $ f                                         ).lines
     -- and use the first that is simply connected. 


 --  islands,            bridges
島=結合"12345678"数;  橋=結合"─═"数;  桥=結合"│║"数;               数=[1..]
 -- each with the associated "value" from the natural numbers _↗



     -- plan & commit the building of bridges
計 :: Location -> BoardDimensions -> Board -> [Board]
計    置@(右,下)   域@(幅,高)          地
 |下>=高=[地]        -- Walk over the board until every location was visited.
 |右>=幅=計(0,下+1)域 地
 |[価]<-目 置 地`価`島      -- When there is an island, read it's "value" 価
    =見込(価-環 置 域 地)  -- substract the value of the already-built bridges; fetch the ways to build bridges with the remaining value
     >>=折る(\種->(一.続(行 置 種 1)域 種 1' '=<<))[地]  -- for each of these ways, try to build a bridge.
      >>=計(右+1,下)域    -- for every possibility where that was successful, go on with the resultant board.
 |實=計(右+1,下)域 地

  -- Ways to build bridges with value 価:
見込 :: Int -> [[Char]]
見込    価
 |価<0=[]   -- not possible to build bridges with negative value
 |価>4=[]   -- nor with value >4  (we're always building south- / eastwards)
 |實=[ [""]      -- value 0
     ,["─","│"]  -- value 1
     ,["─│","║","═"],["─║","═│"],["═║"]]!!価  -- ... and so on

 -- continue, if Location is on the board, with the building of a bridge of type 種
続 :: Location -> BoardDimensions -> Char -> Int -> Char -> Board -> ([Board],Location)
続    置          域                  種      動      空      地
 |存 置 域=建 置 域 種 動 空 地
 |實=([],置)

      -- build that bridge, 
建 :: Location -> BoardDimensions -> Char -> Int -> Char -> Board -> ([Board],Location)
建    置          域                  種      動      空      地
 |目 置 地`含`島=([地],置)  -- but if we've reached an island we're done
 |目 置 地==空 -- if we're in water or what else (空, can also take on the value of 種 if we only want to check if the bridge is already there)
    =続(行 置 種 動)域 種 動 空(換 地 置 種) -- place (換) the bridge and go (行く) to the next location
 |實=([],置)  -- if we've reached something else (i.e. crossing bridges), return no result.

     -- number of connections present at location 置
環 :: Location -> BoardDimensions -> Board -> Int
環 置 域 地=折る(環行 置 域 地)0導  -- for all neighbouring positions
環行 置 域 地(種,動)数
 |置<-行 置 種 動,存 置 域   -- if they're on the board
 ,事<-目 置 地,事==種    --   and there's a bridge in the correct direction
 ,[価]<-事`価`結 橋 桥=数+価  -- check its value and sum it to the previous ones
 |實=数   -- if there's no bridge there, don't sum anything


導=[(種,動)|動<-[1,-1],種<-"─═│║"]     -- directions to go

--     --     --     --     --     --     --     --     --     --     --     --

     -- test for connectedness:
網 :: Location -> BoardDimensions -> Board -> Bool
網    置@(右,下)      域@(幅,高)         地      -- Walk over the board until an island is
 |下>=高=實                                    -- found. 潔 marks all islands connected to
 |右>=幅=網(0,下+1)域 地                        -- that island; then check if any unmarked
 |目 置 地`含`島=折る((&&).折る((&&).反対.(`含`島))實)實(潔 置 域 地)  -- islands are left in the
 |實=網(右+1,下)域 地                                                          -- result.

         -- mark islands connected to the one at 置:
潔 :: Location -> BoardDimensions -> Board -> Board
潔    置           域                 地    =折る(拡 置 域)(換 地 置 '0')[(種,動)|動<-[1,-1],種<-"─═│║"]
 -- mark the island at 置 with '0', then, for all the possible ways to go...
     -- Proceed with the marking in some direction
拡 :: Location -> BoardDimensions -> (Char,Int) -> Board -> [[Char]]
拡 置 域(種,動)地     -- if an island is found in the given direction, give control to 潔 there
 |([地],置)<-続(行 置 種 動)域 種 動 種 地=潔 置 域 地
 |實=地   -- if none is found (i.e. there was no bridge), just return the board without further marking


--     --     --     --     --     --     --     --     --     --     --     --
-- Primitives:

存 :: Location -> BoardDimensions -> Bool
存(右,下)(幅,高)|右>=0,幅>右,0<=下=高>下|實=反対 實  -- check if (右,下) is on the board

行 :: Location -> Char->Int -> Location
行(右,下)種 数|種`含`橋=(右+数,下)|實=(右,下+数)   -- go in some direction (determined by where 種 leads to)

目 :: Location -> Board -> Char
目(右,下)地=地!!下!!右          -- lookup what's at location (右,下)

   -- replace what's at (右,下) with 事
換 :: Board -> Location -> Char -> Board
換 地(右,下)事|(上に,線:下に)<-捌 下 地,(左,古:右)<-捌 右 線=結 上に(結 左(事:右):下に)




変 :: (a -> b) -> [a] -> [b]
変 関[]=[]                       -- Standard Haskell map function (just noticed I didn't actually use it at all)
変 関(物:覧)=関 物:変 関 覧

折る :: (b -> a -> a) -> a -> [b] -> a
折る 関 物[]=物                            -- equivalent 折る=foldl.flip
折る 関 物(事:覧)=折る 関(関 事 物)覧

捌 0覧=([],覧)
捌 数(物:覧)|(一覧,他)<-捌(数-1)覧=(物:一覧,他)   -- splitAt

實=1>0           --true

反対 真|真=1<0|實=實  -- not


結=(++)     -- list linking

一(第,第二)=第    -- fst

価 :: Eq a => a -> [(a,b)] -> [b]
価 _[]=[]                             -- lookup function
価 事((物,数):覧)|事==物=[数]|實=価 事 覧

含 :: Eq a => a -> [(a,b)] -> Bool
含 事 覧|[_]<-価 事 覧=實|實=1<0      -- equivalent 含 x = elem x . map fst


結合 []_=[]                          -- zip
結合(事:覧)(物:一覧)=(事,物):結合 覧 一覧

1
Wow. Vuoi spiegare di cosa parla il cinese?
captncraig,

1
@CMP: in realtà dovrebbe essere giapponese ... non proprio , ho solo cercato cose che sembravano avere più o meno il significato corretto su Wikizionario. - Va bene, ha aggiunto una versione commentata del codice.
cessò di girare in senso antiorario il

5

Python, 1079 caratteri

import sys,re,copy
A=sys.stdin.read()
W=A.find('\n')+1
r=range
V={}
E=[]
for i in r(len(A)):
 if'0'<A[i]<'9':V[i]=int(A[i])
 for d in(1,W):m=re.match('[1-8]( +)[1-8]',A[i::d]);E+=[[i,i+len(m.group(1))*d+d,d,r(3)]]if m else[]
def S(E):
 q,t=0,1
 while q!=t:
  for e in E:
   if any(d[0]and e[3][0]==0and any(i in r(a+c,b,c)for i in r(e[0]+e[2],e[1],e[2]))for a,b,c,d in E):e[3]=[0]
  for i in V:
   m=sum(min(e[3])for e in E if i in e[:2]);n=sum(max(e[3])for e in E if i in e[:2])
   if m>V[i]or n<V[i]:return
   for e in E:
    if m+2>V[i]and i in e[:2]:e[3]=e[3][:V[i]-m+1]
    if n-2<V[i]and i in e[:2]:e[3]=e[3][V[i]-n-1:]
  t=q;q=sum(len(e[3])for e in E)
 Q=[min(V)]
 i=0
 while Q[i:]:
  x=Q[i];i+=1
  for e in E:
   if x in e[:2]:
    if sum(e[3]):
     for y in e[:2]:
      if y not in Q:Q+=[y]
 if len(Q)!=len(V):return
 U=[e for e in E if e[3][1:]]
 if U:
  for w in U[0][3]:U[0][3]=[w];S(copy.deepcopy(E))
 else:
  B=A
  for a,b,c,d in E:
   if d[0]:
    for i in r(a+c,b,c):B=B[:i]+[{1:'─',W:'│'},{1:'═',W:'║'}][d[0]-1][c]+B[i+1:]
  print(B)
  sys.exit(0)
S(E)

Il codice esegue una ricerca esaustiva piuttosto semplice S, usando una certa propagazione dei vincoli per farlo funzionare in un tempo ragionevole. Erappresenta la serie corrente di spigoli, nel formato [da, a, delta, possibili pesi] . da e a sono identificatori dell'isola e delta è 1 per i bordi orizzontali o W (= larghezza delle linee) per i bordi verticali. pesi possibili è un sottoelenco di [0,1,2] che codifica lo stato attuale noto di quel bordo (0 = nessun ponte, 1 = ponte singolo, 2 = ponte doppio).

Sfa tre cose. Innanzitutto propaga le informazioni, come se un bordo non avesse più un peso 0 come possibilità, quindi tutti i bordi che lo attraversano vengono eliminati (i loro possibili pesi sono impostati su [0]). Allo stesso modo, se la somma del peso minimo per i bordi incidente su un'isola è uguale al peso dell'isola, tutti questi bordi sono impostati al minimo.

In secondo luogo, Scontrolla che il grafico sia ancora collegato utilizzando bordi non [0] (il Qcalcolo).

Infine, Sseleziona un limite che non è ancora del tutto determinato e lo chiama in modo ricorsivo, impostandolo su una delle possibilità rimanenti.

Ci vogliono circa 2 minuti per l'esempio più grande.


puoi perdere alcuni personaggi usando le schede anziché gli spazi in alcuni punti e combinando alcune cose in una riga (comeprint(B);sys.exit(0)
Blazer

L'ho hackerato e l'ho portato a 1041 caratteri, ancora funzionante
Blazer

3

C # - 6601 5661 2225

using System;using System.Collections.Generic;using Google.OrTools.ConstraintSolver;
using System.Linq;namespace A{class N{public int R,C,Q;public bool F;public N(int r,
int c,int q){R=r;C=c;Q=q;}}class E{private static int i;public N A,B;public int I;
public E(N a,N b){A=a;B=b;I=i++;}}class H{public void G(string i){var o=P(i);var g=
new List<E>();foreach(var m in o){var r=o.Where(x=>x.R==m.R&&x.C>m.C).OrderBy(x=>x.C)
.FirstOrDefault();if(r!=null){g.Add(new E(m,r));}var d=o.Where(x=>x.C==m.C&&x.R>m.R)
.OrderBy(x=>x.R).FirstOrDefault();if(d!=null){g.Add(new E(m,d));}}var s=new Solver("H")
;int n=g.Count;var k=s.MakeIntVarArray(n,0,2);foreach(var j in o){var w=j;var y=g.Where
(x=>x.A==w||x.B== w).Select(x=>k[x.I]).ToArray();s.Add(s.MakeSumEquality(y,j.Q));}
foreach(var u in g.Where(x=>x.A.R==x.B.R)){var e=u;var v=g.Where(x=>x.A.R<e.A.R&&x.B.R
>e.A.R&&x.A.C>e.A.C&&x.A.C< e.B.C);foreach (var f in v){s.Add(s.MakeEquality(k[e.I]*k[f.
I],0));}}if(o.Count>2){foreach(var e in g.Where(x=>x.A.Q==2&&x.B.Q==2)){s.Add(k[e.I]<=1)
;}foreach(var e in g.Where(x=>x.A.Q==1&&x.B.Q==1)){s.Add(k[e.I]==0);}}var z=s.MakePhase
(k,0,0);s.NewSearch(z);int c=0;while(s.
NextSolution()){if(C(k,o,g)){N(k,o,g);Console.WriteLine();c++;}}Console.WriteLine(c);}
bool C(IntVar[]t,List<N>d,List<E>g){var a=d[0];a.F=true;var s=new Stack<N>();s.Push(a);
while(s.Any()){var n=s.Pop();foreach(var e in g.Where(x=>x.A==n||x.B==n)){var o=e.A==n?
e.B:e.A;if(t[e.I].Value()>0&&!o.F){o.F=true;s.Push(o);}}}bool r=d.All(x=>x.F);foreach
(var n in d){n.F=false;}return r;}void N(IntVar[]t,IList<N>n,List<E>e){var l=new 
List<char[]>();for(int i=0;i<=n.Max(x=>x.R);i++){l.Add(new string(' ',n.Max(x=>x.C)+1)
.ToCharArray());}foreach(var o in n){l[o.R][o.C]=o.Q.ToString()[0];N d=o;foreach(var 
g in e.Where(x=>x.A==d)){var v=t[g.I].Value();if(v>0){char p;int c;if(g.B.R==o.R){p=v==1
?'─':'═';c=o.C+1;var r=l[o.R];while(c<g.B.C){r[c]=p;c++;}}else{p=v==1?'│':'║';c=o.R+1;
while(c<g.B.R){l[c][o.C]=p;c++;}}}}}foreach(var r in l){Console.WriteLine(new string(r))
;}}List<N>P(string s){var n=new List<N>();int r=0;foreach(var l in s.Split(new[]{'\r',
'\n'},StringSplitOptions.RemoveEmptyEntries)){for(int c=0;c<l.Length;c++){if(l[c]!=' '
){n.Add(new N(r,c,l[c]-'0'));}}r++;}return n;}}}

Non particolarmente ben golfato. Utilizza la libreria di programmazione dei vincoli di google o-tools. Crea vincoli per il conteggio totale dei bordi e per eliminare i ponti di attraversamento, ma è un po 'più difficile definire i vincoli per assicurarsi che siano tutti collegati. Ho aggiunto la logica per eliminare i componenti 2 = 2 e 1-1, ma devo ancora passare attraverso l'elenco finale (39 su quello grande) ed eliminare quelli che non sono completamente connessi. Funziona abbastanza velocemente. Richiede solo un paio di secondi sul più grande esempio. Ungolfed:

using System;
using System.Collections.Generic;
using Google.OrTools.ConstraintSolver;
using System.Linq;
namespace Hashi
{
    public class Node
    {
        public int Row, Col, Req;
        public bool Flag;

        public Node(int r, int c, int q)
        {
            Row = r;
            Col = c;
            Req = q;
        }
    }
    public class Edge
    {
        private static int idx = 0;
        public Node A, B;
        public int Index;
        public Edge(Node a, Node b)
        {
            A = a;
            B = b;
            Index = idx++;
        }
    }
    public class HashiSolver
    {
        public void Go(string input)
        {
            IList<Node> nodes = Parse(input);
            var edges = new List<Edge>();
            //add edges between nodes;
            foreach (var node in nodes)
            {
                var r = nodes.Where(x => x.Row == node.Row && x.Col > node.Col).OrderBy(x => x.Col).FirstOrDefault();
                if (r != null)
                {
                    edges.Add(new Edge(node, r));
                }
                var d = nodes.Where(x => x.Col == node.Col && x.Row > node.Row).OrderBy(x => x.Row).FirstOrDefault();
                if (d != null)
                {
                    edges.Add(new Edge(node, d));
                }
            }
            var solver = new Solver("Hashi");
            int n = edges.Count;
            var toSolve = solver.MakeIntVarArray(n, 0, 2);
            //add total node edge total constraints
            foreach (var node in nodes)
            {
                var node1 = node;
                var toConsider = edges.Where(x => x.A == node1 || x.B == node1).Select(x => toSolve[x.Index]).ToArray();
                solver.Add(solver.MakeSumEquality(toConsider, node.Req));
            }
            //add crossing edge constraints
            foreach (var ed in edges.Where(x => x.A.Row == x.B.Row))
            {
                var e = ed;
                var conflicts = edges.Where(x => x.A.Row < e.A.Row &&
                                                 x.B.Row > e.A.Row &&
                                                 x.A.Col > e.A.Col &&
                                                 x.A.Col < e.B.Col);
                foreach (var conflict in conflicts)
                {
                    solver.Add(solver.MakeEquality(toSolve[e.Index] * toSolve[conflict.Index], 0));
                }
            }
            if (nodes.Count > 2)
            {
                //remove 2=2 connections
                foreach (var e in edges.Where(x => x.A.Req == 2 && x.B.Req == 2))
                {
                    solver.Add(toSolve[e.Index] <= 1);
                }
                //remove 1-1 connections
                foreach (var e in edges.Where(x => x.A.Req == 1 && x.B.Req == 1))
                {
                    solver.Add(toSolve[e.Index] == 0);
                }
            }
            var db = solver.MakePhase(toSolve, Solver.INT_VAR_DEFAULT, Solver.INT_VALUE_DEFAULT);
            solver.NewSearch(db);
            int c = 0;
            while (solver.NextSolution())
            {
                if (AllConnected(toSolve, nodes, edges))
                {
                    Print(toSolve, nodes, edges);
                    Console.WriteLine();
                    c++;
                }
            }
            Console.WriteLine(c);
        }
        private bool AllConnected(IntVar[] toSolve, IList<Node> nodes, List<Edge> edges)
        {
            var start = nodes[0];
            start.Flag = true;
            var s = new Stack<Node>();
            s.Push(start);
            while (s.Any())
            {
                var n = s.Pop();
                foreach (var edge in edges.Where(x => x.A == n || x.B == n))
                {
                    var o = edge.A == n ? edge.B : edge.A;
                    if (toSolve[edge.Index].Value() > 0 && !o.Flag)
                    {
                        o.Flag = true;
                        s.Push(o);
                    }
                }
            }
            bool r = nodes.All(x => x.Flag);
            foreach (var n in nodes)
            {
                n.Flag = false;
            }
            return r;
        }
        private void Print(IntVar[] toSolve, IList<Node> nodes, List<Edge> edges)
        {
            var l = new List<char[]>();
            for (int i = 0; i <= nodes.Max(x => x.Row); i++)
            {
                l.Add(new string(' ', nodes.Max(x => x.Col) + 1).ToCharArray());
            }
            foreach (var node in nodes)
            {
                l[node.Row][node.Col] = node.Req.ToString()[0];
                Node node1 = node;
                foreach (var edge in edges.Where(x => x.A == node1))
                {
                    var v = toSolve[edge.Index].Value();
                    if (v > 0)
                    {
                        //horizontal
                        if (edge.B.Row == node.Row)
                        {
                            char repl = v == 1 ? '─' : '═';
                            int col = node.Col + 1;
                            var r = l[node.Row];
                            while (col < edge.B.Col)
                            {
                                r[col] = repl;
                                col++;
                            }
                        }
                        //vertical
                        else
                        {
                            char repl = v == 1 ? '│' : '║';
                            int row = node.Row + 1;
                            while (row < edge.B.Row)
                            {

                                l[row][node.Col] = repl;
                                row++;
                            }
                        }
                    }
                }
            }
            foreach (var r in l)
            {
                Console.WriteLine(new string(r));
            }
        }
        private IList<Node> Parse(string s)
        {
            var n = new List<Node>();
            int row = 0;
            foreach (var line in s.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
            {
                for (int col = 0; col < line.Length; col++)
                {
                    if (line[col] != ' ')
                    {
                        n.Add(new Node(row, col, line[col] - '0'));
                    }
                }
                row++;
            }
            return n;
        }

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