2012年10月23日

Learning Perl 4


l   左右角括號為「整列輸入運算符」,中間夾檔案代號(filehandle)
l   以下程式碼做了:讀取標準輸入、將它存入變數、檢查變數的值是否有定義
while (defined($line = <STDIN>)){
        print “$line”;
}
l   以上程式碼可簡寫為(此簡寫法只限定whileforeach迴圈使用)
while (<STDIN>){
           print “$_”;
}

        foreach (<STDIN>){
           print “$_”;
        }
l   以上方法在while迴圈是一次讀一列;foreach則是在串列語境下一次讀入所有輸入,因此當讀入的檔案越大,foreach將越慢顯示
l   調用引數(invocation argument),命令列上跟在程式名稱後的幾個「單字」
l   調用引數中的「-」連字號代表「標準輸入串流」
l   @ARGVPerl直譯器事先建立的特殊陣列,用來存調用引數所組成的串列
l   鑽石形運算符(diamond operator)<>。用來讀取@ARGV所存的檔案串列,若為空串列則改用標準輸入串流
while (defined($line = <>)){
        chomp($line);       
        print “$line”;
}

while (<>){
   chomp;
   print “$_”;
}
l   當調用引數超過1個時,將會合併為1個檔案,因此不用擔心中間間斷問題
l   鑽石形運算符通常一次處理所有輸入,所以程式當中看到兩個應該是個錯誤
l   鑽石形運算符若無法開啟某個檔案時,會顯示訊息,並直接跳到下一個檔案
l   $0Perl直譯器預設存放程式自己的檔名
l   print @array; 無空格;print “@array”; 有空格
l   print 要處理一串印出的字串,因此在串列語境下執行(在沒加圓括號時)
l   print 可選擇不加圓括號,有加的情況下則被當作函式呼叫,傳回值真或假
l   print (2+3)*4; 會印出5而非20,因為把圓括號當函式用了
l   printf %d 中間可加數字代表預設字元數,正為右推齊,負為左推齊
l   printf %% 為印出百分比符號
l   printf "Item List:\n" . ("%-10s\n" x @items), @items; 請思考這行程式
l   open filehandle_name, “開檔模式”, “filename”; 建立一個新的檔案代號
l   開檔模式字串為「<」表輸入;「>」表輸出(直接覆蓋原有的);「>>」以附加方式輸出
l   開檔模式後可接「:encoding(xxx)」指定編碼
l   close filehandle_name; 關閉檔案代號
l   die函式印出指定訊息,用標準錯誤串流處理,使程式以非零(錯誤)狀態立刻中止,若將「$!」放到錯誤訊息字串裡即代表錯誤訊息字串
l   務必檢查open的傳回值,0為成功;非0為失敗
l   use autodie; 自動幫你操作die函式
l   以「>」、「>>」開啟的filehandle可以在printprintf函式中使用
e.g.1 print LOG “content”;
e.g.2 printf STDOUT “content %d %s”, number, string;
l   filehandlelist content之間沒有逗號,因為filehandle是一個「間接受詞」
I gave Neo a book. or I gave a book to Neo. book是直接受詞;Neo是間接受詞
Program print filehandle contents. or Program print contents to filehandle.同理
l   由於預設輸出filehandle就是STDOUT,因此print/printf可以省略STDOUT,但可以使用「select」改變預設
l   資料輸出到filehandle預設是會經過緩衝區,可以將「$|」的值改為1即可立刻輸出資料
l   某個filehandle在存在的情況下要再次open,則Perl會自動幫你關閉舊有的filehandle,因此非特殊情況下不應該重新open Perl的六個標準filehandle
l   diewarn會自動將訊息送到STDERR
l   因此若想蒐集錯誤訊息的話,可以重新open一個STDERR到指定的檔案,在使用diewarn
l   STDINSTDOUTSTDERR任一個重新open失敗,Perl會自動找回原先的filehandle,前提是尚未改變特殊變數「$^F
l   say是一個在content結尾加上換行符號(\n)print,可搭配filehandle
l   filehandle存入純量變數中的優點:做為副常式的引數、可存入陣列或雜湊中、控制其作用範圍
l   filehandle存入的純量變數通常以「_fh」結尾,e.g. $filehandle_fh
l   printfilehandle後不可接逗號,會被當成串列內容

2012年10月19日

Binary Search in Perl

最近在學Perl...
外加大學資結都在鬼混所以去旁聽大學部的資檔結...

今天老師講到Binary Search
所以就用Perl自己來寫一個
然後就遇到了天大的小問題......

Perl算是動態語言所以變數都不需要型態宣告
且Perl一律使用「倍精度浮點數」,因此沒有整數值這東西
然後剛才在實作Binary Search的時候完全忽略這點的後果就是想不通Bug的原因

問題如下:
定義了一個串列為 3 7 14 20 23 32 41 44 56 57 73 89 93
然後開始用新學的語法開始劈哩啪啦的把Binary Search寫出來
很開心的要來測試一下,而且心裡想說怎麼可能會有Bug......
沒想到測到 7 這個數就發現竟然找不到它......
繼續測下去竟然有更多找不到的值,包括 20 56...
以為是演算法寫錯了,可是怎麼看都沒問題
最後把所有與演算法相關的變數($begin $end $mid)都印出來
才發現小數點的問題......

問題解決:
target begin end mid
7 0 12 6

0 5 2(2.5)

0 1(1.5) 0(0.75)

1(1.75) 1(1.5) 1(?)




20 0 12 6

0 5 2(2.5)

3(3.5) 5 4(4.25)

