2014年3月20日 星期四

[C/C++]二維陣列的傳遞

我們都知道,
C語言是以陣列第一個元素的位址當成是陣列的位址(也就是說-->陣列名稱本身就是存放陣列位址的變數),
在c裡,陣列傳遞是採用傳址呼叫(call by address or call by pointer),
因為在呼叫函數傳遞參數時,無法將整個陣列傳遞(因為陣列可能很大),因此傳的是陣列開頭的位址,
okay,複習一下參數傳遞的觀念,
分為傳值(call by value)、傳址(call by address)、傳參考(call by reference)3種,
1.傳值call by value
當函數被呼叫時,將主程式的變數"複製"一份給副函數,主程式的變數跟副程式複製來的變數互相獨立,並且有各自獨立的記憶體空間,
假設main()呼叫函式funcB(p, q),則funcB中的p和q是把main()傳入的參數「複製」一份,
funcB()對p, q所做的任何運算都不會影響到main()中的p和q,因為funcB()執行完後,不會把複製的p, q丟回給main()。
寫成code:
void swap( int i, int j );
int main()
{
 int i=5, j=10;
 printf("before swap:i=%d(add=%p) j=%d(add=%p)\n",i,&i,j,&j);
 printf("swap()work\n");
 swap(i,j);
 printf("i j in main():i=%d(add=%p) j=%d(add=%p)\n",i,&i,j,&j);
 return 0;
}

void swap( int i, int j )
{
 int temp;
 temp = i;
 i = j;
 j = temp;
 printf("i j in swap():i=%d(add=%p) j=%d(add=%p)\n",i,&i,j,&j);
}
可以看到output中,main()和swap()裡的i和j是獨立的(記憶體位址也不同),
而經過swap進行變數互換後,也沒影響到main()裡原本的變數值,

當換成傳遞陣列時,可以看到結果不一樣了,
void timesTwo( int arr[] );
int main()
{
 int arr[3]={1,3,5};
 for(int i=0; i<3; i++)
 printf("before swap:arr[%d]=%d(add=%p)\n",i,arr[i],&arr[i]);
 printf("arr[] add=%p\n",arr);
 printf("swap()work\n");
 timesTwo(arr);
 for(int i=0; i<3; i++)
 {
  arr[i] = arr[i]*2;
  printf("arr[] in main():arr[%d]=%d(add=%p)\n",i,arr[i],&arr[i]);
 }
 printf("arr[] add =%p\n",arr);
 return 0;
}

void timesTwo( int arr[] )
{
 for(int i=0; i<3; i++)
 {
  arr[i] = arr[i]*2;
  printf("arr[] in swap():arr[%d]=%d(add=%p)\n",i,arr[i],&arr[i]);
 }
 printf("arr[] add =%p\n",arr);
}
證實了開頭所說,C是以陣列第一個元素的位址當陣列的位址,且採用傳址(call by address)方式,
呼叫副函式timesTwo()後,函數所做的改變,直接改變main()裡array的值,這就是傳址(call by address),

傳址同樣可以用在一般變數,只要加上pointer即可,這方法可以讓函數直接改變原本變數值,也省去複製的步驟和記憶體空間,
void swap( int *i, int *j );
int main()
{
 int i = 5, j = 10;
 int *pt1 = &i;
 int *pt2 = &j;
 printf("before swap:\ni=%d(add=%p)\tj=%d(add=%p)\npt1=%d(add=%p)\tpt2=%d(add=%p)\n\n",i,&i,j,&j,*pt1,pt1,*pt2,pt2);
 printf("swap()work\n");
 swap(pt1, pt2);
 printf("i j pt in main():\ni=%d(add=%p)\tj=%d(add=%p)\npt1=%d(add=%p)\tpt2=%d(add=%p)\n\n",i,&i,j,&j,*pt1,pt1,*pt2,pt2);
 return 0;
}

void swap( int *i, int *j )
{
 int temp;
 temp = *i;
 *i = *j;
 *j = temp;
 printf("i j pt in swap():\ni=%d(add=%p)\tj=%d(add=%p)\n\n",*i,i,*j,j);
}
swap()呼叫前後,main()的i j跟swap裡i j用的都是同一塊記憶體,經過swap後,也因此i j直接互換的結果直接改變在main()裡,
這也是為什麼call by address又叫call by pointer,然後本質上他也是call by value的關係了!

