code

2016年11月22日 星期二

Algorithms筆記14 - Master Theorem

Master theorem是用來快速估計一個能寫成recurrence relation的演算法的running time,就是一個被證明的公式:




舉例來說,之前的naiive multiplication algorithm的recurrence relations如下,用master theorem分析之:



改良過的karatsuba乘法,將subproblem減少成只有三個,所以a = 3,用master theorem分析如下:


大約是O(n^1.58),這不是常數倍數的改良,是指數的改良,相當的好。

如果有人能把乘法演算法改成只有2個subproblem呢?事實上目前沒發現過,不過這個recurrence relations就是merge sort的特徵:


所以目前sorting最好的running time只能是O(nlogn)。


這個是什麼?沒錯,就是Binary search:



2016年11月21日 星期一

Algorithms筆記12 - Binary search using D&C strategy

Binary Search

Binary search是一個divide and conquer algorithm:


binary search將一個problem分成兩個等分的subproblem,由於input是sorted array,所以每次都能刪除一個不可能得到答案的subproblem,等於每次problem size變成一半:



最後的combine所有subproblem的答案時,那些被捨棄掉的subproblem是空集合,所以最後一個subproblem中找到(或確定沒找到)的index就是整個母問題的解。

Worst-case Running time analysis

binary search最差的結果就是找不到這個數的index,如果寫成recurrence relation(recursive equations) 的話:


對problem size = n的problem來說,判斷midpoint是否為key所花的時間是一個常數c,如果midpoint不是key,則我們可以拿掉n/2的input,所以剩下的時間就是剩下的n/2 size 的subproblem所需要的worst case running time。



最差都沒找到的話,總共做了 log_2(n) + 1次的midpoint check,以n = 4來說,總共找了n = 4, n = 2, n = 1,總共3次的midpoint check。所以這是theta(log(n))。

Algorithms筆記11 - Divide and Conquer

分而擊之

聽起來很酷,這種策略有以下幾個特徵:

1. 分成許多一樣問題但是input size較小者:

一樣的subproblem很重要,跟greedy algorithm一樣,都必須是同類型的,這樣能解決小的,就能解決大的,例如把一個矩形面積拆成好幾個矩形來算:


但是不能拆成其他形狀,因為這就不是subproblem:



2. 各個擊破後,要把答案拼成原本大問題的答案,這是一個很大的特徵,所以除了切成不同size的subproblem之外,這些subproblems必須要剛好能組合成母問題答案,所以以下的分法是錯的,因為subproblems重疊了:


3. 根據1. 的特性,自然會形成recursive algorithms。


一個D&C linear search例子


這個例子其實沒有什麼divide and conquer 的好處,因為每個subproblem只是原本的input size - 1。這邊是用來解釋如何估算running time:



每次recursion level都做了O(1)的事情,最差會總共做了n次,所以是O(n),或更精確地說是theta(n)。



2016年11月19日 星期六

Scala筆記 30 - Loops

While Loop

mutable variable已經能做到所有imperative programming的事,現在要介紹control structure: loop。

在Scala中,while是一個key word,但事實上我們也可以用pure functional的方式來定義while:

def WHILE(condition: => Boolean)(command: => Unit): Unit =
  if (condition) {
    command
    WHILE(condition)(command)
  }
  else ()

注意condition要call by name,這樣才能在每次deference的時候重新被evaluate,要不然真的變成無窮迴圈了。另外這是一個tail recursion,所以是constant stack size,跟imperative programming中的while一樣。

我們也可以模擬出do-while (但是這邊是while condition is not met),課堂中稱為repeat:

def REPEAT(condition: => Boolean)(command: => Unit): Unit = {
  command
  if (!condition) REPEAT(condition)(command)
}

var x = 1REPEAT( x >= 5 ) {
  x = x + 1;
  println(x)
}

另外模擬repeat-untill,要能寫出以下這種形式:

REPEAT {
  command
} UNTIL ( condition )

神奇,我不會做!想一下之後補上!

For Loop

之前已經用過for expression,這實際上是collection中的foreach method來implement的:



Scala筆記 29 - stateful objects

State in Scala

scala不是純粹的functional language,他可以有state。所謂的state就是一個變數值它會隨著執行的歷史而改變。此時substitution model就不能適用,因為一個變數值在每個時間點可能不一樣。如果要宣告一個可以改變的變數,用var而非val keyword來宣告。

