Generación de rompecabezas de búsqueda de palabras

13

Dada una lista de cadenas, encuentre la matriz cuadrada más pequeña que contiene cada una de las cadenas iniciales. Las cadenas pueden aparecer horizontal, vertical o diagonal y hacia adelante o hacia atrás como en esta pregunta Word Search Puzzle .

Las palabras deben colocarse en el cuadrado, con al menos una palabra en cada dirección (horizontal, vertical y diagonal). Las palabras deberían aparecer solo una vez.

Entonces, la entrada es solo una lista de palabras. Por ejemplo: CAT, TRAIN, CUBE, BICYCLE. Una posible solución es:

B N * * * * *
* I * * C A T
* A C * * * *
* R * Y * * C
* T * * C * U
* * * * * L B
* * * * * * E

Reemplacé las letras de relleno con asteriscos solo por claridad. El resultado deseado debe incluir letras de relleno al azar.

Migue
fuente
¿Se debe encontrar cada palabra en una sola posición (como las búsquedas de palabras típicas)? Por ejemplo, la letra que queda ACen su ejemplo sería otra CATsi es así T.
Geobits
No me queda claro qué quiere decir exactamente con " Las palabras deben colocarse al azar en todas las direcciones ". ¿Cumpliría una respuesta este criterio si, después de exponer las palabras de manera determinista, selecciona aleatoriamente una de las ocho simetrías del cuadrado? O, en el otro extremo, ¿debería seleccionarse la salida uniformemente de todos los cuadrados más pequeños posibles que contienen las palabras? ¿O la línea se dibuja en algún lugar entre esos extremos?
Peter Taylor
Sí, debe encontrarse solo en una posición.
Migue
1
No, realmente no. Todavía no estoy claro cuál es el espacio del que debe tomarse una respuesta al azar, ni cuánta flexibilidad se permite al ponderar los elementos de ese espacio.
Peter Taylor
1
A partir de ahora, la pregunta tiene entradas que no se pueden resolver, pero no se menciona cómo se supone que debe lidiar con eso. Por ejemplo, el conjunto A B C D E F G H I J K L M N O P Q R S T U V W X Y Zno tiene solución.
orlp

Respuestas:

6

JavaScript (ES6), 595628680

Editar algo de limpieza y de combinación de:
- función P fusionó función dentro de R
- calc x y z en la misma .map
- cuando la solución se encontró, juego de x a 0 para salir de bucle exterior
- definiton fusionada y llamada de W

Edit2 más golf, relleno aleatorio acortado, bucle externo revisado ... vea el historial para algo más legible

A diferencia de la respuesta aceptada, esto debería funcionar para la mayoría de las entradas. Solo evita las palabras de una letra. Si se encuentra una salida, es óptima y utiliza las 3 direcciones.

La restricción de evitar repetir palabras es muy difícil. Tuve que buscar palabras repetidas en cada paso agregando palabras a la cuadrícula y en cada carácter de relleno aleatorio.

Subfunciones principales:

  • P (w) verdadero si la palabra palíndromo. Una palabra palindrom se encontrará dos veces cuando se verifican las palabras repetidas.

  • R (s) verifica las palabras que se repiten en la cuadrícula s

  • Las Q llenan la cuadrícula con caracteres aleatorios (es recursivo y retrocede en caso de que se repita la palabra) y puede fallar.

  • W () recursivo, intente llenar una cuadrícula de tamaño dado, si es posible.

La función principal usa W () para encontrar una cuadrícula de salida, intentando desde el tamaño de la palabra más larga en la entrada hasta la suma de la longitud de todas las palabras.

F=l=>{
  for(z=Math.max(...l.map(w=>(w=w.length,x+=w,w),x=0));
      ++z<=x;
      (W=(k,s,m,w=l[k])=>w?s.some((a,p)=>!!a&&
            D.some((d,j,_,r=[...s],q=p-d)=>
              [...w].every(c=>r[q+=d]==c?c:r[q]==1?r[q]=c:0)
              &&R(r)&&W(k+1,r,m|1<<(j/2))
            )
          )
        :m>12&&Q(s)&&(console.log(''+s),z=x) 
      )(0,[...Array(z*z-z)+99].map((c,i)=>i%z?1:'\n'))
    )
    D=[~z,-~z,1-z,z-1,z,-z,1,-1]
    ,R=u=>!l.some(w=>u.map((a,p)=>a==w[0]&&D.map(d=>n+=[...w].every(c=>u[q+=d]==c,q=p-d)),
      n=~([...w]+''==[...w].reverse()))&&n>0)
    ,Q=(u,p=u.indexOf(1),r=[...'ABCDEFGHIJHLMNOPQRSTUVWXYZ'])=>
      ~p?r.some((v,c)=>(r[u[p]=r[j=0|c+Math.random()*(26-c)],j]=v,R(u)&&Q(u)))||(u[p]=1):1
    //,Q=u=>u.map((c,i,u)=>u[i]=c!=1?c:' ') // uncomment to avoid random fill
}

