Sequence 抽象

Sequence 是 AviatorScript 对“集合”的抽象。这个“集合”囊括了数组、Set/Map/List 等等,只要它是是可遍历的集合即可。Sequence 的概念来自 clojure 的,当然,相比 clojure 还是弱了很多,比如 chunk/lazy 都没有支持。

事实上 Sequence 只是继承了 Iterable 接口:

  1. /**
  2. * Sequence mark interface.
  3. *
  4. * @author dennis(killme2008@gmail.com)
  5. *
  6. * @param <T>
  7. */
  8. public interface Sequence<T> extends Iterable<T> {
  9. Collector newCollector(int size);
  10. int hintSize();
  11. }

额外增加了两个方法:

  • hintSize 用于返回集合的元素数量,仅仅是一个 hint,不保证精确。
  • newCollector 返回 collector,用于收集 sequence 里的元素经过某种“变化”后的结果。

Collector 的接口也非常简单:

  1. /**
  2. * Collector to collect elements.
  3. *
  4. * @author dennis(killme2008@gmail.com)
  5. *
  6. * @param <T>
  7. */
  8. public interface Collector {
  9. void add(Object e);
  10. Object getRawContainer();
  11. }
  • add 方法用于添加元素
  • getRawContainer 返回底层的实际容器。

为了更加有体感,可以看一个内部的 Sequence 实现:ArraySequence,用于将数组转成 Sequence。

你在 AviatorScript 中见到的 Tuple、数组、Range、List、Map 和 Set 都实现了对应的 Sequence,这也是为什么他们可以用同一套 API 来操作的原因。

下面我们将详细介绍这些 API。先从遍历开始。所有例子参见 sequence.avsequence2.av

遍历 sequence

遍历 Sequence 的标准方式是 for 循环,我们在上一节已经见到很多例子了:

  1. ## sequence.av
  2. let a = seq.array(int, 1, 2, 3, 4);
  3. let r = range(-5, 5);
  4. let s = seq.set(99, 100, 101);
  5. let m = seq.map("a", 1, "b", 2, "c", 3);
  6. let n = seq.list("car", "bus", "bike");
  7. ## iterate elements
  8. let sum = 0 ;
  9. for e in r {
  10. sum = sum + e;
  11. }
  12. println("sum of range r: " + sum);
  13. for e in m {
  14. println(e.key + "=" + e.value);
  15. }

对于 map 来说,遍历的是 Entry 。这一块在前两节介绍数组和集合的时候已经详细介绍了,不再重复。

操作 sequence 的高阶函数

对于 Sequence 的抽象, AviatorScript 也提供了一套高阶函数来方便地对集合做转换、过滤、查询以及聚合,我们将一一介绍。这些函数的规则都是将 sequence 作为第一个参数。

count

count(seq) 函数用于获取 seq 里的集合元素,它将尽量在 O(1) 的时间复杂度内返回结果,最差情况下退化成 O(n):

  1. ## count
  2. println("count of array: " + count(a));
  3. println("count of range: " + count(r));
  4. println("count of set: " + count(s));
  5. println("count of map: " + count(m));
  6. println("count of list: " + count(n));

is_empty

is_empty 用于返回集合是否为空, is_empty(nil) 返回 true

  1. println("is_empty(array): " + is_empty(a));
  2. println("is_empty(seq.list()): " + is_empty(seq.list()));
  3. println("is_empty(nil): " + is_empty(nil));

输出:

  1. is_empty(array): false
  2. is_empty(seq.list()): true
  3. is_empty(nil): true

include

include(seq, x) 用于判断元素 x 是否在 seq 内,对于 Set 是 O(1) 的时间复杂度,其他是 O(n):

  1. ## include
  2. println("array has 3: " + include(a, 3));
  3. println("map has an entry ('b', 2): " + include(m, seq.entry("b", 2)));
  4. println("range has 10: " + include(r, 10));

同样,对于 map 来说,需要比较的 Map.Entry 对象,你可以通过 seq.entry(key, value) 来构造 entry 对象:

  1. array has 3:true
  2. map has an entry ('b', 2): true
  3. range has 10:false

map

map(seq, fn) 用于将 seq 转换另一个 seq,它将第二个参数 fn 的函数作用在集合里的每个元素上,结果收集到另一个集合(这里就是上文提到的 collector 发生作用的地方)并返回:

  1. ## map
  2. let new_range = map(r, lambda(x) -> x + 1 end);
  3. print("new range is: ");
  4. for x in new_range {
  5. print(x);
  6. print(", ");
  7. }
  8. println()
  9. let new_map = map(m, lambda(e) -> e.value = e.value + 100; return e; end);
  10. println("new map is: " + new_map + ", and type is: "+ type(new_map));

