Plotter curva algebrica


14

Una curva algebrica è un certo "sottoinsieme 1D" del "piano 2D" che può essere descritto come un insieme di zeri {(x,y) in R^2 : f(x,y)=0 }di un polinomio f. Qui consideriamo il piano 2D come il piano reale in R^2modo tale da poter facilmente immaginare come potrebbe apparire una tale curva, in pratica una cosa che puoi disegnare con una matita.

Esempi:

  • 0 = x^2 + y^2 -1 un cerchio di raggio 1
  • 0 = x^2 + 2y^2 -1 un'ellisse
  • 0 = xy una forma a croce , fondamentalmente l'unione dell'asse x e dell'asse y
  • 0 = y^2 - x una parabola
  • 0 = y^2 - (x^3 - x + 1)una curva ellittica
  • 0 = x^3 + y^3 - 3xy il folio di Cartesio
  • 0 = x^4 - (x^2 - y^2) un lemniscato
  • 0 = (x^2 + y^2)^2 - (x^3 - 3xy^2) un trifolium
  • 0 = (x^2 + y^2 - 1)^3 + 27x^2y^2 un astroide

Compito

Dato un polinomio f(come definito di seguito) e gli intervalli x / y, genera un'immagine in bianco e nero di almeno 100x100 pixel che mostra la curva come linea nera su uno sfondo bianco.

Dettagli

Colore : puoi usare altri due colori a tua scelta, dovrebbe essere facile distinguerli.

Trama : invece di un'immagine pixel puoi anche generare questa immagine come ascii-art, in cui i "pixel" di sfondo dovrebbero essere spazio / sottolineato o un altro carattere che "sembra vuoto" e la linea può essere fatta di un carattere che sembra " pieno "come Mo Xo #.

Non devi preoccuparti di aliasing.

Devi solo tracciare linee in cui il segno del polinomio cambia da un lato all'altro della linea (ciò significa che potresti ad esempio usare l'algoritmo del quadrato in marcia), non devi tracciare correttamente "casi patologici come 0 = x^2dove il segno fa non cambiare quando si passa da un lato all'altro della linea, ma la linea dovrebbe essere continua e separare le regioni dei diversi segni di f(x,y).

Polinomio : il polinomio è dato come una (m+1) x (n+1)matrice / elenco di elenchi di coefficienti (reali), nell'esempio seguente i termini dei coefficienti sono riportati nella loro posizione:

[   1 * 1,   1 * x,   1 * x^2,   1 * x^3,  ... , 1 * x^n ]
[   y * 1,   y * x,   y * x^2,   y * x^4,  ... , y * x^n ]
[   ...  ,   ...   ,   ...   ,    ...   ,  ... ,   ...   ]
[ y^m * 1, y^m * x, y^m * x^2, y^m * x^3 , ..., y^m * x^n]

Se preferisci, puoi assumere che la matrice sia quadrata (cosa che può sempre essere eseguita con il necessario riempimento di zero) e, se lo desideri, puoi anche supporre che la dimensione della matrice sia data come input adizionali.

Di seguito, gli esempi di cui sopra sono rappresentati come una matrice definita in questo modo:

Circle:       Ellipse:      Parabola:  Cross:    Elliptic Curve: e.t.c
[-1, 0, 1]    [-1, 0, 1]    [ 0,-1]    [ 0, 0]   [-1, 1, 0,-1]
[ 0, 0, 0]    [ 0, 0, 0]    [ 0, 0]    [ 0, 1]   [ 0, 0, 0, 0]
[ 1, 0, 0]    [ 2, 0, 0]    [ 1, 0]              [ 1, 0, 0, 0]

Casi di test con intervallo x / intervallo y:

(In un formato non così leggibile ma meglio copia-incolla disponibile qui su pastebin .)

Circle:     
[-1, 0, 1]   [-2,2]   [-2,2]
[ 0, 0, 0]
[ 1, 0, 0]

Ellipse:
[-1, 0, 1]   [-2,2]   [-1,1]
[ 0, 0, 0]
[ 2, 0, 0]

