Logo

site iconTimothy

四川人在新加坡,热爱编程和DIY。godns作者。微软MVP。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Timothy RSS 预览

迟来的2023年终总结

2024-01-15 08:05:28

这是一篇迟来的2023年终总结。2023年底的时候出去旅游了,2024年初又回国了一次,所以这篇年终总结终究还是迟到了半个月。

工作

如果用一句话总结去年的工作,那就是:平淡无奇,甚至还变卷了一些。看着曾经并肩作战的同事,一个个都离开了,曾经开会的时候,默认的语言还是英语,如今开会都是默认直接中文了。年初由于组织架构的再次调整,我又被合并到了另外一个组,开始用C++开发线上的实时推荐系统。于是,我的技术栈也因此扩展成了React, Go, C++。在业余时间,我还顺便学习了一下现代C++,毕竟上次用传统的C++已经是N年前了。至于工作本身嘛,我只能说,走一步看一步,我还是先刷着LeetCode热热身吧。

学习

2023年这一整年,最值得令人高兴的事情是:我终于又毕业了!

从疫情开始的线上课程,到后疫情时代的线下课程,总共历时两年的时间,我又毕业了。回顾起当初去学院提交资料,领取学生卡的场景还仿佛就停留在昨天。这两年的时间里,做得比较有规律的事情,就是遇到有课程的时候,早上起来一大早去公司上班,下班后又马不停蹄地飞奔去学校接着上课。晚上回家还得卷一下当天课程的quiz,周末也得继续和小组的同学一起讨论项目,写report。除了假期的周末,平时是几乎没有周末的。这种忙碌而又充实的生活,让我觉得仿佛又回到了多年前的那个学生时代。

这种忙碌的生活一直重复了四个学期,最后一个学期伴随着最后两门的考试,最终到了2023年4月底。6月的一天早上,我跟往常一样在公司上班的时候,收到了学院的邮件,通知可以顺利毕业了。

7月,学校的毕业典礼,早上一场突如其来的大雨,导致路上堵车了40多分钟,本来以为是赶不上最后的毕业典礼了,没想到赶在毕业典礼开始的前几分钟顺利入场了。

有同事问我:本科毕业多年后,还有必要再去读一个master吗?对我而言,我觉得是有必要的。

当年因为家庭经济的原因,没有能出国深造的机会。本科毕业之后,我就开始在国内工作了,直到后来到了新加坡。没能出国体验留学,对我来说难免是一件比较遗憾的事情。后来从NUS毕业来公司的新同事那里了解到,原来国外的master是没有入学考试的,直接拿着本科成绩单和英语考试成绩申请就可以了。而且part-time的证书和full-time是一样的,学费也一样,就连平时上课都是一起上的。

于是,我就疯狂DIY走完了接下来的所有流程:考雅思,翻译本科成绩,准备各种资料,提交申请,直到拿到offer入学。

或许多年以后,我还能回忆起这段毕业N年后迟来的海外留学经历,回忆起考试前的忙碌,作业和项目deadline前的紧张。毕竟,我也为之努力过了,这个曾经的缺憾也终于被填补上了。

当然,在各种不同课程的project中,我还有幸认识到了一群同样在新加坡一边上班一边攻读master的队友们,甚至还有跟我在同一个公司的同事,也算是很新奇的体验了。

趁着毕业之际,把之前在NUS学习时候的各种照片和视频整理了一下,制作了一份毕业纪念视频:

演唱会

Master的课程完结之后,今年倒是看了很多场的演唱会。首先是4月Eason演唱会的新加坡场:

还有不老歌神张学友:

有我最喜欢的爱尔兰乐队: Kodaline

最后还有年底张信哲巡演的新加坡场:

现场的体验确实比平时听Spotify上的音乐更震撼,另外,舞台效果还是挺棒的。

旅行

年底的年假还剩了十来天,所以计划了一场跨越几个城市的“毕业旅行”,先后去了吉隆坡,香港,台北和东京。

吉隆坡篇