new_rangerange 的每个元素递增 1 之后组成的集合, new_map 是给 m 里的每个 entry 的 value 加上 100 后组成的集合。

  1. new range is: -4, -3, -2, -1, 0, 1, 2, 3, 4, 5,
  2. 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 集合:

  1. ## into
  2. let new_map = into(seq.map(), new_map);
  3. println("new map is: " + new_map + ", and type is: "+ type(new_map));

我们将 new_map 这个链表里的每个 entry 对象,通过 seq.add(to_seq, entry) 函数逐一添加到了 seq.map() 返回的 HashMap 对象:

  1. new map is: {a=101, b=102, c=103}, and type is: java.util.HashMap

也可以用他来做集合类型之间的转换,比如数组转成 Set:

  1. let new_set = into(seq.set(), a);
  2. println("new set is: " + new_set + ", and type is: "+ type(new_set));

输出:

  1. 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 调用等价于下面的代码:

  1. fn reduce(seq, fn, init) {
  2. let result = init;
  3. for element in seq {
  4. result = fn(result, element);
  5. }
  6. return result;
  7. }

有了 reduce ,我们可以方便地对数组求和:

  1. let sum_of_a = reduce(a, +, 0);
  2. let sum_of_r = reduce(r, +, 0);
  3. println("some of array is: " + sum_of_a);
  4. println("some of range is: " + sum_of_r);

+ 加法运算符本质上也是一个函数。

可以统计 list 里总的字符串长度:

  1. let len = reduce(n, lambda(len, x) -> len + count(x) end, 0);
  2. println("total string length in list is: " + len);

事实上你可以将 map 函数也看成一个 reduce 调用:

  1. fn mymap(seq, fn) {
  2. reduce(seq,
  3. lambda(c, e) ->
  4. seq.add(c, fn(e))
  5. end,
  6. seq.list())
  7. }
  8. println("test mymap: " + mymap(a, lambda(x) -> x * 2 end));

我们使用 reduce 定义了自己的 map 函数—— mymap ,初始值是 seq.list() 返回的 List,每次迭代我们将 fn(element)的结果添加到了最终的结果 List,并最后返回:

  1. test mymap: [2, 4, 6, 8]

事实上你可以用 reduce 来定义 intofilter 等等函数, 有兴趣可以自行练习。

sort

sort(seq) 仅用于排序数组或者 List,其他 seq 类型无效,其他集合类型需要通过 into 等函数转换成 List 才可以使用:

  1. ## sort
  2. println("sort(list) is: " + sort(n));
  3. println("sort(set) is: " + sort(into(seq.list(), s)));

sort 最终调用的是 Collections.sort 或者 Arrays.sort 排序。

从 5.2 开始, sort 接受一个 comparator 参数,可以传入自定义的排序比较器,例如我们想倒序排列下 List:

  1. let c = comparator(lambda(x, y) -> x > y end);
  2. println("sort(list, c) is: " + sort(n, c));

comparator 函数接受一个比较的谓词函数,并转成 java.util.Comparator 对象,然后传入给 sort 函数执行,最终将 n 倒序排列输出:

  1. sort(list) is: [bike, bus, car]
  2. sort(set) is: [99, 100, 101]
  3. sort(list, c) is: [car, bus, bike]

filter

filter(seq, fn) 用于过滤一个 seq,它将 fn 函数作用在每个元素上,结果返回 true 的收集到新 seq,否则就丢掉:

  1. ## filter
  2. let es = filter(r, lambda(x) -> x %2 == 0 end);
  3. println("filter even number in range:" + es);
  4. let bs = filter(n, lambda(x) -> string.startsWith(x, "b") end);
  5. println("bs is: " + bs);

这段代码将 range 里的偶数过滤出来,并且将 n 这个 list 里面以字符串 b 开头的过滤出来:

  1. filter even number in range:[-4, -2, 0, 2, 4]
  2. bs is: [bus, bike]

接下来的三个函数 every/not_any/some 都是用于判断或者查找 seq 里的元素是否满足特定的条件。

seq.every

