在 PHP 中實現連結串列

參考文件         The SplDoublyLinkedList class

(PHP 5 >= 5.3.0, PHP 7, PHP 8)

連結串列是在許多程式語言中實現的常見資料結構。它是線性的,包含相互連結的節點。
每個節點都包含資料和到相鄰節點的連結。因此,連結串列形成了一個節點鏈。連結串列有不同的變體。
1.單連結串列:單向。它只在向前的方向上移動。
2.雙連結串列:是雙向的。它向前和向後兩個方向遍歷。
3.迴圈連結串列:單向迴圈。
4.迴圈雙向連結串列:雙向迴圈。

我們可以在連結串列中進行各種操作。基本操作如下:
1.遍歷
2.插入
3.刪除
4.更新
5.搜尋
PHP 提供了一個類 SplDoublyLinkedList 用於實現連結串列。它是一個雙向連結串列。

$list = new SplDoublyLinkedList();
$list->push(10);
$list->push(15);
$list->push(20);
displayList($list);
function displayList($list) {
for ($list->rewind(); $list->valid(); $list->next()){
echo $list->current()."<br>";
}
}

push() 方法接受要推送的引數並允許在列表中附加值。該元素將被推到連結串列的末尾。
rewind() 方法從連結串列的開頭回退迭代器。例如,迭代器將移動到列表的第一個元素。
valid() 方法檢查連結串列是否包含更多節點,next() 方法移動到連結串列的下一項。因此,我們可以在 for 迴圈中使用上述示例中的這些方法來遍歷連結串列的元素。
在迴圈內部,current( )方法表示當前元素。因此,將列印當前元素。使用 pop() 方法刪除連結串列中的元素

使用 pop() 方法刪除連結串列中的元素
我們可以使用 pop() 方法從連結串列中刪除最後一個元素。該方法不接受任何引數。

從連結列表中查詢頂部和底部值
我們可以使用 top() 方法找到連結串列的頂部值,對於底部值,我們可以使用 bottom() 方法

echo "top_element=".$list->top()."<br>";
echo "bottom_element=".$list->bottom();

使用 add() 方法在連結串列中插入值
我們可以使用 add() 方法通過指定位置在連結串列中插入元素。該方法有兩個引數。
第一個引數是要插入專案的索引,第二個引數是要插入的專案。例如,以 2 和 50 為引數呼叫 add() 方法並呼叫 displayList() 方法。

$list->add(2,50);
displayList($list);

PHP          link.php

注意插入點會從第n個參數下插入

圖說演算法使用JavaScript(十一)

        堆疊結構在電腦中的應用相當廣泛,時常被用來解決電腦的問題,例如前面所談到的遞迴呼叫、副程式的呼叫,至於在日常生活中的應用也隨處可以看到,例如大樓電梯、貨架上的貨品等等,都是類似堆疊的資料結構原理。
        佇列在電腦領域的應用也相當廣泛,例如計算機的模擬simulation、CPU的工作排程Job Scheduling、線上同時周邊作業系統的應用與圖形走訪得先廣後深搜尋法BFS。由於堆疊與佇列都是抽象資料型態Abstract Data Type,ADT,本章終將為各位介紹相關演算法。
         堆疊在程式設計領域中,包含以下兩種設計方式,分別為陣列結構與鏈結串列結構。

6-1陣列實作堆疊輕鬆學

         以陣列結構來製作堆疊的好處是製作與設計的演算法都相當簡單,但因為如果堆疊本身是變動的話,大小並無法事先規劃宣告,太大時浪費空間,太小則不夠使用。

相關演算法如下
//判斷是否空堆疊
var isEmpy=()=>{
if (top==-1)
return true;
else
return false;
}
//將指定的資料存入堆疊
var push=(data)=>{
if (top>=MAXSTACK-1)
process.stdout.write('堆疊已滿,無法再加入');
else {
top +=1;
stack[top]=data; //將資料存入堆疊
}
}
//從堆疊取出資料
var pop=()=>{
if (isEmpty())
process.stdout.write('堆疊是空');
else {
process.stdout.write('彈出的元素為: '+stack[top]+'\n');
top=top-1;
}
}

JS            array_stack.js

const MAXSTACK=100; //定義最大堆疊容量
stack=[]; //堆疊的陣列宣告
top=-1; //堆疊的頂端

//判斷是是否為空堆疊
var isEmpty=()=>{
	if(top==-1)
		return true;
	else
		return false;
}

//將指定的資料存入堆疊
var push=(data)=> {
	if (top>=MAXSTACK-1)
		process.stdout.write('堆疊已滿,無法再加入');
	else {
		top +=1;
		stack[top]=data;//將資料存入堆疊
	}
}

//從堆疊取出資料
var pop=()=> {
	if (isEmpty())
		process.stdout.write('堆疊是空');
	else {
		process.stdout.write('彈出的元素為:'+stack[top]+'\n');
		top=top-1;
	}
}

//主程式
i=2;
count=0;
const prompt = require('prompt-sync')();
while(true) {
	const i = parseInt(prompt('要推入堆疊,請輸入1,彈出則輸入0,停止操作則輸入-1:'));
	if (i==-1)
		break;
	else if (i==1) {
		const value = parseInt(prompt('請輸入元素值:'));
		push(value);
	}
	else if (i==0)
		pop();
}

process.stdout.write('========================\n');

if (top<0)
	process.stdout.write('\n 堆疊是空的\n');
else {
	i=top;
	while (i>=0) {
		process.stdout.write('堆疊彈出的順序為:'+stack[i]+'\n');
		count +=1;
		i =i-1;
	}
}
process.stdout.write('=====================================\n');

6-2鏈結串列實作堆疊

        使用鏈結串列來製作堆疊的優點是隨時可以動態改變串列長度,能有效利用記憶體資源,不過缺點是設計時,演算法較為複雜。

相關演算法如下:

class Node {    //堆疊鏈結點的宣告
constructor() {
this.data=0; //堆疊資料的宣告
this.next=null; //堆疊中用來指向下一個節點
}
}
top=null;
var isEmpty=()=> {
if(top===null)
return true;
else
return false;
}
//將指定的資料存入堆疊
var push=(data)=> {
new_add_node=new Node();
new_add_node.data=data; //將傳入的值指定為節點的內容
new_add_node.next=top; //將新節點指向堆疊的頂端
top=new_add_node; //新節點成為堆疊的頂端
}
//從堆疊取出資料
var pop=()=> {
if (isEmpty()) {
process.studout.write('===目前為空堆疊====\n');
return -1;
}
else {
ptr=top; //指向堆疊的頂端
top=top.next; //將堆疊頂端的指標指向下一個節點
temp=ptr.data; //取出堆疊的資料
return temp; //將從堆疊取出的資料回傳給主程式
}
}