Desengañado y explicado (incompleto, lo siento muchachos, es mucho trabajo)

F=l=>
{
  var x, z, s, q, D, R, Q, W;
  // length of longest word in z
  z = Math.max( ... l.map(w => w.length))
  // sum of all words length in x
  x = 0;
  l.forEach(w => x += w.length);

  for(; ++z <= x; ) // test square size from z to x
  {
    // grid in s[], each row of len z + 1 newline as separator, plus leading and trailing newline
    // given z==offset between rows, total length of s is z*(z-1)+1
    // gridsize: 2, z:3, s.length: 7 
    // gridsize: 3, z:4, s.length: 13
    // ...
    // All empty, nonseparator cells, filled with 1, so
    // - valid cells have a truthy value (1 or string)
    // - invalid cells have falsy value ('\n' or undefined)
    s = Array(z*z-z+1).fill(1) 
    s = s.map((v,i) => i % z != 0 ? 1 : '\n');

    // offset for 8 directions 
    D = [z+1, -z-1, 1-z, z-1, z, -z, 1, -1]; // 4 diags, then 2 vertical, then 2 horizontal 

    // Function to check repeating words
    R = u => // return true if no repetition
      ! l.some( w => // for each word (exit early when true)
      {
          n = -1 -([...w]+''==[...w].reverse()); // counter starts at -1 or -2 if palindrome word
          u.forEach( (a, p) => // for each cell if grid 
          {
            if (a == [0]) // do check if cell == first letter of word, else next word
               D.forEach( d => // check all directions 
                 n += // word counter
                   [...w].every( c => // for each char in word, exit early if not equal
                     u[q += d] == c, // if word char == cell, continue to next cell using current offset
                     q = p-d  // starting position for cell
                   )
               ) // end for each direction
          } ) // end for each cell
          return n > 0 // if n>0 the word was found more than once
      } ) // end for each word

    // Recursive function to fill empty space with random chars
    // each call add a single char
    Q = 
    ( u, 
      p = u.indexOf(1), // position of first remaining empty cell 
      r = [...'ABCDEFGHIJHLMNOPQRSTUVWXYZ'] // char array to be random shuffled
    ) => {
      if (~p) // proceed if p >= 0
        return r.some((v,c)=>(r[u[p]=r[j=0|c+Math.random()*(26-c)],j]=v,R(u)&&Q(u)))||(u[p]=1)
      else 
        return 1; // when p < 0, no more empty cells, return 1 as true
    }
    // Main working function, recursive fill of grid          
    W = 
    ( k, // current word position in list
      s, // grid
      m, // bitmask with all directions used so far (8 H, 4V, 2 or 1 diag)
      w = l[k] // get current word
    ) => {
      var res = false
      if (w) { // if current word exists
        res = s.some((a,p)=>!!a&&
            D.some((d,j,_,r=[...s],q=p-d)=>
              [...w].every(c=>r[q+=d]==c?c:r[q]==1?r[q]=c:0)
              &&R(r)&&W(k+1,r,m|1<<(j/2))
            )
          )
      } 
      else 
      { // word list completed, check additional constraints
        if (m > 12 // m == 13, 14 or 15, means all directions used
            && Q(s) ) // try to fill with random, proceed if ok
        { // solution found !!
          console.log(''+s) // output grid
          z = x // z = x to stop outer loop
          res = x//return value non zero to stop recursion
        }
      }
      return res
    };
    W(0,s)
  }    
}

Prueba en la consola Firefox / FireBug

F (['TREN', 'CUBO', 'CAJA', 'BICICLETA'])

,T,C,B,O,X,B,H,  
,H,R,U,H,L,I,H,  
,Y,A,A,B,E,C,B,  
,D,H,S,I,E,Y,I,  
,H,E,R,L,N,C,T,  
,G,S,T,Y,F,L,U,  
,H,U,Y,F,O,E,H,  

no llenado

,T,C,B,O,X,B, ,
, ,R,U, , ,I, ,
, , ,A,B, ,C, ,
, , , ,I,E,Y, ,
, , , , ,N,C, ,
, , , , , ,L, ,
, , , , , ,E, ,

F (['TREN', 'ARTES', 'RATA', 'CUBO', 'CAJA', 'BICICLETA', 'TORMENTA', 'CEREBRO', 'PROFUNDIDAD', 'BOCA', 'PESTAÑA']]

,T,A,R,C,S,T,H,
,S,R,R,L,U,D,T,
,T,B,A,T,N,B,P,
,O,B,O,I,S,A,E,
,R,B,A,X,N,H,D,
,M,R,M,O,U,T,H,
,B,I,C,Y,C,L,E,

F (['AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG'])

