SEO网站建设入驻程流,建设银行网银网站特色,技术支持 石家庄网站建设,网站免费建站 网页不需要备案过滤序列,惰性序列我刚刚在GitHub上发布了LazySeq库-这是我最近进行的Java 8实验的结果。 我希望你会喜欢它。 即使您觉得它不是很有用#xff0c;它仍然是Java 8#xff08;以及一般而言#xff09;中的函数式编程的一课。 而且它可能是第一个针对Java 8的社区库#xff0… 过滤序列,惰性序列 我刚刚在GitHub上发布了LazySeq库-这是我最近进行的Java 8实验的结果。 我希望你会喜欢它。 即使您觉得它不是很有用它仍然是Java 8以及一般而言中的函数式编程的一课。 而且它可能是第一个针对Java 8的社区库 介绍 惰性序列是仅在实际需要其元素时才计算的数据结构。 对延迟序列的所有操作map()例如map()和filter()也都是延迟的从而将调用推迟到真正必要的时候。 总是从一开始就使用非常便宜的first / rest遍历惰性序列 分解 head()和tail() 。 惰性序列的一个重要属性是它们可以表示无限的数据流例如随时间推移的所有自然数或温度测量值。 惰性序列会记住已经计算的值因此如果您访问第N个元素则也会计算并缓存从1到N-1所有元素。 尽管如此 LazySeq 处于许多功能语言和算法的核心是不可变的且线程安全的。 基本原理 该库在很大程度上受scala.collection.immutable.Stream启发旨在提供不可变的线程安全的和易于使用的惰性序列实现可能是无限的。 有关某些用例请参见Scala和Clojure中的惰性序列 。 Stream类名称是用Java 8已被使用因此LazySeq被选择类似于lazy-seq Clojure中 。 说到Stream 一开始它看起来像是一个开箱即用的惰性序列实现。 但是引用Javadoc 流不是数据结构 和 一旦对流执行了某个操作该操作将被视为已消耗并且不再可用于其他操作。 换句话说 java.util.stream.Stream只是现有集合的一个瘦包装适合一次使用。 更类似于Iterator 而不是Stream于斯卡拉。 该库试图填补这一空白。 当然在Java 8之前可以实现惰性序列数据结构但是缺少lambda使得使用这种数据结构既乏味又过于冗长。 入门 在10分钟内构建并处理惰性序列。 所有自然数的无限序列 为了创建一个惰性序列您可以使用LazySeq.cons()工厂方法该方法接受第一个元素 head 和一个以后可能用于计算rest tail 的函数。 例如为了产生具有给定起始元素的自然数的惰性序列您只需说 private LazySeqInteger naturals(int from) {return LazySeq.cons(from, () - naturals(from 1));
} 这里真的没有递归。 如果存在的话调用naturals()会很快导致StackOverflowError因为它在没有停止条件的情况下调用自身。 但是() - naturals(from 1)表达式定义了一个函数该函数返回此数据结构将调用的LazySeqInteger 准确地说是Supplier 但仅在需要时才调用。 查看下面的代码您认为几次调用了naturals()函数第一行除外 final LazySeqInteger ints naturals(2);final LazySeqString strings ints.map(n - n 10).filter(n - n % 2 0).take(10).flatMap(n - Arrays.asList(0x10000 n, n)).distinct().map(Integer::toHexString); 的第一次调用naturals(2)返回来自起始懒惰序列2但休息 3 4 5 ...还没有被计算。 稍后我们对该序列进行map() 对其进行filter() take()前10个元素take() 删除重复项等等。所有这些操作都不评估序列并且尽可能地懒惰。 例如take(10)不会急切地求出前10个元素以返回它们。 而是返回新的惰性序列该序列记住它应该在第10个元素处截断原始序列。 同样适用于distinct() 。 它不会评估提取所有唯一值的整个序列否则上面的代码将Swift爆炸遍历无数个自然数。 而是返回仅包含第一个元素的新序列。 如果您要求第二个唯一元素它将懒惰地评估尾巴但只会尽可能多。 退房 toString()输出 System.out.println(strings);
//[1000c, ?] 问号 ? 说 “该集合中可能还有其他内容但我还不知道” 。 您了解1000c来自何处吗 仔细地看 从2开始的无限自然数流开始 向每个元素添加10 因此第一个元素变为12或十六进制的C filter()输出奇数 12为偶数因此保持不变 take()到目前为止序列中的前10元素 每个元素都被两个元素替换该元素加0x1000和元素本身 flatMap() 。 这不会产生成对的序列但是会产生两倍长的整数序列 我们确保只返回distinct()元素 最后我们将整数转换为十六进制字符串。 如您所见这些操作都不是真正需要评估整个流的。 唯一的头正在转变这就是我们最终看到的。 那么何时实际评估此数据结构 在绝对必要时例如在副作用遍历期间 strings.force();//or
strings.forEach(System.out::println);//or
final ListString list strings.toList();//or
for (String s : strings) {System.out.println(s);
} 仅上述所有语句将强制评估整个惰性序列。 如果我们的序列是无限的那不是很聪明但是 strings仅限于前10个元素因此它不会无限运行。 如果只想强制执行序列的一部分只需调用strings.take(5).force() 。 顺便说一句您是否注意到我们可以使用标准Java 5 for-each语法遍历LazySeq strings 这是因为LazySeq实现了List接口因此可以与Java Collections Framework生态系统很好地配合使用 import java.util.AbstractList;public abstract class LazySeqE extends AbstractListE 请记住一旦对惰性序列进行了评估计算它将对它们进行缓存 记住 以备后用。 这使得惰性序列非常适合表示无限或很长的数据流这些数据流计算成本很高。 迭代 建立无限的惰性序列通常可以归结为提供一个初始元素和一个功能该功能可以根据前一个元素生成下一个元素。 换句话说第二个元素是第一个元素的函数第三个元素是第二个元素的函数依此类推。 为这种情况提供了便利的LazySeq.iterate()函数。 现在 ints定义可以如下所示 final LazySeqInteger ints LazySeq.iterate(2, n - n 1); 我们从2开始每个后续元素表示为前一个元素 1。 更多示例斐波那契数列和Collatz猜想 没有斐波那契数字就不会留下关于懒惰数据结构的文章例如 private static LazySeqInteger lastTwoFib(int first, int second) {return LazySeq.cons(first,() - lastTwoFib(second, first second));
} 斐波那契数列也是无限的但我们可以通过多种方式自由变换它 System.out.println(fib.drop(5).take(10).toList()
);
//[5, 8, 13, 21, 34, 55, 89, 144, 233, 377]final int firstAbove1000 fib.filter(n - (n 1000)).head();fib.get(45); 看到无限的数字流是多么容易和自然吗 drop(5).take(10)跳过前5个元素并显示下一个10。在这一点上已经计算了前15个数字以后再也不会计算。 查找高于1000可能是1597 的第一个斐波那契数非常简单。 head()始终由filter()预先计算因此无需进一步评估。 最后但并非最不重要的一点是我们只需索取第45个斐波那契数 从0开始并获得 1134903170 。 如果您尝试访问该编号之前的任何斐波那契数它们将被预先计算并可以快速检索。 有限序列Collatz猜想 Collatz猜想也是一个很有趣的问题。 对于每个正整数n我们使用以下算法计算下一个整数 如果n为偶数则为n n/2 如果n为奇数则为3n 1 例如从10序列开始的外观如下10、5、16、8、4、2、1。该序列在达到1时结束。数学家认为从任何整数开始我们最终都将达到1但尚未证明。 让我们创建一个惰性序列该序列为给定的n生成Collatz级数但只根据需要生成。 如上所述这次我们的序列将是有限的 private LazySeqLong collatz(long from) {if (from 1) {final long next from % 2 0 ? from / 2 : from * 3 1;return LazySeq.cons(from, () - collatz(next));} else {return LazySeq.of(1L);}
} 该实现由定义直接驱动。 对于每个大于1数字返回该数字惰性计算的流的其余部分 () - collatz(next) 。 如您所见如果给定1 我们将使用特殊的of()工厂方法返回单元素惰性序列。 让我们用上述10测试它 final LazySeqLong collatz collatz(10);collatz.filter(n - (n 10)).head();
collatz.size(); filter()允许我们找到大于10的序列中的第一个数字。 请记住惰性序列将必须遍历内容自行评估但只能遍历找到第一个匹配元素的位置。 然后停止确保其计算量尽可能少。 但是为了计算元素总数 size()必须遍历整个序列。 当然这仅适用于有限的惰性序列在无限序列上调用size()最终效果会很差。 如果您对此序列稍作练习您将很快意识到不同数字的序列共享相同的后缀 总是以相同的数字序列结尾。 这请求进行一些缓存/结构共享。 有关详细信息请参见CollatzConjectureTest 。 现实生活 无限的数字序列很棒但在现实生活中却不太实用。 也许还有更多脚踏实地的例子 假设您有一个收藏您需要从该收藏中随机挑选一些物品。 代替集合我将使用一个返回随机拉丁字符的函数 private char randomChar() {return (char) (A (int) (Math.random() * (Z - A 1)));
} 但是有一个转折。 您需要N个N 26拉丁字符个数唯一值。 仅仅几次调用randomChar()并不能保证唯一性。 解决这个问题的方法很少使用LazySeq非常简单 LazySeqCharacter charStream LazySeq.Charactercontinually(this::randomChar);
LazySeqCharacter uniqueCharStream charStream.distinct(); 当需要时 continually()只需为每个元素调用给定的函数。 因此 charStream将是无限的随机字符流。 当然它们不能唯一。 但是uniqueCharStream保证其输出是唯一的。 它通过检查底层charStream下一个元素并拒绝已出现的项目来实现。 现在我们可以说uniqueCharStream.take(4)并确保不会出现重复项。 再次注意 continually(this::randomChar).distinct().take(4)实际上只调用randomChar()一次 只要您不消耗此序列它就会保持延迟并尽可能推迟评估。 另一个示例涉及从数据库加载数据的批次页面。 使用ResultSet或Iterator很麻烦但是将整个数据集加载到内存中通常不可行。 另一种选择是急于加载第一批数据然后提供加载下一批数据的功能。 仅当确实需要数据时才加载数据而我们不会遇到性能或可伸缩性问题。 首先让我们定义抽象API以从数据库中加载批量数据 public ListRecord loadPage(int offset, int max) {//load records from offset to offset max
} 我完全从技术中抽象出来但是您明白了。 假设我们现在定义了LazySeqRecord 它从第0行开始仅在需要时才加载下一页 public static final int PAGE_SIZE 5;private LazySeqRecord records(int from) {return LazySeq.concat(loadPage(from, PAGE_SIZE),() - records(from PAGE_SIZE));
} 通过调用records(0)创建新的LazySeqRecord实例时将加载5个元素的第一页。 这意味着已经计算出前5个序列元素。 如果您尝试访问第6位或更高版本序列将自动加载所有丢失的记录并对其进行缓存。 换句话说您永远不会两次计算相同的元素。 使用序列时更有用的工具是grouped()和sliding()方法。 首先将输入序列分成大小相等的组。 以这个为例还证明这些方法总是很懒惰 final LazySeqCharacter chars LazySeq.of(A, B, C, D, E, F, G);chars.grouped(3);
//[[A, B, C], ?]chars.grouped(3).force(); //force evaluation
//[[A, B, C], [D, E, F], [G]] 同样适用于sliding() chars.sliding(3);
//[[A, B, C], ?]chars.sliding(3).force(); //force evaluation
//[[A, B, C], [B, C, D], [C, D, E], [D, E, F], [E, F, G]] 这两种方法非常有用。 您可以通过滑动窗口查看数据例如计算移动平均值 或将其划分为等长的存储桶。 您可能会发现有用的最后一个有趣的实用程序方法是scan() 它当然是延迟地scan()迭代输入流并通过在输入的前一个和当前元素上应用函数来构造输出的每个元素。 代码段值一千个字 LazySeqInteger list LazySeq.numbers(1).scan(0, (a, x) - a x);list.take(10).force(); //[0, 1, 3, 6, 10, 15, 21, 28, 36, 45] LazySeq.numbers(1)是自然数1、2、3…的序列。 scan()创建一个新的序列从 0并为输入的每个元素自然数将其添加到自身的最后一个元素。 因此我们得到[ 0 01 012 0123 01234 012345 …]。 如果您想要一系列增长的字符串只需替换几种类型 LazySeq.continually(*).scan(, (s, c) - s c).map(s - | s \\).take(10).forEach(System.out::println); 并享受这个美丽的三角形 |\
|*\
|**\
|***\
|****\
|*****\
|******\
|*******\
|********\
|*********\ 或者相同的输出 LazySeq.iterate(, s - s *).map(s - | s \\).take(10).forEach(System.out::println);Java Collections框架的互操作性 LazySeq实现java.util.List接口因此可以在许多地方使用。 此外它还对集合即流和集合实现了Java 8增强 lazySeq.stream().map(n - n 1).flatMap(n - asList(0, n - 1).stream()).filter(n - n ! 0).substream(4, 18).limit(10).sorted().distinct().collect(Collectors.toList()); 但是Java 8中的流是根据LazySeq 惰性评估的基础而LazySeq 。 上面的示例将所有中间步骤推迟到调用collect()为止。 使用LazySeq您可以安全地跳过.stream()并直接处理序列 lazySeq.map(n - n 1).flatMap(n - asList(0, n - 1)).filter(n - n ! 0).slice(4, 18).limit(10).sorted().distinct(); 此外 LazySeq提供了特殊用途的收集器请参阅 LazySeq.toLazySeq() 即使与collect()一起使用也可以避免评估这通常会强制进行完整的收集计算。 实施细节 每个懒惰序列都是围绕急切计算的头和懒洋洋地表示为函数的尾部的思想构建的。 这与经典的单链列表递归定义非常相似 class ListT {private final T head;private final ListT tail;//...
} 但是在延迟序列的情况下 尾部是函数而不是值。 该函数的调用会尽可能推迟 class ConsE extends LazySeqE {private final E head;private LazySeqE tailOrNull;private final SupplierLazySeqE tailFun;Overridepublic LazySeqE tail() {if (tailOrNull null) {tailOrNull tailFun.get();}return tailOrNull;} 有关完整的实现请参见在创建时知道tail时使用的Cons.java和FixedCons.java 例如LazySeq.of(1, 2)与LazySeq.cons(1, () - someTailFun()相对LazySeq.of(1, 2) 例如LazySeq.of(1, 2) 。 陷阱和常见危险 下面介绍常见问题和误解。 评估太多 使用无限序列的最大危险之一就是试图对其进行完全评估这显然会导致无限计算。 无限序列背后的思想不是全面评估它而是在不引入人为限制和意外复杂性的情况下尽可能多地获取它请参见数据库加载示例。 但是评估整个序列太容易遗漏了。 例如调用LazySeq.size() 必须评估整个序列并且将无限运行最终填满堆栈或堆实现细节。 还有其他方法需要完全遍历才能正常运行。 例如allMatch()确保所有元素都匹配给定谓词。 有些方法甚至更危险因为它们是否完成取决于序列中的数据。 例如如果head匹配谓词–或从不匹配则anyMatch()可能立即返回。 有时我们可以使用更具确定性的方法来轻松避免昂贵的操作。 例如 seq.size() 10 //BAD 如果seq是无限的则可能无法正常工作或非常慢。 但是我们可以通过更多可预测的方式实现相同的目标 seq.drop(10).isEmpty() 请记住惰性序列是不可变的因此我们并没有真正改变seq drop(n)通常是O(n)而isEmpty()是O(1) 。 如有疑问请查阅源代码或JavaDoc以确保您的操作不会过于急切地评估您的序列。 当使用LazySeq 也要非常小心因为需要使用java.util.Collection或java.util.List 。 持有不必要的头参考 惰性序列应定义为记住已计算的元素。 您必须意识到这一点否则您的序列尤其是无限序列将Swift填满可用内存。 但是由于LazySeq只是一个奇特的链接列表因此如果您不再保留对head的引用而仅保留中间的某个元素则可以进行垃圾回收。 例如 //LazySeqChar first seq.take(10);
seq seq.drop(10); 前十个元素被删除我们假设没有任何东西引用过seq以前的庚。 这使前十个元素有资格进行垃圾回收。 但是如果我们取消注释第一行并保持参照老head在first JVM不会释放任何内存。 让我们对此进行透视。 下面的代码最终将抛出OutOfMemoryError因为infinite引用将保持序列的开始因此到目前为止创建的所有元素都将 LazySeqBig infinite LazySeq.Bigcontinually(Big::new);
for (Big arr : infinite) {//
} 但是通过内联对continually()调用或将其提取到方法中此代码可以完美运行很好仍然可以永远运行但几乎不使用内存 private LazySeqBig getContinually() {return LazySeq.Bigcontinually(Big::new);
}for (Big arr : getContinually()) {//
} 有什么不同 每个循环都在下面使用迭代器。 LazySeqIterator下面的LazySeqIterator不会保留对旧head()的引用因此如果没有其他引用该head的对象则可以进行垃圾回收当使用for-each时将看到真实的javac输出 for (IteratorBig cur getContinually().iterator(); cur.hasNext(); ) {final Big arr cur.next();//...
} TL; DR 您的序列在遍历时会增加。 如果在另一端成长时保持一端则最终会炸毁。 就像您在Hibernate中的一级缓存一样如果您在一个事务中加载过多。 仅根据需要使用。 转换为纯Java集合 转换很简单但是很危险。 这是以上几点的结果。 您可以通过调用toList()将惰性序列转换为java.util.List LazySeqInteger even LazySeq.numbers(0, 2);
even.take(5).toList(); //[0, 2, 4, 6, 8] 或使用具有更丰富API的Java 8中的Collector even.stream().limit(5).collect(Collectors.toSet()) //[4, 6, 0, 2, 8] 但是请记住Java集合在定义上是有限的因此请避免将惰性序列明确转换为集合。 注意 LazySeq已经是List 因此是Iterable和Collection 。 它还具有高效的LazySeq.iterator() 。 如果可以只需直接传递LazySeq实例即可。 性能时间和空间复杂度 每个序列空除外head()的head()总是很容易计算因此对其进行访问是快速O(1) 。 计算tail()可能占用从O(1) 如果已经计算过到无限时间的所有内容。 以这个有效的流为例 import static com.blogspot.nurkiewicz.lazyseq.LazySeq.cons;
import static com.blogspot.nurkiewicz.lazyseq.LazySeq.continually;LazySeqInteger oneAndZeros cons(1,() - continually(0)
).
filter(x - (x 0)); 它代表1后跟无穷多个0 s。 通过过滤所有正数 x 0 我们得到一个具有相同头部的序列但是对尾部的过滤被延迟延迟。 但是如果现在我们不小心调用oneAndZeros.tail() LazySeq将继续计算该无限序列的越来越多但是由于初始1之后没有正数该操作将永远运行最终抛出StackOverflowError或OutOfMemoryError 这是一个实施细节。 但是如果您达到此状态则可能是编程错误或库的滥用。 通常tail()将接近O(1) 。 另一方面如果您已经“堆叠”了很多操作则调用tail()会Swift一次又一次触发它们因此tail()运行时间在很大程度上取决于您的数据结构。 LazySeq上的大多数操作都是O(1)因为它们是惰性的。 一些操作例如get(n)或drop(n)都是O(n) n表示参数而不是序列长度。 一般而言运行时间将类似于正常的链表。 因为LazySeq记住单个链接列表中所有已经计算出的值所以内存消耗始终为O(n) 其中n n是已经计算出的元素数。 故障排除 错误invalid target release: 1.8在Maven构建期间invalid target release: 1.8 如果在maven生成期间看到此错误消息 [INFO] BUILD FAILURE
...
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project lazyseq:
Fatal error compiling: invalid target release: 1.8 - [Help 1] 这意味着您不使用Java 8进行编译。 下载具有lambda支持的JDK 8并让maven使用它 $ export JAVA_HOME/path/to/jdk8 我收到StackOverflowError或程序无限期挂起 使用LazySeq 有时会出现StackOverflowError或OutOfMemoryError java.lang.StackOverflowErrorat sun.misc.Unsafe.allocateInstance(Native Method)at java.lang.invoke.DirectMethodHandle.allocateInstance(DirectMethodHandle.java:426)at com.blogspot.nurkiewicz.lazyseq.LazySeq.iterate(LazySeq.java:118)at com.blogspot.nurkiewicz.lazyseq.LazySeq.lambda$0(LazySeq.java:118)at com.blogspot.nurkiewicz.lazyseq.LazySeq$$Lambda$2.get(Unknown Source)at com.blogspot.nurkiewicz.lazyseq.Cons.tail(Cons.java:32)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325) 当使用可能无限的数据结构时必须小心。 避免调用必须 size() allMatch() minBy() forEach() reduce() …或可以 filter() distinct() ...遍历整个序列以便给出正确值的操作结果。 有关更多示例和避免方法请参见陷阱 。 到期 质量 该项目是作为练习开始的尚未经过战斗验证。 但是健康的300多个单元测试套件 测试代码/生产代码比率为31可以保护质量和功能正确性。 我还通过LazySeq尾部函数并验证它们被尽可能少地调用来确保LazySeq尽可能地懒。 贡献和错误报告 如果发现错误或缺少功能请随时打开新票证或开始拉取请求 。 我也很想看看LazySeq在野外的更多有趣用法。 可能的改进 就像在FixedCons知道尾巴的情况下使用FixedCons一样请考虑将IterableCons包装在一个节点中而不是建立FixedCons层次结构的Iterable 。 这可以用于所有concat方法。 并行处理支持实现分离器 执照 该项目是在Apache许可的 2.0版下发布的 。 参考来自Java和社区博客的JCG合作伙伴 Tomasz Nurkiewicz 提供的Java 8的惰性序列实现 。 翻译自: https://www.javacodegeeks.com/2013/05/lazy-sequences-implementation-for-java-8.html过滤序列,惰性序列