圖說演算法使用JavaScript(十)

5-3-3單向鏈結串列刪除節點

在單向鏈結型態的資料結構中,如果要在串列中刪除一個節點,如同一列火車拿掉原有的車廂,依據所刪除節點的位置有三種不同的情形:

演算法如下

top=head;
head=head.next;

演算法如下

ptr.next=tail;
ptr.next=null;

演算法如下

Y=ptr.next;
ptr.next=Y.next;

class employee{
        constructor() {
               this.num=0;
               this.salary=0;
               this.name=”;
               this.next=null;
        }
}

JS          del_node.js

class employee{
	constructor(){
		this.num=0;
		this.salary=0;
		this.name='';
		this.next=null;
	}
}

var del_ptr=(head,ptr)=>{   //刪除節點副程式
	top=head;
	if (ptr.num==head.num) {  //[情形1]:刪除點在串列首
		head=head.next;
		process.stdout.write('已刪除第' +ptr.num+' 號員工 姓名:'+ptr.
			name+' 薪資:'+ptr.salary);
	}
	else {
		while (top.next!=ptr)  //找到刪除點的前一個位置
			top=top.next;
		if(ptr.next==null) {   //刪除在串列尾的節點
			top.next=null;
			process.stdout.write('已刪除第'+ptr.num+' 號員工 姓名:'+ptr.
				name+' 薪資:'+ptr.salary+'\n');
		}
		else{
			top.next=ptr.next;
			process.stdout.write('已刪除第 '+ptr.num+' 號員工 姓名:'+ptr.
				name+' 薪資:' +ptr.salary+'\n');
		}		
	}
	return head;
}

findword=0;
namedata=['Allen','Scott','Mary','John','Mark','Ricky',
		  'Lisa','Jasica','Hanson','Daniel','Axel','Jack'];
data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299],
		[1012,42660],[1014,25676],[1018,44145],[1043,52182],
		[1031,32769],[1037,21100],[1041,32196],[1046,25776]];
process.stdout.write('員工編號 薪水 員工編號 薪水 員工編號 薪水 員工編號 薪水\n');
process.stdout.write('--------------------------------------------------\n');

for(i=0; i<3; i++) {
	for (j=0; j<4; j++)
		process.stdout.write(data[j*3+i][0]+ '\t'+data[j*3+i][1]+'\t');
	console.log();
}

head=new employee(); //建立串列首
if(!head) {
	console.log('Error!! 記憶體配置失敗!!');
	return;
}
head.num=data[0][0];
head.name=namedata[0];
head.salary=data[0][1];
head.next=null;

ptr=head;
for (i=1; i<12; i++) { //建立串列
	newnode=new employee();
	newnode.next=null;
	newnode.num=data[i][0];
	newnode.name=namedata[i];
	newnode.salary=data[i][1];
	newnode.next=null;
	ptr.next=newnode;
	ptr=ptr.next;
}
const prompt = require('prompt-sync')();
while(true)  {
	const findword = parseInt(prompt('請輸入要刪除的員工編號,要結束刪除過程,請輸入-1:'));
	if (findword==-1)  //迴圈中斷條件
		break;
	else {
		ptr=head;
		find=0;
		while (ptr!=null) {
			if (ptr.num==findword){
				ptr=del_ptr(head,ptr);
				find=find+1;
				head=ptr;
			}
			ptr=ptr.next;
		}
		if (find==0)
			process.stdout.write('//////////沒有找到///////////\n');
	}
}
ptr=head;
process.stdout.write('\t座號\t  姓名\t成績\n');  //列印剩餘串列資料
process.stdout.write('\t======================\n');  
while (ptr!=null)  {
	process.stdout.write('\t['+ptr.num+' ]\t[ '+ptr.name+' ]\t[ '
		+ptr.salary+']\n');
	ptr=ptr.next;
}

5-3-4單向鏈結串列的反轉

看完了節點的刪除及插入後,各位可以發現在這種具有方向性的鏈結串列結構中增刪節點是相當容易的一件事。而要從頭到尾印整個串列似乎也不難,不過如果要反轉過來列印就真的需要某些技巧了。我們知道在鏈結串列中的節點特性是知道下一個節點的位置,可是卻無從得知它的上一個節點位置,不過如果要將串列反轉,則必須使用三個指標變數。請看下圖說明:

演算法如下:

class employee{
constructor() {
this.num=0;
this.salary=0;
this.name='';
this.next=null;
}
}
var invert=(x)=> { //x為串列的開始指標
p=x; //將p指向串列的開頭
q=null; //q是p的前一個節點
while (p!=null) {
r=q; //將r接到q之後
q=p; //將q接到p之後
p=p.next //p移到下一個節點
q.next=r; //q連結到之前的節點
}
return q;
}

JS          rev_node.js

class employee {
	constructor() {
		this.num=0;
		this.salary=0;
		this.name='';
		this.next=null;
	}
}

findword=0;
namedata=['Allen','Scott','Marry','John','Mark','Ricky',
			'Lisa','Jasica','Hanson','Daniel','Axel','Jack'];
data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299],
		[1012,42660],[1014,25676],[1018,44145],[1043,52182],
		[1031,32769],[1037,21100],[1041,32196],[1046,25776]];

head = new employee();//建立串列首
if(!head) {
	console.log('Error!! 記憶體配置失敗!!');
	return;
}
head.num =data[0][0];
head.name=namedata[0];
head.salary=data[0][1];
head.next=null;

ptr=head;

for(i=1; i<12; i++){  //建立串列
	newnode=new employee();
	newnode.next=null;
	newnode.num=data[i][0];
	newnode.name=namedata[i];
	newnode.salary=data[i][1];
	newnode.next=null;
	ptr.next=newnode;
	ptr=ptr.next;
}