如果一個object的狀態會隨著history改變的話,就是一個stateful object。


在Stream中用mutable variable實作lazy val

之前在筆記25我們說Stream可以是以下的implementation:

def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
  def head = hd
  lazy val tail = tl
  ...
}

我們宣告tail為lazy val(compiler會儲存evaluation,免得stream每次要用到tail就要重新evaluate tail一次(原本的non lazy-val版本如下):

def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
    def isEmpty = false    
    def head = hd
    def tail = tl
  }

我們可以把lazy val的tail改成var來宣告:

def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
  override def head = hd
  private var tlOpt: Option[Stream[T]] = None
  override def tail: Stream[T] = tlOpt match {
    case Some(x) => x
    case None => tlOpt = Some(tl); tail
  }
}

可以看到tail method是一個option,如果已經有值了(case Some(x)),那就回傳那個tail Stream,否則就去evaluate tl參數,將mutable variable tlOpt重新assign Some(tl),並且回傳重新呼叫自己tail method,此時不會進入recursion,因為tlOpt的match type已經改變成Some(x)了。

所以在以上例子可以看到,mutable variable可以用來實作lazy val。


stateful objects的等值與等效

我們之前非常容易判斷兩個value是不是等值,只要看其定義的expression是否一樣,所以可以用substitution model來重寫expression。但是在stateful objects就無法了,因為每個object的state不一樣的話,即便定義一樣,也不能說兩個instance是同一個。

但是兩個不同的instances,如果所有可能的不同順序的operations都能產生一樣的效果,可以說他們是operational equivalence,等效。


2016年11月18日 星期五

Algorithm筆記 10.5 - greedy演算法練習: 將一數拆成找出最多相異數字


如果你也在修Coursera Algorithm Toolbox的話,這是解答!請斟酌使用

問題定義

如何將某數n拆成最多個相異正整數?例如6可以拆成{1,2,3},10可以拆成{1,2,3,4}。

看起來可以提出一個safe move的假設,“永遠從目前最小的未選過的正整數開始”。

證明safe move

假設一數n,其optimal solution為拆成m個數 n = k1 + k2 + k3 + ... + km,且a < k1 < k2 < ... < km。令 x = k1 - a。

我們很輕易可以改寫成以下:
n = (k1-x) + k2 + ... + (km+x),也是一個optimal solution,因為k1-x = a < k2 < ... <km < km+x,都是相異自然數,故此策略為safe move


實作

選取最小正整數a有一個條件,就是n' = n-a >= a+1 = a',這樣在下一個subproblem中最小能選的數a'才能小於等於n',才有解。
舉例來說,8要怎麼拆成最多個正整數相加?按照我們演算法:

8-1 = 7, (7 >= 2)
7-2 = 5, (5 >= 3)
5-3 = 2, (2 < 4)
5-4 = 1, (1 < 5)
5-5 = 0, (分配完畢)


private static List<Integer> optimalSummands(int n) {
    List<Integer> summands = new ArrayList<Integer>();

    //find the min    int min = 1;
    while(n != 0) {
        int a = n - min;

        if (a == 0 || a >= min+1) {
            summands.add(min);
            n = a;
        }

        min = min + 1;
    }

    //write your code here    return summands;
}

running time為O(n)。

2016年11月15日 星期二

Algorithm筆記 10.4 - greedy演算法練習: 最少停留點問題


如果你也在修Coursera Algorithm Toolbox的話,這是解答!請斟酌使用

問題定義

有一棟建築物住了n個人,每個人在家的時間都不一樣(可能有重疊),如何能最少次抵達此建築物而能拜訪所有人?

這個問題可以map成數線上的線段和點,假設有3個人,在家的時段分別為(1點~3點), (2點~5點), (3點~6點),則在數線上表示

0 [1 [2 [3] 4 5] 6] 7 8 9 10 ....

由上圖來推論,我們可以假設safe move為“從最左邊的區間終點a找起(上圖為3),包含a的區間都能用a來表示可以拜訪的時段”。

證明safe move

假設某optimal solution中,最左邊的區間終點y屬於線段(x,y),且y不屬於任何線段的代表點:

1. 某線段(w,z)終點z >= y,且此線段包含y,可以用y表示,則optimal solution會減少1。

_ _ (x _ _ _ y) _ _ z) _

2. 某線段終點z <=y,不成立,因為y是最左邊的區間終點。

3. 某線段(w,z) 終點z > y,且w > y,y不能代表(w,z),不改變optimal solutoin。


以上三種情況包含了任意線段與y所有可能的關係,故根據1. 選擇y為代表點是符合optimal solution,此策略為safe move。

演算法

private static int[] optimalPoints(Segment[] segments) {

    ArrayList<Integer> result = new ArrayList<>();

    //O(nlogn) sort segments ascendingly based on end point    
    Arrays.sort(segments, new Comparator<Segment>() {
        @Override        
        public int compare(Segment o1, Segment o2) {
            return o1.end - o2.end;
        }
    });

    //O(n)    
    int leftmostEnd = segments[0].end;
    for(int i = 0; i < segments.length; i++) {
        if (segments[i].contains(leftmostEnd))
            continue;
        else {
            result.add(leftmostEnd);
            leftmostEnd = segments[i].end;
        }
    }

    //add the last match    
    result.add(leftmostEnd);

    int[] r = new int[result.size()];
    for (int i = 0; i < r.length; i++)
        r[i] = result.get(i);

    return r;
}

Algorithm筆記 10.2 - greedy演算法練習: 最大化dot-product


如果你也在修Coursera Algorithm Toolbox的話,這是解答!請斟酌使用

問題定義

給兩個整數vector v1 = {a1,a2, ... , an} , v2 = {b1, b2, ... , bn},找出最大的dot product?

按照greedy algorithm步驟,我們要先找一個safe move,並且證明其為某個optimal solution的第一步。依照題目,我們假設“v1和v2從大到小排序後所得的dot product為optimal”。

證明safe move

假設一optimal solution w = {a1*b1 + a2*b2 +.. + aj*bj + .... + ak*bk + ....  + an*bn},且ak = max(a1, a2,..,an), bj = max(b1, b2, ...,bn)。

如果w' = 把w中的 aj*bj 和 ak*bk 交換成 ak*bj和 aj*bk,則w' - w = ak*bj+aj*bk - aj*bj - ak*bk = ak*(bj-bk) + aj*(bk-bj) = (ak-aj)*(bj-bk) > 0,因為ak 和 bj分別是兩個數列中的最大值。

演算法

注意要integer相乘可能會oveflow,先cast成為long

private static long maxDotProduct(int[] a, int[] b) {
    Integer[] A = new Integer[a.length];
    Integer[] B = new Integer[a.length];

    //O(n)    
    for (int i = 0; i < a.length; i++) {
        A[i] = a[i];
        B[i] = b[i];
    }

    //O(nlogn)    
    Arrays.sort(A, new Comparator<Integer>() {
        @Override        
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });

    //O(nlogn)    
    Arrays.sort(B, new Comparator<Integer>() {
        @Override        
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });

    //O(n)    
    long result = 0;
    for (int i = 0; i < a.length; i++) {
        result += (long)A[i] * (long)B[i];
    }

    return result;


}






2016年11月14日 星期一

Algorithm筆記 10.3 - greedy演算法練習: 注意眉角

如果你也在修Coursera Algorithm Toolbox的話,這是解答!請斟酌使用


本來這題功課沒什麼好po的,但是有個implementation眉角要注意,所以寫出來警惕一下。下面粉色標出的區塊就是Java comparator的實作,但是由於compare要回傳int,所以不能直接把o2 - o1的值cast成int回傳,這樣可能會變成零,如果兩者的差<1.0的話。


public class FractionalKnapsack {
    private static double getOptimalValue(int capacity, int[] values, int[] weights) {
        double value = 0;

        class Item {
            public int value;
            public int weight;
            public double valuesPerWeight;

            public Item(int value, int weight) {
                this.value = value;
                this.weight = weight;
                this.valuesPerWeight = (double)value / (double)weight;
            }
        }

        //O(n) initalization        
        Item[] items = new Item[values.length];
        for (int i = 0; i < items.length; i++) {
            items[i] = new Item(values[i], weights[i]);
        }

        //O(nlogn) sort        
        Arrays.sort(items, new Comparator<Item>() {
            @Override            
            public int compare(Item o1, Item o2) {
                if (o2.valuesPerWeight - o1.valuesPerWeight > 0)
                    return 1;
                else if (o2.valuesPerWeight - o1.valuesPerWeight < 0)
                    return -1;
                else return 0;
            }
        });

        for(int i = 0; i < items.length; i++) {
            double load = Math.min((double)capacity, (double)items[i].weight);

            if (load >= items[i].weight) { //use all items[i]                
                value += items[i].value;
                capacity -= load;
            }
            else { //use fracation of items[i]                
                value += load * items[i].valuesPerWeight;
                return value;
            }
        }

        return value;
    }