,A,U,B,C,
,T,A,E,Z,
,C,D,O,F,
,Q,C,G,A,

F (['AA', 'AB', 'AC', 'AD', 'AE', 'AF'])

salida no llena - @nathan: ahora no puede agregar otra A x sin repeticiones. Necesitarás una cuadrícula más grande.

,A, ,C,
, ,A,F,
,D,E,B,
edc65
fuente
En su último caso de prueba, ¿no es posible en una cuadrícula de 3x3?
Nathan Merrill
@NathanMerrill no. Más detalles en el texto de respuesta
edc65
código totalmente ilegible :) pero bueno, esa es la desventaja del byte / punto "premio" no seas un compilador humano
firephil
1
@firephil tratando de agregar una explicación, no es fácil ...
edc65
1

C#

Aquí hay una implementación simple con trabajo por hacer. Hay muchas combinaciones para obtener el tamaño más pequeño. Así que solo utilicé el algoritmo más simple que se me ocurrió.

class Tile
{
    public char C;
    public int X, Y;
}

class Grid
{
    List<Tile> tiles;

    public Grid()
    {
        tiles = new List<Tile>();
    }
    public int MaxX()
    {
        return tiles.Max(x => x.X);
    }
    public int MaxY()
    {
        return tiles.Max(x => x.Y);
    }
    public void AddWords(List<string> list)
    {
        int n = list.Count;
        for (int i = 0; i < n; i++)
        {
            string s = list[i];
            if(i==0)
            {
                Vert(s, 0, 0);
            }
            else if(i==n-1)
            {
                int my = MaxY();
                Diag(s, 0, my+1);
            }
            else
            {
                Horiz(s, 0, i);
            }
        }

    }
    private void Vert(string s, int x, int y)
    {
        for (int i = 0; i < s.Length; i++)
        {
            Tile t = new Tile();
            t.C = s[i];
            t.X = x+i;
            t.Y = y;
            tiles.Add(t);
        }
    }
    private void Horiz(string s, int x, int y)
    {
        for (int i = 0; i < s.Length; i++)
        {
            Tile t = new Tile();
            t.C = s[i];
            t.X = x+i;
            t.Y = y;
            tiles.Add(t);
        }
    }
    private void Diag(string s, int x, int y)
    {
        for (int i = 0; i < s.Length; i++)
        {
            Tile t = new Tile();
            t.C = s[i];
            t.X = x++;
            t.Y = y++;
            tiles.Add(t);
        }
    }
    public void Print()
    {
        int mx = this.MaxX();
        int my = this.MaxY();
        int S = Math.Max(mx, my) + 1;
        char[,] grid = new char[S, S];
        Random r = new Random(DateTime.Now.Millisecond);
        //fill random chars
        for (int i = 0; i < S; i++)
        {
            for (int j = 0; j < S; j++)
            {
                grid[i, j] = (char)(r.Next() % 26 + 'A');
            }
        }
        //fill words
        tiles.ForEach(t => grid[t.X, t.Y] = t.C);
        //print
        for (int i = 0; i < S; i++)
        {
            for (int j = 0; j < S; j++)
            {
                Console.Write("{0} ", grid[i,j]);
            }
            Console.WriteLine();
        }
    }
}

class WordSearch
{
    public static void Generate(List<string>list)
    {
        list.Sort((x, y) =>
        { int s = 0; if (x.Length < y.Length)s = -1; else if (y.Length < x.Length)s = 1; return s; });
        list.Reverse();
        Grid g = new Grid();
        g.AddWords(list);
        g.Print();
    }

}

Prueba

class Program
{
    static void Main(string[] args)
    {
        string words = "CAT, TRAIN, CUBE, BICYCLE";
        string comma=",";
        List<string> w = words.Split(comma.ToArray()).ToList();
        List<string> t = new List<string>();
        foreach(string s in w)
        {
           t.Add(s.Trim());
        }
        WordSearch.Generate(t);

        Console.ReadKey();
    }
}
bacchusbeale
fuente
funciona pero no es óptimo: muestra palabras de cadena = "CAT, PERRO, HR, EJECUTAR, CMD";
firephil
¿Comprueba si los caracteres de relleno aleatorios causan la repetición de una palabra en la cuadrícula?
edc65
-1 lo intenté. No sigue las especificaciones at least one word in each direction (horizontal, vertical and diagonal). Ejecutando el programa de prueba, no hay palabras horizontales (3 verticales, 1 diag)
edc65
3
Esta pregunta es code-golf , por lo que debe publicar cuántos bytes en el título, y probablemente acortar su programa un montón. Gracias.
mbomb007
@ edc65 Hace una vertical, una diagonal y todas las demás horizontales. Como comenté para obtener una solución perfecta, se requerirá una enorme cantidad de combinaciones para verificar, así como las especificaciones de la pregunta.
bacchusbeale