ptr=head;
i=0;
process.stdout.write('原始員工串列節點資料:\n');
while (ptr !=null) { //列印串列資料
	process.stdout.write('['+ptr.num+'\t'+ptr.name+'\t'
							+ptr.salary+'] -> ');
	i=i+1;
	if (i>=3) { //三個元素為一列
		console.log();
		i=0;
	}
	ptr=ptr.next;
}

ptr=head;
before=null;
process.stdout.write('\n反轉後串列節點資料:\n');
while (ptr!=null) { //串列反轉,利用三指標
	last=before;
	before=ptr;
	ptr=ptr.next;
	before.next=last;
}
ptr=before;
while (ptr!=null) {
	process.stdout.write('['+ptr.num+'\t'+ptr.name+'\t'
							+ptr.salary+'] ->');
	i=i+1;
	if (i>=3) { //三個元素為一列
		console.log();
		i=0;
	}
	ptr=ptr.next;
}

PHP  陣列 函數

array_pop() 刪除陣列最後一個元素。
array_push() 將一個或多個元素加入末端。
array_reverse() 以相反的順序返回陣列。
array_shift() 刪除首個元素。
array_slice() 刪除指定位置的元素,返回陣列。

php陣列如何實作佇列

在 PHP 中,佇列可以使用陣列來實作。一個佇列就是一個先進先出的資料集合。我們可以在佇列的頭部插入數據,同時從佇列的尾部取出數據。

PHP             queue.php

class Queue{
	protected $queue=[];
	//從尾部加入陣列
	public function enqueue($item){
		array_push($this->queue, $item);
	}
	//從頭部取出陣列
	public function dequeue(){
		if (empty($this->queue)){
			return flase;
		}
		return array_shift($this->queue);
	}
	//獲取陣列的長度
	public function length (){
		return count($this->queue);
	}
	//判斷是否為空值
	public function isEmpty(){
		return empty($this->queue);
	}
}
$queue=new Queue();
$queue->enqueue('a');
$queue->enqueue('b');
$queue->enqueue('c');
$length = $queue->length();
echo $length."<br>";
echo $queue->dequeue()."<br>";
echo $queue->dequeue()."<br>";
echo $queue->dequeue()."<br>";

使用函數

array_push()   在尾端加入陣列
—  array_shift ()   删除陣列中首個元素,并返回被删除元素的值。
—  count()   計算陣列數量
—  empty()   陣列是否為空

JavaScript 概念三明治(六)

Event Table

Event Table 是與Event Queue 互相搭配的資料集合,它負責記錄在非同步目的達成後,有哪些函式或者事件要被執行,這裡指的非同步目的指的是像計時完畢、API資料獲取完畢、事件被觸發。當我們執行setTimeout這個函式時,JavaScript會把給定的函式與像是倒數的秒數之類的附帶資訊(meta data)推送到Event Table裡面,等到一秒過後(目的達成)該函式教會被正式推送到事件除列等待執行,而在這之前JavaScript就是透過Event Table知道有那些事件要被送到事件儲列中。

Event Loop

那麼,什麼又是事件迴圈Event Loop呢?可以把Event Loop想成是另外一個幾乎無時無刻、每一毫秒都在執行的程式,它負責檢查現在主執行環境堆疊是否是空的。如果是空的,再去檢查Event Queue,若Event Queue有函式等待執行,則將這些函式從Event Queue依序到主執行環境並執行。
也因為有事件迴圈與事件儲列的機制,像是事件監聽與透過Ajax拉取資料這類行為才有辦法被達成。例如在監聽網頁點時,一旦使用者做了點擊的動作,對應的邏輯就會被推送到事件儲列內,而事件迴圈看到儲列內有待執行的任務,就會負責去執行。
那麼,現在了解了整個事件儲列的概念,讓我們再度回到setTimeout的例子,來看看一道經典的面試問題。
for (var i=0; i<3; i++){
setTimeout(function(){
console.log(i)
}, 1000)
}
各位可以先想一下,在一秒之後,setTiomeout函式內console.log的輸出的結果是甚麼。這邊用到的概念跟前面講閉包的時候類似,都與先後順序有關。若沒有非同步概念的話,通常會下意識的認為結果會是0、1、2,不過這段程式碼最後其實是會印出3、3、3,不知道有沒有猜對。
搭配識見儲列的概念,現在我們知道setTimeout對應的函式會在主執行環境結束之後才被執行,而等到該函式被執行時,for迴圈裡面的i早就已經因為迴圈而被修改為3了。
要改變三個3的結果,想要看到0、1、2的話該怎麼辦呢?這邊要結合一點前面說到的範疇與閉包的觀念,可以看到for迴圈裡面的i是利用var所宣告,而既然var具有的範疇是根據函式來界定,那麼我只要利用函式,是不是就是夠透過產生閉包,把每個迴圈的i值保留下來呢?當然可以,我們可以利用立即執行函式(IIFE),並把i導入這個立即執行函式,就能夠產生閉包了。

