Long Luo's Life Notes

每一天都是奇迹

By Long Luo

春节前在杭州的最后一个夜晚来市中心看钱塘江夜景。

城市因为有了水,才有了生机,河流和湖泊,总是城市里最美的一道风景!

城在水中映,但要成为美不胜收的风景,却并不容易。首先江面不能太窄,要不就成了河景、溪景,也不能太宽,太宽就失之亲切。

看过了这么多城市的江景,唯有黄浦江夜景是不能不看的。

一面是外滩,见证了上海滩沧桑历史,一面是陆家嘴,未来宏图画卷等待书写;一面是古典,一面是现代,交相辉映,相得益彰,尽显江岸美景。

相比之下,钱塘江的夜景缺乏像小蛮腰一样独具特色的地标。不过,钱塘江还有很多没有开发,相信很快就能看到。

杭州,明年见!

By Long Luo at 2018.02.14 in Bingjiang, Hangzhou, China.

In general, Map is a data structure consisting of a set of key-value pairs, and each key can only appears once in the map. This post summarizes Top 9 FAQ of how to use Java Map and its implemented classes. For sake of simplicity, I will use generics in examples. Therefore, I will just write Map instead of specific Map. But you can always assume that both the K and V are comparable, which means K extends Comparable and V extends Comparable.

1. Convert a Map to List

In Java, Map interface provides three collection views: key set, value set, and keyvalue set. All of them can be converted to List by using a constructor or addAll() method. The following snippet of code shows how to construct an ArrayList from a map.

1
2
3
4
5
6
// key list
List keyList = new ArrayList(map.keySet());
// value list
List valueList = new ArrayList(map.valueSet());
// key value list
List entryList = new ArrayList(map.entrySet());

2. iterate over each entry in a map

Iterating over every pair of key-value is the most basic operation to traverse a map. In Java, such pair is stored in the map entry called Map.Entry.

3. SORT A MAP ON THE KEYS

Map.entrySet() returns a key-value set, therefore the most efficient way of going through every entry of a map is

1
2
3
4
5
6
for (Entry entry:map.entrySet()) {
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}

Iterator can also be used, especially before JDK 1.5

1
2
3
4
5
6
7
8
Iterator itr = map.entrySet().iterator();
while(itr.hasNext ( ) ) {
Entry ent ry = i t r . next ( ) ;
/ / g e t k ey
K key = ent ry . getKey ( ) ;
/ / g e t v a l u e
V value = ent ry . getValue ( ) ;
}

60.3 sort a map on the keys Sorting a map on the keys is another frequent operation. One way is to put Map.Entry into a list, and sort it using a comparator that sorts the value. Li s t l i s t = new Ar rayLi s t (map. ent rySe t ( ) ) ; Co l l e c t i ons . s o r t ( l i s t , new Comparator ( ) { @Override public int compare ( Entry e1 , Entry e2 ) { return e1 . getKey ( ) . compareTo ( e2 . getKey ( ) ) ; } });

The other way is to use SortedMap, which further provides a total ordering on its keys. Therefore all keys must either implement Comparable or be accepted by the comparator.

4. SORT A MAP ON THE VALUES

One implementing class of SortedMap is TreeMap. Its constructor can accept a comparator. The following code shows how to transform a general map to a sorted map.

