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.avlet 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 elementslet 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):
## countprintln("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): falseis_empty(seq.list()): trueis_empty(nil): true
include
include(seq, x) 用于判断元素 x 是否在 seq 内,对于 Set 是 O(1) 的时间复杂度,其他是 O(n):
## includeprintln("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:truemap has an entry ('b', 2): truerange has 10:false
map
map(seq, fn) 用于将 seq 转换另一个 seq,它将第二个参数 fn 的函数作用在集合里的每个元素上,结果收集到另一个集合(这里就是上文提到的 collector 发生作用的地方)并返回:
## maplet 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 集合:
## intolet 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 才可以使用:
## sortprintln("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,否则就丢掉:
## filterlet 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 里的元素是否满足特定的条件。
seq.every
seq.every(seq, fn) , 用于检查 seq 里的元素是否都满足 fn(x) == true ,如果都满足,返回 true ,否则是 false :
## everyprintln("every element in array is greater than zero: "+ seq.every(a, lambda(x) -> x > 0 end));println("every element in range is greater than zero: "+ seq.every(r, lambda(x) -> x > 0 end));
every element in array is greater than zero: trueevery element in range is greater than zero: false
seq.not_any
seq.not_any(seq, fn) 和 seq.every 正好相反,当且仅当 seq 里的每个元素满足 fn(x) == false 才返回 true,其他都返回 false,表示 seq 里没有一个元素满足特定谓词检查:
## seq.not_anyprintln("There are not any elements in array is less than zero: "+ seq.not_any(a, lambda(x) -> x < 0 end));println("There are not any in range is less than zero: "+ seq.not_any(r, lambda(x) -> x < 0 end));
There are not any elements in array is less than zero: trueThere are not any elements in range is less than zero: false
seq.some
seq.some(seq, fn) 返回 seq 中第一个使得 fn(x) == true 的元素,如果没有找到,返回 nil :
## seq.someprintln("Find a element in array is greater than zero: "+ seq.some(a, lambda(x) -> x > 0 end));println("Find a element in range is greater than zero: "+ seq.some(r, lambda(x) -> x > 0 end));println("Find a element in list is starting with 'c': "+ seq.some(n, lambda(x) -> string.startsWith(x, "c") end));
Find a element in array is greater than zero: 1Find a element in range is greater than zero: 1Find a element in list is starting with 'c': car
take_while
take_while(sequence, pred) 用于从集合 sequence 里挑选出 pred(元素) 返回 true 的元素并返回新的集合:
fn is_neg(x) {x < 0}let list = seq.list(-2, -1, 0, 1, 2, 3, 0, 99, -1000, 7);let result = take_while(list, is_neg);p("result of take_while: #{result}");
通过 take_while 我们从 list 里挑选出所有的负数并打印:
result of take_while: [-2, -1, -1000]
反过来,我们也可以“丢弃”所有的负数,这就要用到 drop_while 。
drop_while
let result = drop_while(list, is_neg);p("result of drop_while: #{result}");
从 list 里将所有的负数 drop 出去,剩下的都应该是非负数:
result of drop_while: [0, 1, 2, 3, 0, 99, 7]
group_by
group_by(sequence, keyfn) 可以为集合做分组,它会将 keyfn 函数作用到集合里的每个元素上,返回分组的 key,然后返回相同 key 的将放在同一个 list 里,最终返回一个 map 映射: {key1 -> [e1, e2], key2 -> [e3, e4], ...} :
let result = group_by(list, is_neg);p("result of group_by: #{result}");
执行上面代码将输出:
result of group_by: {false=[0, 1, 2, 3, 0, 99, 7], true=[-2, -1, -1000]}
is_neg 当遇到负数的时候返回 true,其他情况返回 false,因此最终集合就变成了两个分组。
distinct
distinct(sequence) 用于消除集合中的重复元素,返回没有重复的集合:
let result = distinct(list);p("result of distinct: #{result}");
输出
result of distinct: [-2, -1, 0, 1, 2, 3, 99, -1000, 7]
可以看到 0 这个重复元素被移除了,只保留一个。
reverse
reverse(sequence) 用于返回集合的逆序结果,仅可作用于数组、List 等顺序集合:
let result = reverse(list);p("result of reverse: #{result}");
将输出 list 的逆序集合:
list is: [-2, -1, 0, 1, 2, 3, 0, 99, -1000, 7]......result of reverse: [7, -1000, 99, 0, 3, 2, 1, 0, -1, -2]
zipmap
接下来我们将描述几个用于产生集合的函数,先从 zipmap(list1, list2) 开始,它是将两个集合按照 e1-> e2 的顺序映射成一个 map,我们看一个例子:
let m = zipmap(tuple("a", "b", "c"), seq.list(1,2,3,4));p("type of m: " + type(m));p("result of zipmap: #{m}");
我们给 zipmap 分别传入了两个集合,最终生成一个 map:
type of m: java.util.HashMapresult of zipmap: {a=1, b=2, c=3}
如果两个集合的长度不一样,将以最短的集合来截止:
let m = zipmap(tuple("a", "b", "c"), seq.list(1,2,3,4, 5, 6));p("result of zipmap: #{m}");
结果仍然是 {a=1, b=2, c=3} 。
concat
concat(seq1, seq2) 用于连接两个集合,生成一个新的集合,复杂度在 O(m+n),m 和 n 分别是两个集合的长度:
let c = concat(tuple("a", "b", "c"), seq.list(1, 2, 3, 4));p("result of concat: #{c}");
输出:
result of concat: [a, b, c, 1, 2, 3, 4]
自定义 sequence
假设有这么一个场景,你从数据库查询 User 表拿到了一个 java.sql.ResultSet ,你想传入 AviatorScript 处理,并且想使用上面提到的各种函数,那么你可以为 ResultSet 实现一个 seq 包装:
package com.googlecode.aviator.example.seq;import java.sql.ResultSet;import java.sql.SQLException;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import com.googlecode.aviator.runtime.type.Collector;import com.googlecode.aviator.runtime.type.Sequence;import com.googlecode.aviator.runtime.type.seq.ListCollector;import com.googlecode.aviator.utils.Reflector;/*** A sequence wraps java.seql.ResultSet** @author dennis(killme2008@gmail.com)**/public class ResultSetSequence implements Sequence<Map<String, Object>> {private final ResultSet resultSet;public ResultSetSequence(final ResultSet resultSet) {super();this.resultSet = resultSet;}@Overridepublic Iterator<Map<String, Object>> iterator() {return new Iterator<Map<String, Object>>() {@Overridepublic boolean hasNext() {try {return ResultSetSequence.this.resultSet.next();} catch (SQLException e) {throw Reflector.sneakyThrow(e);}}@Overridepublic Map<String, Object> next() {try {Map<String, Object> user = new HashMap<>();user.put("username", ResultSetSequence.this.resultSet.getString("username"));user.put("age", ResultSetSequence.this.resultSet.getString("age"));return user;} catch (SQLException e) {throw Reflector.sneakyThrow(e);}}@Overridepublic void remove() {throw new UnsupportedOperationException();}};}@Overridepublic Collector newCollector(final int size) {return new ListCollector(false);}@Overridepublic int hintSize() {// if we don't known the exact row number, return 0.return 0;}}
核心就是 iterator 方法,我们在 next 中将一行的结果取出来,封装成一个 map 对象返回。
接下来你就可以将这个 ResultSet 包装后扔到 AvaitorScript 中处理:
package com.googlecode.aviator.example.seq;import java.sql.ResultSet;import org.mockito.Mockito;import com.googlecode.aviator.AviatorEvaluator;import com.googlecode.aviator.Expression;public class DemoResultSetSeq {public static void main(final String[] args) throws Exception {// Mock a result set.ResultSet resultSet = Mockito.mock(ResultSet.class);Mockito.when(resultSet.next()).thenReturn(true).thenReturn(true).thenReturn(false);Mockito.when(resultSet.getString("username")).thenReturn("dennis").thenReturn("catty");Mockito.when(resultSet.getInt("age")).thenReturn(30).thenReturn(20);// Use it in aviatorExpression exp = AviatorEvaluator.getInstance().compileScript("examples/result_set_seq.av");exp.execute(exp.newEnv("results", new ResultSetSequence(resultSet)));}}
我们先用 mockito 模拟了一个 ResultSet ,它会返回两行:
username, age-------------dennis, 30catty, 20
然后将 resultSet 包装成 ResultSetSequence ,作为 results 变量传入脚本 examples/result_set_seq.av :
## examples/result_set_seq.avlet users = into(seq.list(), results);println("User names: "+ map(users, lambda(u) -> u.username end));println("users that age is greater than 30: "+ filter(users, lambda(u) -> u.age > 30 end));println("Total age: "+ reduce(users, lambda(n, u) -> n + u.age end, 0));
我们先用 into 函数,将结果从 ResultSet 提取出来,方便后续的操作,接下来我们用 map 获取用户的变量名列表,用 filter 过滤大于 30 岁的用户,用 reduce 求值总的年龄数字:
User names: [dennis, catty]users that age is greater than 30: []Total age: 50