for (var i=0; i<3; i++)
(function(x) {
setTimeout(function(){
console.log(x)
})(i)
}

或者是把var宣告改成let來做宣告
for (let i=0; i<3; i++){
setTimeout(function(){
console.log(i)
}, 1000)
}

Promise

我們在前一個段落提到,為了防止網頁的主程式因為等待某些邏輯運算的回應而停擺,有時候JavaScript的行為需要透過非同步的方式來執行,包括一些瀏覽器的API和對外部伺服器拉取資料的動作,而這些動作是利用瀏覽器的Event Queue來達成非同步的行為。
這的確減少不必要的等待,進而增加了使用流程上的順暢度。不過就程式碼的撰寫方式來看,由於在做這些非同步行為的時候,若不做任何的處理,一般都是以回呼函式的方式來進行,才能確保某一段邏輯在非同步行為完成之後才被執行,若這類邏輯開始複雜的時候,可能會變得難以閱讀。
舉前例講到的例子來說,若以setTimeout來模擬一個非同步的行為,想要確保這件事情的話,我們就必須把一個非同步行為放到另外一個非同步的行為的回呼函式裡面。當這樣子的需求越來越多時,你可能就會看到這樣的程式碼。
const asynActionA = (fn)=>setTimeout(fn, 10000);
const asynActionB = (fn)=>setTimeout(fn, 10000);
const asynActionC = (fn)=>setTimeout(fn, 10000);
const asynActionD = (fn)=>setTimeout(fn, 10000);

asyncActionA(()=>{
console.log("asynActionA");
asynActionB(()=>{
console.log("asynActionB");
asynActionC(()=>{
console.log("asynActionC");
asynActionD(()=>{
console.log("asynActionD");
});
});
});
});
這種巢狀的回呼函式一旦多了起來,就容易造成維護上的困難。一般在正式的專案中都非常不樂見這樣子的程式碼。為了解決這樣的問題,我們就必須認識這個Promise
>Promise簡介
Promise是什麼呢?Promise是JavaScript版本E86以後出現的新語法,這個詞以字面上的意義來看,用比較白話的方式解釋的話有一種:我承諾幫你做某件事情,能不能成功還不一定,但是我做完之後會把結果告訴你的意思。官方的文件描述則是:
Promise是一個代表非同步運作的最終狀態的物件(成功或失敗)
A Promise is an object repressnting the eventual completion
or failure of an asynchronous operation.(MDN)

從技術文件的角度來解釋就顯得比較抽象,不過你應該大致能夠看出一點頭緒,只要抓住幾個關鍵字--也就是「成功」與「失敗」兩種狀態。
>Promise的狀態
進一步總結以上的論點,一個Promise以時間順序來看是有狀態之分的。除了前面講的成功與失敗兩種結果,一般以Pending來描述在執行中,懸而未決的Promise,一個Promise總共會有三種可能的狀態,分別代表進行中、成功與失敗。而相對於Pending這個代表處理中的狀態,不管是進入成功或失敗狀態的Promise,我們都能夠用Settled來表示這個Promise已經被解決了。

*Pending:還在執行中的狀態,表示還沒有特定結果。
*Fulfilled:成功的狀態,代表Promise被實現,對應的回呼函式為resolve。
*Rejected:失敗的狀態,代表Promise被拒絕,對應的回呼函式為reject。
*Settled:表示Promise已經被解決,結果已經確定。

>Promise基本使用方式
從前面文件的描述應該也可以看得出來Promise在JavaScript裡面是以物件的方式存在。這個物件又是怎麼產生呢?接下來我們就來看看要怎麼使用Promise吧!基本的Promise宣告方式如下:
我們進一步仔細看一下這個傳進Promise建構函式,它接收了兩個參數:resolvereject來命名。

resolvereject 其實分別是兩個具有不同目的的函式。resolve(解析)被用於在認為Promise內的行為成功時呼叫reject(拒絕)則用於被認為失敗的邏輯發生時呼叫。使用這兩個詞最為函式的名稱只因為這是一種約定俗成,許多人都用這些詞來稱呼它們。

JavaScript 概念三明治(五)

同步/非同步

「同步」聽起來就是指一次做好幾件事情,其實並不是,同步指的是:
同一時間只做一件事
套用到JavaScript裡面就是指一次只跑一段程式碼,就像JavaScript語法解析器做的,從上到下一行一行執行那樣。當JavaScript載入全域執行環境,也許呼叫了某個函式創造出一個函式執行環境,然後回到全域環境後結束,這一切都是同步來做處裡的,也就是說,在JavaScript裡面不可能同時執行兩個函式、執行多段程式碼邏輯。
而「非同步」呢?就很好理解了,相較於同步,它的概念則相反:
同一時間內處理不只一件事
必須透過非同步的處理方式,才能讓JavaScript在進入全域執行環境到結束的這中間,能夠額外處理一些需要時間進行的邏輯,而又不影響原來的主程式。在非同步的處理方式下,主程式的運行還是會繼續,而非同步進行的工作也還是會進行,只是不是馬上,而是等到主程式執行完畢,JavaScript引擎有空檔後才會被執行。
非同步的行為在瀏覽器前端很常見,像是部落格網站,當網站一打開,JavaScript必須處理畫面上的一些行為,卻又必須跟後端取文章資料。若以同步的方式來進行這個拉取文章的動作,那麼在拉取道完整文章之前,JavaScript就會無法執行任何其他任務。
也就是說,即便JavaScript是一門同步的語言,但為了符合瀏覽器的使用情境,某些時候它必須具有非同步執行邏輯的能力,至於它怎麼處理這些互動的非同步動作呢?就是透過瀏覽器內的事件迴圈的幫助,關於事件迴圈在後面馬上就會看到。
瀏覽器內可不是只有JavaScript引擎而已,還有其他許多部份在JavaScript引擎之外執行的其他程式,像是渲染引擎負責畫面的構築、以及負責處理網路溝通如Http請求以獲得資料的部分。而JavaScript可能在某些時候必須要要求瀏覽器變更畫面的內容,或是觸發網路溝通的流程,以與其他伺服器溝通,但是這些工作可能會是非同步的,只有在JavaScript引擎裡面事同步的運行。

Event Queue 與 Event Loop

Event Queue、Event Loop這兩個部分並不存在於JavaScript內,而是瀏覽器的其中一部份。它們雖然不屬於JavaScript,卻是前端產品開發裡面最重要的概念之一。因此,需要了解它們為前端互動方面帶來什麼樣的幫助和影響。
>JavaScript引擎的組成

讓我們回想一下前面提到的基礎。之前提到過「執行環境堆疊」,函式呼叫時會產生執行環境,若在這個函式執行環境內含有其他函式被呼叫,就會在之上產生另一個執行環境,形成堆疊。而在上層的執行環境結束之前,下層部分的其他程式碼事無法被執行的,包含全域執行環境。                                                                                                

因此,只要在這之中某個堆疊過久,就算只有一個函式執行環境的堆疊,都有可能影響整個主程式(全域執行環境)的運行。不過應用程式裡面總是會有某些功能需要時間來提取/運算,這時候為了不讓整個主程式停下來等待太久,我們可以,而且其實我們很常把這些比較耗時的工作放在主程式以外的某一個部分去執行。
要討論下一段之前,必須先複習一下,前面我們提到的,JavaScript引擎底下根據功能大致上可以分為三個部分:

#全域執行環境
#執行環境堆疊
#記憶體堆疊

然而瀏覽器內可不只有JavaScript引擎,接下來我們要提到一個很重要的概念--Queue(又稱Message/Event/Callback Queue)。
整個瀏覽器的運行並非只有JavaScript引擎組成。前面說到過,因為JavaSript屬於同步執行的語言,同時又為了讓網頁具有像「監聽事件」、「計時」、「拉第三方API」這些類似「背景作業」的功能,瀏覽器提供了另外一些部份來達成,分別是:
#

#Event Queue
#Web API
#Event Table
#Event Loop

整個由上述部分,包含JavaScript引擎所組成的整體,稱為JavaScript Runtime Environment(JRE),瀏覽器內並不只有JavaScript語言本身而已,我們先來看一下瀏覽器內的事件處理。
>Event Queue
Queue(儲列)是什麼樣的概念呢?我們先來看一下,在寫網頁程式的時候,有一些所謂的「內建的」API如setTimeout、setInterval,這些API不存在於JavaScript原始碼內,但你仍然可以在開發時直接使用。因為這些API是屬於瀏覽器所提供的WebAPI。WebAPI並非JavaScript引擎的一部份,但它屬於瀏覽器運行流程的一環。
關於Web API,舉一些例子:
*操作DOM節點的API:document.getElementeById
*AJAX相關API,像是:XMLttpRequest
*計時類型的API,就像剛剛提到的setTimeout
這類Web API在與JavaScript原始碼一起執行的時候,通常不會直接影響JavaScript主執行環境的運行。否則的話若網頁需要與後端伺服器溝通,拉取外部資料時,就只有乾等,無法執行任何其他事情了!
>Event Queue 運行流程
要了解Event Queue的運行,我們必須先找到一件會放到Event Queue執行的事情。所以這邊要先介紹其中幾個瀏覽器的API:setTimeout與setInterval。setTimeout是一個全域的函式,用於將想做的事情延後幾秒進行。將想要執行的邏輯以函式的方式傳入第一個參數,並在第二個參數傳入想要等待的時間,JavaScript就會為你在若干秒之後,呼叫你傳入的函式。
而setInterval呢?與setTimeout相似,只不過setTimeout只會在你給定的秒數之後執行一次,而setInterval則是根據你給定的時間,固定幾秒執行一次。
function executeAfterDelay(){
console.log("一秒之後才會執行");
}
setTimeout(executerAfterDelay, 1000);
console.log("我會先被被執行");
如果了解了這兩個方法的用途,來觀察上圖的程式碼應該不會太難理解,函式內的程式碼會在一秒之後才被呼叫。不過意外的事情總是會發生,這段程式碼裡面,setTimeout可不像一般的函式那樣運行起來那麼單純。
我們可以試試看把setTimeout的時間調整為0,那是不是就會馬上執行對應的函式內容了呢?
function executeAfterDalay() {
console.log("應該要馬上執行");
}
setTimeout(excuteAfterDalay,0);
console.log("應該要最後執行");

整個流程看起來像這樣:
1.JavaScript 引擎執行到瀏覽器提供的setTimeout函式。
2.JavaScript 引擎繼續運行,同時瀏覽器開始根據給定的秒數計時。
3.等待計時完成後,把剛才接收到的函式推送到Event Queue內。
4.等待JavaScript引擎運行完畢,主執行環境結束後,將Event Queue內的函式推送到JavaScript主執行環境,產生堆疊(執行該函式)。

JavaScript 概念三明治(四)

函式陳述式與表達式

JavaScript有兩種語法分類:陳述式與表達式。而了解這兩種語法分類之後,我們才有辦法區別函式的兩種使用方法:函式宣告式、函式表達式在運用上有什麼不同。
陳述式
陳述式Statement就像生活中我們所描述事情時所說的某句話像是「今天出門天氣不好的話記得帶傘」,像是一種聲明、或是對邏輯的描寫。陳述式會影響程式的運行流程,並且一定會做某一些事情,向邏輯判斷就是一種陳述式。
但是陳述式在JavaScript並不會產生數值,可以以此看出它跟表達式的區別。所以不能放在JavaScript內預期會產生數值的地方,像是函式的參數、函式的回傳值、或是宣告變數時等號的右邊(不能被用來指派給另一變數)。舉例來說,常見的陳述式有:

*if/switch判斷式
*for/while迴圈
*變數宣告
*一般函式宣告

表達式
就像你每個月上班或打工,做完該做的事情之後會拿到薪水作為回報一樣。表達式也是一段JavaScript在執行完後會得到一個結果的程式碼。它可以是很長,也可以簡短,只要執行後會回傳結果的一段程式碼,就是表達式。
//函式呼叫
functionInvocation();
//變數指派
a = 3;
//運算式
1 + 2;
a ===3;
ture || false;
true && true;
Array.isArray([]) ? doSomeThing() : doOtherThing();
這些都是表達式。
函式陳述式與函式表達式
在JavaScript裡面,要創造一個函式,可以透過宣告式,也能透過表達式來達成。這兩種方法就很值觀則分別稱成「函式陳述式」跟「函式表達式」。
函式陳述式
函式陳述式是藉由直接給函式名稱來直接宣告一個函式,其實它就是一般的函式宣告。之前有說過變數宣告時,JavaScript會幫你保留記憶體空間,所以它屬於陳述式。而像這樣子一般的函式宣告,跟變數宣告會產生的行為是一樣的,因此當然也屬於陳述式。
//可以在宣告函式的程式碼之前就先呼叫
functionStatement();
function functionStatement(){
//do something
}
函式表達式
另外一種宣告函式的方法是函式表達式,函式表達式的寫法是直接一個把函式指派給另一個變數,之所以能夠這麼做是因為函式本身在JavaScript裡面也是一個物件,所以除了一般的宣告方式之外,函式也能夠作為一個被指派的值。
functionExpression() //functionExpression is not a function
var functionExpression = function(){
//do somthing
}

上方是 functionStatement
下方是函式表達式,有 do somthing

回呼函式

在JavaScript裡面很常聽到這個值,那麼什麼是回呼函式Callback Function?其實它指的就是在函式裡面執行的另外一個函式。它有著一種「等某段邏輯執行完畢之後,在告訴我」的意思。

其餘參數以及物件參數

這邊介紹兩種在函式上非常實用的語法:其餘參數物件作為參數時的展開。

>其餘參數
當一個函式有可能接收一個以上但不確定數量的參數,我們就能夠使用ES6之後提供的其餘參數(Rest Parameter)。其餘參數以...加上變數名稱來表示,且只能擺在最後一個位置,它會將所有後來未在函式裡面定義的變數,蒐集成一個陣列傳入函式。

function addAll(a,b,…rest){
let sum =a+b;
rest.forEach((num)=>{ //參數 rest會是一個陣列
sum = sum +num;
})
return sum;
}
AddAll=addAll(1,2,3,4,5,6);
console.log(AddAll);
>物件參數的展開
當一個物件參數傳入函式時,我們可以把這個物件「展開」,快速地取得物件裡面的屬性作為參數給函式內的邏輯使用。
var userInfo = {
name:'Anakin',
nickName: 'King'
}
function displayUser({name,nickName}){
//do something
}
displayUser(userInfo);

圖說演算法使用JavaScript(九)

5-3徹底完轉單向串列演算法

在JavaScript語言中,如果以動態配置產生鏈結串列的節點,必須先行自訂一個類別,接著在該類別中定義一個指標欄位,用意在指向下一個鏈結點,及至少一個資料欄位。例如我們宣告一學生成績串列節點的結構宣告,並且包含下面兩個資料欄位;姓名name、成績score,與一個指標欄位next。可以宣告如下:
class student{
constructor(){
this.name='';
this.score=0;
this.next=null;
}
}
當各位完成節點類別的宣告後,就可以動態建立鏈結串列中的每個節點。假設我們現在要新增一個節點至串列尾端,且ptr指向串列的第一個節點,在程式上必須設計四個步驟:
1.動態配置記憶體空間給新節點使用。
2.將原列尾端的指標欄next指向新元素所在的記憶體位置。
3.將ptr指標指向新節點的記憶體位置,表示這是新的串列尾端。
4.由於新節點目前為串列最後一個元素,所以將它的指標欄next指向null。
例如要將s1的next變數指向s2,而且s2的next變數指向null:
s1.next = s2;
s2.next = null;
由於串列的基本特性就是next變數將會指向下一個節點,這時s1節點與s2節點間的關係就如下圖所示:
5-3-1單向鏈結串列的連結

JS           concatlist.js

/[示範]:單向串列的連結功能
var concatlist=(ptr1,ptr2)=>{
	ptr=ptr1;
	while (ptr.next!=null) ptr=ptr.next;
	ptr.next=ptr2;
	return ptr1;
}

class employee{
	constructor()  {
		this.num=0;
		this.salary=0;
		this.name='';
		this.next=null;
	}
}

findword=0;
data=[];
namedata1=['Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack'];
namedata2=['May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy'];
for (i=0; i<12;i++){
	data[i]=[];
	data[i][0]=i+1;
	data[i][1]=Math.floor(51+Math.random()*50);
}
const head1=new employee();  //建立第一組串列首
if (!head1) {
	console.log('Error!! 記憶體配置失敗');
	return;
}
head1.num=data[0][0];
head1.name=namedata1[0];
head1.salary=data[0][1];
head1.next=null;
ptr=head1;

for(i=1; i<12; i++){
//建立第一組鏈結串列
	newnode=new employee();
	newnode.num = data[i][0];
	newnode.name=namedata1[i];
	newnode.salary=data[i][1];
	newnode.next=null;
	ptr.next=newnode;
	ptr=ptr.next;
}

for(i=0; i<12; i++){
	data[i][0]=i+13;
	data[i][1]=Math.floor(51+Math.random()*50);
}

const head2=new employee();
if (!head2){
	console.log('Error!! 記憶體配置失敗!!');
	return;
}

head2.num=data[0][0];
head2.name=namedata2[0];
head2.salary=data[0][1];
head2.next=null;
ptr=head2;
for(i=1; i<12; i++){
//建立第二組鏈結串列
  newnode=new employee();
  newnode.num=data[i][0];
  newnode.name=namedata2[i];
  newnode.salary=data[i][1];
  newnode.next=null;
  ptr.next=newnode;
  ptr=ptr.next;
}

i=0;
ptr=concatlist(head1,head2);//將串列相連
console.log('兩個鏈結串列相聯的結果:');
while(ptr!=null){
	process.stdout.write('['+ptr.num+' '+ptr.name+' '+ptr.salary+']=> ');
	i=i+1;
	if(i>3){
		console.log();
		i=0;
	}
	ptr=ptr.next;
}

用一維方式來算

PHP          concatlist.php

$namedata1=array('Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack');

$namedata2=array('May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy');
$namedata=array();

function add_score($arr){
	$count_arr=count($arr);
	$new_arr=array();

	for ($i=0; $i<$count_arr; $i++){
		$score = rand(60,100);
		$temp = $arr[$i].",".$score;
		array_push($new_arr,$temp);
	}
	return $new_arr;
}

function join_arr($arr1,$arr2){
	 $count_arr=count($arr2);
	 for($i=0; $i<$count_arr; $i++){
	 	array_push($arr1,$arr2[$i]);
	 }
	 return $arr1;
}
$new_namedata1=add_score($namedata1);
$new_namedata2=add_score($namedata2);
$namedata=join_arr($new_namedata1,$new_namedata2);

foreach ($namedata as $key => $item) {
	  echo $item." ";
}

後記:

PHP
    使用二維陣列,很難去加、刪,只好用一維陣列並加上符號,來加資料。
       array_push()函數,前面不能加變數,會出錯。
           $my_arr=array_push($my_arr,$add_data);
  會出現錯誤的訊息
      array_push() expects parameter 1 to be array

PHP          concatlist.php

用二維方式來算       

$namedata1=array('Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack');

$namedata2=array('May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy');
$namedata=array();

function add_score($arr){
	$count_arr=count($arr);
	$new_arr=array();
	$temp_arr=array();

	for ($i=0; $i<$count_arr; $i++){
		$score = rand(60,100);
		$temp = $arr[$i];
		$temp_arr=array($temp,$score);
		array_push($new_arr,$temp_arr);
	}
	return $new_arr;
}

function join_arr($arr1,$arr2){
	 $count_arr=count($arr2);
	 for($i=0; $i<$count_arr; $i++){
	 	array_push($arr1,$arr2[$i]);
	 }
	 return $arr1;
}
$new_namedata1=add_score($namedata1);
$new_namedata2=add_score($namedata2);
$namedata=join_arr($new_namedata1,$new_namedata2);

foreach ($namedata as $key => $item) {
	  foreach($item as $value){
	  echo $value." ";
	  }
	  echo "<br>";
}

後記       PHP二維參考資料

PHP      先存成陣列,再用array_push去加
for ($i=0; $i<$count_arr; $i++){
       $score = rand(60,100);
       $temp = $arr[$i];
       $temp_arr=array($temp,$score);
array_push($new_arr,$temp_arr);
}
5-3-2單向串列插入新節點
在單向鏈結串列中插入新節點,如同一列火車加入新的車廂,有三種情況:加於第1個節點之前、加於最後一個節點之後,以及加於此串列中間任一位置。

演算法如下:

newnode.next=first;
first=newnode;

演算法如下:

ptr.next=newnode;
newnode.next=null;

演算法如下:

newnods.next=x.next;
x.next=newnods;

JS          insert_node.js

class employee {
	constructor() {
		this.num=0;
		this.salary=0;
		this.name='';
		this.next=null;
	}
}

var findnode=(head,num)=>{
	ptr=head;
	while (ptr!=null){
		if (ptr.num==num) return ptr;
		ptr=ptr.next;
	}
	return ptr;
}

var insertnode=(head,ptr,num,salary,name)=>{
	InsertNode=new employee();
	if (!InsertNode) return null;
	InsertNode.num=num;
	InsertNode.salary=salary;
	InsertNode.name=name;
	InsertNode.next=null;
	if(ptr==null)  {  //插入第一個節點
		InsertNode.next=head;
		return InsertNode;
	}
	else{
		if(ptr.next==null)  //插入最後一個節點
				ptr.next=InsertNode;
			else{ //插入中間點
				InsertNode.next=ptr.next;
				ptr.next=InsertNode;
			}
	}
	return head;
}

position=0;
data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299],
			[1012,42660],[1014,25676],[1018,44145],[1043,52182],
			[1031,32769],[1037,21100],[1041,32196],[1046,25776]];
