PHP Mandelbrot Set Generator
ฉันต้องทำความคุ้นเคยกับการเขียนโปรแกรมบนเว็บดังนั้นฉันจึงตัดสินใจเขียนหน้า PHP ที่ค่อนข้างเรียบง่ายซึ่งยอมรับคำขอ GET และให้บริการภาพของชุด Mandelbrot ... เป็นตารางของเซลล์สี
ตัวอย่าง:
และ
ฉันส่งการตอบกลับเป็นตารางเพราะฉันยังไม่ต้องการใช้ไลบรารีรูปภาพดังนั้นฉันจึงอยากลองทำทั้งหมดโดยใช้ HTML และฉันไม่สามารถคิดวิธีที่ง่ายกว่านี้ได้
โดยทั่วไปวิธีการทำงานคือคุณระบุตำแหน่งที่คุณต้องการดูในชุดโดยระบุช่อง "ขอบเขต" ขนาดของมุมมองและตารางอัตราส่วนภาพ (เป็นทศนิยม) จากนั้นช่องสีเก้าช่องที่กำหนดว่าภาพ จะเป็นสี
ตรรกะหลักคือพอร์ตของรหัส Cของฉันจากปีที่แล้วและแนวคิดการระบายสีถูกดึงมาจากโครงการที่ฉันทำเมื่อหลายปีก่อนใน Clojure
ฉันกำลังมองหาคำแนะนำเกี่ยวกับอะไร ฉันไม่ได้ใช้ HTML, PHP หรือ CSS มาหลายปีแล้วและฉันก็ไม่เคยดีกับพวกเขามาก่อน มีจำนวนมากที่นี่ดังนั้นฉันไม่คาดหวังว่าจะได้รับการตรวจสอบอย่างละเอียดถี่ถ้วน แต่จะมีอะไรชื่นชม
โดยเฉพาะแม้ว่า:
ฟังก์ชัน
new_color_f()คือหายนะ โดยพื้นฐานแล้วฉันต้องการสร้างฟังก์ชันที่มีการตั้งค่าพารามิเตอร์ไว้แล้วและฉันทำเช่นนั้นโดยปิดทับพารามิเตอร์ของฟังก์ชันปิดล้อม แม้ว่ามันจะยาวและน่าเกลียดและทำให้useไวยากรณ์ของ PHP แย่ลง ข้อเสนอแนะใด ๆ จะได้รับการชื่นชมฟังก์ชั่นก็
defaulting_get()ดูเหมือนกลิ่นเช่นกัน ฉันต้องพิจารณาว่าข้อมูล GET หายไปและอาจว่างเปล่า ซึ่งนำไปสู่การใช้การผสมผสาน??และ?:ตัวดำเนินการที่แปลกประหลาด
ยกเว้น:
- ใช่นี้คือโง่ที่จะทำใน PHP ที่ผมและก็ยังโง่ที่จะทำนี้ใช้
tableHTML ทำให้เกิดปัญหากับรูปแบบการป้อนข้อมูลที่ถูกล้างทุกรายการและยังทำให้เบราว์เซอร์ล่าช้าอย่างไม่น่าเชื่อเมื่อได้รับตารางขนาดใหญ่ ฉันอยากจะได้รับประสบการณ์กับความคิด PHP ก่อนที่จะลองทำอะไรที่ซับซ้อนกว่านี้และฉันชอบโครงการนี้มาตลอด จริงๆแล้วฉันควรทำสิ่งนี้ใน JavaScript หรือใช้ AJAX เพื่อขอรูปภาพแทนการใช้แบบฟอร์ม
เซิร์ฟเวอร์กำลังเรียกใช้ PHP 7.4 สำหรับการอ้างอิง
index.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mandelbrot Generator</title>
<style>
.display_table {
font-size: 1px;
border-spacing: 0;
}
.input_form {
display: table;
}
.input_row {
display: table-row;
}
.input_row label, .input_row input {
display: table-cell;
}
.red_input input, .green_input input, .blue_input input {
border-width: 5px;
}
.red_input input {
border-color: red;
}
.green_input input {
border-color: lightgreen;
}
.blue_input input {
border-color: blue;
}
</style>
</head>
<body>
<form method="get" class="input_form">
<div class="input">
<div class="location_input">
<div class="input_row">
<label>Lower Real Bound</label>
<input name="lreal" type="number" min="-2" max="2" step="any">
</div>
<div class="input_row">
<label>Lower Imaginary Bound</label>
<input name="limag" type="number" min="-2" max="2" step="any">
</div>
<div class="input_row">
<label>View Width</label>
<input name="vwidth" type="number" min="0" max="4" step="any">
</div>
<div class="input_row">
<label>Pixels Wide</label>
<input name="pwidth" type="number" min="0" max="1000" step="any">
</div>
<div class="input_row">
<label>Aspect Ratio</label>
<input name="aratio" type="number" min="0" max="2" step="any">
</div>
</div>
<div class="color_input">
<div class="red_input">
<div class="input_row">
<label>Real</label>
<input name="rr" type="number" min="-1000" max="1000" step="any">
</div>
<div class="input_row">
<label>Imaginary</label>
<input name="rim" type="number" min="-1000" max="1000" step="any">
</div>
<div class="input_row">
<label>Iters</label>
<input name="rit" type="number" min="-1000" max="1000" step="any">
</div>
</div>
<div class="green_input">
<div class="input_row">
<label>Real</label>
<input name="gr" type="number" min="-1000" max="1000" step="any">
</div>
<div class="input_row">
<label>Imaginary</label>
<input name="gim" type="number" min="-1000" max="1000" step="any">
</div>
<div class="input_row">
<label>Iters</label>
<input name="git" type="number" min="-1000" max="1000" step="any">
</div>
</div>
<div class="blue_input">
<div class="input_row">
<label>Real</label>
<input name="br" type="number" min="-1000" max="1000" step="any">
</div>
<div class="input_row">
<label>Imaginary</label>
<input name="bim" type="number" min="-1000" max="1000" step="any">
</div>
<div class="input_row">
<label>Iters</label>
<input name="bit" type="number" min="-1000" max="1000" step="any">
</div>
</div>
</div>
</div>
<button type="submit">Submit</button>
</form>
</body>
</html>
<?php
include "display.php";
const DEF_LOW_REAL = -2;
const DEF_LOW_IMAG = -2;
const DEF_VIEW_WIDTH = 4;
const DEF_PIX_WIDTH = 100;
const DEF_ASP_RATIO = 1;
const DEF_COLOR_MULT = 2;
function defaulting_get($key, $default) {
return ($_GET[$key] ?? $default) ?: $default;
}
$low_real = defaulting_get("lreal", DEF_LOW_REAL); $low_imag = defaulting_get("limag", DEF_LOW_IMAG);
$view_width = defaulting_get("vwidth", DEF_VIEW_WIDTH); $pixels_wide = defaulting_get("pwidth", DEF_PIX_WIDTH);
$aspect_ratio = defaulting_get("aratio", DEF_ASP_RATIO); $view_height = $view_width / $aspect_ratio;
$high_real = $low_real + $view_height; $high_imag = $low_imag + $view_width;
$pixels_high = $pixels_wide / $aspect_ratio; $color_f = new_color_f(
defaulting_get("rr", DEF_COLOR_MULT),
defaulting_get("rim", DEF_COLOR_MULT),
defaulting_get("rit", DEF_COLOR_MULT),
defaulting_get("gr", DEF_COLOR_MULT),
defaulting_get("gim", DEF_COLOR_MULT),
defaulting_get("git", DEF_COLOR_MULT),
defaulting_get("br", DEF_COLOR_MULT),
defaulting_get("bim", DEF_COLOR_MULT),
defaulting_get("bit", DEF_COLOR_MULT),
);
emit_mandelbrot_view(
$color_f, $low_real,
$high_real, $low_imag,
$high_imag, $pixels_wide,
$pixels_high);
display.php
<?php
include "iteration.php";
const COLOR_MAX = (2 << 7) - 1;
function clamp_color($n) {
return max(0, min(COLOR_MAX, $n)); } function checked_produce_rgb($red, $green, $blue) {
$c_red = clamp_color($red);
$c_green = clamp_color($green);
$c_blue = clamp_color(($blue));
return "rgb($c_red, $c_green, $c_blue)"; } function new_color_f($red_real, $red_imag, $red_iter,
$gre_real, $gre_imag, $gre_iter, $blu_real, $blu_imag, $blu_iter) {
$color_func = function($real, $imag, $iters) use
($red_real, $red_imag, $red_iter, $gre_real, $gre_imag, $gre_iter,
$blu_real, $blu_imag, $blu_iter) { return checked_produce_rgb( $real * $red_real + $imag * $red_imag + $iters * $red_iter, $real * $gre_real + $imag * $gre_imag + $iters * $gre_iter, $real * $blu_real + $imag * $blu_imag + $iters * $blu_iter ); }; return $color_func;
}
function produce_pixel($color) { return "<td style='background: $color; color: $color'>_</td>"; } function emit_mandelbrot_view($color_f,
$lower_real, $upper_real,
$lower_imag, $upper_imag,
$pixels_wide, $pixels_high) {
$real_step = ($upper_real - $lower_real) / $pixels_wide;
$imag_step = ($upper_imag - $lower_imag) / $pixels_high;
echo "<table class='display_table'>";
for ($imag = $lower_imag; $imag <= $upper_imag; $imag += $imag_step) {
echo "<tr>";
for ($real = $lower_real; $real <= $upper_real; $real += $real_step) {
$iters = test_point([$real, $imag]); $color = $color_f($real, $imag, $iters);
echo produce_pixel($color);
}
echo "</tr>";
}
echo "</table>";
}
iteration.php
<?php
// Make mutative?
const STD_MAX_ITERS = 200;
const STD_INF_LIMIT = 2;
function square_complex($complex) {
[$real, $imag] = $complex; return [($real * $real) - ($imag * $imag), 2 * $real * $imag]; } function mandelbrot_iteration($init_complex, $curr_complex) { [$i_real, $i_imag] = $init_complex;
$sqrd = square_complex($curr_complex);
$sqrd[0] += $i_real;
$sqrd[1] += $i_imag;
return $sqrd; } function is_under_limit($complex, $inf_limit) { [$real, $imag] = $complex;
return ($real * $real) + ($imag * $imag) <= ($inf_limit * $inf_limit);
}
function test_point($initial_complex, $max_iters = STD_MAX_ITERS,
$inf_limit = STD_INF_LIMIT) { $current = $initial_complex; $i = 0;
for (; $i < $max_iters && is_under_limit($current, $inf_limit); $i++) { $current = mandelbrot_iteration($initial_complex, $current);
}
return $i;
}
คำตอบ
หน้าจอผู้ใช้
ฉันสังเกตเห็นว่าถ้าฉันป้อนค่าลงในแบบฟอร์มแล้วส่งไปค่านั้นจะไม่คงอยู่หลังจากการโหลดหน้าถัดไป จะเป็นการดีที่จะมีค่าคงอยู่ในอินพุตแบบฟอร์ม นี่เป็นเรื่องง่ายที่จะบรรลุเนื่องจากแบบฟอร์มแสดงผลโดยหน้า PHP
เนื่องจากโค้ด PHP ในครึ่งหลังของ index.php เกิดขึ้นหลังจากปิด HTML แล้ว HTML ที่สร้างขึ้นสำหรับตารางจึงอยู่นอกเอกสาร HTML ในทางเทคนิค:
</form> </body> </html> <table class='display_table'>
เบราว์เซอร์สมัยใหม่ส่วนใหญ่จะแสดงตารางราวกับว่ามันอยู่ใน<body>แท็ก แต่มันไม่ใช่โครงสร้างที่ถูกต้องในทางเทคนิค การใช้https://validator.w3.org/ ด้วยแหล่งที่สร้างจะแสดงผลลัพธ์นี้:
หลักการทั่วไปคือการมีโค้ด PHP ส่วนใหญ่ที่ด้านบนของไฟล์ - มีประโยชน์อย่างยิ่งหากมีฟังก์ชันที่ปรับเปลี่ยนส่วนหัว HTTP เช่นการเรียกไปยังheader()เนื่องจากไม่สามารถส่งส่วนหัวได้หลังจากที่ปล่อย HTML แล้ว
โปรดจำไว้ว่า
header()ต้องถูกเรียกก่อนที่จะส่งเอาต์พุตจริงไม่ว่าจะโดยแท็ก HTML ปกติบรรทัดว่างในไฟล์หรือจาก PHP 1
HTML ที่สร้างขึ้นจากการเรียก PHP (เช่นการเรียกemit_mandelbrot_view()ที่ต้องการไปreturnยัง HTML ที่สร้างขึ้นแทนที่จะเรียกechoโดยตรง) สามารถจัดเก็บไว้ในตัวแปรหรือปล่อยออกมาแบบอินไลน์โดยเลือกใช้ไวยากรณ์ทางลัดสำหรับecho- เช่น<?=:
<?php
//setup PHP code here
?>
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... HTML headers here -->
</head>
<body>
<form>
<!-- ... HTML here -->
</form>
<?= emit_mandelbrot_view($color_f...)?>
</body>
</html>
คำแนะนำรหัส
new_color_fเป็นหายนะ โดยพื้นฐานแล้วฉันต้องการสร้างฟังก์ชันที่มีการตั้งค่าพารามิเตอร์ไว้แล้วและฉันทำเช่นนั้นโดยปิดทับพารามิเตอร์ของฟังก์ชันปิดล้อม แม้ว่ามันจะยาวและน่าเกลียดและทำให้ไวยากรณ์การใช้งานของ PHP แย่ลง ข้อเสนอแนะใด ๆ จะได้รับการชื่นชม
เนื่องจากคุณยืนยันเซิร์ฟเวอร์ว่าโค้ดทำงานโดยใช้ PHP 7.4 ดังนั้นโค้ดจึงสามารถอัปเดตเพื่อใช้ประโยชน์จากคุณสมบัติใหม่ ๆ เช่นฟังก์ชันลูกศร
ฟังก์ชันลูกศรสนับสนุนคุณลักษณะเดียวกันกับฟังก์ชันที่ไม่ระบุชื่อยกเว้นว่าการใช้ตัวแปรจากขอบเขตหลักจะเป็นไปโดยอัตโนมัติเสมอ
นี้จะช่วยให้คำสั่งจะถูกกำจัดในusenew_color_f()
ฟังก์ชันrange()นี้สามารถใช้foreachเพื่อลดความซับซ้อนของลูปที่ซ้อนกันภายในemit_mandelbrot_view():
for ($imag = $lower_imag; $imag <= $upper_imag; $imag += $imag_step) { echo "<tr>"; for ($real = $lower_real; $real <= $upper_real; $real += $real_step) {
สามารถทำให้ง่ายขึ้นเพื่อ:
foreach (range($lower_imag, $upper_imag, $imag_step) as $imag) { echo "<tr>"; foreach (range($lower_real, $upper_real, $real_step) as $real) {
แม้ว่าจะกำหนดจำนวนครั้งที่เรียกใช้ฟังก์ชัน แต่อาจไม่แนะนำให้เพิ่มการเรียกใช้ฟังก์ชันเพิ่มเติมเพื่อทำให้ไวยากรณ์ง่ายขึ้น
สำหรับบล็อกนี้ใน index.php:
$color_f = new_color_f( defaulting_get("rr", DEF_COLOR_MULT), defaulting_get("rim", DEF_COLOR_MULT), defaulting_get("rit", DEF_COLOR_MULT), defaulting_get("gr", DEF_COLOR_MULT), defaulting_get("gim", DEF_COLOR_MULT), defaulting_get("git", DEF_COLOR_MULT), defaulting_get("br", DEF_COLOR_MULT), defaulting_get("bim", DEF_COLOR_MULT), defaulting_get("bit", DEF_COLOR_MULT), );
เห็นได้ชัดว่ามีการทำซ้ำจำนวนมากที่นี่ หนึ่งในความเรียบง่ายจะใส่กุญแจสตริงแบบสอบถามลงในอาร์เรย์และห่วงมากกว่าพวกเขาผ่านพวกเขาไปยังฟังก์ชั่นและการจัดเก็บผลในอาร์เรย์ที่สามารถแพร่กระจายnew_color_f()ออกไปสู่การเรียกร้องให้
ในtest_point()ตัววนซ้ำถูกตั้งค่าไว้นอกลูป:
$i = 0; for (; $i < $max_iters && is_under_limit($current, $inf_limit); $i++) { $current = mandelbrot_iteration($initial_complex, $current); }
ซึ่งแตกต่างจากรหัส c $iสามารถประกาศได้ในคำสั่งแรกของforลูปและยังคงมองเห็นได้นอกลูปเนื่องจากตัวแปรไม่ได้ จำกัด อยู่ที่บล็อก แต่เป็นฟังก์ชัน (ถ้ามี) สิ่งนี้อธิบายได้ไม่ดีนักในขอบเขตตัวแปรเอกสาร PHP แม้ว่าจะมีความคิดเห็นเกี่ยวกับเรื่องนี้ก็ตาม
dodothedreamer ที่ gmail ดอทคอม¶ 8 years ago
โปรดทราบว่าไม่เหมือนกับ Java และ C ++ ตัวแปรที่ประกาศภายในบล็อกเช่นลูปหรือ if's จะได้รับการยอมรับและเข้าถึงได้ภายนอกบล็อกดังนั้น:
<?php for($j=0; $j<3; $j++) { if($j == 1) $a = 4; } echo $a; ?>จะพิมพ์ 4.
คำจำกัดความคงที่นี้ดูเหมือนการเพิ่มประสิทธิภาพสไตล์ C
const COLOR_MAX = (2 << 7) - 1;
แก้ไขฉันหากฉันผิด แต่ฉันสงสัยว่ามีการเพิ่มประสิทธิภาพใด ๆ มากกว่าการประกาศสิ่งนี้เป็น255:
const COLOR_MAX = 255;
ป.ล. ผมถูกถามเกี่ยวกับการประกาศคงที่ในกองมากเกิน จากคำตอบจนถึงตอนนี้ดูเหมือนว่าจะเป็นความชอบส่วนตัวเท่าที่มีการประกาศค่า หากคุณต้องการใช้ไวยากรณ์แบบ bit-shift โปรดอย่าลังเลที่จะทำเช่นนั้นแม้ว่าจะได้รับการเตือนล่วงหน้าว่าหากคุณต้องทำงานเป็นทีมร่วมกับผู้อื่นที่อาจเกิดขึ้นเป็นคำถามในการตรวจสอบโค้ด
ประเภท
ช่วยให้เข้าใจรหัสได้ง่ายขึ้นหากคุณระบุประเภทสำหรับลายเซ็นฟังก์ชันทั้งหมด PHP จะตรวจสอบประเภทหากระบุไว้ที่รันไทม์เมื่อใดก็ตามที่เข้าและออกจากฟังก์ชันและเมื่อเขียนลงในคุณสมบัติคลาสช่วยให้คุณตรวจจับและค้นหาข้อผิดพลาดได้เร็วขึ้นมากโดยเฉพาะอย่างยิ่งหากใช้ IDE หรือเครื่องมือวิเคราะห์แบบคงที่ นอกจากนี้ยังช่วยสื่อสารถึงเจตนาของรหัสเพื่อให้ง่ายต่อการเพิ่มเข้าไป
ตัวอย่างการเปลี่ยนแปลงเพิ่มประเภท:
ใน index.php เปลี่ยนเป็นfunction defaulting_get($key, $default) {function defaulting_get(string $key, int $default): int {
ใน display.php เปลี่ยนfunction clamp_color($n) {เป็นfunction clamp_color(int $n): int {
และ
function new_color_f($red_real, $red_imag, $red_iter, $gre_real, $gre_imag, $gre_iter,
$blu_real, $blu_imag, $blu_iter) {
ถึง
/** @return \Closure(int, int, int): string */
function new_color_f(int $red_real, int $red_imag, int $red_iter,
int $gre_real, int $gre_imag, int $gre_iter, int $blu_real, int $blu_imag, int $blu_iter): \Closure {
ฉันไม่แน่ใจว่าฉันมีประเภทของการปิดที่ค่อนข้างถูกต้องใน docblock หรือไม่
ฉันจะทำสิ่งนี้สำหรับทุกฟังก์ชัน
หลีกเลี่ยง use
คุณสามารถหลีกเลี่ยงความจำเป็นได้useโดยใช้ประโยชน์จากไวยากรณ์การปิดสั้น ๆ ที่แนะนำใน PHP 7.4:
/** @return \Closure(int, int, int): string */
function new_color_f(int $red_real, int $red_imag, int $red_iter, int $gre_real, int $gre_imag, int $gre_iter,
int $blu_real, int $blu_imag, int $blu_iter): \Closure { return fn(int $real, int $imag, int $iters): string =>
checked_produce_rgb(
$real * $red_real + $imag * $red_imag + $iters * $red_iter,
$real * $gre_real + $imag * $gre_imag + $iters * $gre_iter,
$real * $blu_real + $imag * $blu_imag + $iters * $blu_iter
);
};
}
PHP จะปิดตัวแปรใด ๆ ที่กล่าวถึงโดยอัตโนมัติหลังจาก=>ปิดสั้น ๆ
ชั้นเรียนและการโหลดอัตโนมัติ
PHP สำนวนสมัยใหม่โดยเฉพาะอย่างยิ่งสำหรับแอปพลิเคชันขนาดใหญ่มีแนวโน้มที่จะมีโค้ดเกือบทั้งหมดในรูปแบบของคลาสแม้ว่าแอปพลิเคชันจะไม่ได้มุ่งเน้นไปที่วัตถุก็ตาม ข้อดีอย่างหนึ่งของการใช้คลาสคือระบบโหลดอัตโนมัติของ PHP สามารถโหลดได้ตามต้องการ สิ่งนี้ไม่สามารถใช้ได้กับฟังก์ชันฟรี ฟังก์ชันคงที่ในชั้นเรียนสามารถใช้แทนฟังก์ชันฟรีได้อย่างสะดวก
การแทนที่ฟังก์ชันฟรีทั้งหมดด้วยฟังก์ชันคลาสแบบคงที่จะทำให้สามารถทำเครื่องหมายฟังก์ชันใด ๆ ที่ใช้เฉพาะภายในคลาสที่ประกาศว่าเป็นprivateซึ่งจะทำให้เข้าใจการไหลของโค้ดได้ง่ายขึ้น
วิธีทั่วไปในการใช้การโหลดอัตโนมัติคือการตั้งค่าโปรเจ็กต์ด้วย Composer จัดระเบียบคลาสทีละไฟล์โดยปฏิบัติตามกฎ PSR-4 สำหรับตำแหน่งและชื่อของแต่ละไฟล์และทำให้ Composer สร้างไฟล์autoload.php สามารถเรียกได้โดยใช้require_onceจากindex.phpหรือจุดเข้าอื่น ๆ ชั้นเรียนใด ๆ ที่จำเป็นหลังจากนั้นจะถูกโหลดโดยอัตโนมัติหากสามารถพบได้
ฉันจะทำให้ new_color_f ส่งคืนวัตถุแทนการปิดและเปลี่ยนชื่อตามนั้น วัตถุนี้สามารถมีเขตข้อมูลส่วนตัวแทนตัวแปรล้อมรอบและมีgetRGBฟังก์ชันสาธารณะ