Java使用Aviator表达式 学习记录(十九)

3,338 阅读6分钟

这是我参与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.avsequence2.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 里的元素是否满足特定的条件。