这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战
Sequence
Sequence 抽象
Sequence 是 AviatorScript 对“集合”的抽象。这个“集合”囊括了数组、Set/Map/List 等等,只要它是是可遍历的集合即可。Sequence 的概念来自 clojure 的,当然,相比 clojure 还是弱了很多,比如 chunk/lazy 都没有支持。
事实上 Sequence 只是继承了 Iterable 接口:
/**
* Sequence mark interface.
*
* @author dennis(killme2008@gmail.com)
*
* @param <T>
*/
public interface Sequence<T> extends Iterable<T> {
Collector newCollector(int size);
int hintSize();
}
额外增加了两个方法:
hintSize用于返回集合的元素数量,仅仅是一个 hint,不保证精确。newCollector返回 collector,用于收集 sequence 里的元素经过某种“变化”后的结果。
Collector 的接口也非常简单:
/**
* Collector to collect elements.
*
* @author dennis(killme2008@gmail.com)
*
* @param <T>
*/
public interface Collector {
void add(Object e);
Object getRawContainer();
}
add方法用于添加元素getRawContainer返回底层的实际容器。
为了更加有体感,可以看一个内部的 Sequence 实现:ArraySequence,用于将数组转成 Sequence。
你在 AviatorScript 中见到的 Tuple、数组、Range、List、Map 和 Set 都实现了对应的 Sequence,这也是为什么他们可以用同一套 API 来操作的原因。
下面我们将详细介绍这些 API。先从遍历开始。所有例子参见 sequence.av 和 sequence2.av。
遍历 sequence
遍历 Sequence 的标准方式是 for 循环,我们在上一节已经见到很多例子了:
## sequence.av
let a = seq.array(int, 1, 2, 3, 4);
let r = range(-5, 5);
let s = seq.set(99, 100, 101);
let m = seq.map("a", 1, "b", 2, "c", 3);
let n = seq.list("car", "bus", "bike");
## iterate elements
let sum = 0 ;
for e in r {
sum = sum + e;
}
println("sum of range r: " + sum);
for e in m {
println(e.key + "=" + e.value);
}
对于 map 来说,遍历的是 Entry 。这一块在前两节介绍数组和集合的时候已经详细介绍了,不再重复。
操作 sequence 的高阶函数
对于 Sequence 的抽象, AviatorScript 也提供了一套高阶函数来方便地对集合做转换、过滤、查询以及聚合,我们将一一介绍。这些函数的规则都是将 sequence 作为第一个参数。
count
count(seq) 函数用于获取 seq 里的集合元素,它将尽量在 O(1) 的时间复杂度内返回结果,最差情况下退化成 O(n):
## count
println("count of array: " + count(a));
println("count of range: " + count(r));
println("count of set: " + count(s));
println("count of map: " + count(m));
println("count of list: " + count(n));
is_empty
is_empty 用于返回集合是否为空, is_empty(nil) 返回 true :
println("is_empty(array): " + is_empty(a));
println("is_empty(seq.list()): " + is_empty(seq.list()));
println("is_empty(nil): " + is_empty(nil));
输出:
is_empty(array): false
is_empty(seq.list()): true
is_empty(nil): true
include
include(seq, x) 用于判断元素 x 是否在 seq 内,对于 Set 是 O(1) 的时间复杂度,其他是 O(n):
## include
println("array has 3: " + include(a, 3));
println("map has an entry ('b', 2): " + include(m, seq.entry("b", 2)));
println("range has 10: " + include(r, 10));
同样,对于 map 来说,需要比较的 Map.Entry 对象,你可以通过 seq.entry(key, value) 来构造 entry 对象:
array has 3:true
map has an entry ('b', 2): true
range has 10:false
map
map(seq, fn) 用于将 seq 转换另一个 seq,它将第二个参数 fn 的函数作用在集合里的每个元素上,结果收集到另一个集合(这里就是上文提到的 collector 发生作用的地方)并返回:
## map
let new_range = map(r, lambda(x) -> x + 1 end);
print("new range is: ");
for x in new_range {
print(x);
print(", ");
}
println()
let new_map = map(m, lambda(e) -> e.value = e.value + 100; return e; end);
println("new map is: " + new_map + ", and type is: "+ type(new_map));
new_range 是 range 的每个元素递增 1 之后组成的集合, new_map 是给 m 里的每个 entry 的 value 加上 100 后组成的集合。
new range is: -4, -3, -2, -1, 0, 1, 2, 3, 4, 5,
new map is: [a=101, b=102, c=103], and type is: java.util.ArrayList
这里我们的函数定义都使用了 lambda 语法,函数的返回结果将加入最终的结果集,所以这里 lambda(e) -> e.value = e.value + 100; return e; end ,最终返回的是 e ,也就是 Map.Entry 对象,所以这里 new_map 结果是一个 ArrayList ,里面的元素是一个一个的 Map.Entry 对象,如果我们想将它转成一个 HashMap 就需要用到下面讲到的 into 函数。
into
into(to_seq, from_seq) 用于将 from_seq 的元素,逐一添加到 to_seq 集合:
## into
let new_map = into(seq.map(), new_map);
println("new map is: " + new_map + ", and type is: "+ type(new_map));
我们将 new_map 这个链表里的每个 entry 对象,通过 seq.add(to_seq, entry) 函数逐一添加到了 seq.map() 返回的 HashMap 对象:
new map is: {a=101, b=102, c=103}, and type is: java.util.HashMap
也可以用他来做集合类型之间的转换,比如数组转成 Set:
let new_set = into(seq.set(), a);
println("new set is: " + new_set + ", and type is: "+ type(new_set));
输出:
new set is: [1, 2, 3, 4], and type is: java.util.HashSet
reduce
reduce(seq, fn, init) 用于“聚合” seq 中的元素,第一次迭代的时候,它将调用第二个参数的函数结合第三个参数初始值 fn(init, element) 作用在每个元素 element 上,返回的结果在后续迭代中继续调用 fn(result, element) , reduce 调用等价于下面的代码:
fn reduce(seq, fn, init) {
let result = init;
for element in seq {
result = fn(result, element);
}
return result;
}
有了 reduce ,我们可以方便地对数组求和:
let sum_of_a = reduce(a, +, 0);
let sum_of_r = reduce(r, +, 0);
println("some of array is: " + sum_of_a);
println("some of range is: " + sum_of_r);
+ 加法运算符本质上也是一个函数。
可以统计 list 里总的字符串长度:
let len = reduce(n, lambda(len, x) -> len + count(x) end, 0);
println("total string length in list is: " + len);
事实上你可以将 map 函数也看成一个 reduce 调用:
fn mymap(seq, fn) {
reduce(seq,
lambda(c, e) ->
seq.add(c, fn(e))
end,
seq.list())
}
println("test mymap: " + mymap(a, lambda(x) -> x * 2 end));
我们使用 reduce 定义了自己的 map 函数—— mymap ,初始值是 seq.list() 返回的 List,每次迭代我们将 fn(element)的结果添加到了最终的结果 List,并最后返回:
test mymap: [2, 4, 6, 8]
事实上你可以用 reduce 来定义 into 、 filter 等等函数, 有兴趣可以自行练习。
sort
sort(seq) 仅用于排序数组或者 List,其他 seq 类型无效,其他集合类型需要通过 into 等函数转换成 List 才可以使用:
## sort
println("sort(list) is: " + sort(n));
println("sort(set) is: " + sort(into(seq.list(), s)));
sort 最终调用的是 Collections.sort 或者 Arrays.sort 排序。
从 5.2 开始, sort 接受一个 comparator 参数,可以传入自定义的排序比较器,例如我们想倒序排列下 List:
let c = comparator(lambda(x, y) -> x > y end);
println("sort(list, c) is: " + sort(n, c));
comparator 函数接受一个比较的谓词函数,并转成 java.util.Comparator 对象,然后传入给 sort 函数执行,最终将 n 倒序排列输出:
sort(list) is: [bike, bus, car]
sort(set) is: [99, 100, 101]
sort(list, c) is: [car, bus, bike]
filter
filter(seq, fn) 用于过滤一个 seq,它将 fn 函数作用在每个元素上,结果返回 true 的收集到新 seq,否则就丢掉:
## filter
let es = filter(r, lambda(x) -> x %2 == 0 end);
println("filter even number in range:" + es);
let bs = filter(n, lambda(x) -> string.startsWith(x, "b") end);
println("bs is: " + bs);
这段代码将 range 里的偶数过滤出来,并且将 n 这个 list 里面以字符串 b 开头的过滤出来:
filter even number in range:[-4, -2, 0, 2, 4]
bs is: [bus, bike]
接下来的三个函数 every/not_any/some 都是用于判断或者查找 seq 里的元素是否满足特定的条件。