Dans quelle mesure cette pièce est-elle éclairée? 🔥 pt. 1


25

Lié à cette question .

Une pièce est définie comme un polygone (pas nécessairement convexe) sans intersection, exprimé sous la forme d'une liste ordonnée de coordonnées bidimensionnelles. Une ampoule suffisamment lumineuse est placée à un endroit précis à l'intérieur de la pièce et émet de la lumière dans toutes les directions. Votre tâche consiste à trouver la superficie totale éclairée de la pièce. Vous pouvez prendre des entrées dans n'importe quel format raisonnable. Les points sur le polygone / la pièce ainsi que les coordonnées de la source lumineuse sont des nombres rationnels. Ils peuvent être pris dans le sens horaire ou antihoraire, l'un ou l'autre format est très bien. Le cas de test dans le problème est donné dans le sens antihoraire.

L'image suivante montre deux exemples de pièces, où le point violacé représente la source de lumière et la région ombrée représente la zone éclairée.Un dessin d'une pièce éclairée - la zone ombrée est éclairée

Cas de test:

(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

Voici une représentation graphique de la solution de ce cas de test. Les deux points qui définissent la solution qui ne se trouvent pas sur le polygone d'origine sont (55/61, 363/61) et (856/55, 357/55). entrez la description de l'image ici

Cette formule peut être utile pour calculer la surface. https://en.wikipedia.org/wiki/Shoelace_formula

Puisqu'il s'agit de , le code le plus court en octets l'emporte.


Pour les curieux, la partie 2 peut prendre un certain temps à publier, car il me faudra une éternité pour dessiner les images, et je ne sais pas non plus comment le résoudre.
truqué le

Les points sur le polygone / la pièce ainsi que les coordonnées de la source lumineuse sont des nombres rationnels.
truqué le

Y a-t-il une limite supérieure sur le nombre de sommets ou votre programme devrait-il théoriquement être capable de gérer un nombre illimité? En outre, votre balise code-golf est cassée. c'est[tag:code-golf]
Veskah

3
Ah, la bonne vieille formule de lacet ! Soit dit en passant, nous avons en fait MathJax, vous n'avez donc pas besoin d'incorporer la formule en tant qu'image.
Giuseppe

1
Oui, ils peuvent alors être garantis dans le sens horaire. Le cas de test est ordonné dans le sens antihoraire, mais je pense que cela relève de «tout format raisonnable».
truqué le

Réponses:


12

Python 3 , 388 398 408 409 415 417 493 octets


Pour le rendre plus précis, augmentez 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)

Approche de base de Monte-Carlo. Étapes énumérées ci-dessous.

  1. Recherchez les plages x et y occupées par la forme.
  2. Créer une liste d'arêtes créées par les sommets
  3. Itérer un grand nombre de fois (plus c'est mieux, mieux c'est)
  4. Créez un point aléatoire (j, k) à l'intérieur de la plage x, y.
  5. Vérifiez si l'un des bords intercepte avec le segment de ligne créé par la lumière et le point aléatoire. Si l'un des bords intercepte, incrémentez la variables
  6. Diviser s par le nombre total, puis multipliez par la zone de plage totale.

Version non golfée:

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

Essayez-le en ligne!

Crédit pour l'algorithme d'intersection de lignes

En outre, remerciez tous les commentateurs utiles pour jouer au golf encore plus loin.


La première ligne peut devenir from random import*(saut de ligne) u=uniformpour -2 octets
Conor O'Brien

1
vous pouvez raser quelques octets supplémentaires en remplaçant chacun des 4 espaces de la fonction par un seul espace, et supprimez l'espace aprèsg=lambda i:
Conor O'Brien

Doit n- il être une puissance de 10? Sinon, vous pouvez enregistrer un octet en utilisant une puissance de 9.
Neil A.

Non, des pouvoirs de 10 ne sont pas requis. Je mettrai toutes vos suggestions demain! En attendant, bonne Saint Valentin à tous!
JPeroutek

Comme l'a mentionné @ ConorO'Brien , vous pouvez supprimer de nombreux espaces blancs de premier plan. Et en plus de l'espace à i:(min, l'espace à x[i]forpeut également être supprimé. , Aussi return float(s/n)*(r*t)peut être return(r*t)*float(s/n). Et je ne suis pas tout à fait sûr, mais ne peut pas les variables ret eêtre retiré et utilisé directement, puisque vous ne les utilisez une fois? Cela donne en quelque sorte un résultat légèrement différent même s'il gn'est pas modifié, de sorte que cette partie me confond un peu (je ne suis pas trop familier avec Python pour comprendre pourquoi le résultat est légèrement différent).
Kevin Cruijssen

5

Haskell , 559 618 632 octets

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

Solution exacte (sauf bogues). Haskell possède une arithmétique rationnelle exacte intégrée.Essayez-le en ligne!

Notez que cela donne 815523/6710, non 814643/6710, pour l'exemple de pièce, et la première intersection de mur est calculée comme(55/61, 363/61) . Je suis assez sûr que c'est correct car l'entrée Monte Carlo converge (lentement) vers le même résultat.

Légende:

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: interface graphique Gloss pour les tests. Cliquez à côté des points pour les déplacer.

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)

Capture d'écran


En fait, tu as raison. Je dois avoir fait une faute de frappe lol. Mettra à jour ces chiffres légèrement
truqué le

1

APL + WIN

Il s'agit d'une version non golfée de ce défi intéressant que je propose pour démontrer ma logique. Ma version ancienne d'APL + WIN n'est pas bien adaptée au golf des structures de contrôle imbriquées. Des APL plus modernes pourraient bien faire mieux - un défi?

Si les lecteurs valident la logique, je vais essayer de jouer au golf avec cette solution. Si la logique est erronée, je supprimerai simplement.

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 octets

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)

Essayez-le en ligne!

Ceci est une autre version réduite de la réponse Python . La méthode de base de Monte Carlo est la même, mais j'ai réorganisé certaines des fonctions pour les raccourcir. Dans ma première itération, j'avais été trop agressif dans le réarrangement, et j'ai ensuite réalisé que je pouvais optimiser à la fois la longueur et la vitesse en revenant à une version de l'algorithme d'intersection plus proche du python.

Voici une version non golfée qui trace également les résultats:

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)

Plot de lumière dans la chambre

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.