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 里的元素是否满足特定的条件。
seq.every
seq.every(seq, fn)
, 用于检查 seq 里的元素是否都满足 fn(x) == true
,如果都满足,返回 true
,否则是 false
:
## every
println("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: true
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 里没有一个元素满足特定谓词检查:
## seq.not_any
println("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: true
There are not any elements in range is less than zero: false
seq.some
seq.some(seq, fn)
返回 seq 中第一个使得 fn(x) == true
的元素,如果没有找到,返回 nil
:
## seq.some
println("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: 1
Find a element in range is greater than zero: 1
Find 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.HashMap
result 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;
}
@Override
public Iterator<Map<String, Object>> iterator() {
return new Iterator<Map<String, Object>>() {
@Override
public boolean hasNext() {
try {
return ResultSetSequence.this.resultSet.next();
} catch (SQLException e) {
throw Reflector.sneakyThrow(e);
}
}
@Override
public 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);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public Collector newCollector(final int size) {
return new ListCollector(false);
}
@Override
public 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 aviator
Expression exp = AviatorEvaluator.getInstance().compileScript("examples/result_set_seq.av");
exp.execute(exp.newEnv("results", new ResultSetSequence(resultSet)));
}
}
我们先用 mockito 模拟了一个 ResultSet
,它会返回两行:
username, age
-------------
dennis, 30
catty, 20
然后将 resultSet 包装成 ResultSetSequence
,作为 results
变量传入脚本 examples/result_set_seq.av
:
## examples/result_set_seq.av
let 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