圖說演算法使用JavaScript(七)

全方位應用的陣列與串列演算法

5-1-1矩陣相加

請設計一程式來宣告3個二維陣列,來實作2個矩陣相加的過程,並顯示兩矩陣相加後的結果。

JS          matrix_add.js

var A = new Array();
var B = new Array();
var C = new Array();

for (let i=0; i<3; i++){
	A[i]=new Array();
	B[i]=new Array();
	C[i]=new Array();
}

A= [[1,3,5],[7,9,11],[13,15,17]];
B= [[9,8,7],[6,5,4],[3,2,1]];

N=3;
for (let i=0;i<3; i++){
	for (let j=0; j<3; j++){
		C[i][j]=A[i][j]+B[i][j];
	}
}
console.log("[矩陣A和矩陣B相加的結果]");
let str='';
for (let i=0; i<3; i++){
	for (let j=0; j<3; j++){
		str=str+C[i][j]+'\t';
	}
	str=str+'\n';
}
console.log(str);

PHP         matrix_add.php


$A_arr=array(array(1,3,5),array(7,9,11),array(13,15,17));
$B_arr=array(array(9,8,7),array(6,5,4),array(3,2,1));
$C_arr=array();
for ($i=0; $i<count($A_arr); $i++){
	 for($j=0; $j<count($A_arr);$j++){
	 	$C_arr[$i][$j] = $A_arr[$i][$j] + $B_arr[$i][$j];
	 }
}
print_r($C_arr);
echo "<br><hr><br>";
foreach ($C_arr as $item ){
     echo "[";
     $t=1;
     foreach ($item as $value){
      if($t<3)  echo $value.",";
      else echo $value; 
        $t++;
			}
     echo "]<br>";
}

函式說明


foreach ($arr as $yourstr){
echo $yourstr //$yourstr => $arr or $value
}

5-1-2矩陣相乘

如果談到兩個矩陣A與B的相乘,是有某些條件限制。首先必須符合A為一個m*n的矩陣,B為一個n*p的矩陣,對A*B之後的結果為一個m*p的矩陣C。

範例:
請設計一程式來實作下列兩個矩陣的相乘結果。

JS       matrix_multiply.js

const M=2;
const N=3;
const P=2;
A=[6,3,5,8,9,7];
B=[5,10,14,7,6,8];
C=[0,0,0,0];
if (M<=0 || N<=0 || P<=0) console.log('[錯誤:維數M,N,P必須大於0]');
for (let i=0; i<M; i++){
	for (let j=0; j<P; j++){
		let Temp=0;
	for (let k=0; k<N; k++) Temp = Temp +parseInt(A[i*N+k])*parseInt(B[k*P+j]);
			C[i*P+j] = Temp;
  }
}
console.log('[AxB的結果是]');
let str='';
for (i=0; i<M; i++){
	for (j=0; j<P; j++){
		str = str+C[i*P+j]+ '\t';
	}
	str = str+'\n';
}
console.log(str);

PHP            matrix_multiply.php

$a_arr = array(array(6,3,5),array(8,9,7));
$b_arr = array(array(5,10),array(14,7),array(6,8));

$a_m = count($a_arr);    //陣列 M列
$a_n = count($a_arr[0]); //陣列 N行

$b_n = count($b_arr);    //陣列 N列
$b_p = count($b_arr[0]); //陣列 P行

$c_arr = array();
if ($a_m <=0 || $a_n <=0 || $b_n<=0 || $b_p<=0 || $a_n!=$b_n){
  echo "條件錯誤";
}
    
//echo "a_m=".$a_m."; a_n=".$a_n."<br>";
//echo "b_n=".$b_n."; b_p=".$b_p."<br>";

for ($i=0 ; $i<$a_m; $i++){
    for ($j=0; $j<$b_p; $j++){
      $temp=0;
     for ($k=0 ; $k<$a_n; $k++){
         $temp = $temp + intval($a_arr[$i][$k])*intval($b_arr[$k][$j]);
         //echo $temp."<br>";
         $c_arr[$i][$j]=$temp;
      }
         //echo $temp."<br>";
   }
    //echo "--<br>";
}
print_r($c_arr);