    public static void stressTest() {
        while (true) {
            int n = ThreadLocalRandom.current().nextInt(1, 1000);
            int capacity = ThreadLocalRandom.current().nextInt(0, 2000000);
            int[] values = new int[n];
            int[] weights = new int[n];

            for (int i = 0; i < n; i++) {
                values[i] = ThreadLocalRandom.current().nextInt(0, 2000000);
                weights[i] = ThreadLocalRandom.current().nextInt(0, 2000000);
            }

            System.out.println(getOptimalValue(capacity, values, weights));
        }
    }

    public static void main(String args[]) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int capacity = scanner.nextInt();
        int[] values = new int[n];
        int[] weights = new int[n];
        for (int i = 0; i < n; i++) {
            values[i] = scanner.nextInt();
            weights[i] = scanner.nextInt();
        }
        System.out.println(getOptimalValue(capacity, values, weights));

        //stressTest();    
    }










Algorithm筆記 10.1 - greedy演算法練習: 找零問題

如果你也在修Coursera Algorithm Toolbox的話,這是解答!請斟酌使用

問題定義

給一個金額n,怎麼找出最少的零錢幣數目?限定幣值為{1, 5, 10}。

按照greedy algorithm步驟,我們要先找一個safe move,並且證明其為某個optimal solution的第一步。依照題目,我們假設“盡量先找幣值大的零錢是一個safe move”。

證明safe move

假設某個optimal solution = w的硬幣中有x個一元硬幣,y個5元硬幣,z個10元硬幣,如果x >= 5,我們可以拿出5個一元硬幣換成1個5元硬幣,所以optimal solution會變成w - 5 + 1 = w - 4,同理把2個y硬幣換成1個z硬幣的話,w值也會變少,得證。

演算法

private static int getChange(int m) {
    //write your code here
    int[] denominations = {10, 5, 1};
    int count = 0;

    for (int i = 0; i < denominations.length; i++) {
        int coins = m / denominations[i];
        count += coins;
        m = m - coins*denominations[i];

        if (m == 0)
            return count;
    }

    return count;
}

這是O(n),因為我們人工去排序了幣值了。

Algorithm筆記 9 - Knapsack演算法 (maximization problem)

問題定義

假設有個set = {(x1,y1), (x2,y2), ... (xn, yn)},我們想要取某個subset使得其中的元素的 y值相加的和最大,前提是x值相加的和不能超過W。

x值可以只取部分,例如x1/2, x2/3, ....

以裝袋為例




一個袋子最多只能裝7個units的物體,我們有三個物體分別是(4 unit, $20), (3 unit, $18), (7 unit, $14),試問要怎麼最大化裝入袋子的錢?

這個knapsack問題已經被證明可以用greedy algorithm找出最佳解:


其safe move就是“優先使用單位額度($/unit)最高者”。

證明Safe move

我們還是要走過一遍證明過程,才能對以上的lemma產生信心對吧?

假設我們有一個optimal solution,裡面至少裝入某些非最高單位額度的物體X。
如果我們把袋中的X取出一單位,放入最高單位額度的物體Y的一單位,則此袋子的總體額度增加了,所以原本的optimal solution並不是optimal solution,且必定至少包含一單位的Y。

我們如果對剩下的物體們的單位一一抽換成Y,此袋子的總體額度就會繼續增加,直到所有袋子中的物體被替換完成,或是Y被完全使用為止。若是後者,則次高單位額度的物體Z也遵從以上的邏輯,每次替換成Z都會增加總體額度。

得證。

演算法pseudocode和running time




上面的implementation是O(n^2),因為外層有一個for n loop,內層要找max單位額度的時候,又要看過所有的Wi,所以是O(n),這個很明顯有改善空間。