當寫到2維陣列(或多維陣列)時,赫然發現事情已經不是如上所想的簡單了,
首先,這樣的陣列傳遞已經無法通過compile了,因為Compiler不知如何翻譯...待會會解釋why,
void timesTwo( int arr[][] );
int main()
{
 int arr2[2][4]={ 1,3,5,7,2,4,6,8 };
 ...
}
如果這樣寫就可以通過compile,但把維度寫死一點都不彈性,未來要傳不同維度array給函數,不就哭了,
void timesTwo( int arr[2][4] ) {};
所以要換成這樣的寫法較佳,
void timesTwo( int arr[][4] ) {};
或這樣,
void timesThree( int (*arr)[4] ) {};
結果都是一樣,並且成功傳給function顯示,

2維陣列的行和列,其實是我們為了清楚理解陣列元素排列而想像出來的,實際上在記憶體配置並非如此,而是以線性模式配置,
不管幾維陣列,都是分配成一塊連續的記憶體空間去處理陣列,
如int arr2[2][4]陣列會分配成2*4*sizeof(int)大小記憶體區塊(如圖),依此類推,

如果要走訪2維陣列的話,要把它對應成一維來處理,
假設陣列int arr2[2][4]={ {1,3,5,7},{2,4,6,8} },
我們要存取arr2[p][q]的值,從記憶體觀點,我們要從arr2[p]的開頭後走q個單位到達arr2[p][q].
所以可以如此表示:arr2[p][q]=p*4+q

code可以這樣寫
void arrFour(int *arr, int rol, int col)
{
 for(int i=0 ; i<rol ; i++) {
  for(int j=0 ; j<col ; j++) {
   arr[i*col+j] = arr[i*col+j]*2;
   printf("array[%d][%d] in arrFour()= %d, address=%p\n", i, j, arr[i*col+j], &arr[i*col+j]);
  }
 }
 printf( "address of array arr2 in arrFour()=%p\n", arr );
}

成功更改array每一個元素,可以看到arrFour執行前後,main()和function裡所使用的記憶體位置都是同一塊,改變也直接作用在原本array的value上,
這樣的寫法也增加了使用上的彈性,不用在宣告函數時寫死,
這也解答了為何 void timesTwo( int arr[][] ) 這樣寫無法compile了,因為compile無從計算起位置.

2014年3月14日 星期五

[SAS] macro的使用

Macro又名巨集,是非常實用的SAS coding技巧,
當你需要執行某程序,並且反覆執行它時,
最簡單的就是copy-paste..copy-paste..copy-paste..copy-paste這段程序,
ex.
假設手中有2005-2010 4筆資料(a2005 - a2008),想知道各年中y_05跟x的OR值等統計資訊,
*直覺懶人法;
proc logistic data=a2005;
  model y1(event="1") = x1;
run;
proc logistic data=a2005;
  model y2(event="1") = x2;
run;
proc logistic data=a2006;
  model y1(event="1") = x1;
run;
proc logistic data=a2006;
  model y2(event="1") = x2;
run;
...
...
...
proc logistic data=a2008;
  model y2(event="1") = x2;
run;
但是,我們都知道這是很笨也很沒效率的作法,也不符合優雅coding的理念,
所以macro是有學起來的必要性的,
如果引進macro寫法會變成怎樣呢,
OPTION NOSYMBOLGEN MPRINT;
%MACRO logiMc(year, y_vb, x_vb);
  proc logistic data=a&year;
    model &y_vb(event="1") = &x_vb;
  run;
%MEND logiMc;

%logiMc(2005,y1,x1)
  %logiMc(2005,y2,x2)
    %logiMc(2006,y1,x1)
      %logiMc(2006,y2,x2)
        %logiMc(2007,x1,y1)
          %logiMc(2007,x2,y2)
            %logiMc(2008,x1,y1)
              %logiMc(2008,x2,y2)
MACRO實際改寫出來的logistic程式碼只有4行(%MACRO和%MEND中間包的code),
原始寫法大約24行左右,節省了20行!
要不要學呢?! 當然看個人: )
其實macro不難,只是多了%和&符號而已,和一些參數設定的概念,
解釋如下:
由&開頭的變項稱為"巨集變項",
在要重複執行程序以外的指令程序,開頭要加上%,姑且稱作"巨集程序",
OPTIONS NOSYMBOLGEN MPRINT;      *在log檢視巨集和變數內容;
%MACRO logiMc(year, y_vb, x_vb);  *%MACRO-->宣告巨集的開頭,以及巨集名稱為logiMc,巨集接收3個參數year, y_vb, x_vb;
  proc logistic data=a&year;  *此處往下3行就是要重複執行的程序所在;
    model &y_vb(event="1") = &x_vb;  *由&開頭的巨集變項,會不斷被下方設定的巨集變數組代換,達到重複執行的目的;
  run;
