At the beginning of the tasks OpFromGraph.c_code()
, Fred pointed to me a few commits which he thought might be and majorly implemented make_thunk()
method. “This is wired, why he provided codes of other method?”, I thought. With only a glance of the codes and some confusions, I turned to CLinker
and gof.Op
.
I thought this might be a simple mission, which wasn’t. The biggest problem was my misunderstand of CLinker
– I thought it is something like PerformLinker
and VM_Likner
, called by orig_func()
and linking the whole graph. But the truth is CLinker
did not serve the fgraph, but the node. In gof.Op.make_thunk()
, if op_use_c_code
is true, it will call make_c_thunk()
and use CLinker
to generate a C code and link the storage. And then return a ret
(what’s a ret
?)
So there’s two equivalence ways to impelemnt c_code()
, to make an Op
faster. One is the way I took – implementing serious of C code method of Op
, so that Op
can return C code accords to fgraph. In this way I need to generate C code fitst. And then I need to break apart those code while ensuring they can be compiled after being resembled by CLinker
. This require a thorough understand of CLinker
. Yes I can only get the basic idea. Therefore I stucked.
The other way is override the make_thunk()
(or make_c_thunk
)method, which is Fred’s way. This is much easier. Because we do not need to seperate the codes, it is generated in a whole and independently. We don’t link the cthunk with storage until it’s generated(really?), which save a lot of problem and make full use of CLinker
’s ability.
Fred already gave me a workable code. I only need to improve it a bit. But my ignorant lead me into another way.Therefoer I decide to post this blog to remind me that everytime before I take a step, I need to full understand what I’m going to do and how I’m going to accomlish it, as well as suggestion from others. Otherwise, the more effort I make, I more resoureces I waste. Also, ask question when confused.
Shame on me this time.
]]>connection_pattern
and infer_shape
were merged. I was supposted to implemented the GPU optimization feature. As I don’t have a machine with Nvidia GPU, I truned to the c_code
method after several days.
Reusing the code of CLinker
would be our original idea. But thing wuold not be that simple. CLinker
generate code at a function scale which mean it treat node OpFromGraph
as a function. Yet we want to generate code at a OP scale. Therefore we need to remove some code in a larger scale to make OpFromGraph.c_code()
return a ‘node like’ c_code.
There’re to solution. One is to add a new feature to CLinker
so that it can detect OpFromGraph
node and do some special behavior. The other one is to avoid high level method like code_gen()
in CLinker
– only use auxilary method to assemble a ‘node like’ c_code. The later solution seems easier and I am working on it. CLinker
is a very complicated class, I try to understand it and work out a workable code in the next two weeks.
Wish myself good luck~~!
]]>OpFromGraph
. Which is the second part of the proposal.
Currently, if a FunctionGraph have repeated subgraph, theano will optimize these sub-graphs individually, which is not only a waste of computational resources but a waste of time. If we can extract a common structure in FunctionGraph and make it a Op
, we can only optimize the sub-graph of this Op
once and reuse it every where. This will speed up the optimization process. And OpFromGraph
provides such feature.
To make OpFromGraph
works well, it should support GPU and can be optimized. Following feature are expected:
__eq__()
and __hash__()
connection_pattern()
and “infer__shape()“`c_code()
I implement two feature in last two week: connection_pattern
and infer_shape
. I hope I can make OpFromGraph
a useful feature at the end of this GSoC :).
The PR of function.copy()
is ready to merged, only need fred to fix a small bug. And in this Friday I passed the mid-term evaluation. So it’s time to take the next step.
In the original proposal ,the next step is to swap output and updates
. After a discussion with Fred, we thought this feature is useless so we skip this and head to the next feature directly – OpFromGraph
.
make class OpFromGraph
work.
OpFromGraph
should init a gof.op
that has no difference with other Op
s and can be optimized. Otherwise it has no sense.
For this, we need to make it work on GPU, make sure it works with C code and document it. Make sure infer_shape()
, grad()
work with it. Ideally, make R_op()
work too.
__hash__()
and __eq__()
method so it is a basicinfer_shape()
method so that it’s optimizablegrad()
work. This should remove the grad_depth parameterThe main idea is to calculatet the shapes of outputs from given input shapes. This is a process similar to executing a function – we cannot know the shape of a variable before knowing the shape of the variables it depends on. So, we can mimic the make_thunk()
method to calculate the shape from output to input. I come out with a draft now, and need some help with test case.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
In this two week, I finished the first feature “Allow user to regenerate a function from compiled one”, and this feature “can be merged. But there’s another PR need to rebase.” So, it’s done.
Also, I get a draft of the code that allow user to swap SharedVariable. When I said ‘draft’, I mean that I’ve finish the code as well as the testcase and they work. I’ll make a PR for review at the beginning of next week. Also I have some new idea need to discuss with Fred.
I hope I can finish all 3 feature in the first 6-week: copy, swap_sharedvariable and delete_update. So that I can focus on OpFromGraph in the next half. It seems that someone has started working on it now. I hope he did not ‘rob’ my job. :)
]]>Function is a callable object whose core is a function ```fn``` generate by linker. Every time a function is call, it will first examinate input data and then evaluate the ```fn``` to get output value.
PARAMETERS:
Container
and In
( seems useless )default[i][2]
is input_storage[i]
; indices[i][0]
is inputs[i]
in FunctionMaker.METHODS:
__init__()
: Initialize input containers value and set up “[]” operator of container and self__call__()
: verify input data types and then execute the self.fn
. Pickup value in some of the the output storages and set up corresponding input_storage in there’s updates.FunctionMaker is a Factory that create function. However, it's not like a factory very much cause every maker only corrsponds to one function. In FunctionMaker some important info of a theano.function are stored, such as Inputs/Outputs(represented by SymbolicKit), FunctionGraph.
PARAMS:
fn
. By default, FAST_RUN
mode use VM_Linker
, FAST_COMPILE
uses PerformLinker
.METHOD:
Linker.make_thunk()
return a theano.function.Linker is a class that allocate storage for allpy_nodes and link them together. Understanding Linker and FunctionGraph definitely helps understands how theano works. The core method of Linker is make_thunk().
PARAMS:
FunctionGraph
accpeted by Linker.accept()
.METHODS:
make_thunk()
is defined in class Linker
. It calls method make_all()
. Every subclass of linker will have slightly different implementation of make_all()
. Bascially, at first, make_all()
will toposort a fgraph, to acquire the order
that apply_nodes should be executed. Next, it will call Op.make_thunk()
, whick return a function. This function take input_storage of node, apply computation on them, and put result on output storage. Meanwhile, make_all()
will allocate storage for all variables . Same variables in different node will have same storages. This is done by a dict sotarge_map
that map variable to storage. At last, Linker return a function that executes list of thunks in certain order to acquire function outputs data.Storage is quite a tricky thing in theano. Theano use a list with one element to be a storage. The element is true data. But all object only refer to the list.
The list works like a pointer. When some objects refer to a storage, they refers to the list, not the true data. Therefore, modifying the data of storage will not change this reference. By doing this, theano can access and modify storage data from several places without change the reference relationship.
A representation of the computational graph of a function. It stores given the input and output variables of one function, by calling node.input and variable.owner recursively we can get the whole graph
PARAMS:
METHODS
__import_r__
and __import__()
: import variable and apply_node to this fgraph.clone_get_equiv
: Clone fgraph. Return new fgraph and a dict that map old variables to new variables.replace()
: Replace all certain variables in fgraph by given new variables.1.Input Variables will be wraped and become In()
s. Each In()
contains variable, it’s value( defaults is none ) as well as some other configuration.
2.Generate fgraph by input and output variables, and then optimize it.
3.Linker toposorts fgraph to get an order
of apply_nodes.Next, Linker allocates storages and links function based on this order
.
4.Done
Update is given by a dict { ori_var:update_var ... }
. Ori_var
is limited to be an SharedVariable therefore it will transfer into In()
and become the input of this function. update_var
will be add into the output of fgraph. Everytime a function called, the function extract the storage of update_var
and use it to set the value of corresponding input.
This is simple after understand how theano works. I implements it following sevaral steps:
1. Copy In()
and Out()
instances in Maker
2. Copy fgraph and get the dict equiv
that map old variables to new variables
3. Copy old storage_map in Function.fn.storage_map
4. Modify copied storage_map accord to equiv, so that in the copied storage_map, new variables are mapped to old storage if memory need to be shared.
5. Reinitialize the maker, linke the function using the copied storage_map
6. Done
Ok, basically this is the report of this week’s work. Now I need to figure out how to implement the following features.
]]>Good looking card. I guass someone will regret for using existing bank account. Also this is my first own credit card. Finally I can make payment without a phone from my dad :)
Google is ready to pay me now. “Are you OK?”( Leijun’s ‘Chinglish’ )
Kinda occupied in the last week. Luckily, I finished those buinesses. And now I am back again.
The first feature I will add to theano is a function that allow user to make a copy of function and allow functions run multi-theadedly. There exist two similar features: pickle()
and copy()
of a function. To figure how I need to work. I need to take 3 steps as preparation:
Done
Function
, FunctionMaker
, Linker
and FunctionGraph
. next
pickle
and copy()
, use them, figure out how them work and the differneces. Then think about my own idea.Ok, now I need to go and take the step two now.
]]>Before the application started, I’ve dived into Theano cored and got the basic idea of what I’am going to do. However, to make the idea more clear and to fit the requirement that student should post every week about their progress. I decide to write two post about Theano core – about how theano work. This is the first post. This post will talk about what is a function? And how a function is generate.
Just recall how we compiled a function func = theano.function( [ inputs ], output )
, we can know that we should start out journey from method function()
, which locates in theano/compile/founction.py
.
In method function()
, after some data verification, it will call orig_func()
or pfunc()
which return a function
that user will get. Since pfunc()
will also call orig_func()
, we are going to look into pfunc()
first.
pfunc()
have two major tasks:
In()
instances. So does shared_variable
. ( In function graph, SharedVariabls are treated as input, updates are treated as output ).updates
and inputs
, transform output into Out
instances.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
Now it time for a look into orig_func()
. orig_func()
will again makes sure that inputs and outputs are transformed into In
an Out
. And then it will use create
method in FunctionMaker
to make a function, which will be return.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
FunctionMaker.__init()__
is where fgraph
is extracted and optimized. FuncitonMaker.create()
is where function
will be compiled and linked. In fact, FunctionMaker.linker.make_thunk()
is where function
is linked.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
|
theano.function
?Each function is a callable object. theano.function
is not a python function. Instead, it a class with method __call__()
. Every funciton stores its own fgraph, maker, storages and many other configurations. However, the core of a function is a function fn()
returned by linker.make_thunk()
. Every time a function is called, it will first verify the input data, and then call self.fn()
to get output values.
Now I know how is a function borned. Also, I know that to complete my missions, I need to focus more on FunctionMaker
and Function
rather that orig_func()
and pfunc()
. However, there still exist some question, such as: What does make_thunk()
do? and What is In()
, Out()
and container
? In the next post, I will have a look at this and other relative data structures.
Before solving this problem I never tried using std::string, I thought string operation in C++ is as hard as C. However, standard library really ease my pains. In fact, with the help of std, I think coding with C++ is much more easier and happier than coding with Ruby and Python now.
Given an input string, reverse the string word by word.
The idea is simple, find each word in the sentence, push then in the stack and pop back after dealing the whole sentence. Be careful to the spaces in the head or tails of the sentence and the repeated spaces.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
Evaluate the value of an arithmetic expression in Reverse Polish Notation. For example: [“2”, “1”, “+”, “3”, “*”] -> ((2 + 1) * 3) -> 9
Also simple idea: Iterater through the list, push numbers to a stack. Everytime we meet operaor, pop top 2 numbers and calculate the result, push the result to the stack. Finally, there will be only one number in the stack, which is the reuslt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Basic algo. There are 3 kind of traversal method: postorder, preorder and inorder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
* BST Iterator
* Factorial Trailing Zeros
* Excel number
* Decimal
* Find Peak Element
* Min stack
* Majority elements
* Trailing zeros
* Tree Travel
Also, after realing leetcode online judge have numerous bug in python test case, I decided to turn to C++. So, here comes the solutions.
Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the root node of a BST.
Calling next()
will return the next smallest number in the BST.
Note: next()
and hasNext()
should run in average O(1)
time and uses O(h)
memory, where h is the height of the tree.
The smallest value is will be on the left-most node, therefore the first thing to do is to dive to the deepest ad left-most of the tree. Next we can return the value of the middle node, then its left node. A list of nodes on path should be maintain to avoid return repeated value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
Given Excel column number, return the corresponding digit number
A conversion of number systems( thought not a typical one) . First we map characters to numbers, them multiply digit in the Nth
location with 26^n
, sum and get the result.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Given an array of size n, find the majority element. The majority element is the element that appears more than n/2
times.
Use a counter(map) to calculate how many times each elements has occured and return if times > length/2
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Given an integer n, return the number of trailing zeroes in n!.
This is more a math problem than a code problem. Notice that 1 x 2 x 3 ... x 5 ... x 10 ...
with every 5
( or n x 5
) there must be a 2
(ornx2
). And the trailing zeros must be the result of (2x5)^n
. Therefore, the problem becomes ‘How many 5 are there in it’s factors’. ( Need to rewrite this paragraph )
1 2 3 4 5 6 7 8 9 |
|
Given an input array where num[i] ≠ num[i+1], find a peak element and return its index.
Easy problem, just iter over and compare each element with its neighbours. If the num[i] can equals num[i+], we first deduplicate, and then compare. Need some attentions on the conner case( length = 0,1 or the peak occurs on the head or tails)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.
* push(x) -- Push element x onto stack.
* pop() -- Removes the element on top of the stack.
* top() -- Get the top element.
* getMin() -- Retrieve the minimum element in the stack.
The key is to maintain double stack: one for normal push() and pop(), the other for the operation getMin(). When a new min value is pushed, we also push it to the min_stack, when it pops, we also pop it from min_stack. So the minimum value is always on the top of min_stack.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Easy job, just a little bit tedious. By implementing a long divide, we can easily find the answer. Remember to store every ( quotient,remainder )pairs so that we can know when it starts repeat itself. The python test case is wrong. See this post. After this solution, I turned to C++, which should have better judge system.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
Ok, let’s talk about the problem:
1 2 3 4 5 |
|
So the first thing I need to do is transfer integers into strings, which will help in the following comparasion. Next, I sorted the numbers based on their first digits because obsiviousl, 9**
is greater than 1**
. Aftr that, I extracted numbers with same first digits and do the comparation.
In the comparation, those numbers will be combine to form the biggest number they can. This is the most important part. At first, I wrote a complicated algorithm which did comparasion using a[:length] > b[:length]
, and lots of code handle conner case – one of the number is longger. The code went really long like
This code went well expect for the test cases like [8308, 830]
or [101,10]
: No matter how I compared thoes numbers, they are ‘equal’. But when I combined them with different order, the result differed. I spent a long time trying to figure(playing)
a(3ds)
method(game)
. Suddenly, I came out with a idea: “why not do the comparision by combine digits in different order and see which result is larger?”. It Works, and the final algorithm became really simple:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
In Last day’s interview, I was asked about my idea of TDD(Test Drive Development), I just answered:“I think it is usual, I wrote test for my codes.”, which equals to answered nothing. These two days I developed leetcode’s answer with the help of OnlineJudge’s test cases, I did realise that writing test cases norishes a developer.
Every time programmers write tests,they try covering every special cases they know, therefore codes pass their own tests are their best products within their ability. But when things come to production, codes will prossibly encouter cases that make it fail, which, just like a hole in developer’s mind. Every time developer find such a case, it find a hole in his mind he never knows, then he fixes it. Day after dats, after millions of holes are fix, his mind hardly has hole. Therefore, his products, his codes, will become unbelievably stable.
That’s my understand of TDD – Test Drive Developer.
Just for fun, don’t take it seriously. :)
]]>也算加深了一点职场上对程序员的要求的了解。一个是技术方面,首先要基础好:理论懂得各种名词基本理论,比如搞互联网通信就知道HTTP和socket是怎么回事,写后台就要回thread和process;另外技能熟练,常用的包的常用概念和API要熟悉。这个决定了程序员能不能用。如果不能用,但是长期留,就可以看看他聪不聪明,聪明慢慢学。如果短期留,最好基础和技能都扎实,直接形成生产力。这里两方面我都没做好:知识不扎实又不能长期留。所以技术上跪。
另一个是做人方面:程序员的价值观最好跟自己团队相近,一起工作才能有化学反应。人与人之间的愉快交流会促进交流和学习,这样大家才能一块成长和迸发新想法,而不是说简单的完成任务,每天原地踏步,死水一潭。今天跟Boss交流,我很不理解的一个方面就是:明明编程语言和包/库是工具,他们具有不同特性,然后程序员应该根据不同需求使用不同语言/包。但是他却根据人的特性,然后给人贴上不同语言的label:”我觉得你更适合python社区“ 这个我一听,心想“怎么能这样呢?搞反了啊。”,然后就激动了,然后就失控了。估计他也聊得不开心。总的来说,双方都没反应,二跪。
反正今天就是跪了一下午,上来发发泄。不过后期的学习计划也明确了下:
好吧,回来说下这今天在做的一道leetcode。以后leetcode和hackerrank相关刷题blog都有这个leetcode battle field
的flag。
先看题目:
Say you have an array for which the ith element is the price of a given stock on day i.Design an algorithm to find the maximum profit. You may complete at most k transactions.
基本题目意思是,给定一个股价序列prices
和可操作(一买一卖)次数k
,求最大利益。
这道题目也是做了两个思路。首先最基本的是,股票必定低买高卖,因此先求极点。这个是没问题的。
第一个思路是,股票一买一卖一个周期,把所有周期求出来,然后sort一下,取前k
个周期求和就是最大利益。这个版本的代码没留着,因为缺陷很明显:如果股票小幅下跌后大幅上涨,我们就不赢该再下跌的时候把股票卖了,这样会浪费操作次数,导致利润降低。
意识到这一点之后,这个题目变的很难。因为要有全局眼光。首先为股票写一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
这个class代表了一直股票的所有关键参数:买卖时间和买卖价格,然后计算出利润。然后这个问题转化成两个:
1 很简单,求所有极值点然后排列组合就好。2 的话,主要满足一个需求股票必须全卖之后才能全买,不能说连着买两次。简单地说就是买卖区间不能重叠。一开始考虑写成一个树,结点是一个股票,他的子结点是买了那只股票之后剩下的其他可能的股票。然后求取depth=k
的所有路径上的利润。但是这个方法很难实现,而且空间复杂度高。后来仔细一想其实这个就是个小递归(动态规划?)。这个问题就变成了:
N手
获得最大利益N-1手
的利益相加必定是最大的N-1手
的最大利益,相加,看看那个利润最大N-1手
采用 1 直到省下一次操作次数,这时可以直接在所有选择中求max跟着这个做出了这个算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
|
这个算法花了很多时间调conner case,结果是没问题的,但是超时。之前用到List.index()
方法,真心慢,没看源码,估计不少Python,丢弃了,依旧超时。目前不知道改进方法。后来认真想想,这个应该用动态规划?回去翻翻书.也可能是中间多了一个Stock()
,应该直接根据buy()
和sale()
写算法会更快。先刷后面的题目。这个POST把这个题目mark下有空慢慢想。
请原谅我这个选择困难症患者在这个无聊的问题上纠结这么久。我为此特地在知乎上搜索了将尽一个小时。他人的观点基本你可以总结成两类
1 2 |
|
按照这里面的思路去回答问题,估计在纠结两百年也纠结不出来:谁知道知识在什么时候用到?未知的世界永远未知。但是在方向不明确,或者去完全没需求和收益的情况下,去实现编译器或者操作系统什么的……抱歉,太浪漫的事情我做不了。So,为了走出这两个极端,我回答了自己几个问题:
1 2 3 4 5 6 |
|
是的,我也曾经浪漫。在课余时间的时候抱着一本《编译原理》在啃,幻想着把习题做完之后成为编程高手。当然我这种渣渣最后还是屈服在了懒惰之下,书只看了前几章就丢在了一边,自己写编译器什么的更是天方夜谭。
即便如此,这段经历带给我的好处也是十分明显的。这个好处体现在,在面对些语言的trick(语法糖)的时候,我能够很快地理解并且掌握。比如之前在看《ruby元编程》,前半部分我就毫无压力,全当是在复习《编译原理》。原因很简单,因为我在《编译原理》中理解了block和变量作用范围的概念,ruby里面再怎么变化,也逃不出编译器(解释器)的范围。
这个书啊,并不是说看完了他能给你提供什么技能,能够帮助招工赚钱,尤其是基础类别的书。它能提供的,更多是一种概念。越是基础的知识,其中的概念就越抽象,也越能代表更多的事物。很多的概念、名词,其实都是新瓶装旧酒。这酒没变,瓶子其实不怎么重要。
概念是个很重要的东西,很多时候缺少一个概念就会使人停滞不前。因为你意识不到自己的缺陷,也无法找到改进的方法。比如之前在看Scrapy的源代码的时候,对signal和callback函数很难理解。查了很多相关库的API文档,懂得了函数的用法,却衍生出了更多不懂的概念,使自己更加迷糊。这说白了,就是对操作系统的工作机理缺乏理解。这就体现了《操作系统》这本书所能给我带来的优势。
其实不仅是书,视频、经历、人等各种能够为我们带来新概念和新事物的东西,都要勇于接受。一堵墙,打破一个点,就有机会窥见另一面的整个世界。
那么……
1 2 3 4 5 6 7 8 |
|
好,那么现在剩下的问题就只有一个了:
1
|
|
呵呵,SB ,买英文书 (= =||凸
]]>Now, I have a try on octopress. Hope it work well~
]]>