Cross:
[ 0, 0]      [-1,2]   [-2,1]
[ 0, 1]

Parabola:
[ 0,-1]      [-1,3]   [-2,2]
[ 0, 0]
[ 1, 0]

Elliptic Curve:
[-1, 1, 0,-1]    [-2,2]   [-3,3]
[ 0, 0, 0, 0]  
[ 1, 0, 0, 0]  

Folium of Descartes:
[  0,  0,  0,  1]    [-3,3]   [-3,3]
[  0, -3,  0,  0]
[  0,  0,  0,  0]
[  1,  0,  0,  0]

Lemniscate:
[  0,  0, -1,  0,  1]    [-2,2]   [-1,1]
[  0,  0,  0,  0,  0]
[  1,  0,  0,  0,  0]

Trifolium:
[ 0, 0, 0,-1, 1]    [-1,1]   [-1,1]
[ 0, 0, 0, 0, 0]
[ 0, 3, 2, 0, 0]
[ 0, 0, 0, 0, 0]
[ 1, 0, 0, 0, 0]

Astroid:
[ -1,  0,  3,  0, -3,  0,  1]    [-1,1]   [-1,1]
[  0,  0,  0,  0,  0,  0,  0]
[  3,  0, 21,  0,  3,  0,  0]
[  0,  0,  0,  0,  0,  0,  0]
[ -3,  0,  3,  0,  0,  0,  0]
[  0,  0,  0,  0,  0,  0,  0]
[  1,  0,  0,  0,  0,  0,  0]

Ho preso l'ispirazione per alcune curve da questo pdf.


" Non devi preoccuparti dell'aliasing " significa che possiamo semplicemente colorare ciascun pixel a seconda che il suo centro si trovi sulla linea?
Peter Taylor,

Non vedo il collegamento con l'aliasing. Ma no, dovrebbe esserci una linea continua che separa le regioni di segni diversi.
flawr

La matrice non è mx n, ma (m+1)x (n+1). Cosa prendiamo come input:, m, noppure m+1,n+1? Oppure possiamo scegliere?
Luis Mendo

Possiamo semplicemente mostrare la funzione grafica in una nuova finestra?
R. Kap

1
@LuisMendo Sì, l'asse può essere nella direzione che preferisci. (Finché sono ortogonali =)
flawr

Risposte:


10

Haskell, 283 275 byte

La funzione gdeve essere chiamata con la matrice e i due intervalli come argomenti. La matrice è solo un elenco di elenchi, gli intervalli ciascuno di un elenco di due elementi.