seq.every(seq, fn) , 用于检查 seq 里的元素是否都满足 fn(x) == true ,如果都满足,返回 true ,否则是 false

  1. ## every
  2. println("every element in array is greater than zero: "
  3. + seq.every(a, lambda(x) -> x > 0 end));
  4. println("every element in range is greater than zero: "
  5. + seq.every(r, lambda(x) -> x > 0 end));
  1. every element in array is greater than zero: true
  2. every 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 里没有一个元素满足特定谓词检查:

  1. ## seq.not_any
  2. println("There are not any elements in array is less than zero: "
  3. + seq.not_any(a, lambda(x) -> x < 0 end));
  4. println("There are not any in range is less than zero: "
  5. + seq.not_any(r, lambda(x) -> x < 0 end));
  1. There are not any elements in array is less than zero: true
  2. There are not any elements in range is less than zero: false

seq.some

seq.some(seq, fn) 返回 seq 中第一个使得 fn(x) == true 的元素,如果没有找到,返回 nil

  1. ## seq.some
  2. println("Find a element in array is greater than zero: "
  3. + seq.some(a, lambda(x) -> x > 0 end));
  4. println("Find a element in range is greater than zero: "
  5. + seq.some(r, lambda(x) -> x > 0 end));
  6. println("Find a element in list is starting with 'c': "
  7. + seq.some(n, lambda(x) -> string.startsWith(x, "c") end));
  1. Find a element in array is greater than zero: 1
  2. Find a element in range is greater than zero: 1
  3. Find a element in list is starting with 'c': car

take_while

take_while(sequence, pred) 用于从集合 sequence 里挑选出 pred(元素) 返回 true 的元素并返回新的集合:

  1. fn is_neg(x) {
  2. x < 0
  3. }
  4. let list = seq.list(-2, -1, 0, 1, 2, 3, 0, 99, -1000, 7);
  5. let result = take_while(list, is_neg);
  6. p("result of take_while: #{result}");

通过 take_while 我们从 list 里挑选出所有的负数并打印:

  1. result of take_while: [-2, -1, -1000]

反过来,我们也可以“丢弃”所有的负数,这就要用到 drop_while

drop_while

  1. let result = drop_while(list, is_neg);
  2. p("result of drop_while: #{result}");

list 里将所有的负数 drop 出去,剩下的都应该是非负数:

  1. 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], ...}

  1. let result = group_by(list, is_neg);
  2. p("result of group_by: #{result}");

执行上面代码将输出:

  1. result of group_by: {false=[0, 1, 2, 3, 0, 99, 7], true=[-2, -1, -1000]}

is_neg 当遇到负数的时候返回 true,其他情况返回 false,因此最终集合就变成了两个分组。

distinct

distinct(sequence) 用于消除集合中的重复元素,返回没有重复的集合:

  1. let result = distinct(list);
  2. p("result of distinct: #{result}");

输出

  1. result of distinct: [-2, -1, 0, 1, 2, 3, 99, -1000, 7]

可以看到 0 这个重复元素被移除了,只保留一个。

reverse

reverse(sequence) 用于返回集合的逆序结果,仅可作用于数组、List 等顺序集合:

  1. let result = reverse(list);
  2. p("result of reverse: #{result}");

将输出 list 的逆序集合:

  1. list is: [-2, -1, 0, 1, 2, 3, 0, 99, -1000, 7]
  2. ......
  3. result of reverse: [7, -1000, 99, 0, 3, 2, 1, 0, -1, -2]

zipmap

接下来我们将描述几个用于产生集合的函数,先从 zipmap(list1, list2) 开始,它是将两个集合按照 e1-> e2 的顺序映射成一个 map,我们看一个例子:

  1. let m = zipmap(tuple("a", "b", "c"), seq.list(1,2,3,4));
  2. p("type of m: " + type(m));
  3. p("result of zipmap: #{m}");

我们给 zipmap 分别传入了两个集合,最终生成一个 map:

  1. type of m: java.util.HashMap
  2. result of zipmap: {a=1, b=2, c=3}

如果两个集合的长度不一样,将以最短的集合来截止:

  1. let m = zipmap(tuple("a", "b", "c"), seq.list(1,2,3,4, 5, 6));
  2. p("result of zipmap: #{m}");

结果仍然是 {a=1, b=2, c=3}

concat

concat(seq1, seq2) 用于连接两个集合,生成一个新的集合,复杂度在 O(m+n),m 和 n 分别是两个集合的长度:

  1. let c = concat(tuple("a", "b", "c"), seq.list(1, 2, 3, 4));
  2. p("result of concat: #{c}");

输出:

  1. result of concat: [a, b, c, 1, 2, 3, 4]

自定义 sequence