SortedMap sortedMap = new TreeMap (new Comparator ( ) { @Override public int compare (K k1 , K k2 ) { return k1 . compareTo ( k2 ) ; } } ) ; sortedMap . putAll (map) ; 60.4 sort a map on the values Putting the map into a list and sorting it works on this case too, but we need to compare Entry.getValue() this time. The code below is almost same as before. Li s t l i s t = new Ar rayLi s t (map. ent rySe t ( ) ) ; Co l l e c t i ons . s o r t ( l i s t , new Comparator ( ) { @Override public int compare ( Entry e1 , Entry e2 ) { return e1 . getValue ( ) . compareTo ( e2 . getValue ( ) ) ; } } ) ; We can still use a sorted map for this question, but only if the values are unique too. Under such condition, you can reverse the key=value pair to value=key. This solution has very strong limitation therefore is not really recommended by me. 60.5 initialize a static/immutable map When you expect a map to remain constant, it’s a good practice to copy it into an immutable map. Such defensive programming techniques will help you create not only safe for use but also safe for thread maps. 60.6. DIFFERENCE BETWEEN HASHMAP, TREEMAP, AND HASHTABLE 217 To initialize a static/immutable map, we can use a static initializer (like below). The problem of this code is that, although map is declared as static final, we can still operate it after initialization, like Test.map.put(3,“three”);. Therefore it is not really immutable. To create an immutable map using a static initializer, we need an extra anonymous class and copy it into a unmodifiable map at the last step of initialization. Please see the second piece of code. Then, an UnsupportedOperationException will be thrown if you run Test.map.put(3,“three”);. public c l a s s Tes t { pr ivate s t a t i c f ina l Map map; s t a t i c { map = new HashMap ( ) ; map. put ( 1 , ” one ” ) ; map. put ( 2 , ” two ” ) ; } } public c l a s s Tes t { pr ivate s t a t i c f ina l Map map; s t a t i c { Map aMap = new HashMap ( ) ; aMap. put ( 1 , ” one ” ) ; aMap. put ( 2 , ” two ” ) ; map = Co l l e c t i ons . unmodifiableMap (aMap) ; } } Guava libraries also support different ways of intilizaing a static and immutable collection. To learn more about the benefits of Guava’s immutable collection utilities, see Immutable Collections Explained in Guava User Guide. 60.6 difference between hashmap, treemap, and hashtable There are three main implementations of Map interface in Java: HashMap, TreeMap, and Hashtable. The most important differences include: The order of iteration. HashMap and HashTable make no guarantees as to the order of the map; in particular, they do not guarantee that the order 60.7. A MAP WITH REVERSE VIEW/LOOKUP 218 will remain constant over time. But TreeMap will iterate the whole entries according the “natural ordering” of the keys or by a comparator. key-value permission. HashMap allows null key and null values. HashTable does not allow null key or null values. If TreeMap uses natural ordering or its comparator does not allow null keys, an exception will be thrown. Synchronized. Only HashTable is synchronized, others are not. Therefore, “if a thread-safe implementation is not needed, it is recommended to use HashMap in place of HashTable.” A more complete comparison is | HashMap | HashTable | TreeMap i t e r a t i o n order | no | no | yes null key value | yes yes | yes yes | no yes synchronized | no | yes | no time performance | O( 1 ) | O( 1 ) | O( log n) implementation | buckets | buckets | red black t r e e Read more about HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap. 60.7 a map with reverse view/lookup Sometimes, we need a set of key-key pairs, which means the map’s values are unique as well as keys (one-to-one map). This constraint enables to create an “inverse lookup/view” of a map. So we can lookup a key by its value. Such data structure is called bidirectional map, which unfortunetely is not supported by JDK. 60.8 both apache common collections and guava provide implementation of bidirectional map, called bidimap and bimap, respectively. both enforce the restriction that there is a 1:1 relation between keys and values. 7. shallow copy of a map Most implementation of a map in java, if not all, provides a constructor of copy of another map. But the copy procedure is not synchronized. That means when 60.9. FOR THIS REASON, I WILL NOT EVEN TELL YOU HOW TO USE CLONE() METHOD TO COPY one thread copies a map, another one may modify it structurally. To prevent accidental unsynchronized copy, one should use Collections.synchronizedMap() in advance.

Map copiedMap = Co l l e c t i ons.synchronizedMap (map) ; Another interesting way of shallow copy is by using clone() method. However it is NOT even recommended by the designer of Java collection framework, Josh Bloch. In a conversation about “Copy constructor versus cloning“, he said I often provide a public clone method on concrete classes because people expect it.

It’s a shame that Cloneable is broken, but it happens. Cloneable is a weak spot, and I think people should be aware of its limitations.

60.9 for this reason, i will not even tell you how to use clone() method to copy a map. 8. create an empty map If the map is immutable, use map = Co l l e c t i ons . emptyMap ( ) ;

Otherwise, use whichever implementation. For example map = new HashMap ( ) ;

THE

By Long Luo

Android NDK

静态注册

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.18.1)

project("hello-jni")

add_library(hello-jni SHARED
hello-jni.cpp)

# Include libraries needed for hello-jni lib
target_link_libraries(hello-jni
android
log)

hello-jni/app/src/main/cpp/hello-jni.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <jni.h>
#include <string>

#include "hello_jni.h"


JNIEXPORT jstring JNICALL
Java_me_longluo_hellojni_HelloJniActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from JNI.";
return env->NewStringUTF(hello.c_str());
}


