过滤序列,惰性序列_Java 8的惰性序列实现
過(guò)濾序列,惰性序列
我剛剛在GitHub上發(fā)布了LazySeq庫(kù)-這是我最近進(jìn)行的Java 8實(shí)驗(yàn)的結(jié)果。 我希望你會(huì)喜歡它。 即使您覺(jué)得它不是很有用,它仍然是Java 8(以及一般而言)中的函數(shù)式編程的一課。 而且它可能是第一個(gè)針對(duì)Java 8的社區(qū)庫(kù)!
介紹
惰性序列是僅在實(shí)際需要其元素時(shí)才計(jì)算的數(shù)據(jù)結(jié)構(gòu)。 對(duì)延遲序列的所有操作map()例如map()和filter()也都是延遲的,從而將調(diào)用推遲到真正必要的時(shí)候。 總是從一開(kāi)始就使用非常便宜的first / rest遍歷惰性序列
分解( head()和tail() )。 惰性序列的一個(gè)重要屬性是它們可以表示無(wú)限的數(shù)據(jù)流,例如隨時(shí)間推移的所有自然數(shù)或溫度測(cè)量值。
惰性序列會(huì)記住已經(jīng)計(jì)算的值,因此,如果您訪問(wèn)第N個(gè)元素,則也會(huì)計(jì)算并緩存從1到N-1所有元素。 盡管如此, LazySeq (處于許多功能語(yǔ)言和算法的核心)是不可變的且線程安全的。
基本原理
該庫(kù)在很大程度上受scala.collection.immutable.Stream啟發(fā),旨在提供不可變的,線程安全的和易于使用的惰性序列實(shí)現(xiàn)(可能是無(wú)限的)。 有關(guān)某些用例,請(qǐng)參見(jiàn)Scala和Clojure中的惰性序列 。
Stream類名稱是用Java 8已被使用,因此LazySeq被選擇,類似于lazy-seq Clojure中 。 說(shuō)到Stream ,一開(kāi)始它看起來(lái)像是一個(gè)開(kāi)箱即用的惰性序列實(shí)現(xiàn)。 但是,引用Javadoc:
流不是數(shù)據(jù)結(jié)構(gòu)
和:
一旦對(duì)流執(zhí)行了某個(gè)操作,該操作將被視為已消耗并且不再可用于其他操作。
換句話說(shuō), java.util.stream.Stream只是現(xiàn)有集合的一個(gè)瘦包裝,適合一次使用。 更類似于Iterator ,而不是Stream于斯卡拉。 該庫(kù)試圖填補(bǔ)這一空白。
當(dāng)然,在Java 8之前可以實(shí)現(xiàn)惰性序列數(shù)據(jù)結(jié)構(gòu),但是缺少lambda使得使用這種數(shù)據(jù)結(jié)構(gòu)既乏味又過(guò)于冗長(zhǎng)。
入門
在10分鐘內(nèi)構(gòu)建并處理惰性序列。
所有自然數(shù)的無(wú)限序列
為了創(chuàng)建一個(gè)惰性序列,您可以使用LazySeq.cons()工廠方法,該方法接受第一個(gè)元素( head )和一個(gè)以后可能用于計(jì)算rest( tail )的函數(shù)。 例如,為了產(chǎn)生具有給定起始元素的自然數(shù)的惰性序列,您只需說(shuō):
private LazySeq<Integer> naturals(int from) {return LazySeq.cons(from, () -> naturals(from + 1)); }這里真的沒(méi)有遞歸。 如果存在的話,調(diào)用naturals()會(huì)很快導(dǎo)致StackOverflowError因?yàn)樗跊](méi)有停止條件的情況下調(diào)用自身。 但是() -> naturals(from + 1)表達(dá)式定義了一個(gè)函數(shù),該函數(shù)返回此數(shù)據(jù)結(jié)構(gòu)將調(diào)用的LazySeq<Integer> (準(zhǔn)確地說(shuō)是Supplier ),但僅在需要時(shí)才調(diào)用。 查看下面的代碼,您認(rèn)為幾次調(diào)用了naturals()函數(shù)(第一行除外)?
final LazySeq<Integer> ints = naturals(2);final LazySeq<String> strings = ints.map(n -> n + 10).filter(n -> n % 2 == 0).take(10).flatMap(n -> Arrays.asList(0x10000 + n, n)).distinct().map(Integer::toHexString);的第一次調(diào)用naturals(2)返回來(lái)自起始懶惰序列2但休息( 3 , 4 , 5 ,...)還沒(méi)有被計(jì)算。 稍后,我們對(duì)該序列進(jìn)行map() ,對(duì)其進(jìn)行filter() , take()前10個(gè)元素take() ,刪除重復(fù)項(xiàng),等等。所有這些操作都不評(píng)估序列,并且盡可能地懶惰。 例如take(10)不會(huì)急切地求出前10個(gè)元素以返回它們。 而是返回新的惰性序列,該序列記住它應(yīng)該在第10個(gè)元素處截?cái)嘣夹蛄小?
同樣適用于distinct() 。 它不會(huì)評(píng)估提取所有唯一值的整個(gè)序列(否則上面的代碼將Swift爆炸,遍歷無(wú)數(shù)個(gè)自然數(shù))。 而是返回僅包含第一個(gè)元素的新序列。 如果您要求第二個(gè)唯一元素,它將懶惰地評(píng)估尾巴,但只會(huì)盡可能多。 退房
toString()輸出:
問(wèn)號(hào)( ? )說(shuō): “該集合中可能還有其他內(nèi)容,但我還不知道” 。 您了解1000c來(lái)自何處嗎? 仔細(xì)地看:
如您所見(jiàn),這些操作都不是真正需要評(píng)估整個(gè)流的。 唯一的頭正在轉(zhuǎn)變,這就是我們最終看到的。 那么,何時(shí)實(shí)際評(píng)估此數(shù)據(jù)結(jié)構(gòu)? 在絕對(duì)必要時(shí)(例如在副作用遍歷期間):
strings.force();//or strings.forEach(System.out::println);//or final List<String> list = strings.toList();//or for (String s : strings) {System.out.println(s); } 僅上述所有語(yǔ)句將強(qiáng)制評(píng)估整個(gè)惰性序列。 如果我們的序列是無(wú)限的,那不是很聰明,但是
strings僅限于前10個(gè)元素,因此它不會(huì)無(wú)限運(yùn)行。 如果只想強(qiáng)制執(zhí)行序列的一部分,只需調(diào)用strings.take(5).force() 。 順便說(shuō)一句,您是否注意到我們可以使用標(biāo)準(zhǔn)Java 5 for-each語(yǔ)法遍歷LazySeq strings ? 這是因?yàn)長(zhǎng)azySeq實(shí)現(xiàn)了List接口,因此可以與Java Collections Framework生態(tài)系統(tǒng)很好地配合使用:
請(qǐng)記住,一旦對(duì)惰性序列進(jìn)行了評(píng)估(計(jì)算),它將對(duì)它們進(jìn)行緩存( 記住 )以備后用。 這使得惰性序列非常適合表示無(wú)限或很長(zhǎng)的數(shù)據(jù)流,這些數(shù)據(jù)流計(jì)算成本很高。
迭代()
建立無(wú)限的惰性序列通??梢詺w結(jié)為提供一個(gè)初始元素和一個(gè)功能,該功能可以根據(jù)前一個(gè)元素生成下一個(gè)元素。 換句話說(shuō),第二個(gè)元素是第一個(gè)元素的函數(shù),第三個(gè)元素是第二個(gè)元素的函數(shù),依此類推。 為這種情況提供了便利的LazySeq.iterate()函數(shù)。 現(xiàn)在, ints定義可以如下所示:
final LazySeq<Integer> ints = LazySeq.iterate(2, n -> n + 1);我們從2開(kāi)始,每個(gè)后續(xù)元素表示為前一個(gè)元素+ 1。
更多示例:斐波那契數(shù)列和Collat??z猜想
沒(méi)有斐波那契數(shù)字,就不會(huì)留下關(guān)于懶惰數(shù)據(jù)結(jié)構(gòu)的文章,例如:
private static LazySeq<Integer> lastTwoFib(int first, int second) {return LazySeq.cons(first,() -> lastTwoFib(second, first + second)); }斐波那契數(shù)列也是無(wú)限的,但我們可以通過(guò)多種方式自由變換它:
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);看到無(wú)限的數(shù)字流是多么容易和自然嗎? drop(5).take(10)跳過(guò)前5個(gè)元素,并顯示下一個(gè)10。在這一點(diǎn)上,已經(jīng)計(jì)算了前15個(gè)數(shù)字,以后再也不會(huì)計(jì)算。
查找高于1000(可能是1597 )的第一個(gè)斐波那契數(shù)非常簡(jiǎn)單。 head()始終由filter()預(yù)先計(jì)算,因此無(wú)需進(jìn)一步評(píng)估。 最后但并非最不重要的一點(diǎn)是,我們只需索取第45個(gè)斐波那契數(shù) (從0開(kāi)始)并獲得
1134903170 。 如果您嘗試訪問(wèn)該編號(hào)之前的任何斐波那契數(shù),它們將被預(yù)先計(jì)算并可以快速檢索。
有限序列(Collat??z猜想)
Collat??z猜想也是一個(gè)很有趣的問(wèn)題。 對(duì)于每個(gè)正整數(shù)n我們使用以下算法計(jì)算下一個(gè)整數(shù):
- 如果n為偶數(shù)則為n n/2
- 如果n為奇數(shù),則為3n + 1
例如,從10序列開(kāi)始的外觀如下:10、5、16、8、4、2、1。該序列在達(dá)到1時(shí)結(jié)束。數(shù)學(xué)家認(rèn)為,從任何整數(shù)開(kāi)始,我們最終都將達(dá)到1,但尚未證明。
讓我們創(chuàng)建一個(gè)惰性序列,該序列為給定的n生成Collat??z級(jí)數(shù),但只根據(jù)需要生成。 如上所述,這次我們的序列將是有限的:
private LazySeq<Long> 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);} }該實(shí)現(xiàn)由定義直接驅(qū)動(dòng)。 對(duì)于每個(gè)大于1數(shù)字,返回該數(shù)字+惰性計(jì)算的流的其余部分( () -> collatz(next) )。 如您所見(jiàn),如果給定1 ,我們將使用特殊的of()工廠方法返回單元素惰性序列。 讓我們用上述10測(cè)試它:
final LazySeq<Long> collatz = collatz(10);collatz.filter(n -> (n > 10)).head(); collatz.size();filter()允許我們找到大于10的序列中的第一個(gè)數(shù)字。 請(qǐng)記住,惰性序列將必須遍歷內(nèi)容(自行評(píng)估),但只能遍歷找到第一個(gè)匹配元素的位置。 然后停止,確保其計(jì)算量盡可能少。
但是,為了計(jì)算元素總數(shù), size()必須遍歷整個(gè)序列。 當(dāng)然,這僅適用于有限的惰性序列,在無(wú)限序列上調(diào)用size()最終效果會(huì)很差。
如果您對(duì)此序列稍作練習(xí),您將很快意識(shí)到,不同數(shù)字的序列共享相同的后綴 (總是以相同的數(shù)字序列結(jié)尾)。 這請(qǐng)求進(jìn)行一些緩存/結(jié)構(gòu)共享。 有關(guān)詳細(xì)信息,請(qǐng)參見(jiàn)CollatzConjectureTest 。
現(xiàn)實(shí)生活?
無(wú)限的數(shù)字序列很棒,但在現(xiàn)實(shí)生活中卻不太實(shí)用。 也許還有更多腳踏實(shí)地的例子? 假設(shè)您有一個(gè)收藏,您需要從該收藏中隨機(jī)挑選一些物品。 代替集合,我將使用一個(gè)返回隨機(jī)拉丁字符的函數(shù):
private char randomChar() {return (char) ('A' + (int) (Math.random() * ('Z' - 'A' + 1))); }但是有一個(gè)轉(zhuǎn)折。 您需要N個(gè)(N <26,拉丁字符個(gè)數(shù))唯一值。 僅僅幾次調(diào)用randomChar()并不能保證唯一性。 解決這個(gè)問(wèn)題的方法很少,使用LazySeq非常簡(jiǎn)單:
LazySeq<Character> charStream = LazySeq.<Character>continually(this::randomChar); LazySeq<Character> uniqueCharStream = charStream.distinct();當(dāng)需要時(shí), continually()只需為每個(gè)元素調(diào)用給定的函數(shù)。 因此, charStream將是無(wú)限的隨機(jī)字符流。 當(dāng)然,它們不能唯一。 但是uniqueCharStream保證其輸出是唯一的。 它通過(guò)檢查底層charStream下一個(gè)元素并拒絕已出現(xiàn)的項(xiàng)目來(lái)實(shí)現(xiàn)。 現(xiàn)在我們可以說(shuō)uniqueCharStream.take(4)并確保不會(huì)出現(xiàn)重復(fù)項(xiàng)。
再次注意, continually(this::randomChar).distinct().take(4)實(shí)際上只調(diào)用randomChar()一次! 只要您不消耗此序列,它就會(huì)保持延遲并盡可能推遲評(píng)估。
另一個(gè)示例涉及從數(shù)據(jù)庫(kù)加載數(shù)據(jù)的批次(頁(yè)面)。 使用ResultSet或Iterator很麻煩,但是將整個(gè)數(shù)據(jù)集加載到內(nèi)存中通常不可行。 另一種選擇是急于加載第一批數(shù)據(jù),然后提供加載下一批數(shù)據(jù)的功能。 僅當(dāng)確實(shí)需要數(shù)據(jù)時(shí)才加載數(shù)據(jù),而我們不會(huì)遇到性能或可伸縮性問(wèn)題。
首先,讓我們定義抽象API,以從數(shù)據(jù)庫(kù)中加載批量數(shù)據(jù):
public List<Record> loadPage(int offset, int max) {//load records from offset to offset + max }我完全從技術(shù)中抽象出來(lái),但是您明白了。 假設(shè)我們現(xiàn)在定義了LazySeq<Record> ,它從第0行開(kāi)始,僅在需要時(shí)才加載下一頁(yè):
public static final int PAGE_SIZE = 5;private LazySeq<Record> records(int from) {return LazySeq.concat(loadPage(from, PAGE_SIZE),() -> records(from + PAGE_SIZE)); }通過(guò)調(diào)用records(0)創(chuàng)建新的LazySeq<Record>實(shí)例時(shí),將加載5個(gè)元素的第一頁(yè)。 這意味著已經(jīng)計(jì)算出前5個(gè)序列元素。 如果您嘗試訪問(wèn)第6位或更高版本,序列將自動(dòng)加載所有丟失的記錄并對(duì)其進(jìn)行緩存。 換句話說(shuō),您永遠(yuǎn)不會(huì)兩次計(jì)算相同的元素。
使用序列時(shí),更有用的工具是grouped()和sliding()方法。 首先將輸入序列分成大小相等的組。 以這個(gè)為例,還證明這些方法總是很懶惰:
final LazySeq<Character> 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]]這兩種方法非常有用。 您可以通過(guò)滑動(dòng)窗口查看數(shù)據(jù)(例如,計(jì)算移動(dòng)平均值 )或?qū)⑵鋭澐譃榈乳L(zhǎng)的存儲(chǔ)桶。
您可能會(huì)發(fā)現(xiàn)有用的最后一個(gè)有趣的實(shí)用程序方法是scan() ,它(當(dāng)然是延遲地scan()迭代輸入流,并通過(guò)在輸入的前一個(gè)和當(dāng)前元素上應(yīng)用函數(shù)來(lái)構(gòu)造輸出的每個(gè)元素。 代碼段值一千個(gè)字:
LazySeq<Integer> 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)是自然數(shù)(1、2、3…)的序列。 scan()創(chuàng)建一個(gè)新的序列,從
0并為輸入的每個(gè)元素(自然數(shù))將其添加到自身的最后一個(gè)元素。 因此我們得到:[ 0 0+1 0+1+2 0+1+2+3 0+1+2+3+4 0+1+2+3+4+5 …]。 如果您想要一系列增長(zhǎng)的字符串,只需替換幾種類型:
并享受這個(gè)美麗的三角形:
|\ |*\ |**\ |***\ |****\ |*****\ |******\ |*******\ |********\ |*********\或者(相同的輸出):
LazySeq.iterate("", s -> s + "*").map(s -> "|" + s + "\\").take(10).forEach(System.out::println);Java Collections框架的互操作性
LazySeq實(shí)現(xiàn)java.util.List接口,因此可以在許多地方使用。 此外,它還對(duì)集合(即流和集合)實(shí)現(xiàn)了Java 8增強(qiáng):
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中的流是根據(jù)LazySeq (惰性評(píng)估)的基礎(chǔ)而LazySeq 。 上面的示例將所有中間步驟推遲到調(diào)用collect()為止。 使用LazySeq您可以安全地跳過(guò).stream()并直接處理序列:
lazySeq.map(n -> n + 1).flatMap(n -> asList(0, n - 1)).filter(n -> n != 0).slice(4, 18).limit(10).sorted().distinct();此外, LazySeq提供了特殊用途的收集器(請(qǐng)參閱: LazySeq.toLazySeq() ),即使與collect()一起使用也可以避免評(píng)估,這通常會(huì)強(qiáng)制進(jìn)行完整的收集計(jì)算。
實(shí)施細(xì)節(jié)
每個(gè)懶惰序列都是圍繞急切計(jì)算的頭和懶洋洋地表示為函數(shù)的尾部的思想構(gòu)建的。 這與經(jīng)典的單鏈列表遞歸定義非常相似:
class List<T> {private final T head;private final List<T> tail;//... }但是,在延遲序列的情況下, 尾部是函數(shù)而不是值。 該函數(shù)的調(diào)用會(huì)盡可能推遲:
class Cons<E> extends LazySeq<E> {private final E head;private LazySeq<E> tailOrNull;private final Supplier<LazySeq<E>> tailFun;@Overridepublic LazySeq<E> tail() {if (tailOrNull == null) {tailOrNull = tailFun.get();}return tailOrNull;}有關(guān)完整的實(shí)現(xiàn),請(qǐng)參見(jiàn)在創(chuàng)建時(shí)知道tail時(shí)使用的Cons.java和FixedCons.java (例如LazySeq.of(1, 2)與LazySeq.cons(1, () -> someTailFun()相對(duì)LazySeq.of(1, 2) ,例如LazySeq.of(1, 2) )。
陷阱和常見(jiàn)危險(xiǎn)
下面介紹常見(jiàn)問(wèn)題和誤解。
評(píng)估太多
使用無(wú)限序列的最大危險(xiǎn)之一就是試圖對(duì)其進(jìn)行完全評(píng)估,這顯然會(huì)導(dǎo)致無(wú)限計(jì)算。 無(wú)限序列背后的思想不是全面評(píng)估它,而是在不引入人為限制和意外復(fù)雜性的情況下,盡可能多地獲取它(請(qǐng)參見(jiàn)數(shù)據(jù)庫(kù)加載示例)。
但是,評(píng)估整個(gè)序列太容易遺漏了。 例如,調(diào)用LazySeq.size() 必須評(píng)估整個(gè)序列,并且將無(wú)限運(yùn)行,最終填滿堆?;蚨?#xff08;實(shí)現(xiàn)細(xì)節(jié))。 還有其他方法需要完全遍歷才能正常運(yùn)行。 例如allMatch()確保所有元素都匹配給定謂詞。 有些方法甚至更危險(xiǎn),因?yàn)樗鼈兪欠裢瓿扇Q于序列中的數(shù)據(jù)。 例如,如果head匹配謂詞–或從不匹配,則anyMatch()可能立即返回。
有時(shí),我們可以使用更具確定性的方法來(lái)輕松避免昂貴的操作。 例如:
seq.size() <= 10 //BAD如果seq是無(wú)限的,則可能無(wú)法正常工作或非常慢。 但是,我們可以通過(guò)(更多)可預(yù)測(cè)的方式實(shí)現(xiàn)相同的目標(biāo):
seq.drop(10).isEmpty()請(qǐng)記住,惰性序列是不可變的(因此我們并沒(méi)有真正改變seq ), drop(n)通常是O(n)而isEmpty()是O(1) 。
如有疑問(wèn),請(qǐng)查閱源代碼或JavaDoc以確保您的操作不會(huì)過(guò)于急切地評(píng)估您的序列。 當(dāng)使用LazySeq ,也要非常小心,因?yàn)樾枰褂胘ava.util.Collection或java.util.List 。
持有不必要的頭參考
惰性序列應(yīng)定義為記住已計(jì)算的元素。 您必須意識(shí)到這一點(diǎn),否則您的序列(尤其是無(wú)限序列)將Swift填滿可用內(nèi)存。 但是,由于LazySeq只是一個(gè)奇特的鏈接列表,因此,如果您不再保留對(duì)head的引用(而僅保留中間的某個(gè)元素),則可以進(jìn)行垃圾回收。 例如:
//LazySeq<Char> first = seq.take(10); seq = seq.drop(10);前十個(gè)元素被刪除,我們假設(shè)沒(méi)有任何東西引用過(guò)seq以前的庚。 這使前十個(gè)元素有資格進(jìn)行垃圾回收。 但是,如果我們?nèi)∠⑨尩谝恍?#xff0c;并保持參照老head在first ,JVM不會(huì)釋放任何內(nèi)存。 讓我們對(duì)此進(jìn)行透視。 下面的代碼最終將拋出OutOfMemoryError因?yàn)閕nfinite引用將保持序列的開(kāi)始,因此,到目前為止創(chuàng)建的所有元素都將:
LazySeq<Big> infinite = LazySeq.<Big>continually(Big::new); for (Big arr : infinite) {// }但是,通過(guò)內(nèi)聯(lián)對(duì)continually()調(diào)用或?qū)⑵涮崛〉椒椒ㄖ?#xff0c;此代碼可以完美運(yùn)行(很好,仍然可以永遠(yuǎn)運(yùn)行,但幾乎不使用內(nèi)存):
private LazySeq<Big> getContinually() {return LazySeq.<Big>continually(Big::new); }for (Big arr : getContinually()) {// }有什么不同? 每個(gè)循環(huán)都在下面使用迭代器。 LazySeqIterator下面的LazySeqIterator不會(huì)保留對(duì)舊head()的引用,因此,如果沒(méi)有其他引用該head的對(duì)象,則可以進(jìn)行垃圾回收,當(dāng)使用for-each時(shí),將看到真實(shí)的javac輸出:
for (Iterator<Big> cur = getContinually().iterator(); cur.hasNext(); ) {final Big arr = cur.next();//... }TL; DR
您的序列在遍歷時(shí)會(huì)增加。 如果在另一端成長(zhǎng)時(shí)保持一端,則最終會(huì)炸毀。 就像您在Hibernate中的一級(jí)緩存一樣,如果您在一個(gè)事務(wù)中加載過(guò)多。 僅根據(jù)需要使用。
轉(zhuǎn)換為純Java集合
轉(zhuǎn)換很簡(jiǎn)單,但是很危險(xiǎn)。 這是以上幾點(diǎn)的結(jié)果。 您可以通過(guò)調(diào)用toList()將惰性序列轉(zhuǎn)換為java.util.List :
LazySeq<Integer> 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] 但是請(qǐng)記住,Java集合在定義上是有限的,因此請(qǐng)避免將惰性序列明確轉(zhuǎn)換為集合。 注意
LazySeq已經(jīng)是List ,因此是Iterable和Collection 。 它還具有高效的LazySeq.iterator() 。 如果可以,只需直接傳遞LazySeq實(shí)例即可。
性能,時(shí)間和空間復(fù)雜度
每個(gè)序列(空除外head()的head()總是很容易計(jì)算,因此對(duì)其進(jìn)行訪問(wèn)是快速O(1) 。 計(jì)算tail()可能占用從O(1) (如果已經(jīng)計(jì)算過(guò))到無(wú)限時(shí)間的所有內(nèi)容。 以這個(gè)有效的流為例:
import static com.blogspot.nurkiewicz.lazyseq.LazySeq.cons; import static com.blogspot.nurkiewicz.lazyseq.LazySeq.continually;LazySeq<Integer> oneAndZeros = cons(1,() -> continually(0) ). filter(x -> (x > 0));它代表1后跟無(wú)窮多個(gè)0 s。 通過(guò)過(guò)濾所有正數(shù)( x > 0 ),我們得到一個(gè)具有相同頭部的序列,但是對(duì)尾部的過(guò)濾被延遲(延遲)。 但是,如果現(xiàn)在我們不小心調(diào)用oneAndZeros.tail() , LazySeq將繼續(xù)計(jì)算該無(wú)限序列的越來(lái)越多,但是由于初始1之后沒(méi)有正數(shù),該操作將永遠(yuǎn)運(yùn)行,最終拋出StackOverflowError或OutOfMemoryError (這是一個(gè)實(shí)施細(xì)節(jié))。
但是,如果您達(dá)到此狀態(tài),則可能是編程錯(cuò)誤或庫(kù)的濫用。 通常tail()將接近O(1) 。 另一方面,如果您已經(jīng)“堆疊”了很多操作,則調(diào)用tail()會(huì)Swift一次又一次觸發(fā)它們,因此tail()運(yùn)行時(shí)間在很大程度上取決于您的數(shù)據(jù)結(jié)構(gòu)。
LazySeq上的大多數(shù)操作都是O(1)因?yàn)樗鼈兪嵌栊缘摹?一些操作,例如get(n)或drop(n)都是O(n) ( n表示參數(shù),而不是序列長(zhǎng)度)。 一般而言,運(yùn)行時(shí)間將類似于正常的鏈表。
因?yàn)長(zhǎng)azySeq記住單個(gè)鏈接列表中所有已經(jīng)計(jì)算出的值,所以內(nèi)存消耗始終為O(n) ,其中n n是已經(jīng)計(jì)算出的元素?cái)?shù)。
故障排除
錯(cuò)誤invalid target release: 1.8在Maven構(gòu)建期間invalid target release: 1.8
如果在maven生成期間看到此錯(cuò)誤消息:
[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進(jìn)行編譯。 下載具有l(wèi)ambda支持的JDK 8,并讓maven使用它:
$ export JAVA_HOME=/path/to/jdk8我收到StackOverflowError或程序無(wú)限期掛起
使用LazySeq ,有時(shí)會(huì)出現(xiàn)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)當(dāng)使用可能無(wú)限的數(shù)據(jù)結(jié)構(gòu)時(shí),必須小心。 避免調(diào)用必須 ( size() , allMatch() , minBy() , forEach() , reduce() ,…)或可以 ( filter() , distinct() ,...)遍歷整個(gè)序列以便給出正確值的操作結(jié)果。 有關(guān)更多示例和避免方法,請(qǐng)參見(jiàn)陷阱 。
到期
質(zhì)量
該項(xiàng)目是作為練習(xí)開(kāi)始的,尚未經(jīng)過(guò)戰(zhàn)斗驗(yàn)證。 但是,健康的300多個(gè)單元測(cè)試套件 (測(cè)試代碼/生產(chǎn)代碼比率為3:1)可以保護(hù)質(zhì)量和功能正確性。 我還通過(guò)LazySeq尾部函數(shù)并驗(yàn)證它們被盡可能少地調(diào)用來(lái)確保LazySeq盡可能地懶。
貢獻(xiàn)和錯(cuò)誤報(bào)告
如果發(fā)現(xiàn)錯(cuò)誤或缺少功能,請(qǐng)隨時(shí)打開(kāi)新票證或開(kāi)始拉取請(qǐng)求 。 我也很想看看LazySeq在野外的更多有趣用法。
可能的改進(jìn)
- 就像在FixedCons知道尾巴的情況下使用FixedCons一樣,請(qǐng)考慮將IterableCons包裝在一個(gè)節(jié)點(diǎn)中而不是建立FixedCons層次結(jié)構(gòu)的Iterable 。 這可以用于所有concat方法。
- 并行處理支持(實(shí)現(xiàn)分離器?)
執(zhí)照
該項(xiàng)目是在Apache許可的 2.0版下發(fā)布的 。
翻譯自: https://www.javacodegeeks.com/2013/05/lazy-sequences-implementation-for-java-8.html
過(guò)濾序列,惰性序列
總結(jié)
以上是生活随笔為你收集整理的过滤序列,惰性序列_Java 8的惰性序列实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Win7提示不是有效的win32应用程序
- 下一篇: Spring Boot和JSP