2014年4月14日 星期一

Ruby: module, include, extend, self, module_function

module

一個最簡單的module

include

在class使用include, 可以讓module內的方法為class的物件方法,在ruby稱為mixin

include可用在模擬多重繼承,因Ruby不像C++可以直接多重繼承,所以宣告為module,在mixin到class裡面

extend

在class使用extend,可以讓module內的方法為class的類別方法

與include的不同點在於

  • include: module內的方法為class的物件方法
  • extend: module內的方法為class的類別方法
module Foo
  def foo
    puts 'foo'
  end
end

class Bar
  extend Foo
end

Bar.foo  # 印出hello foo (foo已成為Bar的類別法)

self in module

self是什麼?就是呼叫Ruby當前物件本身指標所指的,在主程序直接呼叫self返回main。

先看最簡單的module

module Foo
end

直接呼叫self會出現什麼呢?

module Foo
  p self #印出 Foo
end

別忘了ruby式腳本式的語言,這個程式沒寫錯,單純只是宣告module,他裡頭的命令句還是被執行,所以印出Foo

那麼這個Foo物件是什麼?簡單,印出來看看

module Foo
  p self.class #印出Module
end

非常清楚,Ruby號稱所有東西都是物件,所以module也是物件,所以self的使用很清楚了,直接動態的在Foo物件上面加上方法

module Foo
  def self.foo
    puts 'hello foo'
  end
end

Foo.foo # 印出hello foo, 直接呼叫Foo這個Module形態的物件,裡頭的foo方法

所以同理,class裡面的self也是一樣的應用,class也是可以實體化為物件,並動態的加上方法,通常稱為類別方法,這種類別方法,在其他程式語言裡面也很常用,只是實作方式不同,Ruby是把class也當作物件處理

extend self

綜合上面所說的,如果module裡面 extend self會發生什麼事呢?

module Foo
  extend self

  def foo
    puts 'hello foo'
  end
end

Foo.foo # 印出hello foo

如果在class內使用extend會使module內的方法,成為類別方法,那如果在module使用,同樣會成為module方法,這個講法並不好,因為在Ruby裡面都是物件,只是在其他語言稱為類別方法,所以我照著命名

extend self如果有點難懂,先看extend 別的模組

module Foo
  def foo
    puts 'hello foo'
  end
end

module Bar
  extend Foo
end

Bar.foo # 印出hello foo (foo成為bar的類別方法)

就在跟class裡面extend一樣。同理extend self,就可以讓自己本身的方法,可以用module方法的方式呼叫,也可以被class include時,被當成物件方法呼叫

module_function

從名稱得知,使用module_function,則只能當成module function來使用

將要變成module function的方法名稱,使用symbol的方式傳入

module Foo
  def foo
    puts 'hello foo'
  end

  module_function :foo
end

Foo.foo  印出hello foo(變成module方法)

跟上面extend self不同的是,它就只能當module方法,如果在class內include,也不會變成物件方法

module Foo
  def foo
    puts 'hello foo'
  end

  module_function :foo
end

class Brb
  include Foo
end

Foo.foo # 印出hello foo
brb = Brb.new
brb.foo # 跳出NoMethodError錯誤,因為foo只能當module方法,不是當物件方法來呼叫

included

還有一個動態的方法,讓class include時,再決定哪些是類別方法,哪些是物件方法

module內建一個方法叫做include,可以在該module被include時被呼叫,舉個例子

module Foo
  def self.included(receiver)
    puts "#{self}被#{receiver} include進去了"
  end
end

class Brb
  include Foo #印出Foo被Brb include進去了
end

所以我們可以在module內決定include它的class要哪些類別方法,幫它(class)呼叫extend就行了,例如

module Bar
  def bar
    puts 'hello bar'
  end
end

module Foo
  def self.included(receiver)
    receiver.extend Bar # 幫它(class)呼叫extend 並extend Bar進去
  end
end

class Brb
    include Foo
end

Brb.bar # 印出hello bar

同理,也可以幫它呼叫include

module Bar
  def bar
    puts 'hello bar'
  end
end

module Foo
  def self.included(receiver)
    receiver.send :include, Bar # 幫它(class)呼叫include 並include Bar進去
  end
end

class Brb
    include Foo
end

brb = Brb.new
brb.bar # 印出hello bar

也可以在module直接嵌套在module裡面 讓程式馬更簡潔,下面示範嵌套完,並直接呼叫extend與include