十一月的吉隆坡和新加坡差不多,仍然是盛夏。双子塔的夜景不错,Bukit Bintang的夜市人潮汹涌,颇有赛博朋克的风格。从新加坡飞吉隆坡是我遇到最短途的飞行了,飞机起飞不到20分钟,就开始降落了……

香港篇

十二月从新加坡飞去了香港,两天的时间匆匆打卡了小红书上各个比较有名的景点。怪兽大厦是曾经变形金刚电影的取景地,密集的房屋给人很有压抑的感觉。

中途还去打卡了香港大学,正好遇到他们的毕业季

当然,必打卡的地方还有太平山上的夜景

台北篇

年底得知入台证放开申请之后,立马提交了资料,一周左右就到手了。正好从香港飞去台湾也很近,这一段行程算是无缝衔接上了。

位于台北市附近的野柳地址公园风景很不错

十分的瀑布

九份老街

东京篇

如果年底的台北还是深秋,东京就算是彻彻底底的冬天了。从台北乘坐虎航直飞东京,其实也挺近的,正好也用上了刚申请的日本五年签。

打卡了地标建筑晴空塔

徒步上野公园

“混进” 了东京大学

东京都厅展望室的夜景

明治神宫

代代木公园

镰仓,灌篮高手打卡地

涩谷SKY观景台

六本木的圣诞点灯

御茶之水站

也许看腻了热带国家的风景,这次旅行台北和东京整体体验还挺不错,值得再去。

总结

回顾了一下去年对今年的展望:顺利毕业,回国,策划一次年底的旅行,好像都实现了。

今年终于可以腾出一点心思,重新思考一下工作以及后续的发展。希望在新的一年,能重新参与到开源项目中,并阅读一些其他感兴趣的书籍。

The Heap Sort Algorithm Explained

2023-10-10 06:40:30

The Heap

A heap is a specialized binary tree data structure that satisfies the heap property. In a heap, each node has at most two children, and the key (the value) of each node is greater than or equal to (in a max heap) or less than or equal to (in a min heap) the keys of its children. The binary tree representation of a heap is a complete binary tree, meaning that all levels of the tree are completely filled except possibly for the last level, which is filled from left to right.

Heap Sort

Heap sort is a comparison-based sorting algorithm that uses a max heap to sort elements in ascending order. The algorithm works by first building a max heap from the input array. It then repeatedly extracts the maximum element from the heap and places it at the end of the array. This process is repeated until all elements have been extracted and the array is sorted in ascending order.

Heap sort has a time complexity of O(nlogn) and is an in-place sorting algorithm, meaning it does not require any additional memory beyond the input array.

An array can be used to represent the heap structure by using the following indexing scheme:

  • The root of the heap is at index 0.
  • For any node at index i, its left child is at index 2*i+1 and its right child is at index 2*i+2.

Using this indexing scheme, we can represent a heap as an array and perform heap operations on it efficiently.

For example, to build a max heap from an array, we can start at the last non-leaf node (which is at index n/2-1, where n is the size of the array) and perform the heapify operation on each node from this index down to the root. The heapify operation ensures that the node is the largest of the three by swapping it with the largest child if necessary. By calling heapify on each node from the last non-leaf node down to the root, we ensure that the entire array represents a max heap.

An C++ Example

Here is an C++ example that builds a max heap based on the input vector:

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
void heapify(std::vector<int>& vec, int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;

if (left < n && vec[left] > vec[largest]) {
largest = left;
}

if (right < n && vec[right] > vec[largest]) {
largest = right;
}

if (largest != i) {
std::swap(vec[i], vec[largest]);
heapify(vec, n, largest);
}
}

void build_max_heap(std::vector<int>& vec) {
int n = vec.size();

for (int i = n / 2 - 1; i >= 0; i--) {
heapify(vec, n, i);
}
}

The build_max_heap function constructs a max heap from a given vector of integers. A max heap is a binary tree where the value of each node is greater than or equal to the values of its children. The build_max_heap function starts by finding the index of the last non-leaf node in the binary tree, which is n/2 - 1, where n is the size of the vector.

