├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── dfx.json ├── example └── SearchService │ ├── README.md │ ├── Search.mo │ └── Service.mo ├── mops.toml ├── src ├── IterExt.mo ├── Sequence.mo ├── Sort.mo ├── Stream.mo ├── StreamCell.mo ├── Text.mo └── TrieExt.mo └── test └── Main.mo /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "build" 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | env: 11 | DFX_VERSION: 0.13.1 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Install 15 | run: | 16 | echo y | DFX_VERSION=$DFX_VERSION bash -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)" 17 | echo "/home/runner/bin" >> $GITHUB_PATH 18 | npm i -g ic-mops 19 | - name: Build 20 | run: | 21 | set -x 22 | dfx cache install 23 | dfx start --background 24 | dfx canister create Search 25 | dfx build Search 26 | dfx deploy Search 27 | dfx canister create UnitTest 28 | dfx build UnitTest 29 | dfx deploy UnitTest 30 | dfx canister call UnitTest selfTest 31 | - name: "docs" 32 | run: /home/runner/.cache/dfinity/versions/$DFX_VERSION/mo-doc 33 | - name: Upload docs 34 | uses: JamesIves/github-pages-deploy-action@releases/v3 35 | with: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | BRANCH: gh-pages 38 | FOLDER: docs/ 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .vessel 3 | .mops 4 | canisters 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Motoko Sequence 2 | 3 | ![build](https://github.com/matthewhammer/motoko-sequence/workflows/build/badge.svg) 4 | 5 | Cache-friendly, persistent sequential data for Motoko. 6 | 7 | Inspired by **chunky list** representation from [Section 5 of this POPL 1989 paper](https://dl.acm.org/doi/10.1145/75277.75305). -------------------------------------------------------------------------------- /dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "UnitTest": { 4 | "main": "test/Main.mo" 5 | }, 6 | "Search": { 7 | "main": "example/SearchService/Service.mo" 8 | } 9 | }, 10 | "defaults": { 11 | "build": { 12 | "packtool": "mops sources", 13 | "output": "canisters/" 14 | }, 15 | "start": { 16 | "address": "127.0.0.1", 17 | "port": 8000 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/SearchService/README.md: -------------------------------------------------------------------------------- 1 | # Example: Search Service 2 | 3 | Example service uses `Sequence` and `Text` modules from the main `sequence` package. 4 | 5 | [Deployed here.](https://ic.rocks/principal/d3ip4-saaaa-aaaae-aaa2q-cai) 6 | -------------------------------------------------------------------------------- /example/SearchService/Search.mo: -------------------------------------------------------------------------------- 1 | import Buffer "mo:base/Buffer"; 2 | import Char "mo:base/Char"; 3 | import Debug "mo:base/Debug"; 4 | import Hash "mo:base/Hash"; 5 | import Iter "mo:base/Iter"; 6 | import Nat "mo:base/Nat"; 7 | import Result "mo:base/Result"; 8 | import Text "mo:base/Text"; 9 | import Trie "mo:base/Trie"; 10 | 11 | import Db "mo:crud/Database"; 12 | 13 | import Sequence "../../src/Sequence"; 14 | import Texts "../../src/Text"; 15 | import TrieExt "../../src/TrieExt"; 16 | import IterExt "../../src/IterExt"; 17 | 18 | module { 19 | 20 | // uniquely identify a TextFile within the file database 21 | public type FileId = Nat; 22 | 23 | // sequence of text 24 | public type TextSeq = Sequence.Sequence; 25 | 26 | public type TextFile = { 27 | name : ?Text; 28 | meta : ?Text; // e.g., CanCan stores video ID here (or whatever helps render search results visually) 29 | var content : TextSeq; // e.g., CanCan stores some video-related text here, like a comment, explicit hashtag(s), etc 30 | }; 31 | 32 | // Metric measures things about one file and one word. 33 | // (both already known from context). 34 | // at most one Metric each word-file relationship. 35 | public type Metric = { 36 | count : Nat; // how many times does the word appear? 37 | first : Nat; // where does it first appear? 38 | last : Nat; // where does it last appear? 39 | }; 40 | 41 | type FileMetric = { 42 | file : FileId; 43 | metric : Metric; 44 | }; 45 | 46 | public type SearchResult = { 47 | file : FileId; 48 | meta : ?Text; 49 | metric : ?Metric; // (metric is null for no search query) 50 | }; 51 | 52 | public type SearchResults = Sequence.Sequence; 53 | 54 | // stores a database of text files, with keyword search 55 | public class Search() { 56 | 57 | var count = 0; 58 | 59 | let append = Sequence.defaultAppend(); 60 | 61 | // database of text 62 | let db = Db.Database( 63 | func (_, last) { 64 | switch last { 65 | case null 0; 66 | case (?last) { last + 1 }; 67 | }}, 68 | Nat.equal, 69 | #hash(Hash.hash), 70 | ); 71 | 72 | // wordFileMetric stores at most one Metric each word-file relationship 73 | var wordFileMetric : Trie.Trie2D = Trie.empty(); 74 | 75 | // searchIndex relates a word with all its word-file relationships 76 | var searchIndex : Trie.Trie = Trie.empty(); 77 | 78 | /// create a new text sequence 79 | public func create(n : ?Text, m : ?Text, c : Text) : FileId { 80 | db.create({ name = n; meta = m; var content = Sequence.make(c)}) 81 | }; 82 | 83 | /// delete a text sequence 84 | public func delete(id : FileId) : Bool { 85 | switch (db.delete(id)) { 86 | case (#ok(_)) true; 87 | case (#err(_)) false; 88 | } 89 | }; 90 | 91 | /// append to an existing text sequence 92 | public func addText(id : FileId, t : Text) : Bool { 93 | switch (db.read(id)) { 94 | case (#ok(file)) { 95 | file.content := append(file.content, Sequence.make(t)); 96 | true 97 | }; 98 | case (#err(e)) { false }; 99 | } 100 | }; 101 | 102 | 103 | /// read a text sequence 104 | public func readText(id : FileId) : ?Text { 105 | switch (db.read(id)) { 106 | case (#ok(file)) { ?Texts.toText(file.content) }; 107 | case (#err(_)) { null }; 108 | } 109 | }; 110 | 111 | /// read a text sequence slice 112 | public func readSlice(id : FileId, pos : Nat, size : Nat) : ?Text { 113 | switch (db.read(id)) { 114 | case (#ok(file)) { ?Texts.toText(Sequence.slice(file.content, pos, size).1) }; 115 | case (#err(_)) { null }; 116 | } 117 | }; 118 | 119 | public func readMeta (file : FileId) : ?Text { 120 | switch (db.read(file)) { 121 | case (#ok(file)) { file.meta }; 122 | case (#err(_)) { null }; 123 | } 124 | }; 125 | 126 | func refreshIndex() { 127 | 128 | func updateFileWordMetric(id : FileId, pos : Nat, word : Text) { 129 | let wordKey = { key = word; hash = Text.hash(word) }; 130 | let idKey = { key = id; hash = Hash.hash(id) }; 131 | switch (Trie.find(wordFileMetric, wordKey, Text.equal)) { 132 | case null { 133 | wordFileMetric 134 | := Trie.put2D( 135 | wordFileMetric, wordKey, Text.equal, idKey, Nat.equal, 136 | { count = 1; 137 | first = pos; 138 | last = pos; } 139 | ) 140 | }; 141 | case (?files) { 142 | switch (Trie.find(files, idKey, Nat.equal)) { 143 | case null { 144 | wordFileMetric := Trie.put2D( 145 | wordFileMetric, wordKey, Text.equal, idKey, Nat.equal, 146 | { count = 1; 147 | first = pos; 148 | last = pos; 149 | } 150 | ) 151 | }; 152 | case (?metric) { 153 | wordFileMetric := Trie.put2D( 154 | wordFileMetric, wordKey, Text.equal, idKey, Nat.equal, 155 | { count = metric.count + 1; 156 | first = Nat.min(metric.first, pos); 157 | last = Nat.max(metric.last, pos); 158 | } 159 | ) 160 | } 161 | } 162 | }; 163 | } 164 | }; 165 | 166 | func indexFile(id : FileId, f : TextFile) { 167 | let words = Texts.tokens(f.content, #predicate(Char.isWhitespace)); 168 | for (word in Sequence.iter(words, #fwd)) { 169 | updateFileWordMetric(id, word.pos, word.text) 170 | }; 171 | }; 172 | wordFileMetric := Trie.empty(); 173 | for (file in db.entries()) { 174 | indexFile(file.0, file.1) 175 | }; 176 | 177 | searchIndex := 178 | Trie.mapFilter, SearchResults>( 179 | wordFileMetric, 180 | func (word : Text, metrics : Trie.Trie) : ?SearchResults { 181 | ?Sequence.fromTrie( 182 | metrics, 183 | func (file_, metric_) : SearchResult { 184 | { file = file_.key; 185 | meta = readMeta(file_.key); 186 | metric = ?metric_; 187 | } 188 | } 189 | ) 190 | }); 191 | }; 192 | 193 | public func search(q : ?Text, maxResults : Nat) : [SearchResult] { 194 | refreshIndex(); 195 | switch q { 196 | case (?queryy) { 197 | let results = 198 | // to do -- sort by a requested metric (store in unsorted form, unbiased toward a metric) 199 | Sequence.iter( 200 | switch (Trie.find(searchIndex, 201 | { key = queryy ; 202 | hash = Text.hash(queryy) }, 203 | Text.equal)) { 204 | case null #empty; 205 | case (?t) t; 206 | }, 207 | #fwd 208 | ); 209 | Iter.toArray(IterExt.max(results, maxResults)) 210 | }; 211 | case null { 212 | // No query text. 213 | // So, dump all wordFileMetric that we have stored 214 | // (to do : filter by a time window; order by a timestamp) 215 | let dump = Buffer.Buffer(0); 216 | for ((fileId, file_) in db.entries()) { 217 | dump.add({ file = fileId ; 218 | meta = file_.meta ; 219 | metric = null }) 220 | }; 221 | dump.toArray() 222 | } 223 | } 224 | }; 225 | }; 226 | 227 | } 228 | -------------------------------------------------------------------------------- /example/SearchService/Service.mo: -------------------------------------------------------------------------------- 1 | import Search "Search"; 2 | import Debug "mo:base/Debug"; 3 | import Result "mo:base/Result"; 4 | 5 | actor { 6 | public type FileId = Search.FileId; 7 | 8 | flexible var search_ = Search.Search(); 9 | 10 | // do a keyword search, limited to at most maxResults 11 | public query func search(q : ?Text, maxResults : Nat) : async [Search.SearchResult] { 12 | search_.search(q, maxResults) 13 | }; 14 | 15 | /// create a new text sequence 16 | public func create(n : ?Text, m : ?Text, c : Text) : async FileId { 17 | search_.create(n, m, c) 18 | }; 19 | 20 | /// delete a text sequence 21 | public func delete(id : FileId) : async Bool { 22 | search_.delete(id) 23 | }; 24 | 25 | /// append to an existing text sequence 26 | public func addText(id : FileId, t : Text) : async Bool { 27 | search_.addText(id, t) 28 | }; 29 | 30 | /// read a text sequence 31 | public func readText(id : FileId) : async ?Text { 32 | search_.readText(id) 33 | }; 34 | 35 | /// read a text sequence slice 36 | public func readSlice(id : FileId, pos : Nat, size : Nat) : async ?Text { 37 | search_.readSlice(id, pos, size) 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /mops.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | base = "0.7.6" 3 | 4 | [dev-dependencies] 5 | crud = "https://github.com/matthewhammer/motoko-crud" 6 | -------------------------------------------------------------------------------- /src/IterExt.mo: -------------------------------------------------------------------------------- 1 | import Iter "mo:base/Iter"; 2 | 3 | module { 4 | public func max(i : Iter.Iter, maxCount : Nat) : Iter.Iter { 5 | object { 6 | var c = 0; 7 | public func next() : ?X { 8 | if (c < maxCount) { 9 | c += 1; 10 | i.next() 11 | } else { null } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Sequence.mo: -------------------------------------------------------------------------------- 1 | import Nat32 "mo:base/Nat32"; 2 | import Trie "mo:base/Trie"; 3 | import Iter "mo:base/Iter"; 4 | import List "mo:base/List"; 5 | import Stream "Stream"; 6 | 7 | /// Sequences with efficient, balanced representation 8 | module { 9 | 10 | // Sequences with efficient, balanced representation 11 | // 12 | // #### Summary: 13 | // 14 | // - Pure/persistent representation 15 | // - O(log n) sequence operations (get element, split, append) 16 | // - Supports Deque operations in worst-case O(log n) time (base library currently uses a Deque with worst-case linear time) 17 | // 18 | // ### Algorithm details: 19 | // 20 | // See chunky list representation from [Section 5 of this POPL 1989 paper](https://dl.acm.org/doi/10.1145/75277.75305). 21 | // 22 | 23 | 24 | public type Sequence = { 25 | #branch : Branch; 26 | #leaf : X; 27 | #empty; 28 | }; 29 | 30 | public type Branch = { 31 | left : Sequence; 32 | right : Sequence; 33 | level : Level; 34 | size : Nat; 35 | }; 36 | 37 | /// identify positions uniquely 38 | /// 39 | /// (generally not sequential numbers) 40 | public type Pos = Nat; 41 | 42 | /// see POPL paper 43 | public type Level = Nat32; 44 | 45 | public type Stream = Stream.Stream; 46 | 47 | public func empty() : Sequence { 48 | #empty 49 | }; 50 | 51 | public func make(data : X) : Sequence { 52 | #leaf(data) 53 | }; 54 | 55 | public func branch(l : Sequence, midLev : Level, r : Sequence) : Sequence { 56 | switch (l, r) { 57 | case (#empty, _) { r }; 58 | case (_, #empty) { l }; 59 | case (_, _) { 60 | let s = size(l) + size(r); 61 | #branch({ left = l ; level = midLev ; right = r ; size = s }) 62 | }; 63 | } 64 | }; 65 | 66 | // given an infinite stream of levels, we can append pairs of sequences forever : ) 67 | public func makeAppend(levels : Stream) : (Sequence, Sequence) -> Sequence { 68 | func append(s1 : Sequence, s2 : Sequence) : Sequence { 69 | appendLevel(s1, levels.next(), s2) 70 | }; 71 | append 72 | }; 73 | 74 | public func defaultAppend() : (Sequence, Sequence) -> Sequence { 75 | /// seed append function with a deterministic Bernoulli distribution 76 | makeAppend(Stream.Bernoulli.seedFrom(0)) 77 | }; 78 | 79 | public func appendLevel(s1 : Sequence, midLev : Nat32, s2 : Sequence) : Sequence { 80 | switch (s1, s2) { 81 | case (#empty, s2) s2; 82 | case (s1, #empty) s1; 83 | case (#leaf(x), #leaf(y)) { 84 | branch(s1, midLev, s2) 85 | }; 86 | case (#branch(b), #leaf(x)) { 87 | if (b.level < midLev) { 88 | branch(s1, midLev, s2) 89 | } else { 90 | branch(b.left, b.level, appendLevel(b.right, midLev, s2)) 91 | } 92 | }; 93 | case (#leaf(x), #branch(b)) { 94 | if (midLev > b.level) { 95 | branch(s1, midLev, s2) 96 | } else { 97 | branch(appendLevel(s1, midLev, b.left), b.level, b.right) 98 | } 99 | }; 100 | case (#branch(b1), #branch(b2)) { 101 | if (midLev > b1.level and midLev > b2.level) { 102 | // midLevel is the max; no further recursion into s1 or s2 103 | branch(s1, midLev, s2) 104 | } else { 105 | // b1 or b2's level is the max level of the three; descend into the "middle child" 106 | if (b1.level > b2.level) { 107 | branch(b1.left, 108 | b1.level, 109 | appendLevel(b1.right, midLev, s2) 110 | ) 111 | } else { 112 | branch(appendLevel(s1, midLev, b2.left), 113 | b2.level, 114 | b2.right 115 | ) 116 | } 117 | }; 118 | }; 119 | } 120 | }; 121 | 122 | public func fromArray(array : [X], levels : Stream) : Sequence { 123 | var s : Sequence = #empty; 124 | for (a in array.vals()) { 125 | s := appendLevel(s, levels.next(), make(a)); 126 | }; 127 | s 128 | }; 129 | 130 | public func size(s : Sequence) : Nat { 131 | switch s { 132 | case (#empty) 0; 133 | case (#leaf(x)) 1; 134 | case (#branch(b)) b.size; 135 | } 136 | }; 137 | 138 | public func get(s : Sequence, pos : Nat) : ?X { 139 | switch s { 140 | case (#empty) null; 141 | case (#leaf(x)) if (pos == 0) ?x else null; 142 | case (#branch(b)) { 143 | let lSize = size(b.left); 144 | if (pos < lSize) { 145 | get(b.left, pos) 146 | } else { 147 | get(b.right, pos - lSize : Nat) 148 | } 149 | }; 150 | } 151 | }; 152 | 153 | /// split sequence into a pair where the first has the given size 154 | /// 155 | /// for insufficient sequence values, the first result is the full input, and the second result is empty 156 | public func split(s : Sequence, size1 : Nat) : (Sequence, Sequence) { 157 | if (size1 == 0) { 158 | (#empty, s) 159 | } else if (size1 > size(s)) { 160 | (s, #empty) 161 | } else { 162 | switch s { 163 | case (#empty) { (#empty, #empty) }; 164 | case (#leaf(x)) { (#leaf(x), #empty) }; 165 | case (#branch(b)) { 166 | if (size1 == size(b.left)) { // perfect sized match on left 167 | (b.left, b.right) 168 | 169 | } else if (size1 < size(b.left)) { // left size is too big; split it 170 | let (s1, s2) = split(b.left, size1); 171 | let size1Diff = size1 - size(s1) : Nat; 172 | // append, re-using old branch node's level; return extra level (if any) 173 | (s1, appendLevel(s2, b.level, b.right)) 174 | 175 | } else { // left side too small; split right and append 176 | let size1Diff = size1 - size(b.left) : Nat; 177 | let (s1, s2) = split(b.right, size1Diff); 178 | (appendLevel(b.left, b.level, s1), s2) 179 | } 180 | } 181 | } 182 | } 183 | }; 184 | 185 | public func slice(s : Sequence, start : Nat, size : Nat) : (Sequence, Sequence, Sequence) { 186 | if (size == 0) { 187 | let (s1, s2) = split(s, start); 188 | (s1, empty(), s2) 189 | } else { 190 | let (s1, s23) = split(s, start); 191 | let (s2, s3) = split(s23, size); 192 | (s1, s2, s3) 193 | } 194 | }; 195 | 196 | public func pushBack(seq : Sequence, level : Level, data : X) : Sequence { 197 | appendLevel(seq, level, make(data)) 198 | }; 199 | 200 | public func popFront(seq : Sequence) : ?(X, Sequence) { 201 | if (size(seq) > 0) { 202 | let (emp, front, rest) = slice(seq, 0, 1); 203 | switch (emp, front) { 204 | case (#empty, #leaf(x)) ?(x, rest); 205 | case _ { assert false; loop { }}; 206 | } 207 | } else { 208 | null 209 | } 210 | }; 211 | 212 | public func popBack(seq : Sequence) : ?(Sequence, X) { 213 | let s = size(seq); 214 | if (s > 0) { 215 | let (rest, back, emp) = slice(seq, s - 1 : Nat, 1); 216 | switch (back, emp) { 217 | case (#leaf(x), #empty) ?(rest, x); 218 | case _ { assert false; loop { }}; 219 | } 220 | } else { 221 | null 222 | } 223 | }; 224 | 225 | public func peekBack(seq : Sequence) : ?X { 226 | if (size(seq) > 0) { 227 | get(seq, size(seq) - 1 : Nat) 228 | } else null 229 | }; 230 | 231 | public func peekFront(seq : Sequence) : ?X { 232 | get(seq, 0) 233 | }; 234 | 235 | public func pushFront(data : X, level : Level, seq : Sequence) : Sequence { 236 | appendLevel(make(data), level, seq) 237 | }; 238 | 239 | /// Perform [an associative, binary operation](https://en.wikipedia.org/wiki/Monoid#Definition) over the binary tree. 240 | /// 241 | /// Like monoid, but simpler (common input and output type). 242 | public func binaryOp( 243 | s : Sequence, 244 | zero : X, 245 | bop : (X, X) -> X 246 | ) : X { 247 | switch s { 248 | case (#empty) { zero }; 249 | case (#leaf(x)) { x }; 250 | case (#branch(b)) { 251 | bop( binaryOp(b.left, zero, bop), 252 | binaryOp(b.right, zero, bop) 253 | ) 254 | }; 255 | } 256 | }; 257 | 258 | /// Transform sequence into [monoid structure](https://en.wikipedia.org/wiki/Monoid#Definition) 259 | /// 260 | /// The monoid's id element stands in for empty sequences. 261 | /// 262 | /// The leaf function maps a leaf element to a monoid element. 263 | /// 264 | /// The function binOp gives the monoid's binary operation over elements. 265 | public func monoid( 266 | s : Sequence, 267 | id : Y, 268 | leaf : X -> Y, 269 | binOp : (Y, Y) -> Y 270 | ) : Y { 271 | switch s { 272 | case (#empty) { id }; 273 | case (#leaf(x)) { leaf(x) }; 274 | case (#branch(b)) { 275 | binOp(monoid(b.left, id, leaf, binOp), 276 | monoid(b.right, id, leaf, binOp) 277 | ) 278 | }; 279 | } 280 | }; 281 | 282 | /// Like monoid, except that branch function gets full branch node info 283 | public func foldUp( 284 | s : Sequence, 285 | empty : Y, 286 | leaf : X -> Y, 287 | branch : (Branch, Y, Y) -> Y 288 | ) : Y { 289 | switch s { 290 | case (#empty) { empty }; 291 | case (#leaf(x)) { leaf(x) }; 292 | case (#branch(b)) { 293 | branch( b, 294 | foldUp(b.left, empty, leaf, branch), 295 | foldUp(b.right, empty, leaf, branch) 296 | ) 297 | }; 298 | } 299 | }; 300 | 301 | /// Relate child order and iteration order. 302 | /// 303 | /// (all defined here, in code) 304 | public func branchChild(b : Branch, rank : {#fst; #snd}, dir : {#fwd; #bwd}) : Sequence { 305 | switch (rank, dir) { 306 | case (#fst, #fwd) { b.left }; 307 | case (#snd, #fwd) { b.right }; 308 | case (#fst, #bwd) { b.right }; 309 | case (#snd, #bwd) { b.left }; 310 | } 311 | }; 312 | 313 | /** 314 | Fold with directed sequential dependencies. 315 | 316 | Folds the binary tree into an accumulated value, forward or backward. 317 | 318 | Each function is optional, and behaves like the identity function when `null`. 319 | 320 | Branch case accumulates across five steps: 321 | - two subtrees of the branch, and 322 | - three points surrounding them (pre, mid, post branch). 323 | 324 | Leaf case accepts an accumulator, initially `empty`. 325 | */ 326 | public func foldDir( 327 | s : Sequence, 328 | dir : {#fwd; #bwd}, 329 | empty : Y, 330 | leaf : ?((Y, X) -> Y), 331 | preBranch : ?((Y, Branch) -> Y), 332 | midBranch : ?((Y, Branch) -> Y), 333 | postBranch : ?((Y, Branch) -> Y) 334 | ) : Y { 335 | switch s { 336 | case (#empty) { empty }; 337 | case (#leaf(x)) { 338 | switch leaf { 339 | case null empty; 340 | case (?leaf) leaf(empty, x) 341 | } 342 | }; 343 | case (#branch(b)) { 344 | // accumulate in five steps: 345 | // - two subtrees of the branch, and 346 | // - three points surrounding them (pre, mid, post branch). 347 | let a1 = switch (preBranch, dir, postBranch) { 348 | case (null, #fwd, _) empty; 349 | case (_, #bwd, null) empty; 350 | case (?pb, #fwd, _) pb(empty, b); 351 | case (_, #bwd, ?pb) pb(empty, b); 352 | }; 353 | let a2 = foldDir(branchChild(b, #fst, dir), 354 | dir, a1, leaf, 355 | preBranch, midBranch, postBranch); 356 | let a3 = switch midBranch { 357 | case null a2; 358 | case (?mb) mb(a2, b); 359 | }; 360 | let a4 = foldDir(branchChild(b, #snd, dir), 361 | dir, a3, leaf, 362 | preBranch, midBranch, postBranch); 363 | let a5 = switch (preBranch, dir, postBranch) { 364 | case (null, #bwd, _) empty; 365 | case (_, #fwd, null) empty; 366 | case (?pb, #bwd, _) pb(a4, b); 367 | case (_, #fwd, ?pb) pb(a4, b); 368 | }; 369 | a5 370 | }; 371 | } 372 | }; 373 | 374 | type IterRep = List.List>; 375 | 376 | public func iter(s : Sequence, dir : {#fwd; #bwd}) : Iter.Iter { 377 | object { 378 | var seqs : IterRep = ?(s, null); 379 | public func next() : ?X { 380 | switch (dir, seqs) { 381 | case (_, null) { null }; 382 | case (_, ?(#empty, ts)) { seqs := ts; next() }; 383 | case (_, ?(#leaf(x), ts)) { seqs := ts; ?x }; 384 | case (#fwd, ?(#branch(b), ts)) { seqs := ?(b.left, ?(b.right, ts)); next() }; 385 | case (#bwd, ?(#branch(b), ts)) { seqs := ?(b.right, ?(b.left, ts)); next() }; 386 | } 387 | } 388 | } 389 | }; 390 | 391 | public func fromAssocList( 392 | l : List.List<(Trie.Key, V)>, 393 | kv : (Trie.Key, V) -> T) : Sequence { 394 | switch l { 395 | case null { #empty }; 396 | case (?(h,t)) { branch(make(kv h), 0, fromAssocList(t, kv)) }; 397 | } 398 | }; 399 | 400 | public func fromTrie( 401 | t : Trie.Trie, 402 | kv : (Trie.Key, V) -> T) : Sequence { 403 | switch t { 404 | case (#empty) #empty; 405 | case (#leaf(leaf)) fromAssocList(leaf.keyvals, kv); 406 | case (#branch(b)) { 407 | // size (of original sub-trie) can serve as a valid level 408 | branch(fromTrie(b.left, kv), 409 | Nat32.fromNat(b.size), 410 | fromTrie(b.right, kv)) 411 | } 412 | } 413 | }; 414 | 415 | /// separate into (nested) sub-sequences by looking for sub-sequence delimiter elements (omitted from output) 416 | public func tokens(s : Sequence, isDelim : X -> Bool, levels : Stream) : Sequence> { 417 | var outer : Sequence> = empty(); 418 | var inner : Sequence = empty(); 419 | for (x in iter(s, #fwd)) { 420 | if (isDelim(x)) { 421 | outer := pushBack(outer, levels.next(), inner); 422 | inner := empty(); 423 | } 424 | else { 425 | inner := pushBack(inner, levels.next(), x) 426 | } 427 | }; 428 | if (size(inner) > 0) { 429 | outer := pushBack(outer, levels.next(), inner) 430 | }; 431 | outer 432 | }; 433 | 434 | } 435 | -------------------------------------------------------------------------------- /src/Sort.mo: -------------------------------------------------------------------------------- 1 | import Order "mo:base/Order"; 2 | import Iter "mo:base/Iter"; 3 | import Sequence "Sequence"; 4 | import Stream "Stream"; 5 | import Cell "StreamCell"; 6 | import Debug "mo:base/Debug"; 7 | 8 | /// Demand-driven (lazy) sort. 9 | /// 10 | /// Input is a sequence, and output is an iterator. 11 | /// 12 | /// Sorting only occurs to the extent that the iterator is used. 13 | /// 14 | /// Common cases (are all optimal, in terms total comparisons): 15 | /// 16 | /// - Demand zero things (edge case): No comparisons. 17 | /// - Demand 1 thing: O(n) comparisons. 18 | /// - Demand 2 things: More comparisons, but still O(n) total. 19 | /// - Demand all things: O(log n) comparisons. 20 | /// 21 | module { 22 | 23 | public type Iter = Iter.Iter; 24 | public type Seq = Sequence.Sequence; 25 | public type Cell = Cell.Cell; 26 | 27 | public class Sort(toText : X -> Text, 28 | compare : (X, X) -> Order.Order) { 29 | 30 | /// compiler-issue: Type error if I inline this definition. 31 | func cellMake(x : X) : Cell = Cell.make(x); 32 | 33 | /// Create a "merge cell" from two cells 34 | public func merge(s1 : Cell, s2 : Cell) : Cell { 35 | switch (s1, s2) { 36 | case (_, null) s1; 37 | case (null, _) s2; 38 | case (?c1, ?c2) { 39 | switch (compare(c1.0, c2.0)) { 40 | case (#less or #equal) { 41 | ?(c1.0, ?(func () : Cell { 42 | merge( 43 | Cell.laterNow(s1), 44 | s2) 45 | }) 46 | ) 47 | }; 48 | case (#greater) { 49 | ?(c2.0, ?(func () : Cell { 50 | merge( 51 | Cell.laterNow(s2), 52 | s1) 53 | }) 54 | ) 55 | }; 56 | } 57 | } 58 | } 59 | }; 60 | 61 | /// Create a "sort cell" from a sequence. 62 | public func sort(s : Seq) : Cell { 63 | Sequence.monoid>( 64 | s, 65 | null, 66 | cellMake, 67 | merge 68 | ); 69 | }; 70 | 71 | /// Iterate the sorted sequence. 72 | public func iter(s : Seq) : Iter { 73 | object { 74 | // no sorting until first invocation of next 75 | var iter : ?Iter = null; 76 | public func next() : ?X { 77 | switch iter { 78 | case null { 79 | let i = Cell.toIter(sort(s)); 80 | iter := ?i; 81 | i.next() 82 | }; 83 | case (?i) { i.next() }; 84 | } 85 | } 86 | } 87 | }; 88 | 89 | }; 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/Stream.mo: -------------------------------------------------------------------------------- 1 | import Hash "mo:base/Hash"; 2 | import Iter "mo:base/Iter"; 3 | import Nat32 "mo:base/Nat32"; 4 | import Debug "mo:base/Debug"; 5 | 6 | module { 7 | /// (Infinite) streams (no ending). 8 | public type Stream = { next : () -> X }; 9 | 10 | /// Transform infinite iterator 11 | public func fromIter(iter : Iter.Iter) : Stream { 12 | object { 13 | public func next() : X { 14 | switch (iter.next()) { 15 | case null { assert false; loop { } }; 16 | case (?x) { x } 17 | } 18 | } 19 | } 20 | }; 21 | 22 | /// Stream of numbers drawn from a [Bernoulli_distribution](https://en.wikipedia.org/wiki/Bernoulli_distribution) 23 | public module Bernoulli { 24 | public type Value = Nat32; 25 | public func seedFrom(seed : Nat) : Stream { 26 | object { 27 | func hash() : Nat32 { 28 | Hash.hash(nextNum + 1); // avoid zero (hash is also zero) 29 | }; 30 | var nextNum : Nat = seed; 31 | var nextHash : Nat32 = hash(); 32 | public func next() : Value { 33 | let level = Nat32.bitcountTrailingZero(nextHash); 34 | nextNum := nextNum + 1; 35 | nextHash := hash(); 36 | Nat32.fromNat(Nat32.toNat(level)); 37 | }; 38 | } 39 | } 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/StreamCell.mo: -------------------------------------------------------------------------------- 1 | import Iter "mo:base/Iter"; 2 | import Debug "mo:base/Debug"; 3 | 4 | module { 5 | /// Partially-inspected, finite stream representation. 6 | /// 7 | /// Unlike Stream and Iter, represents an _inspected_ stream head/tail. 8 | public type Cell = ?(X, ?(() -> Cell)); 9 | 10 | /// Construct Cell from existing head and tail 11 | public func cell(head : X, tail : Cell) : Cell = 12 | ?(head, ?(func () : Cell { tail })); 13 | 14 | /// Construct one-value Cell from head (only) 15 | public func make(head : X) : Cell = ?(head, null); 16 | 17 | /// Construct Cell from existing data "now" and data "later" 18 | public func nowLater(now : X, later : () -> Cell) : Cell = 19 | ?(now, ?later); 20 | 21 | /// Advance a cell's "later" half, getting it "now" 22 | public func now(later : ?(() -> Cell)) : Cell { 23 | switch later { 24 | case null null; 25 | case (?f) { f() }; 26 | } 27 | }; 28 | 29 | /// Get the later part of the cell, and advance to "now" 30 | public func laterNow(c : Cell) : Cell { 31 | switch c { 32 | case null null; 33 | case (?c) { now(c.1) } 34 | } 35 | }; 36 | 37 | /// Cell from (more lazy) Iter type. 38 | /// 39 | /// Necessarily, tries to advance iterator, but (initially) just once. 40 | public func fromIter(i : Iter.Iter) : Cell { 41 | func nextCell() : Cell { 42 | switch (i.next()) { 43 | case null null; 44 | case (?now) nowLater(now, nextCell); 45 | }}; 46 | nextCell() 47 | }; 48 | 49 | /// Iter from (less lazy) Cell type. 50 | public func toIter(c : Cell) : Iter.Iter { 51 | object { 52 | var cell = c; 53 | public func next() : ?X { 54 | switch cell { 55 | case null null; 56 | case (?c) { 57 | cell := now(c.1); 58 | ?c.0 59 | }; 60 | } 61 | }; 62 | } 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/Text.mo: -------------------------------------------------------------------------------- 1 | import Text "mo:base/Text"; 2 | import Seq "Sequence"; 3 | import Stream "Stream"; 4 | 5 | module { 6 | public type LevelStream = Stream.Stream; 7 | 8 | public type TextSeq = Seq.Sequence; 9 | public type TextSeqSeq = Seq.Sequence; 10 | 11 | public type Token = { pos : Nat; text : Text }; // (original raw position, token text) 12 | public type TokenSeq = Seq.Sequence; 13 | 14 | public type Pattern = Text.Pattern; 15 | 16 | public func toText(s : TextSeq) : Text { 17 | Seq.binaryOp(s, "", Text.concat) 18 | }; 19 | 20 | public func flatten(s2 : TextSeqSeq) : TextSeq { 21 | Seq.foldUp( 22 | s2, 23 | Seq.empty(), 24 | func (t : TextSeq) : TextSeq { #leaf(toText(t)) }, 25 | func (b, l, r) : TextSeq { Seq.branch(l, b.level, r) } 26 | ) 27 | }; 28 | 29 | public func removeSlice(s : TextSeq, pos : Nat, size : Nat) : ?(TextSeq, TextSeq, TextSeq) { 30 | let (s1, s23) = Seq.split(s, pos + size); 31 | let (s2, s3) = Seq.split(s23, size); 32 | ?(s1, s2, s3) 33 | }; 34 | 35 | /// separate text by whitespace, and include all sub-sequences 36 | /// (whitespace and non-whitespace) 37 | /// in the output (a sequence of text sequences). 38 | public func tokens(s : TextSeq, p : Pattern) : TokenSeq { 39 | tokensRec(s, p, 0) 40 | }; 41 | 42 | func tokensRec(s : TextSeq, p : Pattern, pos : Nat) : TokenSeq { 43 | switch s { 44 | case (#empty) #empty; 45 | case (#leaf(text)) { 46 | var seq : TokenSeq = Seq.empty(); 47 | var pos_ = pos; 48 | for (token in Text.tokens(text, p)) { 49 | // (compiler-question(dfx 6.7): why cannot infer type arg here?) 50 | seq := Seq.branch(Seq.make({pos=pos_; text=token}), 0, seq); 51 | pos_ += Text.size(token); 52 | }; 53 | seq 54 | }; 55 | case (#branch(b)) { 56 | Seq.branch(tokensRec(b.left, p, pos), 57 | b.level, 58 | tokensRec(b.right, p, pos + Seq.size(b.left))) 59 | }; 60 | } 61 | }; 62 | 63 | /// Operations that use Sequence.append (via a stateful stream of levels) 64 | public class WithLevels(levels : LevelStream) { 65 | 66 | let append = Seq.makeAppend(levels); 67 | 68 | public func putSlice(s1 : TextSeq, pos : Nat, s2 : TextSeq) : TextSeq { 69 | // to do 70 | #empty 71 | }; 72 | 73 | public func deleteSlice(s : TextSeq, pos : Nat, size : Nat) : TextSeq { 74 | switch (removeSlice(s, pos, size)) { 75 | case null s; 76 | case (?(s1, _, s3)) append(s1, s3); 77 | }; 78 | }; 79 | 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/TrieExt.mo: -------------------------------------------------------------------------------- 1 | import Trie "mo:base/Trie"; 2 | import Iter "mo:base/Iter"; 3 | import List "mo:base/List"; 4 | 5 | module { 6 | /// Returns an [`Iter`](Iter.html#type.Iter) over the entries. 7 | /// 8 | /// Each iterator gets a _persistent view_ of the mapping, independent of concurrent updates to the iterated map. 9 | public func entries(trie : Trie.Trie) : Iter.Iter<(K,V)> = object { 10 | var stack = ?(trie, null) : List.List>; 11 | public func next() : ?(K,V) { 12 | switch stack { 13 | case null { null }; 14 | case (?(trie, stack2)) { 15 | switch trie { 16 | case (#empty) { 17 | stack := stack2; 18 | next() 19 | }; 20 | case (#leaf({keyvals=null})) { 21 | stack := stack2; 22 | next() 23 | }; 24 | case (#leaf({size=c; keyvals=?((k,v),kvs)})) { 25 | stack := ?(#leaf({size=c-1; keyvals=kvs}), stack2); 26 | ?(k.key, v) 27 | }; 28 | case (#branch(br)) { 29 | stack := ?(br.left, ?(br.right, stack2)); 30 | next() 31 | }; 32 | } 33 | } 34 | } 35 | } 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /test/Main.mo: -------------------------------------------------------------------------------- 1 | import Sequence "../src/Sequence"; 2 | import Stream "../src/Stream"; 3 | 4 | import Sort "../src/Sort"; 5 | 6 | import TextSeq "../src/Text"; 7 | 8 | import Buffer "mo:base/Buffer"; 9 | import Debug "mo:base/Debug"; 10 | import Nat "mo:base/Nat"; 11 | import Char "mo:base/Char"; 12 | import Text "mo:base/Text"; 13 | import Iter "mo:base/Iter"; 14 | 15 | actor { 16 | 17 | public type Sequence = Sequence.Sequence; 18 | public type Buffer = Buffer.Buffer; 19 | 20 | let append = Sequence.defaultAppend(); 21 | 22 | /// Event type to illustrate "event log" design pattern, perhaps silly here. 23 | public type Event = { 24 | #build; 25 | #sum; 26 | #min; 27 | #max; 28 | #equalIter; 29 | #bisimulationTest; 30 | #testPop; 31 | #testSlice; 32 | #testSort; 33 | #testTokens; 34 | #selfTest; 35 | }; 36 | 37 | /// Event log type to illustrate "event log" design pattern. 38 | public type EventLog = Sequence; 39 | 40 | /// Event log design pattern. 41 | stable var eventLog : EventLog = Sequence.empty(); 42 | stable var eventCount : Nat = 0; 43 | 44 | /// log the given event kind. 45 | func logEvent(e : Event) { 46 | eventLog := append(eventLog, Sequence.make(e)); 47 | eventCount += 1; 48 | }; 49 | 50 | func build(arr : [X]) : (Sequence, Buffer) { 51 | logEvent(#build); 52 | let b = Buffer.Buffer(0); 53 | for (x in arr.vals()) { 54 | b.add(x); 55 | }; 56 | let levels = Stream.Bernoulli.seedFrom(0); 57 | let s = Sequence.fromArray(arr, levels); 58 | (s, b) 59 | }; 60 | 61 | func sum(x : Sequence) : Nat { 62 | logEvent(#sum); 63 | Sequence.binaryOp(x, 0, Nat.add); 64 | }; 65 | 66 | func min(x : Sequence) : Nat { 67 | logEvent(#min); 68 | Sequence.binaryOp(x, 0, Nat.min); 69 | }; 70 | 71 | /// Returns the maximum value, or null if: 72 | /// - The sequence is empty. 73 | // - The sequence contains a null value. 74 | /// 75 | /// E.g., Consider `?Nat` to be encoding `Nat` with special element (`null`), representing "bottom". 76 | func max(x : Sequence) : ?Nat { 77 | logEvent(#max); 78 | Sequence.monoid( 79 | x, 80 | null, 81 | func(x : ?Nat) : ?Nat { x }, 82 | func (x : ?Nat, y : ?Nat) : ?Nat { 83 | switch (x, y) { 84 | case (null, _) null; 85 | case (_, null) null; 86 | case (?x, ?y) ?Nat.max(x, y) 87 | } 88 | } 89 | ) 90 | }; 91 | 92 | func equalIter(i : Iter.Iter, j : Iter.Iter, 93 | text : X -> Text, equal : (X, X) -> Bool) : Bool { 94 | logEvent(#equalIter); 95 | Debug.print "test equality:"; 96 | loop { 97 | let (x, y) = (i.next(), j.next()); 98 | switch (x, y) { 99 | case (null, null) { 100 | Debug.print " EQUAL."; 101 | return true 102 | }; 103 | case (?x, ?y) { 104 | if (not (equal(x, y))) { 105 | Debug.print " NOT equal: distinct vals."; // to do: more info? 106 | Debug.print (text x); 107 | Debug.print (text y); 108 | return false; 109 | }; 110 | }; 111 | case (?_, _) { 112 | Debug.print " NOT equal: first too long, or second too short"; 113 | return false 114 | }; 115 | case (_, ?_) { 116 | Debug.print " NOT equal: first too short, or second too long"; 117 | return false 118 | }; 119 | } 120 | } 121 | }; 122 | 123 | func equal(x : Sequence, y : Buffer) : Bool { 124 | let i = Sequence.iter(x, #fwd); 125 | let j = y.vals(); 126 | equalIter(i, j, Nat.toText, Nat.equal) 127 | }; 128 | 129 | func bisimulationTest(x : Sequence, y : Buffer) { 130 | logEvent(#bisimulationTest); 131 | assert equal(x, y); 132 | 133 | Debug.print "sequence append"; 134 | let xx = append(x, x); 135 | Debug.print "buffer append"; 136 | y.append(y.clone()); 137 | assert equal(xx, y); 138 | 139 | Debug.print "sequence append"; 140 | let x4 = append(xx, xx); 141 | Debug.print "buffer append"; 142 | y.append(y.clone()); 143 | assert equal(x4, y); 144 | 145 | Debug.print "DUMP sequence and array"; 146 | Debug.print (debug_show x4); 147 | Debug.print (debug_show y.toArray()); 148 | }; 149 | 150 | func testPop() { 151 | logEvent(#testPop); 152 | Debug.print "sequence popFront"; 153 | let first = 10; 154 | let last = 11; 155 | let (s, _) = build([first, 4, 2, 0, 1, 8, 0, 2, 3, 1, 0, last]); 156 | switch (Sequence.popFront(s)) { 157 | case (?(f, rest)) { assert f == first; }; 158 | case _ { assert false; loop { }}; 159 | }; 160 | Debug.print "sequence popBack"; 161 | switch (Sequence.popBack(s)) { 162 | case (?(_, eleven)) { assert eleven == 11; }; 163 | case _ { assert false; loop { }}; 164 | }; 165 | }; 166 | 167 | func testSlice() { 168 | logEvent(#testSlice); 169 | Debug.print "sequence slice"; 170 | let (s, _) = build([1, 2, 3, 4, 5, 6, 7, 8, 171 | 9, 10, 11, 12, 13, 14, 15, 16]); 172 | let (s1, s2, s3) = Sequence.slice(s, 5, 5); 173 | let (_, b2) = build([6, 7, 8, 9, 10]); 174 | assert equal(s2, b2); 175 | }; 176 | 177 | func testSort() { 178 | logEvent(#testSort); 179 | Debug.print "sequence sort"; 180 | let (s, _) = build([10, 4, 2, 0, 1, 8, 0, 2, 3, 1, 0, 2]); 181 | let (_, b) = build([0, 0, 0, 1, 1, 2, 2, 2, 3, 4, 8, 10]); 182 | let sort = Sort.Sort(Nat.toText, Nat.compare); 183 | assert equalIter(sort.iter(s), b.vals(), Nat.toText, Nat.equal) 184 | }; 185 | 186 | func testTokens() { 187 | logEvent(#testTokens); 188 | Debug.print "sequence tokens"; 189 | let (s1, _) = build(["A", " ", "c", "a", "t", " ", "a", "n", "d", " ", "h", "a", "t", "."]); 190 | let (s2, _) = build(["A", "cat", "and", "hat."]); 191 | let levels = Stream.Bernoulli.seedFrom(0); 192 | let s12 = Sequence.tokens(s1, func (t : Text) : Bool { t == " " }, levels); 193 | let s3 = TextSeq.flatten(s12); 194 | Debug.print (debug_show s1); 195 | Debug.print (debug_show s2); 196 | Debug.print (debug_show s12); 197 | Debug.print (debug_show s3); 198 | assert equalIter(Sequence.iter(s2, #fwd), Sequence.iter(s3, #fwd), func (t: Text) : Text { t }, Text.equal) 199 | }; 200 | 201 | public func selfTest() { 202 | logEvent(#selfTest); 203 | Debug.print "BEGIN bi-simulation of Sequence versus Buffer modules"; 204 | let (s0, b0) = build([1, 2, 3, 4, 5, 6, 7, 8, 205 | 9, 10, 11, 12, 13, 14, 15, 16]); 206 | bisimulationTest(s0, b0); 207 | testPop(); 208 | testSlice(); 209 | testSort(); 210 | testTokens(); 211 | Debug.print "SUCCESS"; 212 | }; 213 | 214 | } 215 | --------------------------------------------------------------------------------