Jak oświetlony jest ten pokój? 🔥 pt. 1

25

Powiązane z tym pytaniem .

Pokój jest zdefiniowany jako (niekoniecznie wypukłą) nie przecinają się wielokąt, wyrażone w uporządkowanym wykazie współrzędnych 2-wymiarowych. Wystarczająco jasna żarówka jest umieszczona w określonym punkcie w pomieszczeniu i emituje światło we wszystkich kierunkach. Twoim zadaniem jest znalezienie całkowitej oświetlonej powierzchni pomieszczenia. Możesz przyjmować dane wejściowe w dowolnym rozsądnym formacie. Punkty na wielokącie / pomieszczeniu, a także współrzędne źródła światła są liczbami wymiernymi. Można je pobierać zgodnie z ruchem wskazówek zegara lub przeciwnie do ruchu wskazówek zegara, każdy format jest w porządku. Przypadek testowy dla problemu podano w kierunku przeciwnym do ruchu wskazówek zegara.

Poniższy obrazek pokazuje dwa przykładowe pokoje, w których fioletowa kropka reprezentuje źródło światła, a zacieniony obszar reprezentuje oświetlony obszar.Rysunek oświetlonego pokoju - zacieniony obszar jest oświetlony

Przypadek testowy:

(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

Oto graficzne przedstawienie rozwiązania tego przypadku testowego. Dwa punkty, które definiują rozwiązanie, które nie znajdują się na pierwotnym wielokącie, to (55/61, 363/61) i (856/55, 357/55). wprowadź opis zdjęcia tutaj

Ta formuła może być pomocna w obliczaniu powierzchni. https://en.wikipedia.org/wiki/Shoelace_formula

Ponieważ jest to , wygrywa najkrótszy kod w bajtach.

takielunek
źródło
Dla tych ciekawych część 2 może trochę potrwać, ponieważ narysowanie zdjęć zajmie mi wieczność, a także nie wiem, jak to rozwiązać.
sfałszowano
Punkty na wielokącie / pomieszczeniu, a także współrzędne źródła światła są liczbami wymiernymi.
sfałszowano
Czy istnieje górna granica liczby wierzchołków, czy teoretycznie program powinien być w stanie obsłużyć nieograniczoną liczbę? Ponadto kod tagu golf jest uszkodzony. to[tag:code-golf]
Veskah
3
Ach, stara, dobra formuła sznurowadła ! Nawiasem mówiąc, faktycznie mamy MathJax, więc nie musisz osadzać formuły jako obrazu.
Giuseppe
1
Tak, można zagwarantować, że zostaną zamówione zgodnie z ruchem wskazówek zegara. Przypadek testowy jest zamawiany w kierunku przeciwnym do ruchu wskazówek zegara, ale myślę, że mieści się on w „dowolnym rozsądnym formacie”
sfałszowany

Odpowiedzi:

12

Python 3 , 388 398 408 409 415 417 493 bajtów


Aby zwiększyć dokładność, zwiększ 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)

Podstawowe podejście Monte-Carlo. Kroki wymienione poniżej.

  1. Znajdź zakresy xiy zajmowane przez kształt.
  2. Utwórz listę krawędzi utworzonych przez wierzchołki
  3. Iteruj wiele razy (im więcej, tym lepiej)
  4. Utwórz losowy punkt (j, k) w zakresie x, y.
  5. Sprawdź, czy któraś z krawędzi przecina się z segmentem linii utworzonym przez światło i losowy punkt. Jeśli któraś z krawędzi przecina się, zwiększ wartość zmiennejs
  6. Podziel sprzez całkowitą liczbę, a następnie pomnóż przez całkowity obszar zasięgu.

Wersja bez golfa:

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

Wypróbuj online!

Kredyt za algorytm przecięcia linii

Również podziękowania dla wszystkich pomocnych komentatorów, jak pograć w golfa jeszcze bardziej.

JPeroutek
źródło
Pierwsza linia może zostać from random import*(podział linii) u=uniformdla -2 bajtów
Conor O'Brien
1
możesz ogolić więcej bajtów, zastępując każdą z 4 spacji w funkcji pojedynczą spacją i usuwając spację pog=lambda i:
Conor O'Brien
Czy nmusi to być moc 10? W przeciwnym razie możesz uratować bajt, używając mocy 9.
Neil A.
Nie, moce 10 nie są wymagane. Jutro przedstawię wszystkie twoje sugestie! Do tego czasu, życzymy wszystkim udanych walentynek!
JPeroutek
Jak wspomniano w @ ConorO'Brien , możesz usunąć mnóstwo wiodących białych znaków. Oprócz spacji w i:(min, spację w x[i]formożna również usunąć. Ponadto, return float(s/n)*(r*t)może być return(r*t)*float(s/n). Nie jestem do końca pewien, ale czy nie można usunąć zmiennych ri eużyć ich bezpośrednio, ponieważ używasz ich tylko raz? W jakiś sposób daje nieco inny wynik, mimo że gnie jest modyfikowany, więc ta część trochę mnie dezorientuje (nie jestem zbyt zaznajomiony z Pythonem, aby zrozumieć, dlaczego wynik jest nieco inny).
Kevin Cruijssen
5

Haskell , 559 618 632 bajtów

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

Dokładne rozwiązanie (z wyjątkiem błędów). Haskell ma wbudowaną dokładną racjonalną arytmetykę. Wypróbuj online!

Zauważ, że daje to 815523/6710nie 814643/6710przykładowe pomieszczenie, a pierwsze przecięcie ściany jest obliczane jako (55/61, 363/61). Jestem całkiem pewien, że jest to poprawne, ponieważ wpis w Monte Carlo (powoli) zbiega się do tego samego wyniku.

Legenda:

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: Połysk GUI do testowania. Kliknij obok punktów, aby je przenieść.

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)

Zrzut ekranu

japh
źródło
Właściwie masz rację. Musiałem zrobić literówkę. Lekko zaktualizuje te liczby
sfałszowano
1

APL + WIN

To jest nieogolona wersja tego interesującego wyzwania, które proponuję, aby zademonstrować moją logikę. Moja starożytna wersja APL + WIN nie jest odpowiednia dla zagnieżdżonych struktur kontrolnych. Bardziej nowoczesne APL mogłyby równie dobrze zrobić wyzwanie - wyzwanie?

Jeśli czytelnicy potwierdzą logikę, spróbuję zagrać w golfa w tym rozwiązaniu. Jeśli logika jest nieprawidłowa, po prostu usunę.

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
źródło
0

R , 296 255 bajtów

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)

Wypróbuj online!

To jest kolejna zmniejszona wersja odpowiedzi na Python . Podstawowa metoda Monte Carlo jest taka sama, ale zmieniłem niektóre funkcje, aby były krótsze. W pierwszej iteracji byłem zbyt agresywny w przestawianiu, a potem zdałem sobie sprawę, że mogę zoptymalizować zarówno długość, jak i prędkość, wracając do wersji algorytmu skrzyżowania bliżej pytona.

Oto wersja bez golfa, która również drukuje wyniki:

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)

Działka światła w pokoju

Nick Kennedy
źródło