It then iterates from this index down to the root of the tree, calling the heapify function on each node.

The heapify function takes a node and its two children and ensures that the node is the largest of the three by swapping it with the largest child if necessary. By calling heapify on each node from the last non-leaf node down to the root, the build_max_heap function ensures that the entire binary tree is a max heap.

Now we call this function and construct a max heap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};

// create a maximum heap
build_max_heap(vec);

// print the maximum element
std::cout << "Maximum element: " << vec.front() << std::endl;

// print the whole vector
std::cout << "The heap: ";
for (auto i : vec) {
std::cout << i << " ";
}

return 0;
}

The output is:

1
2
Maximum element: 9
The heap: 9 6 4 5 5 3 2 1 1 3 5

If we append a new number 12 to the end of the vector and re-construct the max heap:

1
2
3
4
5
6
7
vec.push_back(12);
build_max_heap(vec);

std::cout << "\nThe heap: ";
for (auto i : vec) {
std::cout << i << " ";
}

The output is:

1
The heap: 12 6 9 5 5 4 2 1 1 3 5 3

We will see that, after re-construct the max heap, the newly appended element 12 becomes to the top element of this max heap.

The Heap In C++ Standard Library

std::make_heap and std::priority_queue are both C++ standard library containers that are used to implement heaps. However, they have some differences in their functionality and usage:

  • std::make_heap is a function that takes a range of elements and rearranges them into a heap. It does not provide any additional functionality beyond constructing the heap. Once the heap is constructed, you can use other algorithms like std::sort_heap or std::pop_heap to manipulate the heap.

  • std::priority_queue is a container adapter that provides a priority queue data structure. It is implemented using a heap and provides additional functionality beyond just constructing the heap. It allows you to insert elements into the priority queue, remove the highest-priority element, and access the highest-priority element without removing it. It also provides a way to customize the comparison function used to determine the priority of elements.

The Heap Related LeetCode Problems

215. Kth Largest Element in an Array

347. Top K Frequent Elements

703. Kth Largest Element in a Stream

Modern C++ 学习笔记 -- 左值与右值

2023-09-07 06:47:29

左值(lvalues)与右值(rvalues)的概念

左值和右值是Modern C++中引入的新概念。简而言之:

  • 左值位于等号左边,我们可以对左值进行取地址操作。
  • 右值位于等号右边,本质上是一个数值,即 literal constant,我们没法对它进行取地址操作。
1
int x = 999; // x 是左值, 999是右值

我们,可以把左值大致想象为一个容器,而容器中存放的就是右值,如果没有这个容器,其中的右值将会失效。对于以下的程序,我们在编译的时候将会得到类似的错误:

1
error: lvalue required as left operand of assignment

1
2
int x;
123 = x;

很显然,等号左边需要的是一个左值,而123作为一个 literal constant 类型,是没有办法充当左值的。同样,我们也没法对一个右值进行取地址的操作:

1
2
int *x;
x = &123; //无法对右值取地址

编译器报错:

1
error: lvalue required as unary '&' operand

左值到右值的隐式转换

左值在很多情况下有可能被转换为右值,比如在C++中的 - 运算符,它将两个右值作为参数,并将计算结果作为右值返回。

1
2
3
int x = 10;
int y = 5;
int z = x - y;

在上面的程序片段中,我们明显看到x, y本身是左值,但是却以右值的身份参与了减法运算。这是如何做到的呢?答案是编译器对左值做了隐式的转换,将x和y转换成为了右值。C++中其他的乘法,除法和加法运算也是同样如此。

如果左值能被转换成右值,那么右值本身能被转换成左值吗?答案是 否定 的.

左值引用与右值引用

C++中引入引用的概念,是为了在程序中方便的通过引用修改原变量的值,并且,在调用方法传参的过程中,传递引用可以避免拷贝。在通常情况下,左值引用只能指向左值,而右值引用只能指向右值。听起来比较废话,但是也有特殊的情况。

