Quanto è illuminata questa stanza? 🔥 pt. 1


25

In relazione a questa domanda .

Una stanza è definita come un poligono non intersecante (non necessariamente convesso), espresso come un elenco ordinato di coordinate bidimensionali. Una lampadina sufficientemente luminosa viene posizionata in un punto specifico all'interno della stanza ed emette luce in ogni direzione. Il tuo compito è trovare l'area illuminata totale della stanza. È possibile ricevere input in qualsiasi formato ragionevole. I punti sul poligono / stanza e le coordinate della sorgente luminosa sono numeri razionali. Possono essere presi in senso orario o antiorario, entrambi i formati vanno bene. Il caso di prova nel problema è indicato in senso antiorario.

L'immagine seguente mostra due stanze di esempio, in cui il punto violaceo rappresenta la sorgente luminosa e la regione ombreggiata rappresenta l'area illuminata.Un disegno di una stanza illuminata - l'area ombreggiata è illuminata

Caso di prova:

(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

Ecco una rappresentazione grafica della soluzione a quel caso di test. I due punti che definiscono la soluzione che non si trovano sul poligono originale sono (55/61, 363/61) e (856/55, 357/55). inserisci qui la descrizione dell'immagine

Questa formula può essere utile nel calcolo dell'area. https://en.wikipedia.org/wiki/Shoelace_formula

Dato che si tratta di , vince il codice più breve in byte.


Per i curiosi, la seconda parte potrebbe impiegare un po 'di tempo a postare, perché mi ci vorrà un'eternità a disegnare le foto, e anche io non so come risolverlo.
truccato il

I punti sul poligono / stanza e le coordinate della sorgente luminosa sono numeri razionali.
truccato il

C'è un limite superiore al numero di vertici o il tuo programma dovrebbe teoricamente essere in grado di gestire un numero illimitato? Inoltre, il tuo tag code-golf è rotto. è[tag:code-golf]
Veskah,

3
Ah, la buona vecchia formula lacci delle scarpe ! A proposito, in realtà abbiamo MathJax, quindi non è necessario incorporare la formula come immagine.
Giuseppe,

1
Sì, possono essere garantiti per essere ordinati in senso orario, quindi. Il caso di test è ordinato in senso antiorario, ma penso che questo rientri in "qualsiasi formato ragionevole".
truccato il

Risposte:


12

Python 3 , 388 398 408 409 415 417 493 byte


Per renderlo più preciso, aumentare 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)

Approccio di base a Monte-Carlo. Passaggi elencati di seguito.

  1. Trova gli intervalli xey che occupa la forma.
  2. Crea un elenco di spigoli creati dai vertici
  3. Iterate un gran numero di volte (più meglio è)
  4. Crea un punto casuale (j, k) all'interno dell'intervallo x, y.
  5. Controlla se uno dei bordi intercetta con il segmento di linea creato dalla luce e dal punto casuale. Se uno dei bordi si intercetta, incrementare la variabiles
  6. Dividi sper i numeri totali, quindi moltiplica per l'area dell'intervallo totale.

Versione non golfata:

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))

Provalo online!

Credito per algoritmo di intersezione di linea

Inoltre, merito a tutti i commentatori utili su come giocare a golf ancora di più.


La prima riga può diventare from random import*(interruzione di riga) u=uniformper -2 byte
Conor O'Brien il

1
puoi radere qualche altro byte sostituendo ciascuno dei 4 spazi nella funzione con un singolo spazio e rimuovere lo spazio dopog=lambda i:
Conor O'Brien,

Deve nessere una potenza di 10? Altrimenti puoi salvare un byte usando una potenza di 9.
Neil A.

No, non sono richiesti poteri di 10. Domani inserirò tutti i tuoi suggerimenti! Fino ad allora, buon San Valentino a tutti!
JPeroutek,

Come accennato da @ ConorO'Brien , puoi rimuovere un sacco di spazi bianchi principali. E oltre allo spazio in i:(min, è x[i]forpossibile rimuovere anche lo spazio in . Inoltre, return float(s/n)*(r*t)può essere return(r*t)*float(s/n). E io non sono del tutto sicuro, ma non riesco a variabili re eessere rimossi e utilizzati direttamente, dal momento che li usa solo una volta? In qualche modo dà un risultato leggermente diverso anche se gnon viene modificato, quindi quella parte mi confonde un po '(non ho troppa familiarità con Python per capire perché il risultato è leggermente diverso).
Kevin Cruijssen,

5

Haskell , 559 618 632 byte

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

Soluzione esatta (esclusi i bug). Haskell ha un'aritmetica razionale esatta incorporata. Provalo online!

Si noti che ciò fornisce 815523/6710, non 814643/6710, per la stanza di esempio, e il primo incrocio di muro viene calcolato come (55/61, 363/61). Sono abbastanza sicuro che questo sia corretto perché la voce Monte Carlo (lentamente) converge allo stesso risultato.

Leggenda:

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

Bonus: GUI Gloss per i test. Fai clic su accanto ai punti per spostarli.

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)

Immagine dello schermo


In realtà, hai ragione. Devo aver fatto un refuso lol. Aggiornerà leggermente quei numeri
truccati il

1

APL + WIN

Questa è una versione non golfata di questa interessante sfida che offro per dimostrare la mia logica. La mia antica versione di APL + WIN non è adatta alle strutture di controllo annidate del golf. APL più moderni potrebbero fare di meglio - sfida?

Se i lettori convalidano la logica, proverò a giocare a golf con questa soluzione. Se la logica è sbagliata, eliminerò semplicemente.

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                                               

0

R , 296 255 byte

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)

Provalo online!

Questa è un'ulteriore versione ridotta della risposta Python . Il metodo principale di Monte Carlo è lo stesso, ma ho riorganizzato alcune funzioni per renderle più brevi. Nella mia prima iterazione, ero stato troppo aggressivo nel riarrangiamento, e poi mi sono reso conto che potevo ottimizzare sia la lunghezza che la velocità tornando a una versione dell'algoritmo di intersezione più vicino al pitone.

Ecco una versione non golfata che traccia anche i risultati:

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)

Trama di luce nella stanza

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.