Quão iluminada é esta sala? 🔥 pt. 1

25

Relacionado a esta pergunta .

Uma sala é definida como um polígono sem interseção (não necessariamente convexo), expresso como uma lista ordenada de coordenadas bidimensionais. Uma lâmpada suficientemente brilhante é colocada em um ponto específico dentro da sala e emite luz em todas as direções. Sua tarefa é encontrar a área total iluminada da sala. Você pode receber informações em qualquer formato razoável. Os pontos no polígono / sala, bem como as coordenadas da fonte de luz, são números racionais. Eles podem ser tirados no sentido horário ou anti-horário, qualquer um dos formatos é bom. O caso de teste no problema é fornecido no sentido anti-horário.

A figura a seguir mostra duas salas de exemplo, onde o ponto arroxeado representa a fonte de luz e a região sombreada representa a área iluminada.Um desenho de uma sala iluminada - a área sombreada é iluminada

Caso de teste:

(1/2, 18)
(1,3)
(5,1/2)
(7,5)
(12,7)
(16,3)
(15,11)
(8,19)
(3,7)
Light source located at (5,8)
Answer: 815523/6710 ≈ 121.538

Aqui está uma representação gráfica da solução para esse caso de teste. Os dois pontos que definem a solução que não está no polígono original são (55/61, 363/61) e (856/55, 357/55). insira a descrição da imagem aqui

Essa fórmula pode ser útil no cálculo da área. https://en.wikipedia.org/wiki/Shoelace_formula

Como esse é o , o código mais curto em bytes vence.

fraudado
fonte
Para os curiosos, a parte 2 pode demorar um pouco para postar, porque levarei uma eternidade para desenhar as imagens, e eu também não sei como resolvê-las.
fraudado
Os pontos no polígono / sala, bem como as coordenadas da fonte de luz, são números racionais.
equipado
Existe um limite superior no número de vértices ou o seu programa deve teoricamente ser capaz de lidar com um número ilimitado? Além disso, sua tag code-golf está quebrada. é[tag:code-golf]
Veskah
3
Ah, a boa e velha fórmula de cadarço ! A propósito, nós realmente temos o MathJax, então você não precisa incorporar a fórmula como uma imagem.
Giuseppe
1
Sim, eles podem ser garantidos para serem pedidos no sentido horário, então. O caso de teste é encomendado no sentido anti-horário, mas acho que isso se enquadra em "qualquer formato razoável".
manipulado em

Respostas:

12

Python 3 , 388 398 408 409 415 417 493 bytes


Para torná-lo mais preciso, aumente n

from random import*
u=uniform
c=lambda A,B,C:(C[1]-A[1])*(B[0]-A[0])>(B[1]-A[1])*(C[0]-A[0])
I=lambda A,B,C,D:c(A,C,D)!=c(B,C,D)and c(A,B,C)!=c(A,B,D)
def a(l,v,n=9**6,s=0):
 g=lambda i:(min(x[i]for x in v),max(x[i]for x in v))
 for _ in'x'*n:
  h=((u(*g(0)),u(*g(1))),l);s+=any([I(*f,*h)for f in list(zip(v,v[1:]+[v[0]]))])^1
 return(abs(g(0)[0]-g(0)[1])*abs(g(1)[0]-g(1)[1]))*float(s/n)

Abordagem básica de Monte-Carlo. Etapas listadas abaixo.

  1. Encontre os intervalos x e y que a forma ocupa.
  2. Crie uma lista de arestas criadas pelos vértices
  3. Iterar um grande número de vezes (quanto mais, melhor)
  4. Crie um ponto aleatório (j, k) dentro do intervalo x, y.
  5. Verifique se alguma das arestas intercepta com o segmento de linha criado pela luz e pelo ponto aleatório. Se alguma das arestas interceptar, aumente a variávels
  6. Divida spelo número total e multiplique pela área total do intervalo.

Versão não destruída:

import random

def ccw(A,B,C):
    return (C[1]-A[1])*(B[0]-A[0]) > (B[1]-A[1])*(C[0]-A[0])

def intersect(A,B,C,D):
    return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)