namedata=['Allen','Scott','Marry','John','Mark','Ricky',
					'Lisa','Jasica','Hanson','Daniel','Axel','Jack'];
process.stdout.write('員工編號 薪水\t員工編號 薪水\t員工編號 薪水\t員工編號 薪水\n');
process.stdout.write('-------------------------------------------------------------\n');

for (i=0; i<3; i++) {
	for(j=0; j<4; j++)
		process.stdout.write(data[j*3+i][0]+ '\t' +data[j*3+i][1]+'\t');
		console.log();
}
console.log('-------------------------------------------------------------');

head=new employee(); //建立串列首
head.next=null;

if(!head) {
	console.log('Error!! 記憶體配置失敗');
	return;
}
head.num=data[0][0];
head.name=namedata[0];
head.salary=data[0][1];
head.next=null;
ptr=head;

for (i=1; i<12; i++){//建立串列
	newnode=new employee();
	newnode.next=null;
	newnode.num=data[i][0];
	newnode.name=namedata[i];
	newnode.salary=data[i][1];
	newnode.next=null;
	ptr.next=newnode;
	ptr=ptr.next;
}

while(true) {
	process.stdout.write('請輸入要插入其後的員工編號,如輸入的編號不在此串列中,\n');
	const prompt = require('prompt-sync')();
	const position=parseInt(prompt('新輸入的員工節點將視為此串列的串列首,要結束插入的過程,請輸入-1'));
	if (position ==-1)
		break;
	else{
		ptr=findnode(head,position);
		new_num = parseInt(prompt('請輸入新插入的員工編號:'));
		new_salary=parseInt(prompt('請輸入新插入員工的薪水:'));
		new_name=prompt('請輸入新插入的員工姓名:');
		head=insertnode(head,ptr,new_num,new_salary,new_name);
	}
	console.log();
}
ptr=head;
console.log('\t員工編號     姓名\t薪水');
console.log('\t===========================');
while (ptr!=null) {
	process.stdout.write('\t['+ptr.num+' ]\t[ '+ptr.name+' ]\t['+ptr.salary+']\n');
	ptr=ptr.next;
}