%MEND logiMc;                    *%MEND-->宣告巨集的結尾;

/***巨集會來此處抓取設定好的巨集變數組,在程式區不斷做代換;**/
%logiMc(2005,y1,x1)			
  %logiMc(2005,y2,x2)
    %logiMc(2006,y1,x1)
      %logiMc(2006,y2,x2)
        %logiMc(2007,x1,y1)
          %logiMc(2007,x2,y2)
            %logiMc(2008,x1,y1)
              %logiMc(2008,x2,y2)
了解macro的精神後,就可以自由配合do, while, if-then, cat, 或甚至把多樣程序包到一個macro裡面,
for example:
先利用暫存檔作條件判斷
-->做retain 資料處理
-->再依條件output我要的檔案
//此處無需了解變數是什麼,只需了解怎麼編寫這樣的macro
OPTIONS NOSYMBOLGEN MPRINT; 
%MACRO test(o_y, o_y1, m_flag);
  %IF &m_flag=1 %THEN %DO;
    DATA WORK.in7; set WORK.in6;
        if season=&o_y & mdy(12,1,&o_y) <= indate < mdy(2,28,&o_y1) then output;
      run;

    DATA WORK.in8; set WORK.in7;
      by id;
        retain flu&o_y 0;
        flu&o_y = flu&o_y+1;
        if first.id then flu&o_y = 1;
    run;

    DATA in&o_y; set WORK.in8;
      by id;
        if last.id then output;
        keep id flu&o_y;
    run;
%END;
%MEND subset;
  %subset(2004,2005,1)
    %subset(2005,2006,1)
      %subset(2006,2007,1)
這邊會發現我多加了一條"%IF &m_flag=1 %THEN %DO;"和一個新參數"m_flag",
其實他的用意很簡單,就是"開關"的功用,
利用"如果m_flag=1(打開)就執行此組巨集變數"的概念,可以快速開關某組變數要不要跑巨集,
//進階寫法
當變數太多時,可能碰到設定巨集變數組麻煩的情況,
這時候可以配合do來達到更簡潔的寫法,
ex.
*記得,巨集語法開頭要加%;
%MACRO box( xStart, xEnd );
  %do xValue = &xStart %to &xEnd;
    proc surveylogistic data=aa;
      model y = &xValue * &xValue;
    run;
  %end;
%MEND box;
%box( 1, 16 )
Like this ! 歡迎提供補充和建議! : )

2014年2月19日 星期三

[Linux] screen的使用

最近因為專案需要跑大型數據模擬,常需要跑來中研院這邊登錄他們的server做simulation,
有時候模擬跑起來要很久很久,
如果程式沒結束就退出遠端,正在run的程式可能就會當掉,
因此藉機學到了Linux中強大的screen功能!
screen是linux上一個很好用的功能,可以在一個terminal下開啟多個視窗執行,又可以彼此切換,藉由每一個process的PID去modified,
簡單來說,又可以多分頁工作,又可以離線後繼續工作,如此好物,怎能不學!

screen的簡易指令:
screen      #第一次執行會創立一個running SHELL的單一視窗
            #(open a screen session for processing)
ctrl+a+c    #開啟下一個新的單一視窗並切換過去(change to new worker)
ctrl+a+d    #脫離(detach)目前的 screen ,並放到背景執行(jump out of screen),
            #離線繼續工作的強大指令,要在連上只需使用screen -r PID即可
screen -r PID    #選擇一個screen session 進入該程序
screen -ls       #查看所有screen sessions(show the screen working status)
kill PID         #kill process
說明:
平常工作寫或跑到一半就可以把它detach掉,丟到後臺執行後會回到還沒進screen的狀態,此時process其實都還在執行,就算logout也不會停止。
要在連上執行中的process,只要在login後,用screen -ls查看process的PID,在用screen -r PID即可回復該程序!
程序結束後,也只要用kill PID就可以跟該程序說bye bye嚕!

補充:
screen -R      #重新連接最近卸離的 screen 工作環境
screen -d PID  #強制 detach,以便「接手」過來
screen -wipe   #將廢棄的 screen 工作環境清除
exit           #關閉視窗(若只剩一個視窗則為關閉screen, close the screen)
top            #linux中的動態程序觀察功能,查看遠端主機終有哪些程序在執行
               #(leave top->press "q")