module Foo
  module Bar # 將用來當類別方法
      def bar
        puts 'hello bar'
    end
  end

  module Bla # 將用來當物件方法
    def bla
        puts 'hello bla'
    end
  end

  def self.included(receiver)
    receiver.extend Bar # 將Bar設定為類別方法
    receiver.send :include, Bla # 將bla設定為物件方法
  end
end

class Brb
    include Foo
end

Brb.bar
brb = Brb.new
brb.bla

2014年3月19日 星期三

twitter-bootstrap-rails使用font awesome

twitter-bootstrap-rails已經把font awesome編譯在裡面了,不過用法有點不一樣,我是上stack over flow才知道, ,這種更動沒有寫在README裡面誰知道啊

這邊是標準font awesome的用法 譬如

<i class="fa fa-anchor"></i>

不過用twitter-bootstrap-rails編譯在裡頭的font awesome要這樣用

<i class="icon-anchor"></i>

第一個fa不用,第二個fa改成icon

或者直接使用它的Helper

<%= glyph(:anchor) %>

用Helper是比較簡潔一點

2014年3月18日 星期二

ssh 直接複製到檔案結尾 (ssh append to file)

scp 沒辦法,scp會直接覆蓋。rsync沒辦法,會讓兩個檔案同步變成一模一樣...

這時候就只好用ssh 原始操作方式

ssh user@remote "要處理的指令"

那麼就簡單了,如果要抓取遠端的檔案,附加到本地,那就在遠端使用cat指令,然後用>>修改資料串流附加到本地的檔案不就行了嗎?沒錯

ssh user@remote "cat filename" >> /LocalPath/FileName

那我就舉一反三,資料串流改用>就是複製!!! ... 對不起,脫褲子放屁了,直接用scprsync不就好了... 這邊就是發現scprsync只能複製,我們才去找可以附加檔案的方式,別又把這個方法,重新拿回去當複製的的功能用,有句名言當你手上拿著鎚子,你就覺得任何東西都像釘子

好,剛剛是抓遠端檔案附加到本地,那反過來,抓本地檔案附加到遠端呢?我上查到了一個很神奇的用法

cat local-source-file-name | ssh user@some.domain “cat >> remote-target-file-name“

得改用管線的指令,如果用ssh user@some.domain “cat >> remote-target-file-name“ < cat local-source-file-name,一定失敗,因為那串指令會通通送到遠端去執行,cat local-source-file-name這個指令也是在遠端執行,根本不是cat你本地的資料。

總結:

  1. 遠端到本地,直接ssh 遠端 cat 檔案 >> 本地檔案
  2. 本地到遠端,要做管線,cat 本地檔案 | ssh 遠端 cat >> 檔案

2014年3月11日 星期二

安裝zsh + oh-my-zsh 出現 /usr/bin/env: zsh: 沒有此一檔案或目錄 (/usr/bin/env: zsh -: No such file or directory)

zsh 跟 oh-my-zsh 安裝完,可能會出現類似下面錯誤訊息,雖然他沒任何影響,不過我總覺得怪怪的

# 中文系統
/usr/bin/env: zsh: 沒有此一檔案或目錄

# 英文系統
/usr/bin/env: zsh -: No such file or directory

通常會出現這類的訊息,都是指令找不到。

這邊指zsh這個指令找不到,試試看輸入which zsh看看是不是沒有把zsh這個加到$PATH裡面,沒有的話加入就好了。

如果加入了一樣找不到,我trace了一下zsh的執行過程,通常都是跟oh-my-zsh的安裝順序錯了的時候才會發生。

這時編輯家目錄底下檔案vim ~/.zshrc,會看到

# 他先執行了oh-my-zsh.sh
source $ZSH/oh-my-zsh.sh
# 然而oh-my-zsh.sh已經在使用zsh這個指令了

# 這裡才把宣告路徑,所以當然zsh指令找不到
export PATH="/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/bin:/bin:/root/bin:/usr/local/bin"

這時就把它們兩個對調一下就好了

# 像這樣把 export PATH 放到 source $ZSH/oh-my-zsh.sh 上面
export PATH="/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/bin:/bin:/root/bin:/usr/local/bin"
source $ZSH/oh-my-zsh.sh
# 這樣oh-my-zsh.sh裡面就可以用zsh指令了

存檔後,登出再進來,他就可以正常使用zsh指令,不再出現這種錯誤訊息了