左值引用

1
2
3
int x = 10;
int& ref_x = x;
ref_x++;

在上面的示例程序中,我们定义了一个左值x,然后赋值10。随后定义了一个引用,指向x。因此,ref_x成为x的引用,它就叫做左值引用。通过操作ref_x,我们就可以改变x的值。

如果我们把上面的程序简化为:

1
int& ref_x = 10;

在编译的时候,我们会得到类似的错误:

1
cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

显然,左值引用只能指向一个左值,而不能指向一个右值。不错从错误信息中,我们方法可以得出另外一种写法:

1
const int& ref_x = 10;

根据编译器的规则,我们被允许通过定义一个const类型的左值来指向右值。不过既然这个左值被定义成了const,没有办法修改指向的值。

右值引用

C++中的右值引用以&&表示。通过右值引用,可以修改其指向的右值。

1
2
int&& ref_x = 10; //定义右值引用
ref_x--; //通过右值引用修改其指向的右值

如果我们尝试将右值引用指向一个左值:

1
2
int x = 10;
int&& ref_x = x;

编译器也会抛出类似的错误,告诉我们不能把一个右值引用指向左值。

1
error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'

左右值引用的本质

通过一个简单的示例程序,我们就能知道左值引用和有值引用的本质。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void increase(int&& input) {
input++;
}

int main() {
int x = 10;

int& ref_a = &x;
int&& ref_b = std::move(x);

increase(x); // 编译错误,不能传入左值
increase(ref_a); // 编译错误,不能传入 左值引用
increase(ref_b); // 编译错误,右值引用本身是一个左值

increase(std::move(a)); // 编译通过
increase(std::move(ref_a)); // 编译通过
increase(std::move(ref_b)); // 编译通过

increase(7); //编译通过,7是右值

return 0;
}

从上面的代码示例中,我们可以看出右值引用 ref_b 本身也是一个左值,在调用 increase 的时候,需要通过 std::move 转换为右值,编译器才不会报错。

通过以上的例子,我们可以总结出如下的规律:

  • 左右值引用的引入,都是为了避免拷贝。
  • 左值引用通常指向左值,通过添加 const 关键字约束也能指向右值,不过无法对右值进行修改。
  • 右值引用本质上也是一个左值,右值引用通常情况下指向右值,不过通过 std::move 等形式也可以指向左值。

右值引用与移动语义

在前面的例子中,我们已经涉及到了 std::move 这样的操作。右值引用配合 std::move 通常能实现移动语义,从而实现避免拷贝,提升程序性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <vector>

int main() {
std::vector<std::string> list;

std::string str_a = "Hello";
std::string str_b = "World";

list.push_back(str_a);
list.push_back(std::move(str_b));

std::cout << "str_a: " << str_a << std::endl;
std::cout << "str_b: " << str_b << std::endl;
std::cout << "list[0]: " << list[0] << std::endl;
std::cout << "list[1]: " << list[1] << std::endl;
return 0;
}

如果运行上面的示例程序,我们会得到这样的程序输出:

1
2
3
4
str_a: Hello
str_b:
list[0]: Hello
list[1]: World

很明显,在str_a被添加到vector的时候,并没有涉及到移动语义,所以str_a的值被拷贝到了vector中。而在把str_b添加到vector的过程中,由于用到了std::move,所以str_b的值被移动到了vector中。之后再输出vector的值的时候,可以看到其中已经包含了str_astr_b的值。但是str_b本身的值已经被偷走了。

需要注意的是,std::move本身的名字比较有迷惑性,其实它在这里的工作只是把str_b从左值转换成右值,而不会做实际上移动资源的操作。如果我们把添加str_b的代码替换成:

1
list.push_back(static_cast<std::string&&>(str_b));

会达到一样的效果。而真正的秘密在于 std::vector 提供的两种不同的重载方法:

1
2
void push_back( const T& value );
void push_back( T&& value );