5-1-3轉置矩陣
「轉置矩陣」At就是把原矩陣的行座標元素相互調換,假設At為A的轉置矩陣,則有At[j,i]=[i,j],如下圖所示:

範例

    請設計一程式來實作一4*4二微陣列的轉置矩陣。

JS          transpose.js

arrA=[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
N=4;
arrB=[[],[],[],[]];
console.log('原設定的矩陣內容');
for (i=0; i<4; i++){
	str = '';
	for (j=0; j<4; j++){
		str= str+arrA[i][j]+'\t';
	}
	console.log(str);
}

for(i=0; i<4; i++){
	for(j=0; j<4; j++){
		arrB[i][j]=arrA[j][i];
	}
}
console.log('[轉置矩陣的內容為]');
for (i=0; i<4; i++){
	str = '';
	for (j=0; j<4; j++){
		str= str+arrB[i][j]+'\t';
	}
	console.log(str);
}

PHP          transpose.php

$a_arr = array(array(1,2,3,4),array(5,6,7,8),array(9,10,11,12),array(13,14,15,16));
$b_arr = array();

$arr_m = count($a_arr);
$arr_n = count($a_arr[0]);

echo "M-".$arr_m."N-".$arr_n."<br>";

for ($i=0; $i<$arr_m; $i++){
  for($j=0; $j<$arr_n; $j++){
      $b_arr[$i][$j]=$a_arr[$j][$i];
  }
}

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

foreach($b_arr as $item){
   foreach ($item as $value){
    echo $value."   ";
   }
   echo "<br>";
}
5-1-4 稀疏矩陣
稀疏矩陣最簡單的定義就是一個矩陣中大部分的元素為0,即可稱為「稀疏矩陣」Sparse Matrix。例如下列的矩陣就是典型的稀疏矩陣。
當然如果直接使用傳統的二維陣列來儲存上圖的稀疏矩陣也是可以,但事實上許多元素都是0。這樣的做法在矩陣很大時的稀疏矩陣,就會十分浪費記憶體空間。而改進空間的方法就是利用三項式(3-tuple)的資料結構。我們把每一個非零項目以(i,j,item-value)來表示。更詳細的形容,就是假如一個稀疏矩陣有n個非零項目,那麼可以利用一個A(0:n,1:3)的二維陣列來表示。
其中A(0,1)代表此稀疏矩陣的列數,A(0,2)代表此稀疏矩陣的行數,而A(0,3)則是此稀疏矩陣非零項目的總數。另外每一個非零項目以(i,j,item-value)來表示。其中i為此非零項目所在的列數,j為此非零項目所在行數,item-value則此分零項的值。以上圖6*6稀疏矩陣為例,則可以以下表示:
A(0,1)=>表示此舉矩陣的列數。
A(0,2)=>表示此舉矩陣的行數。
A(0,3)=>表示此舉矩陣非零項目的總數。
這種利用3項式(3-tuple)資料結構來壓縮稀疏矩陣,可以減少記憶體不必要的浪費。

JS          sparse.js

var NONZERO=0;
temp=1;
Sparse=[[15,0,0,22,0,-15],[0,11,3,0,0,0],
        [0,0,0,-6,0,0],[0,0,0,0,0,0],
        [91,0,0,0,0,0],[0,0,28,0,0,0]];//宣告稀疏矩陣,稀疏矩陣的所有元素設為0
str='';
console.log('[稀疏矩陣的各個元素]');
for (i=0;i<6;i++) {
   for(j=0;j<6;j++){
      process.stdout.write(Sparse[i][j]+'\t');
      if (Sparse[i][j]!=0) NONZERO=NONZERO+1;
   }
   console.log();
}

Compress=new Array();   //宣告壓縮陣列
for (let i=0; i<NONZERO+1; i++) Compress[i]=[];

//開始壓縮稀疏矩陣
Compress[0][0]=6;
Compress[0][1]=6;
Compress[0][2]= NONZERO;

for (i=0; i<6; i++){
   for (j=0; j<6; j++){
      if (Sparse[i][j]!=0){
         Compress[temp][0]=i;
         Compress[temp][1]=j;
         Compress[temp][2]=Sparse[i][j];
         temp=temp+1;
      }
   }
}
console.log('[稀疏矩陣壓縮後的內容]');//印出壓縮矩陣
for (i=0; i<NONZERO+1;i++){
   for (j=0; j<3; j++){
      process.stdout.write(Compress[i][j]+'\t');
   }
   console.log();
}

PHP            sparse.php

$sparse=array(array(15,0,0,22,0,-15),array(0,11,3,0,0,0),
              array(0,0,0,-6,0,0),array(0,0,0,0,0,0,0),
              array(91,0,0,0,0,0),array(0,0,28,0,0,0));
$M = count($sparse);
$N = count($sparse[0]);
$nonzero=0;//計算沒有為零的數量
$temp=1;
$compress=array();

for ($i=0; $i<$N; $i++){
  for($j=0; $j<$N; $j++){
    if ($sparse[$i][$j]!=0)
      $nonzero=$nonzero+1;
  }
}
echo "原始陣列"."<br>";
foreach ($sparse as $key => $item) {
    foreach($item as $list => $value){
      echo $value."   ";
    }
      echo "<br>";
}

$compress[0][0]=$M;
$compress[0][1]=$N;
$compress[0][2]=$nonzero;

for($i=0; $i<$N; $i++){
  for($j=0; $j<$M; $j++){
    if ($sparse[$i][$j]!=0){
      $compress[$temp][0]=$i;
      $compress[$temp][1]=$j;
      $compress[$temp][2]=$sparse[$i][$j];
      $temp=$temp+1;
    }
  }
}
echo "壓縮後的陣列<br>";

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

圖說演算法使用JavaScript(六)

3-14階梯狀圖形外觀 Staircase

對於給定數量的級別,請用$和符號空格打輸出類似「階梯狀」的圖形外觀。

JS           staircase.js

const draw = no => {
	let pictures = "";
	for (let row=0; row<no; row++){
		str="";
		for (let column = 0; column<no; column++)
			str += column <= row? "$":" ";
		    pictures +=str + "\n";
	    }
		return pictures;
};
console.log(draw(15));

PHP          staircase.php

$my_num = 15; 

function pictures($n){

	for($i=0; $i<$n; $i++){
		  
		  if($i!=0) echo "<br>";
		  
		  for ($j=0; $j<=$i; $j++){
		  echo "$";	
		  }
	}

}

$pictures = pictures($my_num);

3-15金字塔圖形外觀  Pyramid

對於給定數量的級別,請使用$和符號空格打輸出類似「金字塔」的圖形外觀。

課本的JS範例是錯的

我解的是對的↓

PHP           pyramid.php

$my_num = 15; 

//function pictures($n){

	$layer = $my_num; //幾層
	$num = $my_num*2-1; //個數
	$mid = $layer ; //中間數

	for($h=1; $h<=$layer; $h++){
	   for($j=$mid-$h; $j>=1; $j--){
	   	echo "  ";
	   }
	   for($i=1; $i<=$h*2-1; $i++){
	   	echo "$";
	   }
	   echo "<br>";
	 }
echo "<br>".$layer."-".$num."-".$mid;

JavaScript 概念三明治(二)

物件型別

   「物件」指的就是物件。恩,但其實有很多東西本身也算是物件,例如陣列和函式,不相信嗎?讓我們繼續看下去。你可以在JavaScript裡面宣告一個陣列,然後用typeof去得到這個陣列的型別,就可以看出一些端倪:
JavaScript提供了Array這個所有地方都可以使用全域物件,而在這個Array.isArray的方法能夠讓我們使用,來知道某數值是不是一個陣列。
好,那麼剛剛說函式也是物件型別,是這樣嗎?你會發現,當你一樣用typeof去觀察一個function的型別時,所得到的結果不是object,而是function。
先別急著反駁,讓我先用一個最簡單的方式證明你看。前面應該提到過,一個物件型別的數值,可以用.或是[]來做存取。那麼首先我宣告一個函式,並把這個函式當作物件使用,在上面新增一個屬性之後,再取出這個屬性來看看,是不是得到同樣的值:
    函式真的是像物件一樣的東西,事實上,它真的就是。在JavaScript裡面,函式算是一個比較物件,稱為「函式物件」(Function Object),它除了可以被當作函式來呼叫,也能夠當作物件使用。

一級函式  First-Class Function

    當我們說一個語言具有一級函式的特性時,代表這個語言把函式當作變數值一樣看待,也因此可以把函式當作參數一樣傳入另外一個函式裡面,也能夠以函式作為另一個函式的回傳值。在Function Programing 裡面,也是因為這個特性,才有辦法做到複合函式(Function Composition)。
在JavaScript內,函式本身也是一個特殊的物件(就是函式物件),所以才有辦法把函式當作參數傳給另外一個函式來做使用。

高階函式 High Order Function

    高階函式有人稱為HOF。只要是可以接收函式作為參數,或是回傳函式作為輸出的函式,我們就能夠把它稱為高階函式,例如,JavaScript裡面的陣列這個類別,上面有許多讓你很方便能夠對陣列做操作的方法,例如:map方法讓你能夠依序對每個陣列做一些修改、而filter則讓你能夠過濾掉不符合條件的元素。
我們可以試著思考看看他們是怎麼被實做出來的,下面就以Array.map為例,一起來模擬這個方法、邊思考它的運作方式。

試著實作陣列上的map方法

map是在任何陣列上都可以取用的一個方法,它接收一個能夠修改陣列元素的函式。並在運算之後將每個元素被修改後的數值,以另外一個全新陣列最為回傳值。
   由於map其實就是對陣列元素個別巡訪,然後做某些操作之後回傳,所以可以推敲出可能的步驟如下:
1.將包含開發者修改的邏輯函式傳入map方法內。
2.先創造一個全新的陣列,用以存放運算之後的結果。
3.執行一個以陣列長度為執行次數的迴圈。
4.利用迴圈的計數值作為陣列的索引,來找到每個陣列元素。
5.取得陣列元素、逐個取得修改函式運算完的結果。
6.逐個放入運算結果值的陣列並待迴圈節結束後回傳。
有了這些運作的邏輯,我們也能夠嘗試自己實作類似map方法的邏輯,看起來可能會像是這樣:
function arrayMap(fn, array){
 let newArray = [];
  for (let i=0; i < array.length; i++){
   let result = fn(array[i]);
    newArray.push(result);
   }
   return newArray;
}
let array =[1,2,3];
let newArray = arrayMap(function(item){
   return item*2;
},array)
console.log(newArray);

試著實作陣列上的filter方法

    既然都講到這裡了,我們就來看看另一類似的filter方法。filter一樣接受一個函式來進行運算,只不過相對於map是變更陣列的個別元素,filter的目的是過濾不需要的元素,你應該也能從字面上看出來。
傳進filter的函式,會以回傳值的條件判斷,來作為陣列元素會不會被過濾掉的依據,若回傳值與布林值的true等價、可被轉型為true,那麼就保留這個元素,反之這個元素就不會出現在運算結果的陣列裡面。
而我們同樣的能夠推測filter的運作流程,預期上會與map相似:
1.將包含開發者判斷邏輯的函式傳入filter方法內。
2.先創造一個全新的陣列,用以存放運算的結果。
3.執行一個以陣列長度為執行次數的迴圈。
4.利用迴圈的計數值作為陣列的索引,來找到每個陣列元素。
5.取得陣列元素、並逐個取得判斷函式運算文成的結果。
6.依照判斷函式的結果決定是否要放入結果值的陣列,並待迴圈結束後回傳。
function arrayFilter(fn, array){
   let newArray=[];

   for (let i=0; i < array.length; i++){
      let arrayElement = array[i];
      let shouldkeep = fn(arrayElement);

      if (Boolean(shouldkeep)){
         newArray.push(arrayElement);
      }
   }

   return newArray;
}

let array = [1,2,3];
let resultArray = arrayFilter(function (element){
   return element > 1;
},array);

console.log(resultArray);