Java 8 lambda, 1506 1002 972 942 znaków
Chciałem pokonać to wyzwanie, ponieważ jest bardzo interesujące. Wynik (niezbyt golfowy) można zobaczyć tutaj:
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;}
Oczywiście istnieje to również w wersji bez golfa:
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;
}
}
Wygląda więc bardzo trudno, ale jest o wiele łatwiej, niż mogłoby się wydawać. Moim pierwszym pomysłem było użycie jakiegoś algorytmu skrzyżowania, aby sprawdzić, czy linia od mojej pozycji do budynku może być utworzona bez żadnych skrzyżowań. Aby to zrobić, zdecydowałem się użyć algorytmu Cohena-Sutherlanda i narysować linie do wszystkich czterech narożników budynku. To działało całkiem dobrze w pierwszych testach, ale ostatni nie powiódł się. Wkrótce przekonałem się, że jest to przypadek, w którym nie widać rogów, ale część krawędzi. Pomyślałem więc o jakimś castingu promieniowym, takim jak @Blue. Odłożyłem to wyzwanie, ponieważ nie osiągnąłem żadnego postępu. Potem zobaczyłem odpowiedź Blue i przyszedł mi do głowy następujący prosty pomysł: każdy budynek blokuje pewien kąt, pod którym nic więcej nie widać. Muszę tylko śledzić, co można zobaczyć i co jest już ukryte przez inne budynki. Otóż to!
Algorytm działa w następujący sposób: Określa budynek z najmniejszą odległością od osoby. Następnie wyobrażamy sobie cztery linie narysowane od osoby do narożników budynku. Dwa z nich mają ekstremalną wartość: minimalny i maksymalny kąt, pod którym można zobaczyć budynek. Bierzemy je jako zasięg i porównujemy z innymi budynkami, o których wiemy, że można je zobaczyć (żaden na początku). Zakresy mogą się nakładać, obejmować lub nie dotykać. Porównuję zakresy i otrzymuję nowe zakresy budynku, które nie są ukryte przez widoczne budynki. Jeśli po porównaniu z budynkami pozostanie coś jeszcze, budynek będzie również widoczny. Dodajemy pozostały zakres kątów do listy zakresów, aby porównać i zacząć od następnego budynku o następnej większej odległości.
Czasami zakresy mogą się pokrywać w taki sposób, że kończę na zakresie 0 stopni. Te zakresy zostaną przefiltrowane, aby nie pomyłkowo dodać budynku, który nawet nie jest widoczny.
Mam nadzieję, że ktoś zrozumiał to wytłumaczenie :)
Wiem, że ten kod nie jest bardzo golfowy, zrobię to jak najszybciej.
To było naprawdę trudne zadanie. Myślałeś, że znalazłeś rozwiązanie, które działa, ale zamiast tego wciąż jesteś daleko. Myślę, że to rozwiązanie działa całkiem dobrze. Nie jest bardzo szybki, ale przynajmniej działa;) Dzięki za tę łamigłówkę!
Aktualizacja
Znalazłem czas na grę w golfa w jedną funkcję, którą w ten sposób można przekształcić w lambdę. Wszystkie funkcje zostały wywołane tylko raz, dzięki czemu można je umieścić w jednej metodzie. Przełączyłem się z list na zestawy, ponieważ oszczędza to dodatkowe znaki. Deklaracje zostały zebrane razem. Porównania zostały zebrane, a postacie zastąpiono wartością ascii. Porównywanie zasięgu można wyrazić jako wiele trójskładników. Kilka sztuczek tu i tam, aby zapobiec długim wyrażeniom, takim jak Double.NEGATIVE_INFINITY. Tam gdzie to możliwe, wykonywane są przypisania liniowe. Aby zaoszczędzić trochę więcej, przerzuciłem się z porównywania kątów w stopniach na porównywanie radianów. Cała zmiana pozwoliła zaoszczędzić ponad 500 znaków, ale mam nadzieję, że uda się uzyskać wszystko poniżej 1000;)
Tam, gdzie to możliwe, usunąłem ogólne i skróciłem porównanie zwrotów, tworząc tablicę z jednym elementem i sprawdzając zamiast tego jej wartość. Zamieniłem również Double.NEGATIVE_INFINITY na PI2 i -PI2, ponieważ są to górne i dolne granice kątów. Teraz ma w końcu mniej niż 1000 znaków!
Połączyłem pętle w celu znalezienia lokalizacji osób i iteratora budynku, aby zapisać niektóre postacie. Niestety wymaga to od nas wycofania powrotu z pętli i nadal skorzystania z przerwy, ale tym razem bez etykiety. I połączyły max
i distanceSquared
a min
i newDistanceSquared
tak nie są one wymagane w tym samym czasie. Zmieniłem się Integer.MAX_VALUE
na 2e31-1
. Stworzyłem również stałą, half = 0.5
która służy do obliczania narożników budynku. Jest to krótsze w wersji golfowej. Ogółem ocaliliśmy kolejne 30 znaków!