第一个重载方法接受的是左值引用,当传入 str_a 的时候,由于 const 关键字的限制,它的值会被拷贝一份,并放入到vector中,而 str_a 本身的值并不受影响。而第二个重载方法接受的是一个右值引用,push_back方法会把其值放入vector中,并转移 str_b 对字符串值 World 的所有权。这样,当我们再输出它的值的时候,发现已经为空了。

完美转发(std::forward)

完美转发(Perfect Forwarding),转发的意义在于当一个方法把其参数传递给另外一个方法的时候,不光转发参数本身,还包括其属性(左值引用保持左值引用,右值引用保持右值引用)。

std::forward 实际上也是做类型的转换,不同的是 std::move 只把左值转换为右值,std::forward 能转换为左值或右值。

std::forward<T>(arg) 中,如果类型 T 是左值,参数 arg 将被转换为左值,否则 arg 将被转换为右值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <utility>

void target_func(int& arg) { std::cout << "lvalue reference" << std::endl; }

void target_func(int&& arg) { std::cout << "rvalue reference" << std::endl; }

template <typename T> void forward(T&& arg) {
target_func(std::forward<T>(arg));
}

int main() {
forward(5);
int x = 5;
forward(x);
return 0;
}

在以上的示例程序中,forward 使用一个Universal Reference类型接受一个参数,并且通过 std::forward 讲参数转发给 target_func。由于此方法有两个重载,分别接受左值引用和右值引用。在我们分别传入右值 5 和左值 x 的时候,我们发现 forward 这个方法都能准确无误的把参数转发给对应的重载方法。因此,程序的输出分别是:

1
2
rvalue reference
lvalue reference

参考文章

  1. Understanding the meaning of lvalues and rvalues in C++
  2. Cpp Reference
  3. Perfect Forwarding

告别2022

2022-12-27 08:05:28

随着疫情的反复,今年又是没能回去的一年,回想起年初的时候,去摩天轮观了夜景,也去金沙酒店的无边泳池游了一回,一年的时间总是飞逝而过。


https://xiaozhou.net/pics/2022/sg1.jpg

https://xiaozhou.net/pics/2022/sg3.jpg

https://xiaozhou.net/pics/2022/sg2.jpg

工作

新加坡的“复工复产”,比起国内来得更早一些。上半年还都在家里办公,公司员工可以自由选择在家上班或者去公司,从下半年开始,就陆续都回公司上班了。再到年底,几乎全员都被要求回公司上班了。

https://xiaozhou.net/pics/2022/sg4.jpg

然而,今年的经济形式并不好,再加上美联储加息,公司各种相关的负面新闻层出不穷,股价连续暴跌。经历了年中和年末的几波裁员,身边有不少同事幸运的领到了大礼包。说是大礼包一点也不过分,都是N+2起,但是我命中仿佛一直和大礼包没有太大的缘分。当年Moto关闭成都研发中心,传闻N+7的大礼包我也没领到,在领大礼包之前竟然主动离职了,眼睁睁错过一个亿。这次也一样,一波又一波的裁员也没轮到我,除了老实搬砖也干不了啥。公司的这波裁员也上了国内的新闻,脉眿上都被刷屏了,希望明年的经济能尽快好转起来。

学习

之前因为疫情,我的master课程有两个学期都是完全在线上完成的。从今年8月开始,也就是我读master课程的第三个学期,NUS也要求所有的课程和考试都转成线下形式了。好在Shopee离校园很近,下班后可以走到街对面直接乘坐NUS的校车去上课,时间刚刚好。前两个学期都是上网课,也没有太多机会去熟悉校园,这次趁着去学校上课的机会正好把校园逛了个遍:

UTown的风景

https://xiaozhou.net/pics/2022/nus1.jpg

https://xiaozhou.net/pics/2022/nus2.jpg

UTown的大草坪

https://xiaozhou.net/pics/2022/nus3.jpg

https://xiaozhou.net/pics/2022/nus4.jpg

校园礼品店

https://xiaozhou.net/pics/2022/nus5.jpg

校园内的李光前自然历史博物馆

