├── .gitignore ├── Emakefile ├── LICENSE ├── Makefile ├── README ├── README.md ├── mix.exs ├── rebar.config ├── rebar.lock └── src ├── examples └── zlists_random.erl ├── zlists.app.src ├── zlists.erl ├── zlists_disk_log.erl ├── zlists_ets.erl └── zlists_file.erl /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | rebar3.crashdump 3 | -------------------------------------------------------------------------------- /Emakefile: -------------------------------------------------------------------------------- 1 | {'./src/*', [debug_info,load, {outdir, "./ebin/"}, {i, "./include/"}]}. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR=`which rebar || ./rebar` 2 | 3 | all: compile 4 | 5 | compile: 6 | @$(REBAR) compile 7 | 8 | test: force 9 | @$(REBAR) eunit 10 | 11 | clean: 12 | @$(REBAR) clean 13 | 14 | force: ; 15 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ================================================================================== 2 | Welcome to a lazy sequences library. There are many possibilities 3 | to represent and handle lazy sequences. To distinct the approach 4 | implemented in this library from other possible ones, it is decided 5 | to refer it as a Z-Lists, or simply zlists. 6 | ================================================================================== 7 | 8 | 1. Design 9 | The core module is a 'zlists'. It is analogous to module 'lists' in a STDLIB 10 | of Erlang. It contains a base operations supported over Z-Lists. 11 | The other modules 'zlists_X' are utility modules that bridges Z-Lists with 12 | standard Erlang modules which may be considered as data sources e.g. 'ets', 13 | 'dets', 'mnesia', 'disk_log', 'file' etc. 14 | 2. Usage 15 | 2.1. Integrate to your project 16 | This is a rebar'ized project so if you already using rebar 17 | just insert a reference to this git repo at your rebar.config. 18 | Otherwise download this repo, and run shell command "erl -make". 19 | 2.2. Examples 20 | 1> Naturals=zlists:recurrent(1, fun(N)-> N+1 end). 21 | [1|#Fun] 22 | 2> zlists:scroll(10,Naturals). 23 | {[1,2,3,4,5,6,7,8,9,10],[11|#Fun]} 24 | 3> zlists:scroll(10,zlists:drop(5,Naturals)). 25 | {[6,7,8,9,10,11,12,13,14,15],[16|#Fun]} 26 | 4> zlists:scroll(10,zlists:drop(500000,Naturals)). 27 | {[500001,500002,500003,500004,500005,500006,500007,500008, 28 | 500009,500010], 29 | [500011|#Fun]} 30 | 5> Fibonacci=zlists:recurrent(1, 0, fun(X0,S0) -> {X0+S0, X0} end). 31 | [1|#Fun] 32 | 6> zlists:expand(10, Fibonacci). 33 | [1,1,2,3,5,8,13,21,34,55|#Fun] 34 | 7> zlists:expand(10, zlists:drop(10,Fibonacci)). 35 | [89,144,233,377,610,987,1597,2584,4181,6765| 36 | #Fun] 37 | 8> zlists:scroll(10, zlists:drop(10,Fibonacci)). 38 | {[89,144,233,377,610,987,1597,2584,4181,6765], 39 | [10946|#Fun]} 40 | 41 | 3. Create your own Z-List 42 | Create your own custom zlist is easy enough, you can directly construct 43 | a special improper list like: 44 | 45 | [1|fun()-> L=deferred_work(),true=is_list(L),L end] 46 | 47 | Note the | symbol between head element and tail function. If you have 48 | already a proper list and tail function you can construct a lazy list 49 | from them as: 50 | 51 | zlists:new([1,2,3], fun()-> [4,5] end) 52 | 53 | If the lazy list you want to create have an explicit recurrent nature 54 | you can use a zlists:recurrent/2/3 to easily create a lazy list. 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Erlang Zlists: a lazy sequences library. 2 | ---------------------------------------------------- 3 | 4 | There are many possibilities to represent and handle lazy sequences. 5 | For disambiguation reasons the approach implemented here will be 6 | referred to as Z-Lists, or simply zlists. 7 | 8 | 9 | ## Design ## 10 | 11 | The core module is 'zlists', similar to module 'lists' in Erlang's STDLIB. 12 | It contains base operations supported over Z-Lists. The others, called 'zlists_X', 13 | are utility modules that bridge Z-Lists with standard Erlang modules 14 | which may be considered as data sources e.g. 'ets', 'dets', 'mnesia', 'disk_log', 'file' etc. 15 | 16 | ## Usage ## 17 | 18 | ### Integrate to your project ### 19 | 20 | This is a rebar'ized project, so, if you are already using rebar, just insert a reference 21 | to this git repo at your rebar.config. 22 | Otherwise clone this repo, and run ``erl -make``. 23 | 24 | ### Examples ### 25 | 26 | ```erlang 27 | 1> Naturals=zlists:recurrent(1, fun(N)-> N+1 end). 28 | [1|#Fun] 29 | 30 | 2> zlists:scroll(10,Naturals). 31 | {[1,2,3,4,5,6,7,8,9,10],[11|#Fun]} 32 | 33 | 3> zlists:scroll(10,zlists:drop(5,Naturals)). 34 | {[6,7,8,9,10,11,12,13,14,15],[16|#Fun]} 35 | 36 | 4> zlists:scroll(10,zlists:drop(500000,Naturals)). 37 | {[500001,500002,500003,500004,500005,500006,500007,500008,500009,500010], 38 | [500011|#Fun]} 39 | 40 | 5> Fibonacci=zlists:recurrent(1, 0, fun(X0,S0) -> {X0+S0, X0} end). 41 | [1|#Fun] 42 | 43 | 6> zlists:expand(10, Fibonacci). 44 | [1,1,2,3,5,8,13,21,34,55|#Fun] 45 | 46 | 7> zlists:expand(10, zlists:drop(10,Fibonacci)). 47 | [89,144,233,377,610,987,1597,2584,4181,6765| 48 | #Fun] 49 | 50 | 8> zlists:scroll(10, zlists:drop(10,Fibonacci)). 51 | {[89,144,233,377,610,987,1597,2584,4181,6765], 52 | [10946|#Fun]} 53 | ``` 54 | 55 | ### Create your own Z-List ### 56 | 57 | Creating your own custom zlist is quite easy, you can directly construct 58 | a special improper list like this: 59 | 60 | ```erlang 61 | [1|fun()-> L=deferred_work(),true=is_list(L),L end] 62 | ``` 63 | 64 | Note the | symbol between head element and tail function. You can use 65 | a proper list and tail function to construct a lazy list: 66 | 67 | ```erlang 68 | zlists:new([1,2,3], fun()-> [4,5] end) 69 | ``` 70 | 71 | If the lazy list you want to create has explicit recurrent nature, use ```zlists:recurrent/2/3``` 72 | to easily create a lazy list. -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ZLists.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :zlists, 6 | version: "0.0.4", 7 | elixir: "~> 1.4", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps(), 11 | description: description(), 12 | package: package(), 13 | name: "zlists", 14 | source_url: "https://github.com/vjache/erlang-zlists.git" 15 | ] 16 | end 17 | 18 | def application do 19 | [applications: []] 20 | end 21 | 22 | defp deps do 23 | [{:ex_doc, ">= 0.0.0", only: :dev}] 24 | end 25 | 26 | defp description do 27 | """ 28 | Z-Lists -- an erlang lazy lists. 29 | """ 30 | end 31 | 32 | defp package do 33 | # These are the default files included in the package 34 | [ 35 | name: :zlists, 36 | files: [ 37 | "mix.exs", 38 | "rebar.config", 39 | "src" 40 | ], 41 | maintainers: ["Vyacheslav Vorobyov"], 42 | licenses: ["Apache 2.0"], 43 | links: %{"GitHub" => "https://github.com/elixir-lang/mydep.git"} 44 | ] 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [warnings_as_errors, debug_info]}. 2 | {xref_checks, [undefined_function_calls]}. 3 | {dialyzer_opts, [{warnings, [unmatched_returns]}]}. 4 | 5 | {cover_enabled, true}. 6 | {clean_files, [".eunit", "ebin/*.beam"]}. -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /src/examples/zlists_random.erl: -------------------------------------------------------------------------------- 1 | %% Author: vvorobyov 2 | %% Created: Nov 3, 2011 3 | %% Description: TODO: Add description to zlists_randoms 4 | -module(zlists_random). 5 | 6 | %% 7 | %% Include files 8 | %% 9 | 10 | %% 11 | %% Exported Functions 12 | %% 13 | -export([uniform_floats/0, uniform_floats/1]). 14 | 15 | %% 16 | %% API Functions 17 | %% 18 | 19 | uniform_floats() -> 20 | RandFlo=rand:uniform(), 21 | zlists:new([RandFlo], fun()-> uniform_floats() end). 22 | 23 | uniform_floats(State0) -> 24 | {Fl, State1}=rand:uniform_s(State0), 25 | zlists:new([Fl],fun()-> uniform_floats(State1) end). 26 | 27 | %% 28 | %% Local Functions 29 | %% 30 | -------------------------------------------------------------------------------- /src/zlists.app.src: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang; -*- 2 | {application, zlists, [ 3 | {description, "A lazy lists (Z-Lists) for Erlang"}, 4 | {vsn, "0.0.4"}, 5 | {applications, [kernel, stdlib]}, 6 | {maintainers, ["Vyacheslav Vorobyov"]}, 7 | {licenses, ["Apache"]}, 8 | {links, [ 9 | {"Github", "https://github.com/vjache/erlang-zlists"} 10 | ]} 11 | ]}. 12 | -------------------------------------------------------------------------------- /src/zlists.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author 3 | %%% @copyright (C) 2011, Vyacheslav Vorobyov. All Rights Reserved. 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | %%% @doc 17 | %%% This module supports basic operations over lazy/infinite lists 18 | %%% represented as [X1,...,Xn|TailFun], where N >= 1, and 19 | %%% TailFun is a function that takes no arguments and 20 | %%% returns a proper Erlang list or again lazy list. 21 | %%% 22 | %%% This is one of many other possible representations of a lazy 23 | %%% lists but this one is a useful for those applications that 24 | %%% faced by me. 25 | %%% 26 | %%% Also, one can ask "Why lazy lists? What for?" or 27 | %%% "How to use them?". Its a matter of taste or style of 28 | %%% programming. Those who are familiar with Lisp, Clojure, 29 | %%% Haskel knows the answers. Those who from Java or C++ may 30 | %%% remember iterators. 31 | %%% @end 32 | %%% Created : Oct 22, 2011 33 | %%%------------------------------------------------------------------- 34 | -module(zlists). 35 | 36 | %% 37 | %% Include files 38 | %% 39 | -include_lib("eunit/include/eunit.hrl"). 40 | 41 | %% 42 | %% Exported Functions 43 | %% 44 | -export([new/2, 45 | entail/2, 46 | generate/2, 47 | recurrent/2, 48 | recurrent/3, 49 | foreach/2, 50 | foldl/3, 51 | map/2, 52 | seq/3, 53 | splitwith/2, 54 | dropwhile/2, 55 | drop/2, 56 | take/2, 57 | take2/2, 58 | takewhile/2, 59 | takewhile2/2, 60 | filter/2, 61 | expand/1, 62 | expand/2, 63 | append/1, 64 | scroll/2, 65 | merge/2, 66 | merge/3, 67 | merge/1, 68 | merge_using/2, 69 | keymerge/2, 70 | keymerge/3, 71 | cartesian/2, 72 | join/1, 73 | join/3, 74 | join_r/1, 75 | join_r/3, 76 | zip/2, 77 | ziph/2, 78 | unique/1, 79 | unique/2, 80 | count/1, 81 | aggregate/4, 82 | print/1, 83 | print/3]). 84 | 85 | -define(EXPAND(Tail), if is_function(Tail, 0) -> Tail(); true -> Tail end). 86 | 87 | -type zlist(T) :: maybe_improper_list(T, fun(()-> zlist(T)) | [] ) . 88 | 89 | -export_type([zlist/1]). 90 | 91 | %%%%%%%%%%%%%%%%%% 92 | %% API Functions 93 | %%%%%%%%%%%%%%%%%% 94 | 95 | %%------------------------------------------------------------------------------- 96 | %% @doc 97 | %% Creates a lazy list from a list (proper or lazy) and tail function. When 98 | %% iterating through such a lazy list firstly elements from passed list go 99 | %% and when it exhausted, tail function called which may return an another 100 | %% lazy list or a proper list (e.g. empty). 101 | %% @end 102 | %%------------------------------------------------------------------------------- 103 | -spec new(ZList :: zlist(T), Fun :: fun( () -> zlist(T) ) ) -> zlist(T) . 104 | 105 | new([],Fun) when is_function(Fun, 0) -> 106 | Fun(); 107 | new([E],Fun) when is_function(Fun, 0) -> 108 | [E|Fun]; 109 | new([H|Tail],Fun) when is_function(Fun, 0) -> 110 | [H|fun() -> new(?EXPAND(Tail), Fun) end]. 111 | 112 | %%------------------------------------------------------------------------------- 113 | %% @doc 114 | %% Similar to the zlists:new/2 but used fun of arity 1, where an argument is a 115 | %% last item of zlist passed. This function gives a chance to continue after 116 | %% exhaustion of a first zlist passed with a one constructed based on last element. 117 | %% 118 | %% Example: 119 | %% 1> L=zlists:entail([1,2,3,4,5], fun(N)-> lists:seq(N+1,N+10) end). 120 | %% [1|#Fun] 121 | %% 2> zlists:expand(L). 122 | %% [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] 123 | %% @end 124 | %%------------------------------------------------------------------------------- 125 | -spec entail(ZList :: zlist(T), Fun :: fun( (T) -> zlist(T) ) ) -> zlist(T) . 126 | entail([],_Fun) -> 127 | []; 128 | entail([E],Fun) -> 129 | [E|Fun(E)]; 130 | entail([E|TFun],Fun) when is_function(TFun, 0) -> 131 | [E|fun()-> case TFun() of [] -> Fun(E); ZL -> entail(ZL,Fun) end end]; 132 | entail([E|Tail],Fun)-> 133 | [E|fun()-> entail(Tail,Fun) end]. 134 | 135 | %%------------------------------------------------------------------------------- 136 | %% @doc 137 | %% Creates a zlist based on a zlist of seeds and generation function. 138 | %% Semantically it is equivalent to the following code: 139 | %% [ E || Seed <- SeedList, E <- GeneratorFun(Seed)], 140 | %% but the result of this function is lazy. 141 | %% 142 | %% This function can be easily composed with map and append, so it may be 143 | %% considered as a sugar. 144 | %% @end 145 | %%------------------------------------------------------------------------------- 146 | -spec generate(SeedList :: zlist(T), GeneratorFun :: fun( (T) -> zlist(T1) )) -> zlist(T1) . 147 | 148 | generate([], _GeneratorFun) -> 149 | []; 150 | generate([H|Tail], GeneratorFun) when is_function(GeneratorFun, 1) -> 151 | new(GeneratorFun(H), 152 | fun()-> generate(?EXPAND(Tail),GeneratorFun) end). 153 | 154 | %%------------------------------------------------------------------------------- 155 | %% @doc 156 | %% Creates an infinit zlist based on a recurrent formula. I.e. each next item 157 | %% computed based on a previous item. 158 | %% @end 159 | %%------------------------------------------------------------------------------- 160 | -spec recurrent(X0 :: T, RecFun :: fun( (T) -> T )) -> zlist(T) . 161 | 162 | recurrent(X0, RecFun) -> 163 | new([X0], fun()-> recurrent(RecFun(X0), RecFun) end). 164 | 165 | %%------------------------------------------------------------------------------- 166 | %% @doc 167 | %% Creates an infinit zlist based on a recurrent formula with some inner state. 168 | %% I.e. each next item and next state computed based on a previous item and a 169 | %% previous state but only item visible as output in a zlist. 170 | %% 171 | %% Try Fibonacci sequence: 172 | %% 1>Fibs=zlists:recurrent(1, 0, fun(X0,S0) -> {X0+S0, X0} end). 173 | %% [1|#Fun] 174 | %% 2>zlists:expand(10, Fibs). 175 | %% [1,1,2,3,5,8,13,21,34|#Fun] 176 | %% @end 177 | %%------------------------------------------------------------------------------- 178 | -spec recurrent(X0 :: T, S0 :: T1, RecFun :: fun( (T, T1) -> {T, T1} )) -> zlist(T) . 179 | 180 | recurrent(X0, S0, RecFun) -> 181 | new([X0], fun()-> {X1,S1}=RecFun(X0,S0), recurrent(X1, S1, RecFun) end). 182 | 183 | %%------------------------------------------------------------------------------- 184 | %% @doc 185 | %% Just a lazy analog of lists:foreach. Note that the passed zlist may be 186 | %% an infinite by nature, so be sure that you realy want to use foreach function 187 | %% against such a zlist (in some cases it may have a sense if an infinit loop is 188 | %% wanted behaviour). 189 | %% @end 190 | %%------------------------------------------------------------------------------- 191 | -spec foreach(Fun :: fun( (T) -> any() ), ZList :: zlist(T)) -> ok . 192 | 193 | foreach(F, [Hd|Tail]) -> 194 | F(Hd), 195 | foreach(F, ?EXPAND(Tail)); 196 | foreach(F, []) when is_function(F, 1) -> ok. 197 | 198 | %%------------------------------------------------------------------------------- 199 | %% @doc 200 | %% Just a lazy analog of lists:foldl. Note that the passed zlist may be 201 | %% an infinite by nature, so be sure that you realy want to use foldl function 202 | %% against such a zlist (in some cases it may have a sense if an infinit loop 203 | %% with state is wanted behaviour). 204 | %% @end 205 | %%------------------------------------------------------------------------------- 206 | 207 | -spec foldl(Fun, Acc0, ZList) -> Acc1 when 208 | Fun :: fun((Elem :: T, AccIn) -> AccOut), 209 | Acc0 :: term(), 210 | Acc1 :: term(), 211 | AccIn :: term(), 212 | AccOut :: term(), 213 | ZList :: zlist(T). 214 | 215 | foldl(F, Accu, [Hd|Tail]) -> 216 | foldl(F, F(Hd, Accu), ?EXPAND(Tail)); 217 | foldl(F, Accu, []) when is_function(F, 2) -> Accu. 218 | 219 | %%------------------------------------------------------------------------------- 220 | %% @doc 221 | %% Just a lazy analog of lists:map. Note that map function application is not 222 | %% done at once for all elements in a zlist, application occurs as zlist 223 | %% expanded/scrolled. 224 | %% @end 225 | %%------------------------------------------------------------------------------- 226 | -spec map(Fun, ZList1) -> ZList2 when 227 | Fun :: fun((A) -> B), 228 | ZList1 :: zlist(A), 229 | ZList2 :: zlist(B), 230 | A :: term(), 231 | B :: term(). 232 | 233 | map(F, [H|T]) -> 234 | [F(H)|fun()-> map(F, ?EXPAND(T)) end]; 235 | map(F, []) when is_function(F, 1) -> []. 236 | 237 | %%------------------------------------------------------------------------------- 238 | %% @doc 239 | %% Just a lazy analog of lists:seq/3. 240 | %% @end 241 | %%------------------------------------------------------------------------------- 242 | -spec seq(From, To, Incr) -> Seq when 243 | From :: integer(), 244 | To :: integer() | infinity, 245 | Incr :: integer(), 246 | Seq :: [integer()]. 247 | 248 | seq(_First, _Last, 0) -> 249 | []; 250 | seq(First, Last, Inc) when Inc>0, First>Last, is_integer(Last) -> 251 | []; 252 | seq(First, Last, Inc) when Inc<0, First 253 | []; 254 | seq(First, Last, Inc) -> 255 | [First|fun()-> seq(First+Inc, Last, Inc) end]. 256 | 257 | %%------------------------------------------------------------------------------- 258 | %% @doc 259 | %% Just a lazy analog of lists:dropwhile/2. It skips the first elements 260 | %% satisfying predicate and returns a tail either list or zlist. 261 | %% @end 262 | %%------------------------------------------------------------------------------- 263 | -spec dropwhile(Pred, ZList1) -> ZList2 when 264 | Pred :: fun((Elem :: T) -> boolean()), 265 | ZList1 :: zlist(T), 266 | ZList2 :: zlist(T). 267 | 268 | dropwhile(Pred, [Hd|Tail]=Rest) -> 269 | case Pred(Hd) of 270 | true -> dropwhile(Pred, ?EXPAND(Tail)); 271 | false -> Rest 272 | end; 273 | dropwhile(Pred, []) when is_function(Pred, 1) -> []. 274 | 275 | %%------------------------------------------------------------------------------- 276 | %% @doc 277 | %% Drops a first N elements of a specified zlist and returns its remainin tail (zlist). 278 | %% @end 279 | %%------------------------------------------------------------------------------- 280 | 281 | -spec drop( N :: non_neg_integer(), ZList :: zlist(T)) -> zlist(T) . 282 | 283 | drop( 0, Tail) -> 284 | Tail; 285 | drop( N, [_|Tail]) -> 286 | drop(N-1,?EXPAND(Tail)). 287 | 288 | %%------------------------------------------------------------------------------- 289 | %% @doc 290 | %% Just a lazy analog of lists:takewhile/2. It returns first elements while 291 | %% predicate function return true. 292 | %% @end 293 | %%------------------------------------------------------------------------------- 294 | -spec takewhile(Pred, ZList1) -> ZList2 when 295 | Pred :: fun((Elem :: T) -> boolean()), 296 | ZList1 :: zlist(T), 297 | ZList2 :: zlist(T). 298 | 299 | takewhile(Pred, [Hd|Tail]) -> 300 | case Pred(Hd) of 301 | true -> [Hd | fun()-> takewhile(Pred, ?EXPAND(Tail)) end]; 302 | false -> [] 303 | end; 304 | takewhile(Pred, []) when is_function(Pred, 1) -> []. 305 | 306 | %%------------------------------------------------------------------------------- 307 | %% @doc 308 | %% Same as takewhile/2 but "less lazy". If some first values already expanded 309 | %% then 'takewhile' logic applied immediately to them until it faces tail 310 | %% function. This function may exhibit higher performance due to less calls 311 | %% to tail fun. 312 | %% @end 313 | %%------------------------------------------------------------------------------- 314 | -spec takewhile2(Pred, ZList1) -> ZList2 when 315 | Pred :: fun((Elem :: T) -> boolean()), 316 | ZList1 :: zlist(T), 317 | ZList2 :: zlist(T). 318 | takewhile2(Pred, [Hd|Tail]) -> 319 | case Pred(Hd) of 320 | true when is_list(Tail) -> [Hd | takewhile2(Pred, Tail)]; 321 | true when is_function(Tail,0)-> [Hd | fun()-> takewhile2(Pred, Tail()) end]; 322 | false -> [] 323 | end; 324 | takewhile2(Pred, []) when is_function(Pred, 1) -> []. 325 | 326 | %%------------------------------------------------------------------------------- 327 | %% @doc 328 | %% Returns a first N elements of a specified zlist as a zlist. Note that it does 329 | %% this lazely not at once, so the N may be a quite big number without risk of 330 | %% RAM hit. 331 | %% @end 332 | %%------------------------------------------------------------------------------- 333 | 334 | -spec take( N :: non_neg_integer(), ZList :: zlist(T)) -> zlist(T) . 335 | 336 | take( 0, _) -> 337 | []; 338 | take( _, []) -> 339 | []; 340 | take( N, [H|Tail]) -> 341 | new([H],fun()-> take(N-1,?EXPAND(Tail)) end). 342 | 343 | %%------------------------------------------------------------------------------- 344 | %% @doc 345 | %% Same as take/2 but "less lazy". If some first values already expanded 346 | %% then 'take' logic applied immediately to them until it faces tail 347 | %% function. This function may exhibit higher performance due to less calls 348 | %% to tail fun. 349 | %% @end 350 | %%------------------------------------------------------------------------------- 351 | 352 | -spec take2( N :: non_neg_integer(), ZList :: zlist(T)) -> zlist(T) . 353 | 354 | take2( 0, _) -> 355 | []; 356 | take2( _, []) -> 357 | []; 358 | take2( N, [H|Tail]) when is_list(Tail)-> 359 | [H| take2(N-1, Tail)]; 360 | take2( N, [H|Tail]) -> 361 | new([H],fun()-> take2(N-1,?EXPAND(Tail)) end). 362 | 363 | %%------------------------------------------------------------------------------- 364 | %% @doc 365 | %% A lazy analog of lists:filter/2. It filters zlist lazely as list 366 | %% expanded/scrolled. 367 | %% @end 368 | %%------------------------------------------------------------------------------- 369 | -spec filter(Pred, ZList1) -> ZList2 when 370 | Pred :: fun((Elem :: T) -> boolean()), 371 | ZList1 :: zlist(T), 372 | ZList2 :: zlist(T). 373 | 374 | filter(Pred, ZList) when is_function(Pred, 1) -> 375 | Pred1=fun(E)-> not Pred(E) end, 376 | case dropwhile(Pred1, ZList) of 377 | [] -> []; 378 | [_]=R -> R; 379 | [H|T] -> [H| fun()-> filter(Pred, ?EXPAND(T)) end] 380 | end. 381 | 382 | %%------------------------------------------------------------------------------- 383 | %% @doc 384 | %% Unlazies a zlist. I.e. creates a proper list from zlist. 385 | %% @end 386 | %%------------------------------------------------------------------------------- 387 | expand([]) -> 388 | []; 389 | expand([H|T]) -> 390 | [H|expand(T)]; 391 | expand(TFun) when is_function(TFun, 0) -> 392 | expand(TFun()). 393 | 394 | %%------------------------------------------------------------------------------- 395 | %% @doc 396 | %% Partially unlazies a zlist. Using a specified head lengh N creates a new zlist 397 | %% with first N elements available for pattern matching. 398 | %% @end 399 | %%------------------------------------------------------------------------------- 400 | expand(_N, []) -> 401 | []; 402 | expand(4, [_,_,_,_|_]=List) -> 403 | List; 404 | expand(3, [_,_,_|_]=List) -> 405 | List; 406 | expand(2, [_,_|_]=List) -> 407 | List; 408 | expand(1,[_|_]=List) -> 409 | List; 410 | expand(N, [H|T]) -> 411 | [H|expand(N-1,T)]; 412 | expand(N, TFun) when is_function(TFun, 0) -> 413 | expand(N, TFun()). 414 | 415 | %%------------------------------------------------------------------------------- 416 | %% @doc 417 | %% A lazy analog of lists:append/1. 418 | %% @end 419 | %%------------------------------------------------------------------------------- 420 | -spec append(ZListOfZLists:: zlist(zlist(T))) -> zlist(T) when T :: term(). 421 | 422 | append([]) -> 423 | []; 424 | append([ZList]) -> 425 | ZList; 426 | append([ZList | OtherZLists]) -> 427 | new(ZList, fun()-> append(?EXPAND(OtherZLists)) end). 428 | 429 | %%------------------------------------------------------------------------------- 430 | %% @doc 431 | %% This function helps to iterate through zlist. It cuts oh a head of a lenght 432 | %% N and returns this head and a lazy tail. 433 | %% @end 434 | %%------------------------------------------------------------------------------- 435 | 436 | -spec scroll(N, ZList1) -> {List2, ZList3} when 437 | N :: non_neg_integer(), 438 | ZList1 :: zlist(T), 439 | List2 :: [T], 440 | ZList3 :: zlist(T), 441 | T :: term(). 442 | 443 | scroll(N, ZList) when is_integer(N), N >= 0, is_list(ZList) -> 444 | Exp=zlists:expand(N, ZList), 445 | try lists:split(N, Exp) of 446 | {Page,Tail} -> {Page,?EXPAND(Tail)} 447 | catch 448 | error:badarg -> 449 | {Exp, []} 450 | end. 451 | 452 | %%------------------------------------------------------------------------------- 453 | %% @doc 454 | %% A lazy analog of lists:merge/2. 455 | %% @end 456 | %%------------------------------------------------------------------------------- 457 | 458 | -spec merge(ZList1 :: zlist(term()), ZList2 :: zlist(term())) -> zlist(term()). 459 | 460 | merge([], ZList2) -> 461 | ZList2; 462 | merge(ZList1, []) -> 463 | ZList1; 464 | merge([H1|Tail1], [H2|_]=ZList2) when H1 =< H2 -> 465 | [H1| fun()-> merge(?EXPAND(Tail1),ZList2) end]; 466 | merge(ZList1, [H2|Tail2]) -> 467 | [H2| fun()-> merge(ZList1,?EXPAND(Tail2)) end]. 468 | 469 | %%------------------------------------------------------------------------------- 470 | %% @doc 471 | %% A lazy analog of lists:merge/3. 472 | %% @end 473 | %%------------------------------------------------------------------------------- 474 | 475 | -spec merge(Fun, List1, List2) -> List3 when 476 | Fun :: fun((A, B) -> boolean()), 477 | List1 :: zlist(A), 478 | List2 :: zlist(B), 479 | List3 :: zlist((A | B)). 480 | 481 | merge(_Fun, [], ZList2) -> 482 | ZList2; 483 | merge(_Fun, ZList1, []) -> 484 | ZList1; 485 | merge(Fun, [H1|Tail1]=ZList1, [H2|Tail2]=ZList2) -> 486 | H1_not_greater_than_H2=Fun(H1,H2), 487 | if H1_not_greater_than_H2 -> 488 | [H1| fun()-> merge(Fun,?EXPAND(Tail1),ZList2) end]; 489 | true -> 490 | [H2| fun()-> merge(Fun,ZList1,?EXPAND(Tail2)) end] 491 | end. 492 | 493 | %%------------------------------------------------------------------------------- 494 | %% @doc 495 | %% A lazy analog of lists:merge/1. 496 | %% @end 497 | %%------------------------------------------------------------------------------- 498 | 499 | -spec merge(ListOfZLists :: [zlist(term())]) -> zlist(term()). 500 | 501 | merge([]) -> 502 | []; 503 | merge([ZList1]) -> 504 | ZList1; 505 | merge([ZList1,ZList2|Other]) -> 506 | merge([merge(ZList1, ZList2)|Other]). 507 | 508 | %%------------------------------------------------------------------------------- 509 | %% @doc 510 | %% Returns a zlist of merged zlists using specified oreding function. It is 511 | %% supposed that zlists in a list are ordered using the same (specified when 512 | %% merging) ordering function, 513 | %% @end 514 | %%------------------------------------------------------------------------------- 515 | 516 | -spec merge_using(Fun :: fun((T, T) -> boolean()) , 517 | ListOfZLists :: [zlist(T)]) -> zlist(T). 518 | 519 | merge_using(_Fun, []) -> 520 | []; 521 | merge_using(_Fun, [ZList1]) -> 522 | ZList1; 523 | merge_using(Fun, [ZList1,ZList2|Other]) -> 524 | merge_using(Fun, [merge(Fun, ZList1, ZList2)|Other]). 525 | 526 | %%------------------------------------------------------------------------------- 527 | %% @doc 528 | %% A lazy analog of lists:keymerge/3. 529 | %% @end 530 | %%------------------------------------------------------------------------------- 531 | 532 | -spec keymerge(N, List1, List2) -> List3 when 533 | N :: non_neg_integer(), 534 | List1 :: zlist(A), 535 | List2 :: zlist(B), 536 | List3 :: zlist((A | B)). 537 | 538 | keymerge(_N, [], ZList2) -> 539 | ZList2; 540 | keymerge(_N, ZList1, []) -> 541 | ZList1; 542 | keymerge(N, [H1|Tail1], [H2|_]=ZList2) when element(N,H1) =< element(N,H2) -> 543 | [H1| fun()-> keymerge(N,?EXPAND(Tail1),ZList2) end]; 544 | keymerge(N, ZList1, [H2|Tail2]) -> 545 | [H2| fun()-> keymerge(N,ZList1,?EXPAND(Tail2)) end]. 546 | 547 | %%------------------------------------------------------------------------------- 548 | %% @doc 549 | %% Analog of zlists:keymerge/3, but for a multiple zlists. 550 | %% @end 551 | %%------------------------------------------------------------------------------- 552 | 553 | -spec keymerge(N :: non_neg_integer(), ListOfZLists :: [zlist(term())]) -> zlist(term()). 554 | 555 | keymerge(_N,[]) -> 556 | []; 557 | keymerge(_N,[ZList1]) -> 558 | ZList1; 559 | keymerge(N,[ZList1,ZList2|Other]) -> 560 | keymerge(N,[keymerge(N,ZList1, ZList2)|Other]). 561 | 562 | %%------------------------------------------------------------------------------- 563 | %% @doc 564 | %% A lazy analog of lists:splitwith/2. 565 | %% @end 566 | %%------------------------------------------------------------------------------- 567 | 568 | -spec splitwith(Pred, ZList) -> {List1, ZList2} when 569 | Pred :: fun((T) -> boolean()), 570 | ZList :: zlist(T), 571 | List1 :: [T], 572 | ZList2 :: zlist(T), 573 | T :: term(). 574 | 575 | splitwith(Pred, ZList) when is_function(Pred, 1) -> 576 | splitwith(Pred, ZList, []). 577 | 578 | splitwith(_Pred, [], Acc) -> 579 | {lists:reverse(Acc), []}; 580 | splitwith(Pred, [H|Tail]=ZList, Acc) -> 581 | Satisfy=Pred(H), 582 | if Satisfy -> 583 | splitwith(Pred, ?EXPAND(Tail), [H|Acc]); 584 | true -> 585 | {lists:reverse(Acc), ZList} 586 | end. 587 | 588 | %%------------------------------------------------------------------------------- 589 | %% @doc 590 | %% Returns a cartesian product of two zlists as zlist. 591 | %% @end 592 | %%------------------------------------------------------------------------------- 593 | -spec cartesian(ZList1 :: zlist(term()), ZList :: zlist(term())) -> zlist(list()) . 594 | 595 | cartesian([], _ZList2) -> 596 | []; 597 | cartesian(_ZList1, []) -> 598 | []; 599 | cartesian(ZList1, ZList2) -> 600 | generate(ZList1, fun(El)-> map(fun(Er)-> [El,Er] end, ZList2) end). 601 | 602 | %%------------------------------------------------------------------------------- 603 | %% @doc 604 | %% Returns a zlist as a result of join of a list of an ordered zlists. Actually 605 | %% it is a merge join algorithm implementation. (Keys supposed to increase) 606 | %% The function accepts a list of sources where each source is a binary tuple: 607 | %% {KeySpec, OrderedZList}, where KeySpec is an integer position of a key 608 | %% in a tuples in a OrderedZList, or a function that returns a key by term 609 | %% from a OrderedZList. OrderedZList is a zlist sorted accordinly to KeySpec. 610 | %% 611 | %% @end 612 | %%------------------------------------------------------------------------------- 613 | 614 | -spec join(ListOfSources :: [{KeySpec, OrderedZList}]) -> zlist(list(tuple())) 615 | when KeySpec :: non_neg_integer() | fun( (E) -> Key :: term() ), 616 | OrderedZList :: zlist(E). 617 | 618 | join([{_KeySpec1, ZList1}]) -> 619 | map(fun(E)-> [E] end, ZList1); 620 | join([{KeySpec1, ZList1}, {KeySpec2, _}=H |Tail]=_ListOfSources) -> 621 | left_join(form_keyspec(KeySpec1), ZList1, 622 | form_keyspec(KeySpec2), join([H|Tail]), false, increase). 623 | 624 | -spec join_r(ListOfSources :: [{KeySpec, OrderedZList}]) -> zlist(list(tuple())) 625 | when KeySpec :: non_neg_integer() | fun( (E) -> Key :: term() ), 626 | OrderedZList :: zlist(E). 627 | 628 | %%------------------------------------------------------------------------------- 629 | %% @doc 630 | %% Returns a zlist as a result of join of a list of an ordered zlists. Actually 631 | %% it is a merge join algorithm implementation. (Keys supposed to decrease) 632 | %% The function accepts a list of sources where each source is a binary tuple: 633 | %% {KeySpec, OrderedZList}, where KeySpec is an integer position of a key 634 | %% in a tuples in a OrderedZList, or a function that returns a key by term 635 | %% from a OrderedZList. OrderedZList is a zlist sorted accordinly to KeySpec. 636 | %% 637 | %% @end 638 | %%------------------------------------------------------------------------------- 639 | 640 | join_r([{_KeySpec1, ZList1}]) -> 641 | map(fun(E)-> [E] end, ZList1); 642 | join_r([{KeySpec1, ZList1}, {KeySpec2, _}=H |Tail]=_ListOfSources) -> 643 | left_join(form_keyspec(KeySpec1), ZList1, 644 | form_keyspec(KeySpec2), join_r([H|Tail]), false, decrease). 645 | 646 | 647 | %%------------------------------------------------------------------------------- 648 | %% @doc 649 | %% Returns a zlist as a result of join of a list of a two ordered zlists. 650 | %% (Keys supposed to increase) Actually it is a merge join algorithm implementation. 651 | %% There is also a possibility to specify complement option. This option controls 652 | %% whether the entry ignored if there is no match with right counterpart. 653 | %% 654 | %% Examples: 655 | %% 42> zlists:print(zlists:join({1, [{1,left},{2,left},{3,left}]}, {1, [{2,right},{3,right},{4,right}]},true)). 656 | %% [{1,left},undefined] 657 | %% [{2,left},{2,right}] 658 | %% [{3,left},{3,right}] 659 | %% ok 660 | %% 43> zlists:print(zlists:join({1, [{1,left},{2,left},{3,left}]}, {1, [{2,right},{3,right},{4,right}]},{complement,'N/A'})). 661 | %% [{1,left},'N/A'] 662 | %% [{2,left},{2,right}] 663 | %% [{3,left},{3,right}] 664 | %% ok 665 | %% 44> zlists:print(zlists:join({1, [{1,left},{2,left},{3,left}]}, {1, [{2,right},{3,right},{4,right}]},false)). 666 | %% [{2,left},{2,right}] 667 | %% [{3,left},{3,right}] 668 | %% ok 669 | %% 670 | %% @end 671 | %%------------------------------------------------------------------------------- 672 | 673 | -spec join({KeySpec1, OrderedZList1},{KeySpec2, OrderedZList2}, ComplementOpt) -> zlist(list(tuple())) 674 | when KeySpec1 :: non_neg_integer() | fun( (E) -> Key :: term() ), 675 | KeySpec2 :: non_neg_integer() | fun( (E) -> Key :: term() ), 676 | OrderedZList1 :: zlist(E), 677 | OrderedZList2 :: zlist(E), 678 | ComplementOpt :: false | true | {complement, With :: term()}. 679 | join({KeySpec1, OrderedZList1}, {KeySpec2, OrderedZList2}, ComplementOpt) -> 680 | left_join( form_keyspec(KeySpec1), OrderedZList1, 681 | form_keyspec(KeySpec2), map(fun(E)-> [E] end, OrderedZList2), 682 | ComplementOpt, increase). 683 | 684 | %%------------------------------------------------------------------------------- 685 | %% @doc 686 | %% Returns a zlist as a result of join of a list of a two ordered zlists. 687 | %% (Keys supposed to decrease) Actually it is a merge join algorithm implementation. 688 | %% There is also a possibility to specify complement option. This option controls 689 | %% whether the entry ignored if there is no match with right counterpart. 690 | %% Examples: 691 | %% 42> zlists:print(zlists:join_r({1, [{3,left},{2,left},{1,left}]}, {1, [{4,right},{3,right},{2,right}]},true)). 692 | %% [{3,left},{3,right}] 693 | %% [{2,left},{2,right}] 694 | %% [{1,left},undefined] 695 | %% ok 696 | %% 43> zlists:print(zlists:join_r({1, [{3,left},{2,left},{1,left}]}, {1, [{4,right},{3,right},{2,right}]},{complement,'N/A'})). 697 | %% [{3,left},{3,right}] 698 | %% [{2,left},{2,right}] 699 | %% [{1,left},'N/A'] 700 | %% ok 701 | %% 44> zlists:print(zlists:join_r({1, [{3,left},{2,left},{1,left}]}, {1, [{4,right},{3,right},{2,right}]},false)). 702 | %% [{3,left},{3,right}] 703 | %% [{2,left},{2,right}] 704 | %% ok 705 | %% 706 | %% @end 707 | %%------------------------------------------------------------------------------- 708 | 709 | -spec join_r({KeySpec1, OrderedZList1},{KeySpec2, OrderedZList2}, ComplementOpt) -> zlist(list(tuple())) 710 | when KeySpec1 :: non_neg_integer() | fun( (E) -> Key :: term() ), 711 | KeySpec2 :: non_neg_integer() | fun( (E) -> Key :: term() ), 712 | OrderedZList1 :: zlist(E), 713 | OrderedZList2 :: zlist(E), 714 | ComplementOpt :: false | true | {complement, With :: term()}. 715 | join_r({KeySpec1, OrderedZList1}, {KeySpec2, OrderedZList2}, ComplementOpt) -> 716 | left_join( form_keyspec(KeySpec1), OrderedZList1, 717 | form_keyspec(KeySpec2), map(fun(E)-> [E] end, OrderedZList2), 718 | ComplementOpt, decrease). 719 | 720 | 721 | %%------------------------------------------------------------------------------- 722 | %% @doc 723 | %% A lazy analog of lists:zip/2. 724 | %% @end 725 | %%------------------------------------------------------------------------------- 726 | 727 | -spec zip(ZList1, ZList2) -> ZList3 when 728 | ZList1 :: zlist(A), 729 | ZList2 :: zlist(B), 730 | ZList3 :: zlist({A, B}). 731 | zip([], []) -> 732 | []; 733 | zip([H1|Tail1], [H2|Tail2]) -> 734 | new([{H1,H2}],fun()-> zip(?EXPAND(Tail1),?EXPAND(Tail2)) end). 735 | 736 | %%------------------------------------------------------------------------------- 737 | %% @doc 738 | %% Analogous to zlists:zip/2 but allow passed zlists to have a different lengths. 739 | %% @end 740 | %%------------------------------------------------------------------------------- 741 | 742 | -spec ziph(ZList1, ZList2) -> ZList3 when 743 | ZList1 :: zlist(A), 744 | ZList2 :: zlist(B), 745 | ZList3 :: zlist({A, B}). 746 | ziph(_ZList1, []) -> 747 | []; 748 | ziph([], _ZList2) -> 749 | []; 750 | ziph([H1|Tail1], [H2|Tail2]) -> 751 | new([{H1,H2}],fun()-> ziph(?EXPAND(Tail1),?EXPAND(Tail2)) end). 752 | 753 | %%------------------------------------------------------------------------------- 754 | %% @doc 755 | %% Given a sorted zlist returns a zlist where elements unique ('==' used). 756 | %% @end 757 | %%------------------------------------------------------------------------------- 758 | -spec unique(ZList::zlist(T)) -> zlist(T). 759 | unique([]) -> 760 | []; 761 | unique([H | Tail]) -> 762 | [H | fun()-> unique(dropwhile(fun(E)-> E==H end, ?EXPAND(Tail))) end]. 763 | 764 | %%------------------------------------------------------------------------------- 765 | %% @doc 766 | %% Given a sorted zlist and equality fun, returns a zlist where elements unique 767 | %% (equality fun used). Equality fun accepts two elements and return 'true' if 768 | %% they are considered as equal. 769 | %% @end 770 | %%------------------------------------------------------------------------------- 771 | -spec unique(EqFun::fun((T,T)->boolean()), ZList::zlist(T)) -> zlist(T). 772 | unique(_EqFun, []) -> 773 | []; 774 | unique(EqFun, [H | Tail]) when is_function(EqFun, 2) -> 775 | [H | fun()-> unique(dropwhile(fun(E)-> EqFun(E,H) end, ?EXPAND(Tail))) end]. 776 | 777 | %%------------------------------------------------------------------------------- 778 | %% @doc 779 | %% This function mainly for debug purposes, it traverses through entire 780 | %% sequence to count a number of elements. 781 | %% @end 782 | %%------------------------------------------------------------------------------- 783 | 784 | count(ZList) -> 785 | count(ZList, 0). 786 | 787 | count([], N) -> N; 788 | count([_|T], N) -> count(?EXPAND(T), N+1). 789 | 790 | %%------------------------------------------------------------------------------- 791 | %% @doc 792 | %% This function splits a dataset on chunks, apply aggregate function on each 793 | %% chunk and returns aggregated values as a zlist. To split dataset on chunks 794 | %% two entities used: zlist of partition markers and a function which decide 795 | %% whether the element of a dataset belongs to the part marked by marker. 796 | %% 797 | %% Example: 798 | %% Markers=[1,11,21,31,41,51], 799 | %% Values=lists:seq(15, 35), 800 | %% AggFun=fun(L)-> L end, 801 | %% PredFun=fun(Marker,Value)-> Value < Marker end, 802 | %% zlists:expand( 803 | %% zlists:aggregate( 804 | %% AggFun, PredFun, Markers, Values)). 805 | %% 806 | %% The result is: 807 | %% [[],[], 808 | %% [15,16,17,18,19,20], 809 | %% [21,22,23,24,25,26,27,28,29,30], 810 | %% [31,32,33,34,35],[]] 811 | %% @end 812 | %%------------------------------------------------------------------------------- 813 | -spec aggregate(AggFun :: fun( ([T]) -> T1 ), 814 | PredFun :: fun( (M,T) -> boolean() ), 815 | MarkerZList :: zlist(M), 816 | DataZList :: zlist(T) ) -> zlist(T1). 817 | 818 | aggregate(_AggFun, _PredFun, [], _DataZList) -> 819 | []; 820 | aggregate(AggFun, PredFun, [Marker|MTail]=_MarkerZList, DataZList) 821 | when is_function(AggFun, 1), is_function(PredFun, 2) -> 822 | {Ready,DataZList1}=zlists:splitwith(fun(E)-> PredFun(Marker,E) end, DataZList), 823 | [AggFun(Ready) | fun()-> aggregate(AggFun, PredFun, ?EXPAND(MTail), DataZList1) end]. 824 | 825 | 826 | %%------------------------------------------------------------------------------- 827 | %% @doc 828 | %% Debug function to print zlists. Do not use it for infinit zlists. 829 | %% @end 830 | %%------------------------------------------------------------------------------- 831 | print(L) -> 832 | foreach(fun(E)-> io:format("~p~n",[E]) end, L). 833 | 834 | print(SkipN, PrintN ,ZList) -> 835 | print(take(PrintN, drop(SkipN,ZList))). 836 | 837 | %% 838 | %% Local Functions 839 | %% 840 | 841 | left_join(_KeyFun1, ZList1, _KeyFun2, [], ComplementOpt, _KeyDirection) -> 842 | case ComplementOpt of 843 | {complement, With} -> left_cartesian(ZList1, [[With]]); 844 | true -> left_cartesian(ZList1, [[undefined]]); 845 | false -> [] 846 | end; 847 | left_join(_KeyFun1, [], _KeyFun2, _ZList2, _ComplementOpt, _KeyDirection) -> 848 | []; 849 | left_join(KeyFun1, [H1|Tail1]=ZList1, KeyFun2, [[H2|_]|Tail2]=ZList2, ComplementOpt, _KeyDirection) 850 | when is_function(KeyFun1,1), is_function(KeyFun2,1) -> 851 | K1=KeyFun1(H1), 852 | K2=KeyFun2(H2), 853 | if ((K1 < K2) and (_KeyDirection =:= increase)) or 854 | ((K1 > K2) and (_KeyDirection =:= decrease)) -> 855 | TF=fun()->left_join(KeyFun1, ?EXPAND(Tail1), KeyFun2, ZList2, ComplementOpt, _KeyDirection) end, 856 | case ComplementOpt of 857 | false -> 858 | left_join(KeyFun1, ?EXPAND(Tail1), KeyFun2, ZList2, ComplementOpt, _KeyDirection); 859 | {complement, With} -> 860 | new(left_cartesian([H1], [[With]]), TF); 861 | true -> 862 | new(left_cartesian([H1], [[undefined]]),TF) 863 | end; 864 | ((K1 > K2) and (_KeyDirection =:= increase)) or 865 | ((K1 < K2) and (_KeyDirection =:= decrease)) -> 866 | left_join(KeyFun1, ZList1, KeyFun2, ?EXPAND(Tail2), ComplementOpt, _KeyDirection); 867 | true -> 868 | Pred1=fun(E) -> KeyFun1(E) == K1 end, 869 | {K1List, ZList11}=splitwith(Pred1, ZList1), 870 | Pred2=fun([E|_]) -> KeyFun2(E) == K2 end, 871 | {K2List, ZList21}=splitwith(Pred2, ZList2), 872 | new(left_cartesian(K1List, K2List), 873 | fun()-> left_join(KeyFun1, ZList11, KeyFun2, ZList21, ComplementOpt, _KeyDirection) end) 874 | end. 875 | 876 | left_cartesian([El], [Er]) -> % Optimize highly frequent case 877 | [[El|Er]]; 878 | left_cartesian([], _ZList2) -> 879 | []; 880 | left_cartesian(_ZList1, []) -> 881 | []; 882 | left_cartesian(ZList1, ZList2) -> 883 | generate(ZList1, fun(El)-> map(fun(Er)-> [El|Er] end, ZList2) end). 884 | 885 | form_keyspec(KeySpec) 886 | when is_integer(KeySpec) -> 887 | fun(E)-> element(KeySpec, E) end; 888 | form_keyspec(KeySpec) 889 | when is_function(KeySpec,1) -> 890 | KeySpec. 891 | 892 | %% 893 | %% eUnit Functions 894 | %% 895 | append_list_of_lists_test() -> 896 | L1=[1,2,3], 897 | L2=[4,5,6], 898 | Lr=L1++L2, 899 | Lr=expand(append([L1, L2])). 900 | append_list_of_zlists_test() -> 901 | L1=[1,2,3], 902 | L2=[4,5,6], 903 | Lr=L1++[a]++L2++[7,8], 904 | ZL1=new(L1,fun()->[a]end), 905 | ZL2=new(L2,fun()->[7,8]end), 906 | Lr=expand(append([ZL1, ZL2])). 907 | append_zlist_of_zlists_test() -> 908 | L1=[1,2,3], 909 | L2=[4,5,6], 910 | ZL1=new(L1,fun()->[a]end), 911 | ZL2=new(L2,fun()->[7,8]end), 912 | ZL3=new([0],fun()->[c,d,e]end), 913 | Lr=L1++[a]++L2++[7,8]++[0,c,d,e], 914 | Lr=expand(append(new([ZL1],fun()-> [ZL2,ZL3] end))). 915 | -------------------------------------------------------------------------------- /src/zlists_disk_log.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author 3 | %%% @copyright (C) 2011, Vyacheslav Vorobyov. All Rights Reserved. 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | %%% @doc 17 | %%% This is a utility module that complements a disk_log with 18 | %%% lazy list aware functions. 19 | %%% @end 20 | %%% Created : Oct 22, 2011 21 | %%%------------------------------------------------------------------------------- 22 | -module(zlists_disk_log). 23 | 24 | %% 25 | %% Exported Functions 26 | %% 27 | -export([read/2, read/1, write/2, write/3, awrite/2, awrite/3]). 28 | 29 | %%%%%%%%%%%%%%%%%% 30 | %% API Functions 31 | %%%%%%%%%%%%%%%%%% 32 | 33 | %%------------------------------------------------------------------------------- 34 | %% @doc 35 | %% Returns a lazy list of an all terms in an accessible log. 36 | %% @end 37 | %%------------------------------------------------------------------------------- 38 | read(Log) -> 39 | read(Log, 1000). 40 | read(Log, BatchSize) -> 41 | read(Log, BatchSize, start). 42 | read(Log, BatchSize, Cont) -> 43 | case disk_log:chunk(Log, Cont, BatchSize) of 44 | eof -> []; 45 | {error, Reason} -> throw(Reason); 46 | {Cont2, Terms} -> 47 | zlists:new(Terms, fun()-> read(Log, BatchSize, Cont2) end); 48 | {Cont2, Terms, _Badbytes} -> 49 | zlists:new(Terms, fun()-> read(Log, BatchSize, Cont2) end) 50 | end. 51 | 52 | %%------------------------------------------------------------------------------- 53 | %% @doc 54 | %% Writes synchronously a terms from a lazy list to an accessible log. 55 | %% @end 56 | %%------------------------------------------------------------------------------- 57 | write(Log, ZList) -> 58 | write(Log, ZList, 1000). 59 | write(Log, ZList, BatchSize) -> 60 | write(Log, ZList, BatchSize, fun disk_log:log_terms/2 ). 61 | %%------------------------------------------------------------------------------- 62 | %% @doc 63 | %% Writes asynchronously a terms from a lazy list to an accessible log. 64 | %% @end 65 | %%------------------------------------------------------------------------------- 66 | awrite(Log, ZList) -> 67 | awrite(Log, ZList, 1000). 68 | awrite(Log, ZList, BatchSize) -> 69 | write(Log, ZList, BatchSize, fun disk_log:alog_terms/2 ). 70 | 71 | write(Log, ZList, BatchSize, LogFun) -> 72 | case zlists:scroll(BatchSize, ZList) of 73 | {Batch,[]} -> 74 | ok=LogFun(Log, Batch); 75 | {Batch,ZList1} -> 76 | ok=LogFun(Log, Batch), 77 | write(Log, ZList1, BatchSize, LogFun) 78 | end. -------------------------------------------------------------------------------- /src/zlists_ets.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author 3 | %%% @copyright (C) 2011, Vyacheslav Vorobyov. All Rights Reserved. 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | %%% @doc 17 | %%% This is a utility module that complements an ets with 18 | %%% lazy list aware functions. 19 | %%% @end 20 | %%% Created : Oct 22, 2011 21 | %%%------------------------------------------------------------------------------- 22 | -module(zlists_ets). 23 | 24 | %% 25 | %% Exported Functions 26 | %% 27 | -export([from_first/1, 28 | from_keyl/2, 29 | from_keyr/2, 30 | from_last/1, 31 | keys_from_first/1, 32 | keys_from_last/1, 33 | keys_from_keyl/2, 34 | keys_from_keyr/2, 35 | select/3, 36 | select_reverse/3, 37 | upload/3, 38 | upload/2]). 39 | 40 | %%%%%%%%%%%%%%%%%% 41 | %% API Functions 42 | %%%%%%%%%%%%%%%%%% 43 | 44 | %%------------------------------------------------------------------------------- 45 | %% @doc 46 | %% Returns a lazy list iterating from first element to last. 47 | %% @end 48 | %%------------------------------------------------------------------------------- 49 | -spec from_first(Tab :: ets:tab()) -> zlists:zlist(tuple()). 50 | from_first(Tab) -> 51 | from_keyl(Tab, ets:first(Tab)). 52 | %%------------------------------------------------------------------------------- 53 | %% @doc 54 | %% Returns a lazy list iterating from first key to last. 55 | %% @end 56 | %%------------------------------------------------------------------------------- 57 | -spec keys_from_first(Tab :: ets:tab()) -> zlists:zlist(term()). 58 | keys_from_first(Tab) -> 59 | First=ets:first(Tab), 60 | [First | keys_from_keyl(Tab, First)]. 61 | %%------------------------------------------------------------------------------- 62 | %% @doc 63 | %% Returns a lazy list iterating through objects in a normal order 64 | %% starting from the specified key. 65 | %% @end 66 | %%-------------------------------------------------------------------------------- 67 | from_keyl(Tab,Key) -> 68 | zlists:generate( 69 | keys_from_keyl(Tab, Key), 70 | fun(K)-> ets:lookup(Tab, K) end ). 71 | %%------------------------------------------------------------------------------- 72 | %% @doc 73 | %% Returns a lazy list iterating through keys in a normal order 74 | %% starting from the specified key. 75 | %% @end 76 | %%------------------------------------------------------------------------------- 77 | keys_from_keyl(_Tab, '$end_of_table') -> 78 | []; 79 | keys_from_keyl(Tab, Key) -> 80 | NextKey=ets:next(Tab, Key), 81 | [NextKey | fun()-> keys_from_keyl(Tab, NextKey) end]. 82 | %%------------------------------------------------------------------------------- 83 | %% @doc 84 | %% Returns a lazy list iterating from last element to first. 85 | %% @end 86 | %%------------------------------------------------------------------------------- 87 | -spec from_last(Tab :: ets:tab()) -> zlists:zlist(zlists:zlist(tuple())). 88 | from_last(Tab) -> 89 | from_keyr(Tab,ets:last(Tab)). 90 | %%------------------------------------------------------------------------------- 91 | %% @doc 92 | %% Returns a lazy list iterating from last key to first. 93 | %% @end 94 | %%------------------------------------------------------------------------------- 95 | -spec keys_from_last(Tab :: ets:tab()) -> zlists:zlist(term()). 96 | keys_from_last(Tab) -> 97 | Last=ets:last(Tab), 98 | [Last | keys_from_keyr(Tab, Last)]. 99 | %%------------------------------------------------------------------------------- 100 | %% @doc 101 | %% Returns a lazy list iterating through objects in reverse order 102 | %% starting from the specified key. 103 | %% @end 104 | %%------------------------------------------------------------------------------- 105 | from_keyr(Tab,Key) -> 106 | zlists:generate( 107 | keys_from_keyr(Tab, Key), 108 | fun(K)-> ets:lookup(Tab, K) end ). 109 | 110 | %%------------------------------------------------------------------------------- 111 | %% @doc 112 | %% Returns a lazy list iterating through keys in reverse order 113 | %% starting from the specified key. 114 | %% @end 115 | %%------------------------------------------------------------------------------- 116 | keys_from_keyr(_Tab, '$end_of_table') -> 117 | []; 118 | keys_from_keyr(Tab, Key) -> 119 | NextKey=ets:prev(Tab, Key), 120 | [NextKey | fun()-> keys_from_keyr(Tab, NextKey) end]. 121 | 122 | %%------------------------------------------------------------------------------- 123 | %% @doc 124 | %% This function is analogous to the one in an ets but returns a lazy list. 125 | %% @end 126 | %%------------------------------------------------------------------------------- 127 | -spec select(Tab :: ets:tab(), 128 | MatchSpec :: ets:match_spec(), 129 | Limit :: non_neg_integer()) -> zlists:zlist(term()). 130 | select(Tab, MatchSpec, Limit) -> 131 | select_(ets:select(Tab, MatchSpec, Limit)). 132 | 133 | select_('$end_of_table') -> 134 | []; 135 | select_({List,Cont}) -> 136 | zlists:new(List, fun()-> select_(ets:select(Cont)) end). 137 | %%------------------------------------------------------------------------------- 138 | %% @doc 139 | %% This function is analogous to the one in an ets but returns a lazy list. 140 | %% @end 141 | %%------------------------------------------------------------------------------- 142 | -spec select_reverse(Tab :: ets:tab(), 143 | MatchSpec :: ets:match_spec(), 144 | Limit :: non_neg_integer()) -> zlists:zlist(term()). 145 | select_reverse(Tab,MatchSpec, Limit) -> 146 | select_reverse_(ets:select_reverse(Tab, MatchSpec, Limit)). 147 | 148 | select_reverse_('$end_of_table') -> 149 | []; 150 | select_reverse_({List,Cont}) -> 151 | zlists:new(List, fun()-> select_(ets:select_reverse(Cont)) end). 152 | 153 | %%------------------------------------------------------------------------------- 154 | %% @doc 155 | %% Uploads an ets table with an objects from a lazy list using specified 156 | %% batch size. 157 | %% @end 158 | %%------------------------------------------------------------------------------- 159 | -spec upload(Tab :: ets:tab(), ZList :: zlists:zlist(tuple()), BatchSize :: non_neg_integer()) -> ok. 160 | upload(Tab, ZList, BatchSize) -> 161 | case zlists:scroll(BatchSize, ZList) of 162 | {Page,[]} -> 163 | ets:insert(Tab, Page), 164 | ok; 165 | {Page,ZTail} -> 166 | ets:insert(Tab, Page), 167 | upload(Tab, ZTail, BatchSize) 168 | end. 169 | 170 | %%------------------------------------------------------------------------------- 171 | %% @doc 172 | %% The same as uploda/3 but batch size is equal to 1000. 173 | %% @end 174 | %%------------------------------------------------------------------------------- 175 | -spec upload(Tab :: ets:tab(), ZList :: zlists:zlist(tuple())) -> ok. 176 | upload(Tab, ZList) -> 177 | upload(Tab, ZList, 1000). 178 | -------------------------------------------------------------------------------- /src/zlists_file.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author 3 | %%% @copyright (C) 2011, Vyacheslav Vorobyov. All Rights Reserved. 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | %%% @doc 17 | %%% This is a utility module that complements a file module with 18 | %%% lazy list aware functions. 19 | %%% @end 20 | %%% Created : Apr 19, 2011 21 | %%%------------------------------------------------------------------------------- 22 | -module(zlists_file). 23 | 24 | %% 25 | %% Include files 26 | %% 27 | 28 | %% 29 | %% Exported Functions 30 | %% 31 | -export([read/2, 32 | expand_binary/2, 33 | as_berps/2, 34 | read_berps/1, 35 | read_berps/2, 36 | open_berps/1, 37 | open_berps/2, 38 | close_on_eof/1, 39 | close_on_eof/2, 40 | consult_berps/1, 41 | write_berps/2, 42 | dump_berps/2]). 43 | 44 | %% 45 | %% API Functions 46 | %% 47 | 48 | %%------------------------------------------------------------------------------- 49 | %% @doc 50 | %% Returns a content of a file as a Z-List. 51 | %% @end 52 | %%------------------------------------------------------------------------------- 53 | -spec read(IoDevice :: file:io_device(), 54 | PageSize :: non_neg_integer()) -> zlists:zlist( binary() | list( non_neg_integer() ) ). 55 | 56 | read(Fd, Sz) -> 57 | case file:read(Fd, Sz) of 58 | {ok, Data} when is_binary(Data) -> 59 | [Data | fun()-> read(Fd, Sz) end]; 60 | {ok, Data} when is_list(Data) -> 61 | zlists:new(Data,fun()-> read(Fd, Sz) end); 62 | eof -> []; 63 | {error, Reason} -> throw(Reason) 64 | end. 65 | 66 | %%------------------------------------------------------------------------------- 67 | %% @doc 68 | %% Tries to make a binary head of Z-List to have at least BinSize bytes. This 69 | %% function is aplicable only against Z-List of binaries. 70 | %% @end 71 | %%------------------------------------------------------------------------------- 72 | -spec expand_binary(BinZList :: zlists:zlist(binary()), 73 | BinSize :: non_neg_integer()) -> zlists:zlist(binary()). 74 | 75 | expand_binary([Bin|ZTail]=BinZList,Sz) when byte_size(Bin) >= Sz -> 76 | if byte_size(Bin) == Sz -> BinZList; 77 | true -> 78 | <> = Bin, 79 | [Chunk,BTail|ZTail] 80 | end; 81 | expand_binary([Bin],Sz) when byte_size(Bin) < Sz -> 82 | [Bin]; 83 | expand_binary([Bin|ZTail],Sz) -> 84 | case zlists:expand(1,ZTail) of 85 | [H|ZTail1] -> expand_binary([<>|ZTail1],Sz); 86 | [] -> [Bin] 87 | end. 88 | 89 | %%------------------------------------------------------------------------------- 90 | %% @doc 91 | %% Takes a Z-List of bytes representing BERPs and returns Z-List of terms. An 92 | %% input Z-List either Z-List of binaries or Z-List of integers B in [0..255]. 93 | %% @end 94 | %%------------------------------------------------------------------------------- 95 | -spec as_berps(BytesZList :: zlists:zlist(binary()) | zlists:zlist(non_neg_integer()), 96 | Opts :: [safe]) -> zlists:zlist(term()). 97 | as_berps([],_Opts) -> []; 98 | as_berps([Bin|_]=BinZList,Opts) 99 | when is_binary(Bin) -> 100 | case expand_binary(BinZList, 4) of 101 | [<<0 :32/unsigned-integer>>] -> []; 102 | [<>|T] -> 103 | [<>|T1]=expand_binary(T, Sz), 104 | [binary_to_term(Bert, Opts)|fun()-> as_berps(zlists:expand(1,T1),Opts) end] 105 | end; 106 | as_berps(ZList,Opts) -> 107 | case zlists:expand(4,ZList) of 108 | []-> throw({dbg, ZList}); 109 | [0,0,0,0] -> []; 110 | [A,B,C,D|T] -> 111 | <> = <>, 112 | {Data,T1}=zlists:scroll(Sz,T), 113 | [binary_to_term(list_to_binary(Data),Opts)|fun()-> as_berps(zlists:expand(1,T1),Opts) end] 114 | end. 115 | 116 | %%------------------------------------------------------------------------------- 117 | %% @doc 118 | %% Equivalent to read_berps(Io, []). 119 | %% @end 120 | %%------------------------------------------------------------------------------- 121 | -spec read_berps(IoDevice :: file:io_device()) -> zlists:zlist(term()). 122 | read_berps(Io) -> 123 | read_berps(Io, []). 124 | 125 | %%------------------------------------------------------------------------------- 126 | %% @doc 127 | %% Returns a Z-List of terms contained in an IO device as BERPs. 128 | %% @end 129 | %%------------------------------------------------------------------------------- 130 | -spec read_berps(IoDevice :: file:io_device(), 131 | Opts :: [safe]) -> zlists:zlist(term()). 132 | read_berps(Io, Opts) -> 133 | case file:read(Io, 4) of 134 | eof -> []; 135 | {ok,<>} -> 136 | {ok,<>}=file:read(Io, Sz), 137 | [binary_to_term(Bert, Opts)|fun()-> read_berps(Io) end] 138 | end. 139 | 140 | %%------------------------------------------------------------------------------- 141 | %% @doc 142 | %% Just a helper function to open a BERP file as a Z-List at one op. 143 | %% Checks if suffix is ".gz" to decide enflate or not. 144 | %% @end 145 | %%------------------------------------------------------------------------------- 146 | -spec open_berps(Filename :: file:filename()) -> {file:io_device(), zlists:zlist(term())}. 147 | open_berps(Filename) -> 148 | IsGZip = is_gz(Filename), 149 | open_berps(Filename, IsGZip). 150 | 151 | %%------------------------------------------------------------------------------- 152 | %% @doc 153 | %% Just a helper function to open a BERP file as a Z-List at one op. 154 | %% @end 155 | %%------------------------------------------------------------------------------- 156 | -spec open_berps(Filename :: file:filename(), 157 | IsGZip :: boolean()) -> {file:io_device(), zlists:zlist(term())}. 158 | open_berps(Filename, IsGZip) -> 159 | {ok,Io}=file:open( 160 | Filename, 161 | [read,raw, 162 | binary,read_ahead | if IsGZip-> [compressed]; true -> [] end ]), 163 | {Io, read_berps(Io)}. 164 | 165 | %%------------------------------------------------------------------------------- 166 | %% @doc 167 | %% Reads entire file into one binary, and wraps it with Z-List that will parse 168 | %% it as a sequence of BERPs. 169 | %% @end 170 | %%------------------------------------------------------------------------------- 171 | -spec consult_berps(Filename :: file:filename()) -> zlists:zlist(term()). 172 | consult_berps(Filename) -> 173 | {ok,Data}=file:read_file(Filename), 174 | IsGZip=is_gz(Filename), 175 | IData= if IsGZip -> zlib:gunzip(Data); true -> Data end, 176 | as_berps([IData], []). 177 | 178 | %%------------------------------------------------------------------------------- 179 | %% @doc 180 | %% Writes a terms passed as Z-List to IO device using BERP format. 181 | %% @end 182 | %%------------------------------------------------------------------------------- 183 | -spec write_berps(IoDevice :: file:io_device(), Terms :: zlists:zlist(term()) ) -> ok. 184 | write_berps(Io, ZList) -> 185 | zlists:foreach( 186 | fun(Term) -> 187 | Bin = term_to_binary(Term), 188 | Sz = byte_size(Bin), 189 | ok=file:write(Io, <>) 190 | end, 191 | ZList). 192 | %%------------------------------------------------------------------------------- 193 | %% @doc 194 | %% Writes a terms passed as Z-List to file using BERP format. 195 | %% @end 196 | %%------------------------------------------------------------------------------- 197 | -spec dump_berps(Filename :: file:filename(), Terms :: zlists:zlist(term()) ) -> ok. 198 | dump_berps(Filename, ZList) -> 199 | IsGZip = is_gz(Filename), 200 | {ok,Io}=file:open(Filename, 201 | [write,raw, 202 | binary,delayed_write | if IsGZip-> [compressed]; true -> [] end ]), 203 | try write_berps(Io, ZList) 204 | after 205 | file:close(Io) 206 | end. 207 | 208 | close_on_eof({Io, ZList}) -> 209 | close_on_eof(Io, ZList). 210 | 211 | close_on_eof(Io, ZList) -> 212 | zlists:new(ZList, fun()-> file:close(Io),[] end). 213 | 214 | %% 215 | %% Local Functions 216 | %% 217 | 218 | is_gz(Filename) -> 219 | lists:suffix( 220 | ".gz", 221 | if is_list(Filename) -> Filename; 222 | is_atom(Filename) -> atom_to_list(Filename); 223 | is_binary(Filename) -> binary_to_list(Filename) 224 | end). 225 | --------------------------------------------------------------------------------