JavaScript 概念三明治(三)

箭頭函式

E86之後,出現一種新的創造函式的方法,普遍被稱為箭頭函式,使得創造函式變得簡潔許多。相對於以前使用function關鍵字來宣告的用法,箭頭函式只需要用=>搭配括號跟區塊,就可以簡單地創造出函式,而因為這個等於大於的符號看起來很像箭頭,所以才被稱為箭頭函式。

const arrowFunction =() =>{
//do something.
};
箭頭函式的用法如上,可以看到這個用法已經不需要function關鍵字了,取而代之的是在括號跟區塊中間的箭頭。而你應該也可以注意到,箭頭函式的用法就與前面提到的函式表達式非常相近。沒錯,箭頭函式本身是匿名的,這意味它只可以被使用在函式表達式裡。
善用函式表達式
我們不妨試試看直接創造一個箭頭函式,但不將它指派給任何變數試試看。

()=>{
console.log("this is an arrow function");
};
你會發現,因為它是表達式,所以直接執行並不會出錯,就像我們直接在全域環境創造一個數字、一個字串或一個物件一樣。它會回傳一個函式物件,不過JavaScript看到這裡只會知道,「喔,這裡有一個函式」,所以該函式不會被執行,這段程式碼對JavaScript來講其實沒有什麼意義。
接下來,直覺敏銳的讀者應該可以猜到我想要說什麼了,其實轉個彎想一下,既然它只能用在表達式,那麼也許我也能把它用在前面立即執行函式(IIFE)的寫法上。
(()=>{
console.log("arrow function has been invoked");
})() //加上()會先去執行()的函式