https://xiaozhou.net/pics/2022/nus6.jpg

参加线下课程,虽然每次得往学校里跑,不过整体体验还是不错的,那种N年前在大学上课的感觉又回来了,哥的青春也跟着回来了:

自从第三学期开始,明显感觉比之前两个学期轻松了许多,因为前两个学期都是选满了三门课程,周末的时间几乎全用来做project和肝作业了。后两个学期分别只有两门课程,workload自然也小了许多。正值年底,第三个学期也结束了,来年开学后还有最后一个学期,不出意外的话,我就可以毕业了。希望来年最后两门课能卷出个高分,顺利毕业。

开源项目

今年投入开源项目的精力并不多,开学上课过后,也没有太多精力维护开源项目。当然,其间也投入了小部分精力继续维护已有的项目,更新了几个版本。另外,也花了一点时间给GitHub配置了项目赞助功能,还意外得到了一些赞助。 除此之外,LunarVim 是我今年开始玩的Vim配置,用起来挺顺手,于是我也给这个开源项目的作者赞助了一点美刀,算是聊表心意。

今年开源项目的总赞助收入是: 9.99新币

https://xiaozhou.net/pics/2022/github.png

投资

今年的股票和期权账户只能用惨不忍睹来形容。去年底开始,公司股票开启暴跌模式,年初的时候,特斯拉的期权又亏了2W多刀。快到年底的时候,特斯拉和公司股票又开始狂跌。掐指一算,哥的身价亏了几百个W的软妹币。不过虽然亏,税还得照缴,生活也还得继续。今年的投资,就这样了吧。账户也不想看了,也许再等两三年再看看会不会好一点。

程序员划水实践

说是划水实践其实一点也不错,年底的时候约上NUS的校友(其实都是程序员),实践了一下程序员应该如何正确的划水。从巴西立公园租了几个皮艇,一路划到乌敏岛,中午在一个海上的渔家餐馆吃饭,然后再划回公园。单程4公里,来回大概8公里。

https://xiaozhou.net/pics/2022/boat1.jpg

https://xiaozhou.net/pics/2022/boat2.jpg

旅行

年底第三学期刚结束,去电影院看了Coldplay的一场演唱会,然后开始准备旅行攻略。

https://xiaozhou.net/pics/2022/sg5.jpg

之前在Youtube上有看到西澳Perth的各种旅行视频,被种了草。趁着周末的空余时间,提交了澳洲的旅行签证申请。看官网本来以为审批的过程需要3个月以上,如果真是这样的话,年底旅行得换个地方了,当时的Plan B是准备去新加坡旁边的马来西亚走一圈。没想到的是提交完材料之后一周多的时间就收到审批通过的邮件。然后,也就顺理成章的有了这次的澳洲之行。

旅行前好好的准备了一下行程,还顺便把新加坡的驾照转换成了国际驾照,按照行程安排,在网上选酒店,租车,其间也参考了不少的旅行攻略。

https://xiaozhou.net/pics/2022/trip1.jpg

这旅行的大致路线是:

  1. 从新加坡直飞Perth,把我信用卡积分转换得到的几万新航里程用来换了一张免费的单程机票,相当于是白嫖了。去Perth后直接租车,游览本地的景点,然后顺着西海岸线路一路往北,去几个著名的景点打卡。
  2. 在Perth的最后一晚,回到Perth市区,第二天一早开车去机场还车,然后从Perth飞去墨尔本拜访一下十几年没见的前同事一家,顺便在墨尔本周边逛逛。
  3. 从墨尔本直飞回新加坡,其实这段行程略长,在飞机上8小时,跨越了3个时区。

行程计划:

https://xiaozhou.net/pics/2022/trip.png

万事已俱备,在十二月初的时候,跟公司请了一周的年假,换了一些澳币现金,开启了这次的澳洲之旅。掐指一算,这也是我在疫情快三年之后,第一次乘飞机出去的长途旅行。

https://xiaozhou.net/pics/2022/trip2.jpg

https://xiaozhou.net/pics/2022/trip3.jpg

