Code and Think

你熟悉数据结构和算法吗?个人以为:对于大多数程序员或者开发工程师答案都是否定的吧。在我说出此话的同时,你也可以猜到我此方面的能力了。

其实,在我看来,可以专心写代码,追求代码质量的工程师是令人羡慕的。那个过程如同艺术家在完成自己心爱的作品;如同科学家在专心论证自己的假设,如同一位匠人在追求那凡人无法企及的技艺。

然而,现实中一位IT工程师的工作不是这么”简单”的。多数情况下每日书写代码时间不会超过4个小时。为何如此,原因各有不同,我想不少人会有此问题。

心态

当有一天你发现,看着手中的工作,你所积累的经验和你想象中的目标有差距时,你会怎么办?
我会选择顺其自然,放下一些执念,做好当前该做的事情,时不时的享受一下生活,这一切不是挺好吗?

那还要尝试新东西和改变吗?当然要,但心中不是想着”一定要怎样”,而是放平心态。尝试会有两个结果:

  1. 让自己更加了解自己,思考以及启发,成长于此过程中
  2. 发现自己的积累还可以做些其他事情,给自己一个改变的机会。

这个过程有些微妙,如同心中湖水平如镜时,也会有那突然涟漪泛起一刻,而你会去欣赏和感受,直到再此平如镜,看着镜中的自己更加清晰。

可能有人会说,你不抱着”一定要怎样”态度去做事情,不会成功。我不反驳,但要想清楚何为成功。

简单说:我愿意保持平常心去尝试一些机会,如果感觉还不错可以投入一些精力,毕竟想把事情做的有点模样,投入还是不可少的。

代码

说回Code或者是一段program,我喜欢这样描述它,它是对一组规则的描述。
人类有很多语言,计算机也是如此。在我看来,所有语言都可以用于描述所有事物(或规则,我喜欢把周围的一切都看成”规则” 这个话题今后有机会儿再聊)
而学习一门计算机语言也如同学一门外语一样,你通常掌握的只是描述事物的表达方式。

那数据结构和算法的晦涩难懂,又是一个什么感觉。其如同你学习了英语,可以用于日常沟通。突然有一天,在法律场景下,你需要用法律专业措辞描述一个场景。
没错,就是这样一个感觉,你需要熟悉很多专业词汇,术语,如同数据结构;你需要搞明白法律相关基本规则和思维逻辑,如同算法。
所以一时有点蒙也是很正常的。面对这样的场景,这时你有一个主动问题,你想做吗? 还有一个被动问题,有机会做吗?

此时不得不承认,如果你有相关经验的积累,你获得此次机会的概率会高一些。毕竟你一眼看去会很快懂得相关的数据结构和算法的设计意图和目的。如果你没有,你可能很长时间都无法理解其想表达的本意,也就无从下手描述具体规则细节了。此时你的”想做”可能就变得微乎其微了。

此时回到最初的话题,心终会平静,如果还想,那就投入一些精力。如果接受现状,那就放下。
此时我更愿意放平心态做一些事情,平衡生活和工作,平衡兴趣和专业。保持学习心态

下面说点实际的吧,最近遇到的一些有趣的程序。本人平时工作中很少遇到此类场景,偶尔遇到值得深入一下

数据结构

这个问题实际上我至今没有搞清楚,没有清楚的部分是此结构使用的场景和存在的意义(要解决什么问题)。