一樣用括號,並把函式放進括號裡面,清楚告訴JavaScript這是一個表達式,而且我要優先運算,然後得到運算結果所回傳的函式物件之後,直接執行它。這樣一來,我就可以用簡潔的方法來撰寫IIFE了。

簡潔的回傳值
使用箭頭函式時,若所要回傳的內容單純只有一個表達式的時候,這個時候我們可以省略區塊{},而直接接在箭頭符號後面,這麼一來在函式被呼叫之後,這個表達式會直接被回傳。
當然,箭頭既然函式本身也是表達式,而JavaScript裡面函式又可以被傳給另為一個函式,那麼我應該有可以這麼做:
有看到特別的地方嗎?上方呼叫add函式的時候連續使用的兩個括號,這可不是連續呼叫兩次的意思!而是因為add函式本身回傳的是另外一個函式,會有兩次的呼叫是說:在執行第一個函式完收到第二個函式之後就直接執行了。

與function關鍵字的差異

箭頭函式相較於一般函式function關鍵字的用法,一般來說的優點都是語法簡潔、好閱讀等等,實際上也真的是這樣,它的確能夠讓開發者增加不少開發速度,不過它與使用function關鍵字創造出來的函式有些微的不同,甚至在某些點上有非常大的不一樣。這些差別不一定就是好或是壞,端看你如何使用而已,所以在這個段落的後面篇幅將會列舉一些較為明顯的差異來說明。
>>箭頭函式沒有arguments物件
什麼是arguments物件?arguments這個字代表了引數,另外一個與隻非常相近的詞稱為參數,引數用在函式宣告時,而呼叫函式時所傳進去的值則叫做參數。
換句話說,宣告一個函式時要先定義好參數,使函式時必須傳入引數。這兩個詞蠻常被搞混的,在別的地方搞錯沒有關係,不過在這邊為了介紹arguments物件,必須要釐清他們的差異才行。
   function add(a,b){  //a,b是參數parameters
return a+b;
}
add(1,3); //引數
所以函式裡面的arguments物件其實就是用來接收傳入函式的引數。一個使用function關鍵字來創造的函式,在函式被呼叫時,除了直接透過函式的參數名稱來拿到對應傳入的引數值,但是也可以用物件來取用個別的引數內容,這是JavaScript提供的預設物件。
arguments物件是一個很像陣列的資料,你可以透過索引來拿到個別的引數值。
但它沒有陣列上那些方便的方法像是foreach或是map,真的單純只是長得像陣列而已,有些人會把它稱為是一個「類陣列」Array-Liked的資料結構。而一個跟陣列比較像的屬性只有length,所以如果要依序拿到每個引數值,則必須用一般的for迴圈。
function add(a, b) {
for (var i=0; i<arguments.length; i++){
if (typeof arguments[i] !== "number"){
return false;
}
}
return a+b;
}
add(1,2); //3
add(1,"some thing else"); //false
而arguments物件在箭頭函式內可不存在的,所以如果想要利用它來組合一些邏輯的話,千萬記得這一點。
const arrowFunction =() =>{
console.log(arguments);
}
arrowFunction();
//ReferenceError:arguments is not defined