假设有这么一个场景,你从数据库查询 User 表拿到了一个 java.sql.ResultSet ,你想传入 AviatorScript 处理,并且想使用上面提到的各种函数,那么你可以为 ResultSet 实现一个 seq 包装:

  1. package com.googlecode.aviator.example.seq;
  2. import java.sql.ResultSet;
  3. import java.sql.SQLException;
  4. import java.util.HashMap;
  5. import java.util.Iterator;
  6. import java.util.Map;
  7. import com.googlecode.aviator.runtime.type.Collector;
  8. import com.googlecode.aviator.runtime.type.Sequence;
  9. import com.googlecode.aviator.runtime.type.seq.ListCollector;
  10. import com.googlecode.aviator.utils.Reflector;
  11. /**
  12. * A sequence wraps java.seql.ResultSet
  13. *
  14. * @author dennis(killme2008@gmail.com)
  15. *
  16. */
  17. public class ResultSetSequence implements Sequence<Map<String, Object>> {
  18. private final ResultSet resultSet;
  19. public ResultSetSequence(final ResultSet resultSet) {
  20. super();
  21. this.resultSet = resultSet;
  22. }
  23. @Override
  24. public Iterator<Map<String, Object>> iterator() {
  25. return new Iterator<Map<String, Object>>() {
  26. @Override
  27. public boolean hasNext() {
  28. try {
  29. return ResultSetSequence.this.resultSet.next();
  30. } catch (SQLException e) {
  31. throw Reflector.sneakyThrow(e);
  32. }
  33. }
  34. @Override
  35. public Map<String, Object> next() {
  36. try {
  37. Map<String, Object> user = new HashMap<>();
  38. user.put("username", ResultSetSequence.this.resultSet.getString("username"));
  39. user.put("age", ResultSetSequence.this.resultSet.getString("age"));
  40. return user;
  41. } catch (SQLException e) {
  42. throw Reflector.sneakyThrow(e);
  43. }
  44. }
  45. @Override
  46. public void remove() {
  47. throw new UnsupportedOperationException();
  48. }
  49. };
  50. }
  51. @Override
  52. public Collector newCollector(final int size) {
  53. return new ListCollector(false);
  54. }
  55. @Override
  56. public int hintSize() {
  57. // if we don't known the exact row number, return 0.
  58. return 0;
  59. }
  60. }

核心就是 iterator 方法,我们在 next 中将一行的结果取出来,封装成一个 map 对象返回。

接下来你就可以将这个 ResultSet 包装后扔到 AvaitorScript 中处理:

  1. package com.googlecode.aviator.example.seq;
  2. import java.sql.ResultSet;
  3. import org.mockito.Mockito;
  4. import com.googlecode.aviator.AviatorEvaluator;
  5. import com.googlecode.aviator.Expression;
  6. public class DemoResultSetSeq {
  7. public static void main(final String[] args) throws Exception {
  8. // Mock a result set.
  9. ResultSet resultSet = Mockito.mock(ResultSet.class);
  10. Mockito.when(resultSet.next()).thenReturn(true).thenReturn(true).thenReturn(false);
  11. Mockito.when(resultSet.getString("username")).thenReturn("dennis").thenReturn("catty");
  12. Mockito.when(resultSet.getInt("age")).thenReturn(30).thenReturn(20);
  13. // Use it in aviator
  14. Expression exp = AviatorEvaluator.getInstance().compileScript("examples/result_set_seq.av");
  15. exp.execute(exp.newEnv("results", new ResultSetSequence(resultSet)));
  16. }
  17. }

我们先用 mockito 模拟了一个 ResultSet ,它会返回两行:

  1. username, age
  2. -------------
  3. dennis, 30
  4. catty, 20

然后将 resultSet 包装成 ResultSetSequence ,作为 results 变量传入脚本 examples/result_set_seq.av

  1. ## examples/result_set_seq.av
  2. let users = into(seq.list(), results);
  3. println("User names: "
  4. + map(users, lambda(u) -> u.username end));
  5. println("users that age is greater than 30: "
  6. + filter(users, lambda(u) -> u.age > 30 end));
  7. println("Total age: "
  8. + reduce(users, lambda(n, u) -> n + u.age end, 0));

我们先用 into 函数,将结果从 ResultSet 提取出来,方便后续的操作,接下来我们用 map 获取用户的变量名列表,用 filter 过滤大于 30 岁的用户,用 reduce 求值总的年龄数字:

  1. User names: [dennis, catty]
  2. users that age is greater than 30: []
  3. Total age: 50