构建此结构是基于一个代码说明文档(非Java语言)实现的,所以只有猜测着写了。就像我之前所说,如果你没有相关经验,可能一时间无法理解其真实意义,构建具体逻辑也就变得困难了

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.gino;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class MyEither<L, R> {
private List<L> left = new ArrayList<>();
private List<R> right = new ArrayList<>();

private final Class<L> leftClass;
private final Class<R> rightClass;

public MyEither(Class<L> leftClass, Class<R> rightClass) {
this.leftClass = leftClass;
this.rightClass = rightClass;
}

public boolean hasLeft() {
return this.left.size() != 0;
}

public boolean hasRight() {
return this.right.size() != 0;
}

public boolean isLeft(L value) {
return this.left.contains(value);
}

public boolean isRight(R value) {
return this.right.contains(value);
}

public L fromLeft() {
if (hasLeft()) {
L l = left.get(0);
left.remove(0);
return l;
} else {
return null;
}
}

public R fromRight() {
if (hasRight()) {
R r = right.get(0);
right.remove(0);
return r;
} else {
return null;
}
}

public void put(Object value) {
if (this.leftClass.isInstance(value)) {
this.left.add((L) value);
} else if (this.rightClass.isInstance(value)) {
this.right.add((R) value);
} else {
throw new ClassCastException();
}
}

public List<L> getLefts(Function<List<L>, List<L>> fun) {
return fun.apply(this.left);
}

public List<R> getRights(Function<List<R>, List<R>> fun) {
return fun.apply(this.right);
}

public Map<L, R> map() {
int i = Math.max(left.size(), right.size());
return IntStream.range(0, i).boxed()
.collect(Collectors.toMap(index -> index > left.size() - 1 ? null : left.get(index), index -> index > right.size() - 1 ? null : right.get(index)));
}

public static void main(String[] args) {
MyEither<String, Double> myEither = new MyEither<>(String.class, Double.class);
myEither.put(10D);
myEither.put("test");
myEither.put(101D);
myEither.put(102D);


System.out.println(myEither.hasLeft());
System.out.println(myEither.isLeft("test"));
System.out.println(myEither.getLefts(x -> x.stream().map(String::toUpperCase).collect(Collectors.toList())));
System.out.println(myEither.hasRight());
System.out.println(myEither.isRight(10D));
System.out.println(myEither.getRights(x -> x.stream().map(v -> v / 100D).collect(Collectors.toList())));

System.out.println(Collections.singletonList(myEither.map()));

// negative case
try {
myEither.put(10);
} catch (Exception e) {
System.out.println("wrong type");
}
}
}

结构阐述

  • 左右两个元素,类型不同,最初实现只是单元素,看到文档中有获取左右元素的list类型返回,修改为list类型左右元素
  • 根据不同类型放置到左右两边
  • map转化是有问题的,当右元素长度比左元素长度多2及以上时,map转换是有问题的。
  • 由于不了解其使用场景,所以总是感觉其存在价值不大,为何不用直接用Map处理呢?

写此结构时还是锻炼一下Java基础,有收获的

算法

经典算法题,硬币问题,非贪婪算法

题目描述:如有你手里有面值为1,7,10的货币(也可设定为其它组合),数量充足。现给出一个具体金额,你如何找到货币个数最小的组合

废话不多说,直接上答案

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
package com.gino;

import java.util.*;
import java.util.stream.Collectors;

public class Coin {

public static List<List<Integer>> solutions = new ArrayList<>();

public int count(List<Integer> coins, int num, int charge, List<Integer> solution) {
if (charge == 0) {
solutions.add(solution);
return 1;
}
if (charge < 0) {
return 0;
}
if (num <= 0 && charge >= 1) {
return 0;
}

List<Integer> copy = new ArrayList<>(solution);
solution.add(coins.get(num - 1));
return count(coins, num - 1, charge, copy) + count(coins, num, charge - coins.get(num - 1), solution);
}

public static void main(String[] args) {
System.out.print("Enter your charge: ");
Scanner scanner = new Scanner(System.in);
int charge = scanner.nextInt();
Coin coin = new Coin();
List<Integer> coins = Arrays.asList(1, 7, 10);

System.out.println(coin.count(coins, coins.size(), charge, new ArrayList<>()));

System.out.println(Arrays.toString(solutions.toArray()));
List<Integer> min = solutions.stream().min(Comparator.comparingInt(List::size)).orElse(new ArrayList<>());
System.out.println(Arrays.toString(min.toArray()));

List<List<Integer>> collect = solutions.stream().min(Comparator.comparingInt(List::size)).stream().collect(Collectors.toList());
System.out.println(Arrays.toString(collect.toArray()));

System.out.println(solutions.stream().mapToInt(List::size).min().orElse(0));
}
}

我给出的解决方法是其中一种,非动态规划(说实话,那种我没有看懂)此解题思路还是很有意思的,使用递归,核心思想是:
在尝试路径中,要么钱减少,要么货币种类减少,列举所有组合可能性,获得最终结果

程序输出了所有组合的可能性,以及寻找到最优解。感觉这个解题思路和睿智,思路来自于网络。收获:

  • 解题过程的思考,很有意思
  • Stream的实际场景使用

简单应用

一个实际场景应用,其实也是数据结构问题,但是这个使用场景明确,所以容易清晰实现思路

主要思想,寻找最近汇率数据,或给定搜索日期,查询当日最新的汇率数据。不多解释了,代码很容懂。

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
package com.gino;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