import Data.List
t=transpose
u=tail
z=zipWith
l%x=sum$z(*)l$iterate(*x)1                                   --generate powers and multiply with coefficients
e m y x=[l%x|l<-m]%y                                         --evaluate the encoded polynomial
a#b=[a,a+(b-a)/102..b]                                       --create a range
g m[u,w][i,j]=unlines$v[map((0<).e m y)$u#w|y<-i#j]          --evaluate the function on the grid, get the sign
f g=u[u$u$map fst$scanl(\(r,l)c->(c==l,c))(1<0,1<0) l|l<-g]  --find +- or -+ transitions within lines
a&b|a&&b=' '|0<1='#'                                         --helper function for creating the string
v g=z(z(&))(f g)(t$f$t g)                                    --create the string

Ecco le uscite per i casi più interessanti: Nota che ho dovuto ridimensionare la risoluzione da 100x100 a circa 40x40 in modo tale che si adatta perfettamente alla console (basta cambiare il 102 hardcoded in un numero più piccolo). Si noti inoltre che l'asse y è rivolto verso il basso.


Ci sono un paio di golf piuttosto piccoli che puoi fare qui. L'ultima riga utilizza parentesi quando è possibile utilizzare $per salvare un byte. Entrambi i luoghi in cui si utilizza mappotrebbero essere (<$>), e poiché si utilizza solo euna volta che è possibile estrarre l' (0<)interno è la definizione. Inoltre epotrebbe essere nominato (!)per salvare 3 byte.
Post Rock Garf Hunter,

E l'aggiunta znella definizione di vconsente di eliminare 4 parentesi (intorno z(&)e f g).
Post Rock Garf Hunter,

È inoltre possibile rinominare #un singolo carattere (ad es. s) E far corrispondere il motivo agli elenchi anziché g. (ad es. s[a,b]=[a,a+(b-a)/102..b];g m u i=unlines$v[m!y<$>s u|y<-s i])
Post Rock Garf Hunter,

6

Matlab, 114 100 92 byte

Lo strumento giusto per il lavoro? Uso l'interessante modo in cui Matlab printfgenera un polinomio come stringa. Questo polinomio può essere fornito al ezplotquale traccia la curva implicita sul dominio specificato. Per leggibilità il codice viene presentato con le nuove righe dopo; che non è necessario e non viene conteggiato per le dimensioni.

function P(A,W,H,h,w)
t=0:h*w-1;
ezplot(sprintf('+%d*x^%.0f*y^%d',[A(:)';t/h;rem(t,h)]),[W,H])

Progresso del golf come frammento espandibile.


Output dei casi di test (fare clic per la visualizzazione completa): Casi test


2
Soluzione davvero piacevole usando sprintf/ezplot!
Flawr,

L'uso di al fixposto di floorpotrebbe aiutarti a raggiungere il conteggio dei byte a due cifre :-)
Luis Mendo,

Puoi anche usare [h,w]=size(A);t=0:h*w-1;per salvare altri tre byte!
Flawr,

@LuisMendo In realtà, posso fare di meglio. Ero triste che il printf di Matlab non avesse un segnaposto intero, ma supporta comunque cose del genere %.0f. Ciò significa che posso eliminare del tutto il pavimento e lasciarlo printfriparare!
algmyr,

@flawr Uso la seconda parte nelle successive iterazioni. Mi rendo conto che la mia formattazione con l'ultima versione migliore non era perfettamente ovvia. Formattazione modificata per chiarire questo aspetto.
algmyr,

6

Python 2, 261 byte

E=enumerate
M,[a,c],[b,d]=input()
e=(c-a)/199.
I=200
J=-int((b-d)/e-1)
print'P2',I,J,255
i=I*J
while i:i-=1;x,y=c-i%I*e,b+i/I*e;u,v,w=map(sum,zip(*((z*p/x,z*q/y,z)for q,R in E(M)for p,t in E(R)for z in[t*x**p*y**q])));print int(255*min(1,(w*w/(u*u+v*v))**.5/e))

Formato di input: matrix,xbounds,ybounds(ad es[[-1,0,1],[0,0,0],[1,0,0]],[-2,2],[-2,2] .). Formato di output: PGM semplice .

Ciò stima la distanza da ogni centro di pixel alla curva usando l'approssimazione del primo ordine d ( x , y ) = | p ( x , y ) | / | ∇ p ( x , y ) |, dove ∇ p è il gradiente del polinomio p . (Questa è la distanza da ( x , y ) all'intersezione del piano tangente in ( x , y , p ( x , y )) con il piano xy .) Quindi i pixel in cui d (x , y) ha una larghezza di meno di un pixel della curva proporzionalmente a d ( x , y ), risultando in belle linee antialiaste (anche se non è un requisito).

produzione

Ecco gli stessi grafici con la funzione distanza divisa per 16 per renderla visibile.


Aspetta, quindi dove nel codice avviene l'effettiva rappresentazione grafica?
R. Kap

@ R.Kap Il codice scrive un'immagine in formato PGM semplice su stdout. C'è printun'istruzione per l'intestazione dell'immagine e printun'istruzione nel whileciclo per il valore di ciascun pixel.
Anders Kaseorg,

Wow, è davvero figo! Ti dispiacerebbe approfondire un po 'il tuo algoritmo di stampa?
Flawr,

@flawr Ho ampliato un po 'la spiegazione; risponde alle tue domande?
Anders Kaseorg,

@AndersKaseorg Sì, grazie mille!
Flawr

5

Python 3.5 + MatPlotLib + Numpy, 352 byte:

from matplotlib.pyplot import*;from numpy import*
def R(M,S,U,r=range):N=linspace;E='+'.join([str(y)+'*'+m for y,m in[q for i,g in zip(M,[[i+'*'+p for p in['1']+['x^%d'%p for p in r(1,len(M[0]))]]for i in['1']+['y^%d'%i for i in r(1,len(M))]])for q in zip(i,g)if q[0]]]);x,y=meshgrid(N(*S,200),N(*U,200));contour(x,y,eval(E.replace('^','**')),0);show()

Una funzione denominata. Abbastanza a lungo, ma ehi, sono solo felice di essere riuscito a portare a termine il compito. Accetta 3 input, che sono la m by nmatrice, il x-range e il y-range, che dovrebbero essere tutti in array (ad esempio,[[-1,0,1],[0,0,0],[1,0,0]],[-2,2],[-2,2] ). Emette il grafico completato in una nuova finestra grafica interattiva. Giocherò più a lungo quando posso, ma per ora, ne sono felice.

Risultati finali per i casi di test:

Uscita finale


5

MATL , 67 61 byte

8Wt:qwq/t2:"wid*2M1)+i:q!^]!2&!w[1IK2]&!**ss&eZS5Y62&Y+|4=0YG

Questo codice viene eseguito nella versione 18.5.0 della lingua, che precede la sfida. Ingresso utilizza opzionale m, ni parametri. La matrice ha punti e virgola come separatori di riga. Il formato di input esatto (usando la parabola come esempio) è

[-1,3]
3  
[-2,2]
2
[0,-1; 0, 0; 1, 0]

Il codice produce un'immagine con dimensioni 255 × 255. Questo può essere verificato utilizzando @Suever 's MATL linea compilatore, che, tra le altre caratteristiche molto interessanti, include output grafico. Vedi per esempio

Questo compilatore è ancora in una fase sperimentale. Si prega di segnalare eventuali problemi a @Suever in chat room MATL . Se il pulsante "Esegui" non funziona, prova ad aggiornare la pagina e fai di nuovo clic.

Se si preferisce l' output ASCII , il codice deve essere leggermente modificato (le modifiche riguardano solo i primi due e gli ultimi quattro caratteri del codice sopra):

101t:qwq/t2:"wid*2M1)+i:q!^]!2&!w[1IK2]&!**ss&eZS5Y62&Y+|4<42*c

Questo produce una griglia ASCII 100 × 100 che utilizza il carattere *per rappresentare la curva. Puoi anche provarlo con @Dennis ' Provalo online! piattaforma:

Si noti che le proporzioni dell'output ASCII vengono modificate perché i caratteri sono leggermente più alti di quelli larghi.

Spiegazione

Il codice prima calcola il polinomio a due variabili su una griglia x - y . Questo fa un uso pesante della trasmissione , calcolando un array 4D intermedio in cui ogni dimensione rappresenta valori x , valori y , esponenti x , y esponenti .

Da quella funzione, viene calcolata la linea di livello zero. Poiché la sfida specifica che è necessario rilevare solo i cambiamenti di segno, il codice applica la convoluzione 2D con un blocco 2 × 2 di quelli e contrassegna un pixel come appartenente alla linea se non i quattro valori del blocco hanno lo stesso segno.

8W      % Push 2^8, that is, 256. (The ASCII-output version pushes 101 instead)
t:q     % Duplicate. Push range [0 1 ... 255]
wq      % Swap. Subtract 1 to obtain 255
/       % Divide. Gives normalized range [0 1/255 2/255... 1]
t       % Duplicate
2:"     % For loop: do this twice
  w     %   Swap top two elements in the stack
  i     %   Input two-number array defining x range (resp. y in second iteration)
  d     %   Difference of the two entries
  *     %   Multiply by normalized range
  2M1)  %   Push the array again and get its first entry
  +     %   Add. This gives the range for x values (resp. y)
  i     %   Input m (n in second iteration)
  :q    %   Range [0 1 ...m-1] (resp. [0 1 ...n-1])
  !     %   Convert to column array
  ^     %   Power, element-wise with broadcast. This gives a matrix of size m×256
        %   (resp. n×256) of powers of x (resp. y) for the range of values computed
        %   previously
]       % End for loop
!       % Transpose. This transforms the n×256 matrix of powers of y into 256×n
2       % Push 2
&!      % Permute dimensions 1 and 3: transforms the 256×n matrix into a 4D array
        % of size 1×n×256×1
w       % Swap top two elements in the stack: bring 256×m matrix to top
[1IK2]  % Push vector [1 3 4 2]
&!      % Permute dimensions as indicated by the vector: transforms the m×256 matrix
        % into a 4D array of size m×1×1×256
*       % Multiply element-wise with broadcast: gives 4D array of size m×n×256×256
        % with mixed powers of x and y for at the grid of x, y values
*       % Implicitly input m×n matrix. Multiply element-wise with broadcast: gives
        % 4D array of size m×n×256×256
ss      % Sum along first two dimensions: gives 4D array of size 1×1×256×256
&e      % Squeeze singleton dimensions: gives matrix of size 256×256. This is the
        % two-variable polynomial evaluated at the x, y grid.
        % Now we need to find the zero level curve of this function. We do this by 
        % detecting when the sign of the function changes along any of the two axes
ZS      % Matrix of sign values (1, 0 or -1)
5Y6     % Predefined literal: matrix [1 1; 1 1]
2&Y+    % Compute 2D convolution, keeping only the valid (central) part
|4=     % True if absolute value of result is 4, which indicates no sign changes.
        % (The ASCII version computes a negated version of this, for better display)
0YG     % Display as image. (The ASCII-output version does the following instead:
        % multiply by 42 and convert to char. 42 is ASCII for '*', and character 0 
        % is shown as space. The 2D char array is then implicitly displayed)

Tutti i casi di test

Ecco tutti gli input nel formato appropriato, nel caso tu voglia provare:

Circle:
[-2,2]
3
[-2,2]
3
[-1, 0, 1; 0, 0, 0; 1, 0, 0]

Ellipse:
[-2,2]
3
[-1,1]
3
[-1, 0, 1; 0, 0, 0; 2, 0, 0]

Cross:
[-1,2]
2
[-2,1]
2
[0, 0; 0, 1]

Parabola:
[-1,3]
3  
[-2,2]
2
[0,-1; 0, 0; 1, 0]

Elliptic Curve:
[-2,2]
3
[-3,3]
4
[-1, 1, 0,-1; 0, 0, 0, 0; 1, 0, 0, 0]

Folium of Descartes:
[-3,3]
4
[-3,3]
4
[0,  0,  0,  1; 0, -3,  0,  0; 0,  0,  0,  0; 1,  0,  0,  0]


Lemniscate:
[-2,2]
3
[-1,1]
5
[0,  0, -1,  0,  1; 0,  0,  0,  0,  0; 1,  0,  0,  0,  0]

Trifolium:
[-1,1]
5
[-1,1]
5
[0, 0, 0,-1, 1; 0, 0, 0, 0, 0; 0, 3, 2, 0, 0; 0, 0, 0, 0, 0; 1, 0, 0, 0, 0]

Astroid
[-1,1]
7
[-1,1]
7
[-1,  0,  3,  0, -3,  0,  1; 0,  0,  0,  0,  0,  0,  0; 3,  0, 21,  0,  3,  0,  0; 0,  0,  0,  0,  0,  0,  0; -3,  0,  3,  0,  0,  0,  0; 0,  0,  0,  0,  0,  0,  0; 1,  0,  0,  0,  0,  0,  0]

2
Ancora più leggibile di Perl. Ottimo lavoro, anche un bel compilatore online!
Flawr,

@flawr più leggibile di Perl LOL. Per quanto riguarda il compilatore online, è tutto il lavoro di Suever!
Luis Mendo,

1
@flawr Now con convoluzione!
Luis Mendo,

1
<3 convoluzione!
Flawr,
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.