Java 8 lambda, 1506 1002 972 942 caracteres
Eu queria vencer esse desafio, pois é muito interessante. O resultado (pouco golfe) pode ser visto aqui:
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;}
Claro que isso também existe na versão não-destruída:
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;
}
}
Parece muito difícil, mas é muito mais fácil do que se possa pensar. Minha primeira idéia foi usar um algoritmo de interseção para verificar se uma linha da minha posição até o prédio pode ser feita sem nenhuma interseção. Para fazer isso, decidi usar o algoritmo de Cohen-Sutherland e desenhar linhas para todos os quatro cantos do edifício. Isso funcionou muito bem nos primeiros testes, mas o último falhou. Logo descobri que é um caso em que você não pode ver os cantos, mas parte de uma aresta. Então pensei em algum tipo de casting de raios como o @Blue fez. Afastei esse desafio, pois não obtive algum progresso. Então eu vi a resposta de Blue e a seguinte idéia simples veio à minha mente: Cada construção bloqueia algum ângulo em que nada mais pode ser visto. Eu só preciso acompanhar o que pode ser visto e o que já está escondido por outros edifícios. É isso aí!
O algoritmo funciona da seguinte maneira: determina o edifício com a menor distância para a pessoa. Então imaginamos quatro linhas traçadas da pessoa para os cantos do edifício. Dois deles têm um valor extremo: o ângulo mínimo e máximo em que o edifício pode ser visto. Nós os tomamos como um intervalo e os comparamos com outros edifícios dos quais sabemos que podem ser vistos (nenhum no começo). Os intervalos podem se sobrepor, incluir um ao outro ou não tocar em nada. Comparo os intervalos e recebo novos intervalos do edifício que não estão ocultos pelos edifícios visíveis. Se ainda restar algo depois de compará-lo com os prédios à vista, o prédio também será visível. Adicionamos o intervalo de ângulo restante à lista de intervalos para comparar e começar com o próximo edifício com a próxima distância mais longa.
Às vezes, os intervalos podem se sobrepor de maneira que eu acabe com um intervalo de 0 graus. Esses intervalos serão filtrados para não adicionar por engano um prédio que nem seja visível.
Espero que alguém tenha entendido essa explicação :)
Eu sei que esse código não é muito jogado, vou fazer isso o mais rápido possível.
Essa foi uma tarefa realmente desafiadora. Você achou que havia encontrado uma solução que funcionasse, mas ainda está longe. Eu acho que essa solução funciona muito bem. Não é muito rápido, mas pelo menos funciona;) Obrigado por esse quebra-cabeça!
Atualizar
Encontrei tempo para jogar a coisa toda em uma única função, que pode ser transformada em uma lambda. Todas as funções foram chamadas apenas uma vez e, portanto, podem ser colocadas em um método. Mudei de listas para conjuntos, pois isso salva alguns caracteres adicionais. As declarações foram reunidas. As comparações foram reunidas e os caracteres foram substituídos pelo seu valor ASCII. O intervalo de comparação pode ser expresso como muitos ternários. Alguns truques aqui e ali para evitar expressões longas como Double.NEGATIVE_INFINITY. Sempre que possível, são feitas atribuições em linha. Para economizar um pouco mais, passei de comparar os ângulos em graus para comparar os radianos. Toda a mudança salvou mais de 500 caracteres, espero que tudo fique abaixo de 1000;)
Removai os genéricos sempre que possível e reduzi a comparação de retorno criando uma matriz de um elemento e verifiquei seu valor. Também substituí o Double.NEGATIVE_INFINITY por PI2 e -PI2, pois esses são os limites superior e inferior dos ângulos. Agora, finalmente, tem menos de 1000 caracteres!
Mesclei os loops para encontrar a localização das pessoas e o iterador da construção para salvar alguns caracteres. Infelizmente, isso exige que tiremos o retorno do loop e ainda utilizemos uma pausa, mas desta vez sem um rótulo. I fundiu max
e distanceSquared
e min
e newDistanceSquared
como eles não são necessários ao mesmo tempo. Eu mudei Integer.MAX_VALUE
para 2e31-1
. Também criei uma constante half = 0.5
que é usada para calcular os cantos do edifício. Isso é mais curto na versão golfed. No geral, salvamos outros 30 caracteres!