JNIEXPORT jint JNICALL
Java_me_longluo_hellojni_HelloJniActivity_add(JNIEnv *env, jobject /* this */, jint a, jint b) {
int result = a + b;
return result;
}

hello-jni/app/src/main/cpp/hello_jni.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef HELLO_JNI_H
#define HELLO_JNI_H

#include <jni.h>

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL
Java_me_longluo_hellojni_HelloJniActivity_stringFromJNI(JNIEnv *env, jobject /* this */);

JNIEXPORT jint JNICALL
Java_me_longluo_hellojni_HelloJniActivity_add(JNIEnv *env, jobject /* this */, jint a, jint b);


#ifdef __cplusplus
}
#endif

#endif // HELLO_JNI_H
1
2
3
4
5
6
7
ndkVersion '25.1.8937393'

externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}

动态注册

hello-jni-dynamic/app/src/main/cpp/CMakeLists.txt

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
cmake_minimum_required(VERSION 3.18.1)

# Declares and names the project.

project("hello-jni-dynamic")

# Automatically all files in a directory to a target
file (GLOB_RECURSE HELLOJNI_SRCS CONFIGURE_DEPENDS
"src/*.cpp"
"src/*.h"
)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
hello-jni

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
${HELLOJNI_SRCS})

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
hello-jni

# Links the target library to the log library
# included in the NDK.
${log-lib}
)

hello-jni-dynamic/app/src/main/cpp/src/hello-jni.cpp

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
#include "hello-jni.h"

jint JNI_OnLoad(JavaVM *vm, void *reserved){

UnionJNIEnvToVoid uenv;
uenv.venv = nullptr;
jint result = -1;
JNIEnv* env;

ALOGI("JNI_OnLoad");

if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
ALOGE("ERROR: GetEnv failed");
goto bail;
}

env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
ALOGE("ERROR: registerNatives failed");
goto bail;
}

result = JNI_VERSION_1_6;

bail:
return result;
}

hello-jni-dynamic/app/src/main/cpp/src/hello-jni.h

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
#ifndef HELLO_JNI_H
#define HELLO_JNI_H

#include <jni.h>
#include <string>
#include "logger.h"
#include "constants.h"

#define SIZE(X) sizeof(X) / sizeof(X[0])


typedef union {
JNIEnv *env;
void *venv;
} UnionJNIEnvToVoid;


/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
int numMethods) {
jclass clazz;
clazz = env->FindClass(className);

if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}

if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}

return JNI_TRUE;
}


/*
* Register native methods for all classes we know about.
*
* returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv *env) {
if (!registerNativeMethods(env, HelloJNI::constants,
HelloJNI::constants_methods,
SIZE(HelloJNI::constants_methods))
) {
return JNI_FALSE;
}

return JNI_TRUE;
}


/*
* This is called by the VM when the shared library is first loaded.
*/
jint JNI_OnLoad(JavaVM *vm, void *reserved);

#endif //HELLO_JNI_H

hello-jni-dynamic/app/src/main/cpp/src/constants.h

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
#ifndef HELLO_CONSTANTS_H
#define HELLO_CONSTANTS_H

#include <jni.h>


namespace HelloJNI{

static jfloat JniVersion(JNIEnv *env, jobject object) {
return 1.6;
}

static jstring JniDynamicRegister(JNIEnv *env, jobject object){
return env->NewStringUTF("Hello Jni Dynamic Register");
}

static const char *constants = "me/longluo/hellojni/JniLibrary";

static JNINativeMethod constants_methods[] = {
{"nativeGetJniVersion", "()F", (void *) JniVersion},
{"nativeGetJniDynamicRegister", "()Ljava/lang/String;", (void *) JniDynamicRegister}
};

static std::string jString2String(JNIEnv *env, jstring jStr) {
if (!jStr) {
return "";
}

const jclass stringClass = env->GetObjectClass(jStr);
const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jStr, getBytes, env->NewStringUTF("UTF-8"));

size_t length = (size_t) env->GetArrayLength(stringJbytes);
jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL);

std::string ret = std::string((char *)pBytes, length);
env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT);

env->DeleteLocalRef(stringJbytes);
env->DeleteLocalRef(stringClass);

return ret;
}
}

#endif //HELLO_CONSTANTS_H

参考文献

  1. JNI
  2. NDK 使用入门
  3. CMake
  4. 将 NDK 与其他构建系统配合使用
  5. 示例:hello-jni

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/intro.html