CentOS zsh 自行編譯

CentOS用yum安裝的zsh版本有點舊,這邊記錄一下,怎麼下載自己安裝

# 下載最新版的 zsh (筆下日期 2014/03/11) 並存檔為 zsh.tar.bz2
wget http://sourceforge.net/projects/zsh/files/latest/download\?source\=files -O zsh.tar.bz2

# 解壓縮
tar xvjf zsh.tar.bz2

# 你的版本不一定是5.0.5,我筆下日期 2014/03/11 到5.0.5版,你的可能更新
cd zsh-5.0.5

# 普通編譯流程,configure後 看缺什麼lib 裝一裝
./configure
make
sudo make install

# 把"/usr/local/bin/zsh" 這串字加到 sudo tee -a /etc/shells 裡
echo "/usr/local/bin/zsh" | sudo tee -a /etc/shells

# 上面那一步 你的 chsh -l 才查得到zsh
# 可以切換到/usr/local/bin/zsh 了
chsh -s /usr/local/bin/zsh

# 重新登入 shell就切換過去了
exit # and relogin

# 跟bash一樣,放個.zshrc檔案在使用者根目錄,讓系統進入zsh前讀取
touch ~/.zshrc # 你的變數 或alias就可以加在這裡面了
# 有點跟bash不一樣的地方是,沒有.zsh_profile,讓系統只讀第一次的檔案

# 如果你有裝oh-my-zsh,建議直接建立軟連結(上面那一步touch就不用做了,做了就先把~/.zshrc刪掉就可以了)
ln -s ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

2014年3月5日 星期三

Linux系統which與type指令,對比$PATH與Hashed路徑

我安裝Ruby後,為了搞定路徑花了老半天,後來弄懂了Linux指令在whichtype$PATHHashed就清楚多了。

如果你的系統有內建Ruby,打which ruby,會發現他的路徑在usr/bin/ruby底下,接著不管你用了什麼方法裝了新的Ruby,就把新的路徑在$PATH裡加到usr/bin的前面,系統通常就預設執行在$PATH裡面,放在前面先找到的那個Ruby。

但是有個情況,你加了$PATH以後沒有用,系統還是一直去執行舊的Ruby。可是打which ruby明明就是新的路徑呀?

我遇到的情況是這樣打type ruby會發現ruby is hased(/usr/bin/ruby),這是什麼意思呢?為什麼which rubytype ruby的路徑會不一樣。這種情況是ruby這個命令,在系統一開始就被寫在系統的hash table裡面。這邊簡單分類一下whichtype的不同:

  • which:
  • 單純從$PATH裡面尋找到的路徑。
  • type:
  • 指令真實被執行時使用的路徑。

which只是單純顯示在$PATH,type才是真實下命令按Enter後去執行的路徑。

所以安裝了新Ruby,可是系統若之前就把Ruby放在hash table裡面,他就會一直執行舊版本的Ruby,這時候只要重登目前這個Shell,讓hash table refresh一下就好了。如果還是不行,系統不曉得寫在什麼地方,他就是喜歡自動把舊Ruby放到hash table裡面。那就只好手動改了,在bash底下執行:

hash -r #清掉hash table裡所有的資料

上面這樣子是全清掉,全部的hash table重新建立。或者也可以像下面這樣:

hash -d ruby #單純在hash table清掉Ruby

這樣子hash table裡面沒有ruby了,系統就得乖乖執行$PATH裡面找到的ruby囉。

不只有ruby唷,不管安裝什麼php,python還是sqlite之類的,如果跟系統預設衝突,而且還是放在hash table裡面的,都可以用這個方法解決路徑的問題

2014年2月21日 星期五

陣列洗牌程式(shuffle array)

陣列如何把它的順序打亂,作出類似洗牌的效果,我一直都很頭痛,搞得非常的複雜。至從用了Ruby,Array物件包含shuffle方法之後,我就沒思考過陣列洗牌的問題了,反正Ruby幫我處理得好好的。

Ruby

# 52張牌的牌堆
poker = (1..52).to_a    # => [1, 2, 3, ... , 52] 
# 洗牌打亂
shuffled = poker.shuffle           # => [22, 32, 12, ...
# 也可以直接打亂原來的陣列
poker.shuffle!          # => [39, 47, 3, ...

Javascript

Javascript我就頭痛了,我得自己寫洗牌的方法。我在網路上找到了這個演算法,仔細看了之後,才知道原來洗牌可以這麼簡單:

// 原本for迴圈是一行程式,太難理解,這邊改寫成多行
function shuffle(o){
    for(var j, x, i = o.length; i;){
        j = Math.floor(Math.random() * i);

        // javascript的array是0-base
        // 所以迴圈第一次進入,--i後表示陣列最後一個位置。
        x = o[--i];
        o[i] = o[j]; 
        o[j] = x;
        // 以上三行代表以x為temp, o[i], o[j]做交換
    } 
    return o; //回傳陣列,我一開始也看錯看成回傳0
};

變數說明

  • 引數o: 將被洗牌的陣列
  • for迴圈內
    • i : 將會從陣列的最後一個位置,慢慢往前移到第一個位置(但移到第一個位置時for迴圈不執行,因為Javascript的數值0也代表false,會離開迴圈。0代表flase這點跟Ruby不一樣)
    • j : 將會被亂數選擇,選到要被交換的位置
    • x : 用來暫存o[i]的數值,幫助o[i]與o[j]做數值交換

就這樣從最後一個位置開始,依次往前隨機挑選一個位置與它交換(可能挑到自己,表示不交換),來達到洗牌的效果,陣列多大,就執行幾次,時間複雜度O(n),效率也不錯。 注意這邊傳入的陣列是call by reference,會修改原來呼叫方法時傳入的陣列,所以直接:

// 5張牌的牌堆
poker = [1, 2, 3, 4, 5];
shuffle(poker);
// poker 就直接被打亂了

如果不想打亂原來的牌堆,就稍微改寫一下,不要動到原來的陣列:

function shuffle(o){ //v1.0
    var ary = o.slice(0);
    for(var j, x, i = ary.length; i;){
        j = Math.floor(Math.random() * i);
        x = ary[--i], ary[i] = ary[j];
        ary[j] = x;
    }
    return ary;
};

這邊用到了一個小技巧slice(start, end),這是javascript用來複製陣列的一個區塊,指令開始與結束的位置,小技巧是如果傳入0,就直接複製整個陣列。如果你想說:

啊直接ary = o 不是更快

呵呵,我建議你從C的指標在開始複習一下。

C語言

使用C語言,改些小地方,C語言的rand()產生的是0 ~ RAND_MAX的一個數字,所以直接j = rand() % i;就行了。還有C語言的0也表示false的意思,大概只有Ruby跟大家不同,目前我只見過Ruby的0表示true。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int *shuffle(int *o, int length){
    int i,j,x;
    for(j, x, i=length; i;){
        j = rand() % i;
        x = o[--i];
        o[i] = o[j];
        o[j] = x;
    }
    return o;
}

int main(void) {
    srand(time(NULL));
    int i, poker[] = {1, 2, 3, 4, 5};
    shuffle(poker, 5);
    for(i=0; i&lt;5; i++)
        printf("%d ", poker[i]);
    return 0;
}

這邊我是用call by address的寫法,如果你不想改變原來的陣列。C語言有一堆記憶體的函式讓你用,你可以宣告一塊記憶體空間(malloc),然後記憶體複製(memcpy),之後傳入shuffle去洗牌

不建議在shuffle函式裡面宣告記憶體區塊,因為什麼時候要free掉記憶體很麻煩,交給呼叫他的函式去決定什麼時候free掉比較正確,shuffle函式就專心洗牌就好。

JQuery

在網頁上面,要把一些HTML的元件打亂,可以直接在JQuery裡面加個物件方法給他:

jQuery.fn.shuffleElements = function () {
    var o = $(this);
    // 這邊的for迴圈,使用原本的一行寫法
    for (var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};

裡面那個for迴圈洗牌的演算法沒變。

至於一些基本的jQuery用法,jQuery.fn,可以直接取得jQuery的prototype,關於javascript的prototype是什麼,請至函式 prototype 特性觀看。這邊使用jQuery.fn就可以直接在jQuery的protoype裡面加入shuffleElements方法,然後就直接傳入一堆HTML元件讓他去洗牌,譬如你的網頁有一堆圖片,簡單一點就這樣:

var img = $("img").shuffleElements();
$("body").append(img);

這麼寫的話,可以簡單地把你網頁裡面的圖片全部洗牌之後,再全部放回<body>最下面。這邊主要是講洗牌,你可以改一改把它變成<ul>的順序之類的,或其他的應用。