def lit_area(light, vertices):
    # points: list of points
    # i     : x => i=0
    #       : y => i=1
    get_range = lambda i: (min(x[i] for x in vertices), max(x[i] for x in vertices))
    xr = abs(get_range(0)[0] - get_range(0)[1])
    yr = abs(get_range(1)[0] - get_range(1)[1])

    edges = list(zip(vertices, vertices[1:] + [vertices[0]]))

    num_sims = 1000000

    num_successes = 0
    for _ in range(num_sims):
        guess_x = random.uniform(*get_range(0))
        guess_y = random.uniform(*get_range(1))

        light_guess_line = ((guess_x, guess_y), light)

        if not any([intersect(*e, *light_guess_line) for e in edges]):
            num_successes += 1
    return float(num_successes / num_sims) * (xr * yr)


if __name__ == "__main__":
    points = [
    (1/2, 18),
    (1,3),
    (5,1/2),
    (7,5),
    (12,7),
    (16,3),
    (15,11),
    (8,19),
    (3,7)
    ]
    light_source = (5,8)
    print("Area lit by light: %f"% lit_area(light_source, points))

Experimente online!

Crédito pelo algoritmo de interseção de linha

Além disso, agradeça a todos os comentaristas úteis sobre como jogar isso ainda mais.

JPeroutek
fonte
A primeira linha pode se tornar from random import*(quebra de linha) u=uniformpara -2 bytes
Conor O'Brien
1
você pode depilar mais alguns bytes substituindo cada um dos 4 espaços da função por um único espaço e removendo o espaço apósg=lambda i:
Conor O'Brien
Tem nque ser uma potência de 10? Caso contrário, você pode salvar um byte usando uma potência de 9.
Neil A.
Não, potências de 10 não são necessárias. Vou colocar todas as suas sugestões amanhã! Até lá, feliz dia dos namorados, pessoal!
JPeroutek 15/02
Como o @ ConorO'Brien mencionou, você pode remover muitos dos principais espaços em branco. Além do espaço em i:(min, o espaço em também x[i]forpode ser removido. Além disso, return float(s/n)*(r*t)pode ser return(r*t)*float(s/n). E eu não estou totalmente certo, mas não pode as variáveis re eser removido e usado diretamente, já que você só usá-los uma vez? De alguma forma, dá um resultado ligeiramente diferente, embora gnão seja modificado, de modo que essa parte me confunde um pouco (não estou muito familiarizado com o Python para entender por que o resultado é um pouco diferente).
Kevin Cruijssen 15/02
5

Haskell , 559 618 632 bytes

r(a:b)=b++[a]
s=zip<*>r
(?)a=sum.zipWith(*)a
o(a,b)=r a?b-a?r b
(a,b)!(c,d)=(c-a,d-b)
(a,b)#(c,d)=a*d-b*c
x i a@(e,f)b j c d|let k@(g,h)=a!b;l=c!d;m=c!a;n=l#k;o=m#l/n;p=m#k/n;q|i>0=o<0||o>1|let=o<=0||o>=1;r|n==0||q||p<0||p*j>1=[]|let=[(e+o*g,f+o*h)]=r
(a&b)(c:e@(d:_))|let(f,g)=span(/=d)b;h=zip f$r$f++[d]=concat[[k,l]|(i,j)<-h,[[k],[l]]<-[x 1 i j 0 a<$>[c,d]],and[x 0 m n 1 a o==[]|o<-[k,l],(m,n)<-h,(m,n)/=(i,j)]]++(a&g)e
(_&_)_=[]
z a b=sum[o$unzip[c,a,d]|e@(f:_)<-[[c|c<-b,and[all(==c)$x 1 d e 1 a c|(d,e)<-s b]]],(c,d)<-s$a&until((f==).head)r b$e++[f]]/2

Solução exata (salvo erros). Haskell integrou aritmética racional exata. Experimente online!

Observe que isso fornece 815523/6710, não 814643/6710, para a sala de exemplo, e a primeira interseção da parede é calculada como (55/61, 363/61). Tenho certeza de que isso está correto porque a entrada de Monte Carlo (lentamente) converge para o mesmo resultado.

Lenda:

z light roomPoints
    -- Main function, returns lit area.
    -- Compute list of visible corners in the room, then calls (&).