https://medium.com/@sarafanshul/jni-101-introduction-to-java-native-interface-8a1256ca4d8e

https://medium.com/@sarafanshul/jni-201-dynamic-registration-a1ad7fa50b21

https://gist.github.com/okamayana/79c98545eb99c4877979

By Long Luo

招聘其实本质上是在做风险评估,根据简历和面试确定风险最小的那个人。

其实这个风险评估可以推而广之适用于涉及选择的各个场合。

最近在协助公司招人,也为项目组面试了一些人,对招人也有了一些思考。

为了避免招到不合适的人,将风险降到最低,那么就会从各方面均衡考虑。

从面试官的角度来看,为什么要名校和名企出身的人。

第一,因为名企已经帮你做了一次筛选了,尤其是校招进入BAT的,更是万里挑一,所以风险可以降到最低。

第二,学历其实也是一项筛选。学历可以证明你的学习能力以及把握机会的能力(高考是一次可以改变人生轨迹的机会。)。

第三,如果没有名企和学历的光环,那么你就要用你做成功的事情来证明你自己确实出类拔萃。

综合,应聘者需要用一些东西来证明自己比其他应聘者更优秀,而且实力已经得到证明,而不是到新公司体现和证明。

By Long Luo @2017-11-28 at Shenzhen.

By Long Luo

约瑟夫问题( \(\textit{Josephus Problem}\) )是每个学计算机的同学都会遇到的经典编程题,可以考察链表、递归、数学等知识,下面我们就来通过这道题对好好学习下算法和编程吧,Let’s go!

一、什么是约瑟夫问题?

据说著名犹太历史学家 \(\textit{Josephus}\) 有过以下的故事:

在罗马人占领乔塔帕特后,\(39\) 个犹太人与 \(\textit{Josephus}\) 及他的朋友躲到一个洞中,\(39\) 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,\(41\) 个人排成一个圆圈,由第 \(1\) 个人开始报数,每报数到第 \(3\) 人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。

然而 \(\textit{Josephus}\) 和他的朋友并不想遵从这个规则,\(\textit{Josephus}\) 要他的朋友先假装遵从,他将朋友与自己安排在第 \(16\) 个与第 \(31\) 个位置,于是逃过了这场死亡游戏。

二、约瑟夫问题算法分析

约瑟夫问题可用代数分析来求解,将这个问题扩大好了,假设现在您与 \(N\) 个朋友不幸参与了这个游戏,您要如何保护您与您的朋友?

只要画两个圆圈就可以让自己与朋友免于死亡游戏,这两个圆圈内圈是排列顺序,而外圈是自杀顺序,如下图所示:

图1. 约瑟夫问题游戏顺序 Josephus Problem

假如使用编程来求解的话,只要将 \(\textit{array}\) 当作环状来处理就可以了,在 \(\textit{array}\) 中由计数 \(1\) 开始,每找到 \(3\) 个无数据区就填入 \(1\) 个计数,直到计数达 \(41\) 为止,然后将 \(\textit{array}\) 由索引 \(1\) 开始列出,就可以得知每个位置的自杀顺序,这就是约瑟夫排列,\(41\) 个人而报数 \(3\) 的约琴夫排列如下所示:

14 36 1 38 15 2 24 30 3 16 34 425 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23

由上可知,最后一个自杀的是在第 \(31\) 个位置,而倒数第二个自杀的要排在第 \(16\) 个位置,而之前的人都死光了,所以他们也就不知道约瑟夫与他的朋友并没有遵守游戏规则了。

这是一道经典算法问题,每个学编程的同学都会遇到。一般常见的问法有以下几种:

  1. 输出自杀顺序;
  2. 输出最后一个自杀同学编号。

下面我们就来动手实践下,具体实现代码如下所示:

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
private static int Josephus(int n, int m) {
int[] peopleArr = new int[n];
for (int i = 0; i < n; i++) {
peopleArr[i] = i + 1;
}

int index = 0;
int length = n;
int count = 1;

while (length > 0) {
if (peopleArr[index % n] > 0) {
if (count % m == 0) {
// 找到要出圈的人,并把圈中人数减1
System.out.print(peopleArr[index % n] + " ");
peopleArr[index % n] = -1;
count = 1;
index++;
length--;
} else {
index++;
count++;
}
} else {
// 遇到空位了,就跳到下一位,但count不加1,也就是这个位置没有报数
index++;
}
}

return index;
}
阅读全文 »
0%