補充2:
ctrl+a+n       #Next == 切換到下個 window
ctrl+a+p       #Previous == 前一個 window
ctrl+a ctrl+a  #Other == 在兩個 window 間切換
ctrl+a+w       #Windows == 列出已開啟的 windows 有那些
ctrl+a+0       #切換到第 0 個 window
ctrl+a+1..9    #切換到第 1..9 個window
ctrl+a+a       #發出 C-a,在 emacs, ve, bash, tcsh 下可移到行首
ctrl+a+t       #Time,顯示當前時間,和系統的 load
ctrl+a+K(大寫)  #kill window,強行關閉當前的 window
ctrl+a+[       #進入 copy mode,在 copy mode 下可以回滾、搜索、複製就像用使用 vi 一樣
ctrl+b+Backward  #PageUp
ctrl+f+Forward   #PageDown

2013年12月17日 星期二

AMPPS 2.2 upgrade 解決 Apache 無法啓動

有在用AMPPS而且是MAC user的朋友,
可能會發現,自從升級成Mac OS X 10.9 Mavericks後,
打開AMPPS要啟動Apache,怎麼點都沒反應,至於Mysql則沒問題,
所以這陣子都靠terminal代其勞,
打開terminal->
sudo bash /Applications/AMPPS/apache/bin/apachectl start
sudo bash /Applications/AMPPS/apache/bin/apachectl stop
最近AMPPS官方團隊也推出補救方案->AMPPS 2.2,
download->install->it's work!!

2013年11月15日 星期五

[PHP] 用Xdebug協助除錯

寫程式總會碰到除蟲的痛,
僅管PHP算是比較好寫的程式,
但單只靠伺服器吐回來的陽春錯誤訊息,在小程式裡上可以應付,
當寫到規模比較大的程式時,這樣的error是不夠幫助我們除蟲的,
Xdebug,是一個幫助PHP除錯的延伸應用,
除了既有的error message以外,連call stack也會顯示出來,方便許多。
安裝步驟(我以AMPPS為例):
1.先查詢目前所使用PHP的資訊
->2種方法可查詢
a.使用phpinfo()
<?php phpinfo(); ?>
b.或進入AMPPS page查詢

2.phpinfo get.直接全選+複製所有內容,

3.到Xdebug wizard page,在空格處貼上剛複製的phpinfo,送出後會自動分析所需要的xdebug版本
(當然也可以直接到官網自行選擇所符合的版本下載),

4.自動跳出xdebug.版本.dll的下載點,還有設定說明,

5.下載dll檔到ext folder裡(ex. C:\Ampps\php\ext),

6.打開php.ini,在下方加入以下設定碼->save->finish!
(使用AMPPS,要打開apache下的php.ini喔)
zend_extension = "C:\Program Files\Ampps\php\ext\php_xdebug-2.2.3-5.3-vc9.dll"
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_host=localhost
xdebug.remote_port=9000
7.Restart Apache!!
8.隨便測試一個寫錯的程式,Bingo!!

對照沒有xdebug前,原始的error message

Ref.

2013年11月5日 星期二

[PHP]CURL常遇錯誤(call to undefined function curl_init)

今天實作php的curl功能,
畢擷取和傳送資料是很重要的功能,
以前有fopen()可以用,後來發現curl更強大,寫起來也更簡潔,
不過在實做時,碰到了這樣的問題,

code is here:
<?php
 $ch = curl_init();
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_URL, "www.yahoo.com.tw");
 curl_setopt($ch, CURLOPT_HEADER, false);
 curl_setopt($ch, CURLOPT_USERAGENT, "Google Bot");
 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
 $output = curl_exec($ch);
 curl_close($ch);
 echo $output;
?>
上網google發現是蠻常見的問題,
記下解決方法,方便大家參考,
可能原因
1.curl沒被啟用
2.缺少curl.dll檔

首先:
1. 打開php.ini->搜尋"extension=php_curl.dll"->去掉前面的";"->save->即可重啟apache

2. 如果方法1沒有解決問題->同樣在php.ini搜尋"extension_dir"->查目錄位址->到該目錄下檢查是否有"php_curl.dll"->沒有的話就下載 php_curl.dll

->搞定之後->把php目錄中的"libeay32.dll"和"ssleay32.dll"copy至c:\windows\system32裡面->重啟apache
Finish!