3(3.5) 3(3.25) 3(?)




56 0 12 6

7 12 9(9.5)

7 8(8.5) 7(7.75)

8(8.75) 8(8.5) 8(?)

從上面的表格可以看到幾個發生問題的值的搜尋過程
圓括號內的值是沒有把浮點數轉為整數的實際值
可以看出最後應該要找到結果的那邊begin都大於end
難怪while迴圈都會在此終止......

最後解法就是將 ($begin+$end)/2 以 int來轉型 → int (($begin+$end)/2)

以下程式碼:
use 5.012;

my @datas = qw( 3 7 14 20 23 32 41 44 56 57 73 89 93 );
my $locn =  &binary_search(56, @datas);
say $locn;

sub binary_search{
 my ($target, @list) = @_;
 my ($begin, $end, $mid) = (0, $#list, 0);

 while($begin <= $end){
  $mid = int(($begin + $end) / 2); #就是這行卡了我老半天...
  if($target > $list[$mid]){
   $begin = $mid + 1;
  }else{
   if($target < $list[$mid]){
    $end = $mid - 1;
   }else{
    return int($mid);
   }
  }
 }
 return -1;
}

Learning Perl 3


l   副常式就是一般所謂的函式,可重複利用的程式碼片段,不過在Perl裡通常是說使用者自訂的叫做「副常式」,內建的則是「函式」
l   副常式名稱規則與變數一樣,通常是開頭加上「&」,但有禁止的情況
l   副常式宣告:
sub name{
# your code
}
l   副常式呼叫:&name;
l   由於Perl所規定的副常式都具有傳回值,因此副常式的最後一次求值動作便當成傳回值,若最後一行為print則傳回1
l   在副常式名稱後面加上引數串列(argument list)就可傳值,此串列值將被記在「@_」這個陣列變數中,在此副常式結束前都存在
l   若全域變數有@_,也不用擔心副常式的參數陣列會有問題,副常式當中呼叫另一副常式也一樣,因為在自己的@_出現前會先將之前的存起來
l   Perl當中所有的變數預設都是「全域變數」
l   用「my」運算符建立副常式內部的語彙變數(lexical variable),因此這些變數作用範圍僅限於此副常式內,通常也用在區塊內e.g. if, while, foreach
l   因此可以習慣性以 my($x, $y, $z) = @_; 放於副常式開頭將變數指定給特定語彙變數
l   高水位標記:
sub max{
        my($max_so_far) = shift @_;
        foreach (@_){
                if($_ > $max_so_far){
                        $max_so_far = $_;
}
}
$max_so_far;
}
l   my($num) = @_; #串列語境;my $num = @_; #純量語境
l   my沒用圓括號就只能宣告一個變數,用圓括號才能宣告多個
l   use strict」放在程式開頭或任何想套用此規則的區塊內,便能強制使用一些良好的程式語言規則,或使用use 5.012便自動為你載入strict
l   字串比較只能用「eq」運算符而非「==
l   副常式除了回傳純量值亦可除回串列值
l   state代替my來宣告語彙變數,並且在兩次副常式呼叫之間保存變數的值

2012年10月18日

Learning Perl 2


l   純量是單數(singular);串列和陣列則複數(plular)
l   串列(list)指的是資料的有序集合;而陣列(array)是指可存放資料(包含串列)的那個「變數」
l   串列可以不存放在陣列當中,獨立存在;但每個陣列變數一定要存放一個串列(就算是空的)
l   $#變數名稱」:取得陣列的最後一個元素的索引值
l   陣列索引值放負數表示從尾端開始算,-1最後一個,-2倒數第二個……
l   串列字面值(list literal):圓括號內一串以逗號分隔開的值(數值或字串)
l   串列可用「..」範圍運算符表示兩整數之間間隔為1的串列,小數則取整數,只能由小到大往上計數
l   qw(quoted words/quoted by whitespace),簡化字串陣列的建立,並視每個字串為以單引號來處理
l   界定符(delimiter)不只是圓括號,任何標點符號都可以
l   以串列賦值:($fred, $barney, $dino) = (“first”, 2, nudef)
l   以串列交換兩變數值:($fred, $barney) = ($barney, $fred)
l   @陣列名稱」可直接讀取整個陣列
l   $calar(Scalar)純量;@rray(array)陣列
l   poppush的第一個引數絕對是「陣列」,對串列poppush無意義
l   pop/push從陣列的尾端(索引值高)處理;shift/unshift從陣列的開頭(索引值低)處理;splice從中間任意指定起始位置處理
l   為了避免雙引號內出現非陣列意圖的「@」,可使用反斜線加在「@」之前或用單引號括住字串
l   變量出現在雙引號當中,為了使直譯器與我們有同樣共識,可使用大括號「{}」,e.g. “the value is ${name}.”
l   foreach的控制變數只是對象串列的複製品,因此在迴圈當中做修改會影響到陣列或串列內的內容
l   選擇控制變數不需要擔心之前有重複的,因為迴圈結束之後之前的變數內容會恢復
l   foreach若省略控制變數,則Perl會用預設變數「$_
l   reverse運算符將串列的值反轉後傳回
l   sort讀取串列中的值,並依字符來排序後傳回
l   語境(context):決定變量該扮演甚麼腳色。當Perl剖析(parsing)運算式時會預期它是「純量值」或「串列值」,就稱為語境
l   陣列的名稱,在串列語境下產生一串元素;在純量語境下傳回陣列元素個數
l   強制指定「純量語境」:scalar
l   <STDIN>在純量語境下傳回輸入資料的下一列;串列語境下傳回剩餘的各列