https://xiaozhou.net/pics/2022/trip4.jpg

新加坡是一年四季都是夏天,Perth正好在南半球,也是夏季。所以出行的衣服可以无缝衔接,都是夏装就行了。长袖长裤也需要备两件,因为跟新加坡不一样的是,澳洲白天和晚上温差更大一些,晚上的风能让你感觉一秒入秋。从新加坡飞Perth只需要大概5小时左右的时间,更神奇的是,西澳的Perth和新加坡,以及和国内都一样,都是东八区,无时差。

https://xiaozhou.net/pics/2022/trip5.jpg

这次租的车是个日产的SUV,在国内的同款叫东风日产奇骏。澳洲的车跟新加坡的也一样,驾驶台在右侧,车辆靠左行驶。从西澳Perth沿着海岸线一路北上了几百公里,感觉风景绝佳,一旁是公路,另一侧就是大海。随手一拍也能出不错的照片。

https://xiaozhou.net/pics/2022/trip13.jpg

Geraldton的海滩

https://xiaozhou.net/pics/2022/trip6.jpg

HMAS Sydney II Memorial

https://xiaozhou.net/pics/2022/trip7.jpg

https://xiaozhou.net/pics/2022/trip12.jpg

The Pinnacles Desert

https://xiaozhou.net/pics/2022/trip8.jpg

Cairn Beach

https://xiaozhou.net/pics/2022/trip9.jpg

Cervantes

https://xiaozhou.net/pics/2022/trip10.jpg

Phillip Island

https://xiaozhou.net/pics/2022/trip11.jpg

实事证明,4天的时间游Perth还是不够的,粉红湖泊因为季节不对,关闭了,Perth的野生动物园也没时间去,Rottnest Island值得花上一整天的时间去,可惜也因为时间关系没包含在此次行程中。这次只是一路北上自驾,从Perth一直往南又是另外一条不错的线路,看来下次有机会还得再去深度体验一下。

这次用Dji的Action 3和手机拍了很多4K的视频,所有的素材整理出来有接近15GB。回新加坡之后,我把之前买的Final Cut Pro翻了出来,第一次学视频剪辑,剪出了一个3分多钟的vlog,供同学们欣赏:

来年期望

  • Master课程顺利毕业
  • 回国一次
  • 策划一次去其他国家的旅行

客制化键盘初体验 RAMA WORKS KARA

2022-08-28 20:40:32

前言

时间穿越回2019年,自从我把我的 HHKB Type-S 改装成了蓝牙版本 [1],这个键盘就成为工作中的主力键盘,一直用到了现在。无线连接很方便,充电一次可以用两三周,这是因为电池寿命衰退的缘故,其实之前充电一次大概能用一个月。另外,最方便的是完全可定制的键位和firmware支持的8层可自定义按键,相当的强大。我一直以为,这将是我所折腾的最后一个键盘了。可惜人算不如天算,还没有玩过客制化键盘的我怎么能停止折腾的步伐呢?

偶然有一次,在公司看到了另外一个喜欢折腾键盘的同事的客制化键盘,彻底坚决了我再折腾一个客制化键盘的心。由于用HHKB多年,既然要折腾一个客制化键盘,当然还是希望选择HHKB的layout。其实支持这种HHKB布局的客制化键盘可选择的范围并不是很多,之前参考过的有Drop上的Tokyo 60系列,还有一个国人自己折腾的HTKB客制化套件。再后来无意中关注到了RAMA WORKS的KARA系列,看了一下广告[2],觉得这键盘还真不错,做工不赖,虽然是个塑料壳子,但是颜色很骚气。金属壳子的客制化键盘虽然逼格更高,但是日常通勤背着估计有点重。

KARA系列提供了多种颜色可以选择,图片中后三种颜色 ICED, AZUR, HAZE 都是我喜欢的颜色。好吧,就它了!