如果先把 vi/wi算出來並且排序(最好是O(nlogn)),就可以免去loop body中的linear search,所以knapsack algorithm最後會是O(nlogn) + O(n) = O(nlogn)。




2016年11月13日 星期日

Scala筆記 28 - 用Streams解決Search Problem,以倒水問題為例

Search Problem

所謂search problem就是在一個問題的可能解決地圖中,找到一條解決路徑,最好也是optimal的解決路徑,Scala課程中以倒水問題為例,而功課則要學生找出Bloxorz這個遊戲的解決路徑。

倒水問題如下:
有n個已知容量的空杯子,試求最少步驟能裝出x容量的水?我們能對杯子做的動作有:
1. 倒滿
2. 清空
3. 從a杯倒到b杯

Modeling States

這個跟我之前上Berkely AI課程的小精靈問題一樣,我們先把n個空杯子用一個Vector[Int]來代表,任何Vector[Int]可能的值就是一個state,並且先列出所有可以走的actions組合,因為待會就要在search space中利用這些actions來建立search path:

class Pouring(capacity: Vector[Int]) { //各種容量的杯子
  //states  
  type State = Vector[Int]
  val initalState = capacity map (_ => 0) //一開始所有杯子是空的
  
  //move types  
  trait Move
  case class Empty(glass: Int) extends Move
  case class Fill(glass: Int) extends Move
  case class Pour(from: Int, to: Int) extends Move

  //all indices  
  val glasses = 0 until capacity.length

  //all possible moves/actions  
  val moves =
    (for(g <- glasses) yield Empty(g)) ++
    (for(g <- glasses) yield Fill(g)) ++
      (for(g1 <- glasses; g2 <- glasses; if g1 != g2)
        yield Pour(g1,g2))

}

Modeling Moves

我們會從initial state開始長出三個可能的move path,分別是Empty, Fill, Pour,所以我們還需要知道從某個state經過一個move之後,會變成哪一個state? 我們在trait Move中加入一個change method:(這邊的邏輯比較不直覺,我會寫成State有一個change(move:Move)的方法,而不是在Move class中寫一個change(state:State))

//move types
trait Move {
  def change(state:State):State
}

case class Empty(glass: Int) extends Move {
  override def change(state: State): State = state updated(glass, 0)
}

case class Fill(glass: Int) extends Move {
  override def change(state: State): State = state updated(glass, capacity(glass))
}

case class Pour(from: Int, to: Int) extends Move{
  override def change(state: State): State = {
    val amount = math.min( state(from), capacity(to) - state(to) )
    (state updated(from, state(from)-amount)) updated (to, state(to) + amount)
  }
}

collection的updated method是一個immutable method,會產生一個新的collection,但是在某個index的value不同而已。

Modeling Paths

我們接著要來model path,某一條path其實就是每次move的歷史,我們把最新的move放在list head,有了這個history,我們可以利用每個move間的change來計算出最後的end state:

//Pathsclass Path(history:List[Move]) {
  def extend(move:Move) = (new Path(move::history))
  override def toString = (history.reverse mkString(" ")) + "-->" + endState
def endState: State = trackState(history) private def trackState(xs: List[Move]): State = xs match { case Nil => initalState case move::xs1 => move change trackState(xs1) } }

val initialPath = new Path(Nil)

上面trackState會先recursively抵達Nil,然後把initialState當作參數傳給第一個move的change method:

(lastMove change ...... (move2 chage (move1 change initialState)))

所以這是一個foldRight operation,我們可以把endState改寫成:

def endState: State = history.foldRight(initalState)( _ change _ )


Search space exploration

我們有了所有的models,現在可以開始做Breadth-first search。BFS search的過程類似從洋蔥的核心(initalState),一層層往外extend出同樣長度的path,對洋蔥中的某一層來說,已經長出了一些paths,接下來要長出下一層,這邊就要用到Streams:

def from(paths:Set[Path]): Stream[Set[Path]] =
  if (paths.isEmpty) Stream.empty  
  else {
    val more:Set[Path] = for {
      path:Path <- paths
      extendedPath:Path <- moves map(x => path.extend(x))
    } yield extendedPath

    paths #:: from(more) //union the extended paths  }

val pathSets = from(Set(initialPath))


用set的原因是,重複的path對我們來說沒有意義。用stream的原因則是要控制exploration深度,如果用list的話,必須要加上搜尋深度的限制才行,否則變成無窮迴圈。

開始搜尋解答

假設有兩個容量為4和6的空瓶:

val problem = new Pouring(Vector(4,6))
problem.moves

所有的可能moves如下:

res0: IndexedSeq[problem.Move] = Vector(Empty(0), Empty(1), Fill(0), Fill(1), Pour(0,1), Pour(1,0))

搜尋一層可能的paths,problem.pathSets.take(1).toList
res1: List[Set[problem.Path]] = List(Set(-->Vector(0, 0)))

搜尋二層可能的paths,problem.pathSets.take(2).toList
res1: List[Set[problem.Path]] = List(
Set(-->Vector(0, 0)),
Set(
Pour(0,1)-->Vector(0, 0),
Fill(0)-->Vector(4, 0),
Empty(1)-->Vector(0, 0),
Empty(0)-->Vector(0, 0),
Pour(1,0)-->Vector(0, 0),
Fill(1)-->Vector(0, 6))
)

我們現在可以寫這個倒水問題的solution method:

def solution(target:Int): Stream[Path] =
  for {
    pathSet <- pathSets    path <- pathSet
    if path.endState contains target
  } yield path


這個邏輯就是stream神奇的地方,pathSets是一個Stream[Set[Path]] type,本來應該是不會evaluate,但是由於stream的head是strict evaluation,所以head一定會evaluate,直到遇到第一個滿足if條件為止,此時stream就不會在evaluate下去。

深度不能確定是多少,要看滿足第一個if的path何時出現。This is not an optimized solution, might be very slow and not optimal!

total source code for now:

class Pouring(capacity: Vector[Int]) { //各種容量的杯子
  //states  type State = Vector[Int]
  val initalState = capacity map (_ => 0) //一開始所有杯子是空的
  //move types  trait Move {
    def change(state:State):State  }
  case class Empty(glass: Int) extends Move {
    override def change(state: State): State = state updated(glass, 0)
  }
  case class Fill(glass: Int) extends Move {
    override def change(state: State): State = state updated(glass, capacity(glass))
  }
  case class Pour(from: Int, to: Int) extends Move{
    override def change(state: State): State = {
      val amount = math.min( state(from), capacity(to) - state(to) )
      (state updated(from, state(from)-amount)) updated (to, state(to) + amount)
    }
  }

  //all indices  
  val glasses = 0 until capacity.length

  //all possible moves/actions  
  val moves:IndexedSeq[Move] =
    (for(g <- glasses) yield Empty(g)) ++    
    (for(g <- glasses) yield Fill(g)) ++      
    (for(g1 <- glasses; g2 <- glasses; if g1 != g2)        
      yield Pour(g1,g2))


  //Paths (我們記錄所有的moves,List head是最後的move)  
  class Path(history:List[Move]) {
    def extend(move:Move) = (new Path(move::history))
    def endState: State = history.foldRight(initalState)( _ change _ )
    override def toString = (history.reverse mkString(" ")) + "-->" + endState
  }

  val initialPath = new Path(Nil)

  def from(paths:Set[Path]): Stream[Set[Path]] =
    if (paths.isEmpty) Stream.empty    
    else {
      val more:Set[Path] = for {
        path:Path <- paths
        extendedPath:Path <- moves map(x => path.extend(x))
      } yield extendedPath

      paths #:: from(more) //union the extended paths    
    }

  val pathSets = from(Set(initialPath))

  def solution(target:Int): Stream[Path] =
    for {
      pathSet <- pathSets      
      path <- pathSet
      if path.endState contains target
    } yield path


}



2016年11月11日 星期五

Scala筆記 27 - 重寫牛頓法開平方根,利用Streams

利用Streams重寫牛頓法開平方根

在筆記4中,我們之前嘗試做了牛頓法來開平方根,基本上是一個不斷收斂的過程,是否收斂是利用isGoodEnough function來檢驗:



如果改成Stream版本非常簡潔:

def sqrtStream(x: Double): Stream[Double] = {
  def improve(guess: Double) = (guess + x / guess) / 2  
  lazy val guesses: Stream[Double] = {
    println("eval") 
    1 #:: (guesses map improve)
  }
  guesses
}