public class FxRate {
class Item {
LocalDate localDate;
String assetName;
Double value;
LocalDateTime localDateTime;

public Item(LocalDate localDate, String assetName, Double value, LocalDateTime localDateTime) {
this.localDate = localDate;
this.assetName = assetName;
this.value = value;
this.localDateTime = localDateTime;
}

public LocalDateTime getLocalDateTime() {
return this.localDateTime;
}
}

List<Item> cacheList = new ArrayList<>();

public Map<String, Double> getData(LocalDate localDate, List<String> assetNameList, Optional<LocalDateTime> localDateTime) {
HashMap<String, Double> result = new HashMap<>();
if (localDateTime.isEmpty()) {
for (String assetName : assetNameList) {
Item item = cacheList.stream().filter(x -> assetName.equals(x.assetName)).filter(x -> localDate.equals(x.localDate)).max(Comparator.comparing(Item::getLocalDateTime)).orElse(null);
result.put(assetName, item.value);
}
return result;
} else {
for (String assetName : assetNameList) {
Item item = cacheList.stream().filter(x -> assetName.equals(x.assetName)).filter(x -> localDate.equals(x.localDate)).filter(x -> !localDateTime.get().isBefore(x.localDateTime)).max(Comparator.comparing(Item::getLocalDateTime)).orElse(null);
result.put(assetName, item.value);
}
return result;
}
}

public void addData(LocalDate localDate, String assetName, Double value, LocalDateTime localDateTime) {
cacheList.add(new Item(localDate, assetName, value, localDateTime));
}

public static void main(String[] args) {
FxRate fxRate = new FxRate();
fxRate.addData(LocalDate.of(2020, 4, 1), "USDSGD", 1.33, LocalDateTime.of(2020, 4, 1, 10, 40));
fxRate.addData(LocalDate.of(2020, 4, 1), "USDAUD", 1.29, LocalDateTime.of(2020, 4, 1, 10, 40));
fxRate.addData(LocalDate.of(2020, 4, 1), "USDSGD", 1.34, LocalDateTime.of(2020, 4, 1, 14, 30));
fxRate.addData(LocalDate.of(2020, 4, 1), "USDAUD", 1.28, LocalDateTime.of(2020, 4, 1, 14, 30));
fxRate.addData(LocalDate.of(2020, 4, 2), "USDSGD", 1.31, LocalDateTime.of(2020, 4, 2, 8, 40));
fxRate.addData(LocalDate.of(2020, 4, 2), "USDAUD", 1.30, LocalDateTime.of(2020, 4, 2, 8, 40));
fxRate.addData(LocalDate.of(2020, 4, 2), "USDSGD", 1.29, LocalDateTime.of(2020, 4, 2, 14, 30));
fxRate.addData(LocalDate.of(2020, 4, 2), "USDAUD", 1.32, LocalDateTime.of(2020, 4, 2, 14, 30));

List<String> assetNameList = Arrays.asList("USDSGD", "USDAUD");

Map<String, Double> result = fxRate.getData(LocalDate.of(2020, 4, 1), assetNameList, Optional.empty());
System.out.println(Collections.singletonList(result));
result = fxRate.getData(LocalDate.of(2020, 4, 1), assetNameList, Optional.of(LocalDateTime.of(2020, 4, 1, 14, 20)));
System.out.println(Arrays.asList(result));
}

}

其实代码还有”优化”空间,还可以把方法继续简化,但是炫技、代码可读性、代码性能还是要平衡一下的,我觉此状态已经不错了。代码写的太复杂,过后自己读起来也是痛苦的。
收获:

  • 一个实际问题解决的思考过程
  • Optional 和 Stream的使用,此类处理可以使用parallelStream哦
  • 我真的是记忆不好,很多方法调用还需要去查,还是用的太少,如果天天写也形成”肌肉记忆”了

总结

再说几句相关感受,最近在给团队面试候选人,不知道各位对现在的面试什么感觉?我的是:

  1. 面试难度往往大于实际工作难度,我也是这么做的,问了一些超出实际工作的问题
  2. 应试般的准备一些问题,”套路”式的问与答,是否真的可以解决今后的问题不知道。(大厂带出的风格,你不刷题,都不好意思参加面试)
  3. “客观”考察技术,忽略”人格”属性。满足基本条件时,能做与想做,你要那一个?面试往往无法充分了解一个人

这样的面试有问题?有点问题,但是无法避免,因为你需要在短时间基于标准内量化,评定一个候选人就不可避免。
如果我今后遇到此类问题,我应该都刷刷leetcode然后再去面试吗?顺其自然吧~ 哈哈~

个人观点,欢迎交流,不必”认真”,一切随缘。

题外话

最近在思考学习、投资、生活、孩子教育等问题,四十不惑,心态也在发生着细小的变化,眼光需要放远些了。

上周末参加了 PMP 和 MBA 两个活动,多和人交流感觉还不错的。老师也在说,大家都是有那么一股拼劲所以才会聚到一起。我现在更想把这股劲使到更有”价值”的点上,这是我目前的需求。一两句无法说清,有机会以后再说吧。

收~~~