满怀欣喜的去官网看了一圈,原来这款KARA是去年推出的系列了,当时只是官网组织团购了一波,后面再也没补货了。难道这公司也是跟小米学的饥饿营销?幸运的是,在新加坡本地的一个键盘玩家群,刚好碰到有个出二手未拆封的HAZE。官网去年价格160美刀发售,折合新币222.9,这个卖家出价是260新币,后来跟他砍价到了250新币。比起官网发售的价格也没多太多。就这样,HAZE被我纳入囊中了。

购物清单

虽然KARA的官方套件中已经包含了组装客制化键盘的大部分零件:键盘外壳,静音隔层和放置键帽的Plate,还送一根USB Type-C的线。键帽和轴是没有默认提供的,这也是键盘客制化中可玩性很高的部分。市面上可以买到的轴有很多种,颜色也多种多样,再也不限于当年Cherry原厂的那几种MX轴了。多方面搜索了一下之后,决定在Drop上下单键帽和开轴器。在新加坡本地的一个键盘玩家网站买好了润滑剂和轴,还去Shopee买了润滑轴需要的工具。下面列了一个整体的清单:

Drop是一个美国的网站,下单后快递直接从美帝邮寄到新加坡。不知道是疫情的缘故还是其他,这个包裹等了快一个月。清单上,我把USD部分的金额转换成了新币。所以,最后的总价是640.85新币,合人民币大概3000+了。我去!这是我目前折腾过最贵的键盘了!

键帽我选择了GMK LASTER系列的Synthwave M170,这种深紫色的键帽加上蓝色的印刷字母,跟键盘壳子比较搭配。之前用了太多的青轴和茶轴,这次换个不一样的,买了Gazzew的紫色线性轴。

等所有的东西准备齐,就可以开始着手组装键盘了。

组装过程

打开盒子,PCB板,各种配件和说明书都有了。

原厂出产的轴除了特别说明是润滑过的之外,默认是不带润滑的。其实这也是组装键盘里面最耗时的部分。如果不想折腾,建议直接买那种已经润滑过的轴,能省不少时间。组装的第一步就是把所有的轴用开轴器拆开,然后分别把轴里面经常摩擦的部分手动刷上润滑剂。根据润滑过后的测试,润过的轴和没润过的还是差别挺大的,主要是声音方面要更沉稳好听一些。

关于如何润滑轴,Youtube上有两个很不错的视频,手把手教你如何润滑轴和Stabilizer [3] ,看完就可以开始上手了。

按照新手入门开始润第一个轴,大概需要5分钟时间。后续熟练的话大概需要2-3分钟左右。如果按照平均润一个轴需要话3分钟左右计算,60个键的键盘就需要3个小时了。总之,这是个很耗时的过程,润轴的时间我大概听了2-3个podcast,不然实在太无聊了。

等所有的轴都润好,再直往PCB板上装上Stabilizer,组装算是正式进入正题了。拿出PCB,把之前拆开润好的轴,一个一个按在板子上固定,然后通电测试。RAMA WORKS的板子做工还真不错。

等所有的轴和键帽安装完毕,把键盘背面再固定一下,盖上盖子,就算大工告成了。

接上笔记本测试,这灯光效果还真不错,骚气袭人,亮瞎眼。

最后接上VIA Configurator,把这个键盘按照HHKB蓝牙的使用习惯,配置好各种自定义按键,就能正式开始使用了。

后记

客制化的选择实在是太多了,比起各种键盘官网发售的标准配置,能替换和定制的东西也很多,包括但不限于:各种轴,各种键帽,配重模块,外壳的材质,静音处理,等等。也许这也是客制化的吸引力所在吧。而热拔插轴的应用,也让玩客制化键盘的门槛低了一些,再也不需要电烙铁了。

或许是HHKB玩太久的缘故,这种线性轴的声音更好听一些,力道更轻盈一些。我打算把这个键盘带到公司,在工作中用一段时间再更深入体会一下。

附录

  1. HHKB蓝牙改造指北
  2. RAMA WORKS KARA 系列
  3. How to Lube MX Style Switches
  4. How to Lube and Tune Stabilizers