PHP Mandelbrot Set Generator

Sep 04 2020

ฉันต้องทำความคุ้นเคยกับการเขียนโปรแกรมบนเว็บดังนั้นฉันจึงตัดสินใจเขียนหน้า 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;
    }

คำตอบ

3 SᴀᴍOnᴇᴌᴀ Sep 05 2020 at 06:36

หน้าจอผู้ใช้

ฉันสังเกตเห็นว่าถ้าฉันป้อนค่าลงในแบบฟอร์มแล้วส่งไปค่านั้นจะไม่คงอยู่หลังจากการโหลดหน้าถัดไป จะเป็นการดีที่จะมีค่าคงอยู่ในอินพุตแบบฟอร์ม นี่เป็นเรื่องง่ายที่จะบรรลุเนื่องจากแบบฟอร์มแสดงผลโดยหน้า 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 โปรดอย่าลังเลที่จะทำเช่นนั้นแม้ว่าจะได้รับการเตือนล่วงหน้าว่าหากคุณต้องทำงานเป็นทีมร่วมกับผู้อื่นที่อาจเกิดขึ้นเป็นคำถามในการตรวจสอบโค้ด

1 bdsl Sep 13 2020 at 22:55

ประเภท

ช่วยให้เข้าใจรหัสได้ง่ายขึ้นหากคุณระบุประเภทสำหรับลายเซ็นฟังก์ชันทั้งหมด 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ฟังก์ชันสาธารณะ