sqrtStream(2).take(10).toList

但是guesses的定義相當奇怪,它是一個stream,從1開始去append到遞迴定義的tail Stream,而這個tail Stream就是把guess Stream中所有的數字都去做improve,有點難懂。嘗試來解譯上面的expression怎麼evaluate的:

首先直到toList被evaluate,guesses才會被evaluate,且只要evaluate stream中的前十個items:

itreation 1
此時guesses是的head是1,剩下的tail stream的定義是(guesses map improve)

iteration 2
此時要evaluate iteration 1中的tail stream的head,也就是
improve(1) = (1 + 2 / 1) / 2
剩下的tail stream 為head = improve(1)的stream

iteration 3
此時要evaluate iteration 2中的tail stream的head,也就是
improve( improve(1) )

.
.

所以  sqrtStream(2).take(10).toList
相當於精準控制了iteration做了10次,不用去管convergence的問題。

這個範例讓我感覺如果寫一個recursive stream定義的話,這個stream被take幾次相當于對其items重新加上幾次的recursive定義,所以第二個item被定義成recursion 1次,第三個item被定義成recursion 2次,以此類推。

在此例中,guesses也可以用def keyword定義成function,但是 "eval"字串被印出了10次,而lazy val宣告的guesses只會印出一次。我猜測由於stream本身是lazy val的關係,def版本的guesses並不會多做其他的事情,不過不知道怎麼驗證這件事。

Scala筆記 26 - 無限長度的Streams!

無限制長度的Stream

有了lazy evaluation這個工具,我們可以寫出無限長度的數列Stream,我是把它看成一種,例如自然數Stream:

def nats(from:Int) :Stream[Int] = Stream.cons(from, nats(from+1))

nats(1).take(10).toList //res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

上面的expression只有當toList被evaluate時,才會去evalute nats的每個item。所以take(10)會開始evaluate nats(1)這個Stream的前十個item:

Streams.cons(1, nats(2))   //1st item is 1
Steams.cons(2, nats(3))   //2nd item is 2
.
.
Streams.cons(10, nats(11)) //10th item is 10
停止evaluation

有了這個自然數列,我們可以根據某個演算法來計算出一個Prime Stream。此古老演算法檢視所有的自然數,每遇到一個新的自然數,就把它的倍數從自然數列中去除掉,非常天真的演算法,但是符合Streams特性:

def sievePrimes(nats:Stream[Int]) : Stream[Int] = {
  nats.head #:: sievePrimes( nats filter(x => x% nats.head != 0 ))
}

sievePrimes(nats(2)).take(10).toList //res1: List[Int] = List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)



Scala筆記 25 - Lazy Evaluation

三種evaluation modes

某個定義在使用到的時候才會被evaluate,並且儲存已經evaluate過的結果,所以不會重複evaluate同一個定義,這稱作lazy evaluation:

lazy val y = { print(”y”); 2 }

如果每次使用都會被evaluate一次,稱為by-name evaluation,這就是一般call by reference的參數:

def z = 4   //z: z[] => Int,這就是一個function
z           //res0: Int = 4,此時才被evaluate

最後一種就是strict evaluation,只要宣告時就馬上evaluate:

val x = { print(”x”); 1 }

之前的Stream cons的定義用到了by-name evaluation,這會變得非常慢,可以改成lazy evaluation就能避免這個問題,但是就會用到額外的記憶體空間:

def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
  def head = hd
  lazy val tail = tl
  ...
}

Haskell default是使用lazy evaluation,但是Scala因為有mutable data types,所以怕造成不可預測的混亂,提供了optional的lazy evaluation。

所以以下的expr,如果被evaluate的話:

def expr = {
  val x = { print(”x”); 1 }
  lazy val y = { print(”y”); 2 }
  def z = { print(”z”); 3 }
  z + y + x + z + y + x
}

expr

1. 印出x,並且x被assigned成1
2. y不被evaluete,因為沒人用到它
3. z不被evaluate,因為沒人用到它
4. 開始evaluate z + y + x + z + y + x
5. evaluate z,印出z,z被mapped成3
6. evaluate y,印出y,且y被assigned成2,總共3+2
7. 取出x值,總共3+2+1
8. evaluate z,印出z,總共3+2+1+3
9. 取出y值,總共3+2+1+3+2
10. 取出x值,總共3+2+1+3+2+1