成功把Yahoo網頁抓過來了.

[PHP]辨認USER上網的裝置和瀏覽器

開發網站平台時常常需要考慮到user是用什麼裝置連進來,
因為不同裝置或瀏覽器在支援上會有差異,
(ex. iOS對flash的支援)
或針對行動裝置用戶傳送不同的效果或頁面等,
都可能要用到這樣的判斷,
實際研究後,發現只要在server端做個判斷功能即可。
辨識功能主要透過 $_SERVER[ ] 的回傳資訊和建立一個變數(ex. $browser)來作邏輯判斷。
利用$_SERVER['USER_AGENT']可以回傳user的作業系統和瀏覽器版本。

如果user使用智慧型行動裝置,$_SERVER['HTTP_X_WAP_PROFILE']則會回傳1個link(連結至行動裝置資訊的xml檔),藉由判斷他回傳有無,判別是否來自行動裝置
(據說不是所有行動裝置都會傳送這個link,不過至少到N95都有,所以應該影響不大)
ok,接下來就是code了!
改寫後程式碼如下:
<?php
//Detect mobile user client 
function checkMobile() {

    $mbBrowser = 0;
    $userAgent = $_SERVER['HTTP_USER_AGENT'];    //取得用戶端的使用環境
    $accept = $_SERVER['HTTP_ACCEPT'];
    
    //"i" 不區分大小寫
    if (preg_match('/(up.browser|up.link|mmp|symbian|smartphone|midp|wap|phone|android)/i',
      strtolower($userAgent))) {
    
      $mbBrowser++;
    }
    //非行動裝置
    if (strpos(strtolower($userAgent),'windows') > 0) {
      $mbBrowser = 0;
    }
    //有HTTP_X_WAP_PROFILE則必是行動裝置
    if ((strpos(strtolower($accept),'application/vnd.wap.xhtml+xml') > 0)
      or ((isset($_SERVER['HTTP_X_WAP_PROFILE'])
      or isset($_SERVER['HTTP_PROFILE'])))) {
 
      $mbBrowser++;
    }
    //額外放入暴力判斷
    $mobileSource = strtolower(substr($userAgent, 0, 4));
    $mbArray = array(
    'w3c ','acs-','alav','alca','amoi','audi','avan','benq','bird','blac',
    'blaz','brew','cell','cldc','cmd-','dang','doco','eric','hipt','inno',
    'ipaq','java','jigs','kddi','keji','leno','lg-c','lg-d','lg-g','lge-',
    'maui','maxo','midp','mits','mmef','mobi','mot-','moto','mwbp','nec-',
    'newt','noki','oper','palm','pana','pant','phil','play','port','prox',
    'qwap','sage','sams','sany','sch-','sec-','send','seri','sgh-','shar',
    'sie-','siem','smal','smar','sony','sph-','symb','t-mo','teli','tim-',
    'tosh','tsm-','upg1','upsi','vk-v','voda','wap-','wapa','wapi','wapp',
    'wapr','webc','winw','winw','xda ','xda-'
    );
    if (in_array($mobileSource,$mbArray)) {
      $mbBrowser++;
    }
    //ALL_HTTP是ASP的variable
    if (strpos(strtolower($_SERVER['ALL_HTTP']),'OperaMini') > 0) {
      $mbBrowser++;
    }

    if ($mbBrowser > 0) {
      return true;
    } else {
      return false;
    }   
}
?>
next, test it!
簡單做個html+css頁面讓output美觀點, 假設我想讓mobile-device user看見手機,non-mobile user看見PC, HTML+CSS code:
<html>
<head>
<title>Detect Mobile Test</title>

<style type="text/css">
	#pic {
	z-index:1;
	position: absolute;
	left:0px;
	top:0px;
	}
	#word {
	position: absolute;
	font-size:120%;
	z-index:2;
	left:65px;
	top:100px;
	}
</style>

</head>
<body>
<?php 

if(checkMobile()) {
	echo '<div id="word">by Mobile</div>';
	echo '<div id="pic"><img src="./mobile.jpg" /></div>';
}
else {
	echo '<div id="word">by PC</div>';
	echo '<div id="pic"><img src="./monitor.jpg" /></div>';
}
?>
</body>
</html>
1. 模擬user來自非行動裝置

2. 模擬user來自行動裝置

Reference.
1. User agent WIKI
2. PHP: $_SERVER
3. ASP資料
4. iPhone、Android手機模擬器 (Google Chrome內建)