(&) light roomPoints' visibleCorners
    -- Compute visibility polygon. visibleCorners is the subset of points
    -- that are visible from the light. The first point of roomPoints'
    -- must coincide with the first visibleCorner.
x pEndpoints p1 p2 qSegment q1 q2
    -- Intersect line segments (p1, p2) and (q1, q2).
    -- If pEndpoints, exclude endpoints p1, p2.
    -- If not qSegment, allow intersection to extend past q2 (i.e. raycast).
r   -- Rotate list by one, used to construct closed loops etc.
s   -- Construct closed loop
(!) -- Vector between two points
(?) -- Dot product
(#) -- Cross product
o   -- Polygon area

Bônus: GUI brilhante para testes. Clique ao lado dos pontos para movê-los.

import qualified Graphics.Gloss as G
import qualified Graphics.Gloss.Interface.IO.Interact as GI

solnPoly a b|let c@(d:_)=[c|c<-b,and[all(==c)$x 1 d e 1 a c|(d,e)<-s b]]=a&until((d==).head)r b$c++[d]
solnArea = z

main =
  let fromRatP (x, y) = (fromRational x, fromRational y)
      displayScale = 10
      scalePoints = G.scale (fromInteger displayScale) (fromInteger displayScale)
      displayMode = G.InWindow "" (512, 512) (0, 0)
      drawBasePoly pointSz ps =
          mconcat $ G.lineLoop ps :
                    [G.translate x y (G.circleSolid pointSz) | (x, y) <- ps]
      drawVisPolyOf light ps =
          G.color G.blue $ drawBasePoly 0.2 $ map fromRatP $ solnPoly light ps
      drawLight (x, y) =
          G.translate x y $
          G.color G.yellow (G.circleSolid 0.5) <> G.circle 0.5
      draw (light, ps) =
          mconcat [
              scalePoints $ drawLight (fromRatP light),
              scalePoints $ drawBasePoly 0.4 (map fromRatP ps),
              scalePoints $ drawVisPolyOf light ps,
              G.translate (-200) (-50) $ G.scale 0.2 0.2 $
                G.color G.blue $ G.text $ "Lit area: " ++ show (solnArea light ps)
          ]
      event (GI.EventKey (GI.MouseButton GI.LeftButton) GI.Down _ (curx_, cury_)) (light, ps) =
          let dist (x,y) (x',y') = (x'-x)^2 + (y'-y)^2
              curx = curx_ / fromInteger displayScale
              cury = cury_ / fromInteger displayScale
              cursorR = (fromInteger$round curx, fromInteger$round cury)
              maxDist = 3
              snapAmount = 1
              (d, i) = minimum [(dist p cursorR, i) | (p, i) <- zip (light : ps) [0..]]
              snapTo n a = fromInteger$n*round(a/fromInteger n)
              snapCursor = (snapTo snapAmount curx, snapTo snapAmount cury)
              light' | i == 0 && d < maxDist^2 = snapCursor
                     | otherwise = light
              ps' | i > 0 && d < maxDist^2 = take (i-1) ps ++ [snapCursor] ++ drop i ps
                  | otherwise = ps
          in (light', ps')
      event _ state = state
      state0 =
        ((2, 2), [(0, 0), (10, 0), (10, 5), (20, 0), (20, 20), (15, 5),
                  (10, 10), (6, 10), (10, 12), (0, 12), (4, 10), (0, 10)])
  in G.play displayMode G.white 60
            state0
            draw
            event
            (\_ -> id)

Captura de tela

japh
fonte
Na verdade, você está certo. Eu devo ter feito um erro de digitação lol. Atualizarão esses números levemente
fraudados
1

APL + WIN

Esta é uma versão não destruída deste desafio interessante que ofereço para demonstrar minha lógica. Minha versão antiga do APL + WIN não é adequada para estruturas de controle aninhadas de golfe. APLs mais modernos poderiam se sair melhor - desafiar?

Se os leitores validarem a lógica, tentarei jogar esta solução no golfe. Se a lógica estiver errada, simplesmente excluirei.

r←b Room v

⍝Separate x and y coordinates of vertices               
x←v[;1] ⋄ y←v[;2]

⍝Intercept and slope of each line segment and ray through each vertex
s←(y,¨1⌽y)⌹¨(1E¯9+1,[1.1]¨x,¨1⌽1E¯9+x)
l←(y,¨b[2])⌹¨(1E¯9+1,[1.1]¨x,¨b[1]+1E¯9)                          

⍝Coordinates of vertices
x←x,¨1⌽x ⋄ y←y,¨1⌽y                                                  

⍝Initialise intersection matrix
r←((⍴s),0)⍴0

⍝Evaluate intersection matrix 
:for i :in ⍳⍴l 
    t←0⍴0
    :for j :in ⍳⍴s
        t←t,⍎1⍕∊((↑∊l[i])-↑∊s[j])÷((1↓∊s[j])-1↓∊l[i]) 
    :endfor
    z←r←r,t
:endfor 

⍝Identify x coordinates of valid intersections in the direction of the ray
:for i :in ⍳⍴l 
    t←(r[i;i])
    :for j :in ⍳⍴s
        :if t<b[1] 
            r[j;i]←r[j;i]×(r[j;i]<t)^r[j;i]>⌊/∊x[i]
        :else
            r[j;i]←r[j;i]×(r[j;i]>t)^r[j;i]<⌈/∊x[i]
        :endif
    :endfor
 :endfor

⍝Identify the edges intersected
e←(+/r≠0)/⍳⍴l 

⍝Intersection x coordinates
cx←(+/r)[e]

⍝Intersection y coordinates
cy←⍎1⍕+/¨(s[e])× 1,¨(+/r)[e]

⍝Replace original coordinates that are in shadow
x[e]←(1↓¨x[e]),¨cx
y[e]←(1↓¨y[e]),¨cy

⍝Calculate lit area
r←+/.5×(|-/¨x)×|-/¨y                                               
Graham
fonte
0

R , 296 255 bytes

function(s,l,u=cbind(s,s[,1]),d=t(diff(t(u))),q=l-u,r=apply(s,1,range),g=c(diff(r)))mean(replicate(1e6,!any((q[i<-1:ncol(s)*2]*(p=runif(2)*g+r[1,]-u)[j<-i-1]>p[i]*q[j])!=(q[i+2]*p[i+1]>q[i+1]*p[i+2])&(p[i]*d[j]>p[j]*d[i])!=(q[i]*d[j]>q[j]*d[i]))))*prod(g)

Experimente online!

Esta é outra versão reduzida da resposta Python . O método básico de Monte Carlo é o mesmo, mas reorganizei algumas das funções para reduzi-las. Na minha primeira iteração, eu tinha sido muito agressivo no rearranjo, e então percebi que podia otimizar o comprimento e a velocidade retornando a uma versão do algoritmo de interseção mais próxima do python.

Aqui está uma versão não destruída que também plota os resultados:

find_lit_ungolf <- function(shape, light, plot = TRUE) {
  lines <- cbind(shape ,shape[,1])
  diffs <- t(diff(t(lines)))
  light_minus_lines <- light - lines
  shape_range <- apply(s,1,range)
  shape_range_diff <- c(diff(shape_range))
  successes <- t(replicate(
    n = 1e5,
    {
      random_point <- runif(2) * shape_range_diff + shape_range[1, ]
      random_minus_lines <- random_point - lines
      q <- light_minus_lines
      p <- random_minus_lines
      d <- diffs
      i <- 1:ncol(s)*2
      success <-
        !any((q[i]*p[i-1]>p[i]*q[i-1])!=(q[i+2]*p[i+1]>q[i+1]*p[i+2])&(p[i]*d[i-1]>p[i-1]*d[i])!=(q[i]*d[i-1]>q[i-1]*d[i]))
      c(random_point, success)
    }))
  colnames(successes) <- c("x", "y", "success")
  if (plot) {
    shape <- t(shape)
    colnames(shape) <- c("x", "y")
    print(ggplot(as_tibble(successes), aes(x, y)) +
      geom_point(aes(colour = factor(success)), alpha = 0.3) +
      geom_polygon(data = as_tibble(shape), alpha = 0.2) +
      annotate("point", light[1], light[2], col = "yellow"))
  }
  mean(successes[, 3]) * prod(shape_range_diff)
}
find_lit_ungolf(s, l)

Lote de luz no quarto

Nick Kennedy
fonte