執行結果印出 xzyz,然後expr被evaluate成12

Scala筆記 24 - Streams

lazy evaluate的Stream

Streams是個有趣的資料結構,動態性的evaluate list的tail,我在之前用過的語言Java, C, C++都未曾遇過,也有可能我鑽研不夠深入,或許現在有了?我知道Java 8有stream,不過不確定跟Scala這個stream相不相同。

Streams跟List幾乎是一樣的使用方式,差只差在constructor中的tail list是call by reference:

val xs = Stream.cons(1, Stream.cons(2, Stream.empty))

有趣的是看REPL的type是

xs: Stream.Cons[Int] = Stream(1, ?)


如果寫一個range function,stream的版本如下:

def streamRange(lo: Int, hi: Int): Stream[Int] =
  if (lo >= hi) Stream.empty  
  else Stream.cons(lo, streamRange(lo + 1, hi))

這跟我們一般會寫的list版本一模一樣:

def listRange(lo: Int, hi: Int): List[Int] =
  if (lo >= hi) Nil  
  else lo :: listRange(lo + 1, hi)


Stream怎麼implement出來的?

stream是implement這個trait:

trait Stream[+A] extends Seq[A] {
  def isEmpty: Boolean
  def head: A
  def tail: Stream[A]
  ...
}

跟list一樣,只要定義了以上三個methods,其他的所有methods都能建築其上。

它的singleton companion object:

object Stream {
  def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
    def isEmpty = false    
    def head = hd
    def tail = tl
  }
  val empty = new Stream[Nothing] {
    def isEmpty = true    
    def head = throw new NoSuchElementException(”empty.head”)
    def tail = throw new NoSuchElementException(”empty.tail”)
  }
}

可以看到cons中的tl參數是call by reference,所以只有在tail method被呼叫到的時候才會evaulate。

所以如果我們執行以下的function:

def streamRange(lo: Int, hi: Int): Stream[Int] = {
  print(lo+” ”)
  if (lo >= hi) Stream.empty  
  else Stream.cons(lo, streamRange(lo + 1, hi))
}

streamRange(1, 10).take(3).toList

實際上會印出123,因為toList會真正要evaluate整個Stream,並且轉換成list。


Scala筆記 23 - 用for做random value generator

用trait做random value generator

這是常見的一種架構,先定義一個trait:


trait Generator[T] {
  def generate : T}

然後藉由實做這個trait得出各種不同type的random value generators:


val integers = new Generator[Int] {
  override def generate: Int = new Random().nextInt()
}

val booleans = new Generator[Boolean] {
  override def generate: Boolean = integers.generate > 0}

val pairs = new Generator[(Int,Int)] {
  override def generate: (Int, Int) = (integers.generate, integers.generate)
}

這樣定義ok, 但是使用上很麻煩,每次延伸一個新的種類,都要new一個generator出來,能不能不要用這種方式呢? 


嘗試用for試試看

我們希望達到的類似這樣的架構來定義新的種類的generators (所以for statment中的generators不限於collection type, 而是某個有implement map的object都可):


根據我們之前在解讀for的筆記中知道,compiler會把以上的for statement解譯成以下:


所以我們只要幫我們的random generator 實作map, flatmap就可以用for來使用。

我們的Generator trait新增了兩個 methods:

trait Generator[T] {
  self => //相當於 Generator.this

  def generate : T //產生random value
  //回傳一個Generator[S],  
//generate的定義為把self產生的random value經過f mapping  def map[S](f:T => S):Generator[S] = new Generator[S] {
    override def generate: S = f(self.generate)
  }

  //回傳一個Generator[S],  
//generate的定義為把self產生的random value經過f map Generator[S]instance  
//然後回傳此generatorgenerate method  
def flatMap[S](f: T => Generator[S]): Generator[S] = new Generator[S] {
    def generate : S = f(self.generate).generate
  }

}


如何解讀新的Generator?

如果用這個Generator trait的定義來實作booleans的隨機產生器的話,可以看到以下的substitution steps:


所以booleans被替換成跟我們在上一段的寫法一樣。

why all the efforts?!


解讀用for定義pairs的generator:


好難懂!!!!! 以後有遇到需求再回來研究吧!