圖說演算法使用JavaScript(八)

5-2陣列與多項式

多項式是數學中相當重要的表現方式,通常如果使用電腦來處理多項式的各種相關運算,可以將多項式以陣列Arrray或鏈結串列Linked List來儲存。本節中,我們還是集中討論多項式以陣列結構表示的相關應用。
5-2-1多項式陣列表示法
這個稱為P(x)為一n次多項式,而一個多項式使用陣列結構儲存在電腦中,可以使用兩種模式。
1.使用一個n+2長度的一維陣列存放,陣列的第一個位置儲存最大的指數n,其他位置依照指數n遞減,依序儲存相對應的係數:
使用這種方法的優點就是在電腦中運用時,對於多項式的各種運算(如加法與乘法)較為方便設計。不過如果多項式的係數為多半為零,就顯得太浪費空間了。
2.只儲存多項式中非零項目。如果有m項非零項目,則使用2m+1長的陣列來儲存每一個非零項目的係數及指數,但陣列的第一個元素則為此多項式非零項的個數。
這種方法的優點是可以節省不必要的記憶空間浪費,但缺點則是在多項式各種多項式演算法設計時,會較為複雜許多。

JS            poly_add.js

ITEMS=6;
var PrintPoly=(Poly,items)=>{
  MaxExp=Poly[0];
  for (i=1; i<Poly[0]+2;i++){
    MaxExp=MaxExp-1;
    if (Poly[i]!=0){
      if(MaxExp+1!=0)
        process.stdout.write(Poly[i]+'X^'+(MaxExp+1)+' ');
      else
        process.stdout.write(' '+Poly[i]);
      if (MaxExp>=0)
        process.stdout.write('+');
    }
  }
}
var PolySum=(Poly1,Poly2)=>{
  result=[];
  result[0]=Poly1[0];
  for(i=1;i<Poly1[0]+2;i++){
    result[i]=Poly1[i]+Poly2[i]; //等冪的係數相加
  }
  PrintPoly(result,ITEMS);
}

PolyA=[4,3,7,0,6,2]; //用方法一,降冪方式敘述:3X^4+7X^3+6X+2
PolyB=[4,1,5,2,0,9]; //用降冪方式敘述:X^4+5X^3+2X^2+9
process.stdout.write('多項式A=> ');
PrintPoly(PolyA,ITEMS);
console.log();
process.stdout.write('多項式B=> ');
PrintPoly(PolyB,ITEMS);
console.log();
process.stdout.write('A+B => ');
PolySum(PolyA,PolyB);

PHP           poly_add.php

$ploy_a=array(4,3,7,0,6,2);
$ploy_b=array(4,1,5,2,0,9);

function get_poly ($arr){
  $len=count($arr);
  $maxexp=$arr[0];
  $re_poly="";

  for ($i=1; $i<$arr[0]+2; $i++){
    
    if ($len !=($arr[0]+2)){
      return "多項式格式錯誤";
      break; //檢驗長度
    }
    
    $maxexp=$maxexp-1;
    if ($arr[$i]!=0){
      if(($maxexp+1)!=0)
        $re_poly=$re_poly.$arr[$i]."X^".($maxexp+1)." "; //字串加法
      else 
        $re_poly=$re_poly." ".$arr[$i];
      
      if($maxexp>=0)
        $re_poly=$re_poly."+";
    }
  }

  return $re_poly; 
  //echo $len."-".$maxexp."<br>";
}

function poly_add($arr_a,$arr_b){
   $result=array();
   $result[0]=$arr_a[0];

   for ($i=1; $i<$arr_a[0]+2; $i++){
   
    if ($arr_a[0] !=$arr_b[0]){
      return "多項式長度錯誤";
      break; //兩個是否不同
    }
    
    $result[$i]=$arr_a[$i]+$arr_b[$i];
   }
   return $result;
}

$get_poly_a = get_poly($ploy_a);
$get_poly_b = get_poly($ploy_b);
$get_poly_add = get_poly(poly_add($ploy_a,$ploy_b));

echo $get_poly_a."<br>";
echo $get_poly_b."<br>";
echo $get_poly_add."<br>";

沒有思考到,級數不一樣時的加法。

如:X^5 + 4X^3 +1   跟 X^4 + 5X^3 + 12X + 5