Cidades: Sightlines

18

Estou na posição (0, 0) de uma cidade bidimensional infinita, perfeitamente dividida em blocos centralizados em cada ponto da rede, alguns dos quais contêm edifícios. Uma construção em um determinado ponto (x, y) ocupa todo o quadrado com cantos opostos em (x-.5, y-.5) e (x + .5, y + .5) , incluindo sua borda. Um edifício é visível se houver algum segmento de linha de (0, 0) até um ponto no edifício que não cruze nenhum outro edifício.

Por exemplo, eu (o @) posso ver 6 edifícios ( *) na seguinte cidade:

  *
 *
*
*@
x**
 *  y

Não consigo ver o prédio marcado com um x, em (-1, -1) porque está obstruído pelos dois adjacentes; ou aquele marcado com a yem (3, -2) porque está obstruído pela borda do edifício (1, -1) .

Entrada

Uma sequência multilinha ou lista de linhas, opcionalmente preenchida com espaços em um retângulo. Ele conterá apenas:

  • um único @(minha posição)
  • Espaços
  • *, que representam edifícios.

Sempre haverá pelo menos um edifício e, portanto, pelo menos um edifício visível.

Resultado

O número de edifícios visíveis.

Casos de teste

*@
1

* *******
 @     * 
7

*****
**@**
*****
4

   *
  **
@ **
2

*      *
 *    * 
@
4

@
 *
  ***
1

Obrigado a @Geobits pelo título .

lirtosiast
fonte
Relacionado.
Martin Ender
Sobre o caso de teste 3, ele é cercado por 8 *, mas o resultado é 4. Mas esses cantos não parecem estar bloqueados por outros edifícios. Existe uma regra para não incluir cantos?
LukStorms
1
@LukStorms Imagine que cada uma das estrelas seja na verdade cubos, como no minecraft. Se você estivesse no meio disso, seria capaz de ver apenas 4 quarteirões.
Blue
Você gostaria de esperar antes de eu entrar na minha solução de golfe (muito em breve) antes de conceder a recompensa? :)
Leif Willerts

Respostas:

4

Unidade + C #, 589 bytes

Esta é provavelmente a pior linguagem para se jogar código (leia-se: pior que Java), mas o Unity vem com muitos recursos úteis para esse desafio.

EDIT: perdeu alguns espaços, retorna o comprimento da lista, não o contador

Golfe:

using UnityEngine;using System.Collections;public class c:MonoBehaviour{public int h(string[]i){ArrayList k=new ArrayList();for(int y=0;y<i.Length;y++){char[]l=i[y].ToCharArray();int x=0;foreach(char c in l){if(c=='*'){GameObject b=GameObject.CreatePrimitive(PrimitiveType.Cube);b.transform.position=new Vector3(x,y);}if(c=='@')transform.position=new Vector3(x,y);x++;}}for(int n=0;n<3600;n++){RaycastHit h;Physics.Raycast(transform.position,Quaternion.Euler(0,0,n/10)*Vector3.up,out h);if(h.collider!=null){GameObject o=h.collider.gameObject;if(!k.Contains(o))k.Add(o);}}return k.Count;}}

Ungolfed:

using UnityEngine;
using System.Collections;

public class citiessightlines : MonoBehaviour {

    public ArrayList todelete;   // Anything concerning this array just has to do with cleanup of 
                                 //objects for testing, and doesn't contribute to the byte count.
    void Start()
    {
        todelete = new ArrayList();
    }
    public int calcSight(string[]input)
    {
        todelete = new ArrayList();
        int total = 0;
        ArrayList check = new ArrayList();
        for (int y=0;y < input.Length; y++)
        {
            char[] line = input[y].ToCharArray();
            for (int x = 0; x < line.Length; x++)
            {
                char c = line[x];
                if (c == '*')
                {
                    GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                    cube.transform.position = new Vector3(x, y);
                    todelete.Add(cube);
                }
                if (c == '@')
                {
                    transform.position = new Vector3(x, y);
                }
            }
        }
        for (int angle=0; angle < 3600; angle++)
        {
            RaycastHit hit;
            Physics.Raycast(transform.position, Quaternion.Euler(0, 0, angle/10) * Vector3.up, out hit);
            if (hit.collider!=null)
            {
                GameObject hitObject = hit.collider.gameObject;
                if (!check.Contains(hitObject)&&hitObject!=this)
                {
                    total += 1;
                    check.Add(hitObject);
                }
           }
        }
        return total;
    }
}

Eu usei 3600 raycasts porque falha no quinto caso de teste com menos. Ainda pode falhar em casos de teste ainda maiores / mais precisos.

Infelizmente, as compilações de webgl e desktop parecem ter problemas, então tudo o que tenho é o código-fonte para testar no github .

Azul
fonte
read: worse than JavaIsso é 383 bytes mais curto que a solução Java!
user8397947
@dorukayhan quero dizer que pela maioria dos built-ins são mais detalhado do que Java
Azul
Eu não sei sobre c #, mas você não poderia substituir total+=1por total++? Eu acho que outra maneira de salvar alguns personagens é criar o cubo do edifício e definir sua posição em uma declaração. Você não parece reutilizar a cubevariável em nenhum lugar.
Frozn
@Frozn Eu não estou realmente fazendo isso no meu código Golfed
Azul
Apenas olhou o código e viu que você alterou a contagem lá. Sempre assumo que a versão golfada é apenas uma versão com espaço em branco da versão mais longa, mas obviamente não é o caso aqui. Em relação à segunda parte: acho que sim. É GameObject b=GameObject.CreatePrimitive(PrimitiveType.Cube);b.transform.position=new Vector3(x,y);. Eu não sei se é possível em C #, mas em Java alguém poderia escrever GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position=new Vector3(x,y);.
Frozn 16/06/16
3

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 maxe distanceSquarede mine newDistanceSquaredcomo eles não são necessários ao mesmo tempo. Eu mudei Integer.MAX_VALUEpara 2e31-1. Também criei uma constante half = 0.5que é usada para calcular os cantos do edifício. Isso é mais curto na versão golfed. No geral, salvamos outros 30 caracteres!

Frozn
fonte
Bom golfe! Peguei uma rota mais fácil com todo o raycasting embutido, mas é bom saber que ajudei! (BTW eu provavelmente mudarei para sets também) #
Blue