Java 8 lambda, 1506 1002 972 942 caracteres
Quería superar este desafío, ya que es muy interesante. El resultado (no muy golfista) se puede ver aquí:
import java.util.*;f->{Set<double[]>B=new HashSet(),r,n;double a,M,m,P=Math.PI*2,z=.5;int x=0,y,v=0,i,j,c[],p,q,l=g.length;for(;x<l;x++)for(y=0;y<g[x].length;y++)if(g[x][y]>63)for(;;){c=new int[]{-1};M=2e31-1;for(i=0;i<l;i++)for(j=0;j<g[i].length;j++)if(g[i][j]==42)if((m=(p=x-i)*p+(q=y-j)*q)<M){M=m;c=new int[]{i,j};}if(c[0]<0)break;g[c[0]][c[1]]=0;double[]A={(a=Math.atan2((c[1]-=y)-z,(c[0]-=x)-z))<0?a+P:a,(a=Math.atan2(c[1]+z,c[0]-z))<0?a+P:a,(a=Math.atan2(c[1]+z,c[0]+z))<0?a+P:a,(a=Math.atan2(c[1]-z,c[0]+z))<0?a+P:a};r=new HashSet();M=-P;m=P;for(double d:A){M=d>M?d:M;m=d<m?d:m;}r.add(new double[]{m,M});for(double[]t:B){n=new HashSet();for(double[]h:r)for(double[]u:t[0]<h[0]?t[1]<h[0]?new double[][]{h}:t[1]<h[1]?new double[][]{{t[1],h[1]}}:new double[0][]:t[0]>h[1]?new double[][]{h}:t[1]>h[1]?new double[][]{{h[0],t[0]}}:new double[][]{{h[0],t[0]},{t[1],h[1]}})if(u[0]<u[1])n.add(u);r=n;}B.addAll(r);if(!r.isEmpty())v++;}return v;}
Por supuesto, esto también existe en la versión sin golf:
import java.util.*;
public class AngleCheck {
static int getViewableBuildingsC(char[][] grid) {
Set<double[]> blocked = new HashSet(), ranges, newRanges;
double angle, max, min, PI2 = Math.PI * 2, half = 0.5;
int x = 0, y, viewable = 0, i, j, building[], dX, dY, length = grid.length;
for (; x < length; x++) {
for (y = 0; y < grid[x].length; y++) {
if (grid[x][y] > 63) {
for (;;) {
building = new int[]{-1};
max = 2e31-1;
for (i = 0; i < length; i++) {
for (j = 0; j < grid[i].length; j++) {
if (grid[i][j] == 42) {
if ((min = (dX = x - i) * dX + (dY = y - j) * dY) < max) {
max = min;
building = new int[]{i, j};
}
}
}
}
if (building[0] < 0)
break;
grid[building[0]][building[1]] = 0;
double[] angles = {
(angle = Math.atan2((building[1] -= y) - half, (building[0] -= x) - half)) < 0 ? angle + PI2 : angle,
(angle = Math.atan2(building[1] + half, building[0] - half)) < 0 ? angle + PI2 : angle,
(angle = Math.atan2(building[1] + half, building[0] + half)) < 0 ? angle + PI2 : angle,
(angle = Math.atan2(building[1] - half, building[0] + half)) < 0 ? angle + PI2 : angle};
ranges = new HashSet();
max = -PI2;
min = PI2;
for (double d : angles) {
max = d > max ? d : max;
min = d < min ? d : min;
}
ranges.add(new double[]{min, max});
for (double[] reference : blocked) {
newRanges = new HashSet();
for (double[] currentRange : ranges) {
for (double[] subRange : reference[0] < currentRange[0] ?
reference[1] < currentRange[0] ?
// whole range after referencerange
new double[][]{currentRange}
:
reference[1] < currentRange[1] ?
// lower bound inside referencerange, but upper bound outside
new double[][]{{reference[1], currentRange[1]}}
:
// whole range inside referencerange -> nothing free
new double[0][]
:
// greater or equal lower bound
reference[0] > currentRange[1] ?
// whole range before referencerange
new double[][]{currentRange}
:
// ranges overlap
reference[1] > currentRange[1] ?
// range starts before and ends in reference range
new double[][]{{currentRange[0], reference[0]}}
:
// referencerange is in the range -> two free parts, one before, one after this
new double[][]{{currentRange[0], reference[0]}, {reference[1], currentRange[1]}}) {
if (subRange[0] < subRange[1])
newRanges.add(subRange);
}
}
ranges = newRanges;
}
blocked.addAll(ranges);
if (!ranges.isEmpty()) {
viewable++;
}
}
}
}
}
return viewable;
}
}
Por lo tanto, parece muy difícil, pero es mucho más fácil de lo que uno podría pensar. Mi primera idea fue usar un algoritmo de intersección para verificar si una línea desde mi posición hasta el edificio se puede hacer sin intersecciones. Para hacer esto, decidí usar el algoritmo Cohen-Sutherland y dibujar líneas en las cuatro esquinas del edificio. Esto funcionó bastante bien para las primeras pruebas, pero la última falló. Pronto descubrí que es un caso en el que no se pueden ver las esquinas, sino una parte de un borde. Así que pensé en algún tipo de casting de rayos como @Blue lo hizo. Dejé de lado ese desafío, ya que no obtuve algún progreso. Entonces vi la respuesta de Blue y me vino a la mente la siguiente idea simple: cada edificio bloquea algún ángulo en el que no se puede ver nada más. Solo necesito hacer un seguimiento de lo que se puede ver y lo que ya está oculto por otros edificios. ¡Eso es!
El algoritmo funciona de la siguiente manera: determina el edificio con la menor distancia a la persona. Luego imaginamos cuatro líneas dibujadas desde la persona a las esquinas del edificio. Dos de estos tienen un valor extremo: el ángulo mínimo y máximo en el que se puede ver el edificio. Los tomamos como un rango y los comparamos con otros edificios de los cuales sabemos que se pueden ver (ninguno al principio). Los rangos pueden superponerse, incluirse entre sí o no tocarse en absoluto. Comparo los rangos y obtengo algunos nuevos rangos del edificio que no están ocultos por los edificios visibles. Si queda algo después de compararlo con los edificios a la vista, el edificio también es visible. Agregamos el rango de ángulo restante a la lista de rangos para comparar y comenzar con el próximo edificio con la siguiente distancia más larga.
A veces, los rangos pueden superponerse de una manera que termino con un rango de 0 grados. Estos rangos se filtrarán para no agregar por error un edificio que ni siquiera se puede ver.
Espero que alguien haya entendido esta explicación :)
Sé que este código no se juega mucho, lo haré lo antes posible.
Esa fue una tarea realmente desafiante. Pensaste que encontraste una solución que funciona, pero aún estás lejos. Creo que esta solución funciona bastante bien. No es muy rápido, pero al menos funciona;) ¡Gracias por ese rompecabezas!
Actualizar
Encontré el tiempo para jugar golf todo en una sola función, que por lo tanto se puede convertir en una lambda. Todas las funciones solo se llamaron una vez y, por lo tanto, se pueden poner en un solo método. Cambié de listas a conjuntos ya que esto guarda algunos caracteres adicionales. Las declaraciones se han reunido. Las comparaciones se han reunido y los caracteres fueron reemplazados por su valor ascii. La comparación de rango se puede expresar como muchos ternaries. Algunos trucos aquí y allá para evitar expresiones largas como Double.NEGATIVE_INFINITY se hicieron. Siempre que sea posible, se realizan asignaciones en línea. Para ahorrar un poco más, pasé de comparar los ángulos en grados a comparar los radianes. Todo el cambio salvó más de 500 caracteres, aunque espero tenerlo todo por debajo de 1000;)
Eliminé los genéricos cuando fue posible y acorté la comparación de retorno creando una matriz de un elemento y verifiqué su valor. También reemplacé el Double.NEGATIVE_INFINITY con PI2 y -PI2 ya que estos son los límites superior e inferior de los ángulos. ¡Ahora finalmente tiene menos de 1000 caracteres!
Fusioné los bucles para encontrar la ubicación de las personas y el iterador del edificio para guardar algunos personajes. Desafortunadamente, esto nos obliga a mover el retorno fuera del ciclo y aún usar un descanso, pero esta vez sin una etiqueta. Fusioné max
y distanceSquared
, y min
, y newDistanceSquared
, ya que no se requieren al mismo tiempo. He cambiado Integer.MAX_VALUE
a 2e31-1
. También creé una constante half = 0.5
que se usa para calcular las esquinas del edificio. Esto es más corto en la versión de golf. ¡En general, salvamos otros 30 caracteres!