Java 8 лямбда, 1506 1002 972 942 символів
Мені хотілося перемогти цей виклик, оскільки це дуже цікаво. Результат (не дуже вибагливий) можна побачити тут:
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;}
Звичайно, це також існує у версії, що не має волі:
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;
}
}
Це виглядає дуже складно, але це простіше, ніж можна подумати. Моя перша ідея полягала в тому, щоб використовувати якийсь алгоритм перетину, щоб перевірити, чи може бути проведена лінія від мого положення до будівлі без будь-яких перетинів. Для цього я вирішив скористатися алгоритмом Коена-Сазерленда та намалювати лінії до всіх чотирьох кутів будівлі. Це працювало досить добре для перших тестів, але останнє не вдалося. Я незабаром з’ясував, що це випадок, коли ви можете бачити не кути, а частину краю. Тому я подумав про якийсь рентгенівський кастинг, як @Blue, це зробив. Я вирішив це завдання, оскільки не досяг певного прогресу. Тоді я побачив відповідь Блю і мені прийшла в голову наступна проста ідея: Кожен будівельний блок блокує певний кут, в якому нічого іншого не видно. Мені просто потрібно стежити за тим, що можна побачити і що вже приховано іншими будівлями. Це воно!
Алгоритм працює так: Він визначає будівлю з найменшою відстані до людини. Тоді ми уявляємо чотири лінії, проведені від людини до кутів будівлі. Дві з них мають надзвичайне значення: Мінімальний і максимальний кут, під яким можна побачити будівлю. Ми приймаємо їх як діапазон і порівнюємо їх з іншими будівлями, про які ми знаємо, що їх можна побачити (на початку жодна). Діапазони можуть перетинатися, включати один одного або взагалі не торкатися. Я порівнюю діапазони і отримаю нові діапазони будівлі, які не заховані видимими будівлями. Якщо після порівняння його з будівлями, що залишилися, залишилось щось, будівля також може бути видно. Залишився діапазон кутів до списку діапазонів для порівняння та розпочнемо з наступної будівлі з наступної більшої відстані.
Іноді діапазони можуть перетинатися таким чином, що я закінчую діапазон 0 градусів. Ці діапазони будуть відфільтровані, щоб помилково не додати будівлю, яку навіть не можна переглянути.
Сподіваюся, хтось зрозумів це пояснення :)
Я знаю, що цей код не дуже гольф, я зроблю це якнайшвидше.
Це було справді складне завдання. Ви думали, що знайшли рішення, яке працює, але натомість ви ще далеко. Я думаю, що це рішення працює досить добре. Це не дуже швидко, але принаймні це працює;) Дякую за цю головоломку!
Оновлення
Я знайшов час для того, щоб переграти все це в єдину функцію, яку таким чином можна перетворити на лямбда. Усі функції були викликані лише один раз, і тому їх можна об'єднати в один метод. Я перейшов зі списків на набори, оскільки це економить додаткові символи. Декларації були складені разом. Порівняння були зібрані і символи були замінені на значення ascii. Порівнюючи діапазон можна виразити як багато тернарів. Деякі хитрощі тут і там, щоб запобігти довгим виразам, як Double.NEGATIVE_INFINITY було зроблено. По можливості проводяться рядкові призначення. Щоб заощадити трохи більше, я перейшов від порівняння кутів у градусах до порівняння радіанів. Ціла зміна врятувала понад 500 символів, я сподіваюся, що все це буде менше 1000;
Я видалив дженерики, де це можливо, і скоротив порівняльний показник, створивши масив одного елемента і перевірив його значення. Я також замінив Double.NEGATIVE_INFINITY на PI2 та -PI2, оскільки це верхня та нижня межі кутів. Тепер нарешті менше 1000 символів!
Я об'єднав петлі для пошуку місця розташування осіб та ітератора будівлі, щоб зберегти деякі символи. На жаль, це вимагає, щоб ми повернули вихід із циклу і все-таки використали перерву, але цього разу без мітки. Я злився max
і distanceSquared
та min
і newDistanceSquared
як вони не потрібні , в той же час. Я змінився Integer.MAX_VALUE
на 2e31-1
. Також я створив константу, half = 0.5
яка використовується для обчислення кутів будівлі. Це коротше у гольф-версії. Загалом ми зберегли ще 30 символів!