├── .gitignore ├── LICENSE ├── README.md ├── adapters ├── csv_adapter_test.go ├── csv_load.go ├── csv_write.go ├── sql_load.go ├── sql_write.go ├── struct_load.go ├── struct_load_test.go └── utils.go ├── aggs ├── agg_numeric.go ├── agg_others.go └── index.go ├── backend ├── column.go ├── columnar_backend.go └── index.go ├── conds ├── comaparision_test.go ├── comparision.go ├── cond_joins.go ├── index.go ├── logical.go └── logical_test.go ├── dataframe ├── columnar_dataframe.go ├── default_aggregatable.go ├── default_aggregatable_test.go ├── default_filterable.go ├── default_filterable_test.go ├── default_joinable.go ├── default_joinable_test.go ├── default_printable.go ├── default_transformable.go ├── default_transformable_test.go ├── functions.go └── index.go ├── example.go ├── examples ├── order_details.csv ├── orders.csv └── products.csv ├── go.mod ├── go.sum └── types ├── boolean.go ├── boolean_test.go ├── datetime.go ├── datetime_test.go ├── decimal.go ├── decimal_test.go ├── index.go ├── integer.go ├── integer_test.go ├── missing.go ├── string.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/go,linux,macos,windows,intellij,intellij+all,intellij+iml 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=go,linux,macos,windows,intellij,intellij+all,intellij+iml 4 | 5 | ### Go ### 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | ### Go Patch ### 23 | /vendor/ 24 | /Godeps/ 25 | 26 | ### Intellij ### 27 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 28 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 29 | 30 | # User-specific stuff 31 | .idea/**/workspace.xml 32 | .idea/**/tasks.xml 33 | .idea/**/usage.statistics.xml 34 | .idea/**/dictionaries 35 | .idea/**/shelf 36 | 37 | # Generated files 38 | .idea/**/contentModel.xml 39 | 40 | # Sensitive or high-churn files 41 | .idea/**/dataSources/ 42 | .idea/**/dataSources.ids 43 | .idea/**/dataSources.local.xml 44 | .idea/**/sqlDataSources.xml 45 | .idea/**/dynamic.xml 46 | .idea/**/uiDesigner.xml 47 | .idea/**/dbnavigator.xml 48 | 49 | # Gradle 50 | .idea/**/gradle.xml 51 | .idea/**/libraries 52 | 53 | # Gradle and Maven with auto-import 54 | # When using Gradle or Maven with auto-import, you should exclude module files, 55 | # since they will be recreated, and may cause churn. Uncomment if using 56 | # auto-import. 57 | # .idea/artifacts 58 | # .idea/compiler.xml 59 | # .idea/jarRepositories.xml 60 | # .idea/modules.xml 61 | # .idea/*.iml 62 | # .idea/modules 63 | # *.iml 64 | # *.ipr 65 | 66 | # CMake 67 | cmake-build-*/ 68 | 69 | # Mongo Explorer plugin 70 | .idea/**/mongoSettings.xml 71 | 72 | # File-based project format 73 | *.iws 74 | 75 | # IntelliJ 76 | out/ 77 | 78 | # mpeltonen/sbt-idea plugin 79 | .idea_modules/ 80 | 81 | # JIRA plugin 82 | atlassian-ide-plugin.xml 83 | 84 | # Cursive Clojure plugin 85 | .idea/replstate.xml 86 | 87 | # Crashlytics plugin (for Android Studio and IntelliJ) 88 | com_crashlytics_export_strings.xml 89 | crashlytics.properties 90 | crashlytics-build.properties 91 | fabric.properties 92 | 93 | # Editor-based Rest Client 94 | .idea/httpRequests 95 | 96 | # Android studio 3.1+ serialized cache file 97 | .idea/caches/build_file_checksums.ser 98 | 99 | ### Intellij Patch ### 100 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 101 | 102 | # *.iml 103 | # modules.xml 104 | # .idea/misc.xml 105 | # *.ipr 106 | 107 | # Sonarlint plugin 108 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 109 | .idea/**/sonarlint/ 110 | 111 | # SonarQube Plugin 112 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 113 | .idea/**/sonarIssues.xml 114 | 115 | # Markdown Navigator plugin 116 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 117 | .idea/**/markdown-navigator.xml 118 | .idea/**/markdown-navigator-enh.xml 119 | .idea/**/markdown-navigator/ 120 | 121 | # Cache file creation bug 122 | # See https://youtrack.jetbrains.com/issue/JBR-2257 123 | .idea/$CACHE_FILE$ 124 | 125 | # CodeStream plugin 126 | # https://plugins.jetbrains.com/plugin/12206-codestream 127 | .idea/codestream.xml 128 | 129 | ### Intellij+all ### 130 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 131 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 132 | 133 | # User-specific stuff 134 | 135 | # Generated files 136 | 137 | # Sensitive or high-churn files 138 | 139 | # Gradle 140 | 141 | # Gradle and Maven with auto-import 142 | # When using Gradle or Maven with auto-import, you should exclude module files, 143 | # since they will be recreated, and may cause churn. Uncomment if using 144 | # auto-import. 145 | # .idea/artifacts 146 | # .idea/compiler.xml 147 | # .idea/jarRepositories.xml 148 | # .idea/modules.xml 149 | # .idea/*.iml 150 | # .idea/modules 151 | # *.iml 152 | # *.ipr 153 | 154 | # CMake 155 | 156 | # Mongo Explorer plugin 157 | 158 | # File-based project format 159 | 160 | # IntelliJ 161 | 162 | # mpeltonen/sbt-idea plugin 163 | 164 | # JIRA plugin 165 | 166 | # Cursive Clojure plugin 167 | 168 | # Crashlytics plugin (for Android Studio and IntelliJ) 169 | 170 | # Editor-based Rest Client 171 | 172 | # Android studio 3.1+ serialized cache file 173 | 174 | ### Intellij+all Patch ### 175 | # Ignores the whole .idea folder and all .iml files 176 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 177 | 178 | .idea/ 179 | 180 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 181 | 182 | *.iml 183 | modules.xml 184 | .idea/misc.xml 185 | *.ipr 186 | 187 | # Sonarlint plugin 188 | .idea/sonarlint 189 | 190 | ### Intellij+iml ### 191 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 192 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 193 | 194 | # User-specific stuff 195 | 196 | # Generated files 197 | 198 | # Sensitive or high-churn files 199 | 200 | # Gradle 201 | 202 | # Gradle and Maven with auto-import 203 | # When using Gradle or Maven with auto-import, you should exclude module files, 204 | # since they will be recreated, and may cause churn. Uncomment if using 205 | # auto-import. 206 | # .idea/artifacts 207 | # .idea/compiler.xml 208 | # .idea/jarRepositories.xml 209 | # .idea/modules.xml 210 | # .idea/*.iml 211 | # .idea/modules 212 | # *.iml 213 | # *.ipr 214 | 215 | # CMake 216 | 217 | # Mongo Explorer plugin 218 | 219 | # File-based project format 220 | 221 | # IntelliJ 222 | 223 | # mpeltonen/sbt-idea plugin 224 | 225 | # JIRA plugin 226 | 227 | # Cursive Clojure plugin 228 | 229 | # Crashlytics plugin (for Android Studio and IntelliJ) 230 | 231 | # Editor-based Rest Client 232 | 233 | # Android studio 3.1+ serialized cache file 234 | 235 | ### Intellij+iml Patch ### 236 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 237 | 238 | 239 | ### Linux ### 240 | *~ 241 | 242 | # temporary files which can be created if a process still has a handle open of a deleted file 243 | .fuse_hidden* 244 | 245 | # KDE directory preferences 246 | .directory 247 | 248 | # Linux trash folder which might appear on any partition or disk 249 | .Trash-* 250 | 251 | # .nfs files are created when an open file is removed but is still being accessed 252 | .nfs* 253 | 254 | ### macOS ### 255 | # General 256 | .DS_Store 257 | .AppleDouble 258 | .LSOverride 259 | 260 | # Icon must end with two \r 261 | Icon 262 | 263 | # Thumbnails 264 | ._* 265 | 266 | # Files that might appear in the root of a volume 267 | .DocumentRevisions-V100 268 | .fseventsd 269 | .Spotlight-V100 270 | .TemporaryItems 271 | .Trashes 272 | .VolumeIcon.icns 273 | .com.apple.timemachine.donotpresent 274 | 275 | # Directories potentially created on remote AFP share 276 | .AppleDB 277 | .AppleDesktop 278 | Network Trash Folder 279 | Temporary Items 280 | .apdisk 281 | 282 | ### Windows ### 283 | # Windows thumbnail cache files 284 | Thumbs.db 285 | Thumbs.db:encryptable 286 | ehthumbs.db 287 | ehthumbs_vista.db 288 | 289 | # Dump file 290 | *.stackdump 291 | 292 | # Folder config file 293 | [Dd]esktop.ini 294 | 295 | # Recycle Bin used on file shares 296 | $RECYCLE.BIN/ 297 | 298 | # Windows Installer files 299 | *.cab 300 | *.msi 301 | *.msix 302 | *.msm 303 | *.msp 304 | 305 | # Windows shortcuts 306 | *.lnk 307 | 308 | # End of https://www.toptal.com/developers/gitignore/api/go,linux,macos,windows,intellij,intellij+all,intellij+iml 309 | /.idea/ 310 | -------------------------------------------------------------------------------- /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 | # Data Frames for Go 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/AdikaStyle/go-df)](https://goreportcard.com/report/github.com/AdikaStyle/go-df) 3 | 4 | 5 | An effort to make a fully featured data frame/wrangling library 6 | 7 | ## Example: 8 | ```go 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | "github.com/AdikaStyle/go-df/adapters" 14 | . "github.com/AdikaStyle/go-df/aggs" 15 | "github.com/AdikaStyle/go-df/backend" 16 | . "github.com/AdikaStyle/go-df/conds" 17 | . "github.com/AdikaStyle/go-df/dataframe" 18 | . "github.com/AdikaStyle/go-df/types" 19 | "os" 20 | ) 21 | 22 | func main() { 23 | orders := adapters.LoadCSV("examples/order_details.csv", '|') 24 | 25 | products := adapters.LoadCSV("examples/products.csv", '|'). 26 | Select("ProductID", "ProductName") 27 | 28 | view := orders. 29 | Group(By{"ProductID"}, Aggs{"UnitPrice": Sum(), "Quantity": Sum()}). 30 | OrderBy("ProductID", backend.Asc). 31 | InnerJoin(products, On("ProductID")). 32 | Filter(And( 33 | Gte("UnitPrice", Decimal(600)), 34 | Gt("Quantity", Decimal(1000))), 35 | ). 36 | Print(os.Stdout) 37 | 38 | left, right := view.Split(Gte("ProductID", Decimal(30))) 39 | 40 | fmt.Printf("\nLeft side of split:\n") 41 | left.Print(os.Stdout) 42 | 43 | fmt.Printf("\nRight side of split:\n") 44 | right.Print(os.Stdout) 45 | } 46 | ``` 47 | 48 | Will print the following: 49 | ``` 50 | +--------------------+----------+-----------+------------------------+ 51 | | UnitPrice | Quantity | ProductID | ProductName | 52 | +--------------------+----------+-----------+------------------------+ 53 | | 786.5999999999999 | 1057 | 2 | Chang | 54 | | 704.2000000000004 | 1158 | 16 | Pavlova | 55 | | 706.2999999999995 | 1103 | 40 | Boston Crab Meat | 56 | | 1770.7999999999997 | 1263 | 56 | Gnocchi di nonna Alice | 57 | | 2761 | 1496 | 59 | Raclette Courdavault | 58 | | 1638.8 | 1577 | 60 | Camembert Pierrot | 59 | | 2227.7999999999993 | 1083 | 62 | Tarte au sucre | 60 | | 829.8999999999999 | 1057 | 71 | Flotemysost | 61 | +--------------------+----------+-----------+------------------------+ 62 | Dataframe has 4 columns and 8 rows in total. 63 | 64 | Left side of split: 65 | +--------------------+----------+-----------+------------------------+ 66 | | UnitPrice | Quantity | ProductID | ProductName | 67 | +--------------------+----------+-----------+------------------------+ 68 | | 706.2999999999995 | 1103 | 40 | Boston Crab Meat | 69 | | 1770.7999999999997 | 1263 | 56 | Gnocchi di nonna Alice | 70 | | 2761 | 1496 | 59 | Raclette Courdavault | 71 | | 1638.8 | 1577 | 60 | Camembert Pierrot | 72 | | 2227.7999999999993 | 1083 | 62 | Tarte au sucre | 73 | | 829.8999999999999 | 1057 | 71 | Flotemysost | 74 | +--------------------+----------+-----------+------------------------+ 75 | Dataframe has 4 columns and 6 rows in total. 76 | 77 | Right side of split: 78 | +-------------------+----------+-----------+-------------+ 79 | | UnitPrice | Quantity | ProductID | ProductName | 80 | +-------------------+----------+-----------+-------------+ 81 | | 786.5999999999999 | 1057 | 2 | Chang | 82 | | 704.2000000000004 | 1158 | 16 | Pavlova | 83 | +-------------------+----------+-----------+-------------+ 84 | Dataframe has 4 columns and 2 rows in total. 85 | ``` 86 | 87 | ## Features 88 | - Predefined type system: Boolean, Integer, Decimal, String, DateTime, Missing. 89 | - Select columns. 90 | - Update/Append Columns. 91 | - Filter by custom condition. 92 | - Concatenate two data frames. 93 | - Cast columns to other types. 94 | - Apply a function to entire column. 95 | - Split by custom condition. 96 | - Group by multiple columns with field aggregations. 97 | - Order by single field (ASC/DESC). 98 | - Inner join with another data frame. 99 | - Left join with another data frame. 100 | - Right join with another data frame. 101 | - [TODO] Outer join with another data frame. 102 | - Pretty Print a data frame. 103 | - Iterate through rows. 104 | - Iterate through columns. 105 | - Easy to extend design. 106 | 107 | ## Supported Adapters 108 | - From CSV. 109 | - To CSV. 110 | - From SQL. 111 | - To SQL. 112 | - [TODO] From Structs. 113 | - [TODO] To Structs. 114 | -------------------------------------------------------------------------------- /adapters/csv_adapter_test.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AdikaStyle/go-df/aggs" 6 | "github.com/AdikaStyle/go-df/backend" 7 | "github.com/AdikaStyle/go-df/conds" 8 | df "github.com/AdikaStyle/go-df/dataframe" 9 | "github.com/AdikaStyle/go-df/types" 10 | "github.com/stretchr/testify/assert" 11 | "math/rand" 12 | "os" 13 | "path/filepath" 14 | "testing" 15 | ) 16 | 17 | func TestLoadCSV(t *testing.T) { 18 | od := LoadCSV("../examples/order_details.csv", '|') 19 | assert.NotNil(t, od) 20 | 21 | products := LoadCSV("../examples/products.csv", '|') 22 | assert.NotNil(t, products) 23 | 24 | products.Select("ProductID", "ProductName") 25 | 26 | aggregated := od. 27 | Group(df.By{"ProductID"}, df.Aggs{"UnitPrice": aggs.Sum(), "Quantity": aggs.Sum()}). 28 | OrderBy("ProductID", backend.Asc). 29 | InnerJoin(products, conds.On("ProductID")). 30 | Filter(conds.Gte("UnitPrice", types.Decimal(1500))) 31 | 32 | aggregated.Print(os.Stdout) 33 | 34 | path := filepath.Join(os.TempDir(), fmt.Sprintf("go-df-%d.csv", rand.Int())) 35 | fmt.Printf("output saved to: %s\n", path) 36 | WriteCSV(path, aggregated, '|') 37 | 38 | aggregated2 := LoadCSV(path, '|') 39 | 40 | rows := mapOfProductId(aggregated2) 41 | 42 | aggregated.VisitRows(func(id int, row backend.Row) { 43 | other, found := rows[row["ProductID"].String()] 44 | assert.True(t, found) 45 | assert.NotNil(t, other) 46 | assert.EqualValues(t, row, other) 47 | }) 48 | } 49 | 50 | func mapOfProductId(aggregated2 df.Dataframe) map[string]backend.Row { 51 | rows := make(map[string]backend.Row) 52 | aggregated2.VisitRows(func(id int, row backend.Row) { 53 | rcopy := make(backend.Row) 54 | for k, v := range row { 55 | rcopy[k] = v 56 | } 57 | rows[row["ProductID"].String()] = rcopy 58 | }) 59 | return rows 60 | } 61 | -------------------------------------------------------------------------------- /adapters/csv_load.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "bytes" 5 | "encoding/csv" 6 | "github.com/AdikaStyle/go-df/backend" 7 | "github.com/AdikaStyle/go-df/dataframe" 8 | "github.com/AdikaStyle/go-df/types" 9 | "github.com/palantir/stacktrace" 10 | "io/ioutil" 11 | ) 12 | 13 | func ReadCsv(content []byte, delimiter rune) dataframe.Dataframe { 14 | return ReadCsvWithHeaders(content, delimiter, nil) 15 | } 16 | 17 | func ReadCsvWithHeaders(content []byte, delimiter rune, headers backend.Headers) dataframe.Dataframe { 18 | records, err := readCsv(content, delimiter) 19 | types.PanicOnError(err) 20 | 21 | if headers == nil { 22 | headers = AnalyzeDataset(records[0], records[1:]) 23 | } 24 | 25 | be := backend.New(headers) 26 | populateCSV(be, records) 27 | 28 | return dataframe.New(be) 29 | } 30 | 31 | func LoadCSV(path string, delimiter rune) dataframe.Dataframe { 32 | return LoadCSVWithHeaders(path, delimiter, nil) 33 | } 34 | 35 | func LoadCSVWithHeaders(path string, delimiter rune, headers backend.Headers) dataframe.Dataframe { 36 | content, err := ioutil.ReadFile(path) 37 | types.PanicOnError(err) 38 | 39 | return ReadCsvWithHeaders(content, delimiter, headers) 40 | } 41 | 42 | func populateCSV(be backend.Backend, records [][]string) { 43 | for i := 1; i < len(records); i++ { 44 | row := make(backend.Row) 45 | for hid, h := range be.GetHeaders() { 46 | row[h.Name] = string2TypedValueKind(records[i][hid], h.Kind) 47 | } 48 | be.AppendRow(row) 49 | } 50 | } 51 | 52 | func readCsv(content []byte, delimiter rune) ([][]string, error) { 53 | r := csv.NewReader(bytes.NewBuffer(content)) 54 | r.Comma = delimiter 55 | records, err := r.ReadAll() 56 | if err != nil { 57 | return nil, stacktrace.RootCause(err) 58 | } 59 | return records, nil 60 | } 61 | -------------------------------------------------------------------------------- /adapters/csv_write.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "encoding/csv" 5 | "github.com/AdikaStyle/go-df/backend" 6 | "github.com/AdikaStyle/go-df/dataframe" 7 | "github.com/AdikaStyle/go-df/types" 8 | "os" 9 | ) 10 | 11 | func WriteCSV(outPath string, df dataframe.Dataframe, delimiter rune) { 12 | _ = os.Remove(outPath) 13 | file, err := os.OpenFile(outPath, os.O_RDWR|os.O_CREATE, os.ModePerm) 14 | types.PanicOnError(err) 15 | 16 | w := csv.NewWriter(file) 17 | w.Comma = delimiter 18 | 19 | types.PanicOnError(w.Write(toCSVHeader(df))) 20 | 21 | headers := df.GetHeaders() 22 | csvRow := make([]string, len(headers)) 23 | df.VisitRows(func(id int, row backend.Row) { 24 | toCSVRow(headers, row, csvRow) 25 | types.PanicOnError(w.Write(csvRow)) 26 | }) 27 | 28 | w.Flush() 29 | types.PanicOnError(file.Close()) 30 | } 31 | 32 | func toCSVHeader(df dataframe.Dataframe) []string { 33 | var out []string 34 | for _, h := range df.GetHeaders() { 35 | out = append(out, h.Name) 36 | } 37 | return out 38 | } 39 | 40 | func toCSVRow(headers backend.Headers, row backend.Row, csvRow []string) { 41 | for idx, h := range headers { 42 | csvRow[idx] = row[h.Name].String() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /adapters/sql_load.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "database/sql" 5 | "github.com/AdikaStyle/go-df/backend" 6 | "github.com/AdikaStyle/go-df/dataframe" 7 | "github.com/AdikaStyle/go-df/types" 8 | ) 9 | 10 | func LoadSQL(rows *sql.Rows) dataframe.Dataframe { 11 | if rows == nil { 12 | panic("unable to parse nil rows (LoadSQL)") 13 | } 14 | defer rows.Close() 15 | 16 | if err := rows.Err(); err != nil { 17 | panic(err) 18 | } 19 | 20 | cols, err := rows.Columns() 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | csvRows := parseRows(cols, rows) 26 | 27 | headers := AnalyzeDataset(cols, csvRows) 28 | be := backend.New(headers) 29 | df := dataframe.New(be) 30 | 31 | row := make(backend.Row) 32 | 33 | for _, r := range csvRows { 34 | for idx, col := range cols { 35 | row[col] = string2TypedValueKind(r[idx], headers[idx].Kind) 36 | if be.GetHeaders()[idx].Kind == types.KindUnknown { 37 | be.GetHeaders()[idx].Kind = row[col].Kind() 38 | } 39 | } 40 | be.AppendRow(row) 41 | } 42 | 43 | return df 44 | } 45 | 46 | func parseRows(headers []string, rows *sql.Rows) [][]string { 47 | rawResult := make([][]byte, len(headers)) 48 | result := make([]string, len(headers)) 49 | 50 | dest := make([]interface{}, len(headers)) 51 | for i := range rawResult { 52 | dest[i] = &rawResult[i] 53 | } 54 | 55 | var out [][]string 56 | for rows.Next() { 57 | outRow := make([]string, len(headers)) 58 | err := rows.Scan(dest...) 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | for i, raw := range rawResult { 64 | if raw == nil { 65 | result[i] = "0" 66 | } else { 67 | result[i] = string(raw) 68 | } 69 | } 70 | 71 | for idx := range result { 72 | outRow[idx] = result[idx] 73 | } 74 | 75 | out = append(out, outRow) 76 | } 77 | 78 | return out 79 | } 80 | -------------------------------------------------------------------------------- /adapters/sql_write.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/AdikaStyle/go-df/backend" 7 | "github.com/AdikaStyle/go-df/dataframe" 8 | "github.com/AdikaStyle/go-df/types" 9 | "log" 10 | "strings" 11 | ) 12 | 13 | func WriteSQL(tableName string, df dataframe.Dataframe, db *sql.DB, batchSize int) { 14 | sb := newSqlBatcher(tableName, df, db, batchSize) 15 | sb.Do() 16 | } 17 | 18 | type sqlBatcher struct { 19 | db *sql.DB 20 | insertQuery string 21 | batchSize int 22 | df dataframe.Dataframe 23 | 24 | stmt *sql.Stmt 25 | tx *sql.Tx 26 | } 27 | 28 | func newSqlBatcher(tableName string, df dataframe.Dataframe, db *sql.DB, batchSize int) *sqlBatcher { 29 | query := buildSqlInsertStmt(tableName, df) 30 | return &sqlBatcher{db: db, batchSize: batchSize, df: df, insertQuery: query} 31 | } 32 | 33 | func (sb *sqlBatcher) Do() { 34 | var idx = 0 35 | var batchIdx = 1 36 | total := sb.df.GetRowCount() 37 | sb.nextStmt() 38 | sb.df.VisitRows(func(id int, row backend.Row) { 39 | idx++ 40 | 41 | _, err := sb.stmt.Exec(sqlValues(sb.df, row)...) 42 | types.PanicOnError(err) 43 | 44 | if idx%sb.batchSize == 0 { 45 | log.Printf("Writing batch num: %d, %d/%d\n", batchIdx, batchIdx*sb.batchSize, total) 46 | sb.flush() 47 | sb.nextStmt() 48 | idx = 0 49 | batchIdx++ 50 | } 51 | }) 52 | 53 | if idx > 0 { 54 | batchIdx++ 55 | log.Printf("Writing batch num: %d, %d/%d\n", batchIdx, total-(batchIdx-1)*sb.batchSize, total) 56 | sb.flush() 57 | idx = 0 58 | } 59 | } 60 | 61 | func (sb *sqlBatcher) nextStmt() { 62 | tx, err := sb.db.Begin() 63 | types.PanicOnError(err) 64 | 65 | stmt, err := tx.Prepare(sb.insertQuery) 66 | types.PanicOnError(err) 67 | 68 | sb.tx = tx 69 | sb.stmt = stmt 70 | } 71 | 72 | func (sb *sqlBatcher) flush() { 73 | types.PanicOnError(sb.tx.Commit()) 74 | } 75 | 76 | func buildSqlInsertStmt(tableName string, df dataframe.Dataframe) string { 77 | return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", tableName, sqlHeaders(df), sqlArgs(df)) 78 | } 79 | 80 | func sqlHeaders(df dataframe.Dataframe) string { 81 | headers := make([]string, len(df.GetHeaders())) 82 | for i, h := range df.GetHeaders() { 83 | headers[i] = h.Name 84 | } 85 | 86 | return strings.Join(headers, ",") 87 | } 88 | 89 | func sqlArgs(df dataframe.Dataframe) string { 90 | headers := make([]string, len(df.GetHeaders())) 91 | for i := range headers { 92 | headers[i] = "?" 93 | } 94 | 95 | return strings.Join(headers, ",") 96 | } 97 | 98 | func sqlValues(df dataframe.Dataframe, row backend.Row) []interface{} { 99 | values := make([]interface{}, len(row)) 100 | for i, h := range df.GetHeaders() { 101 | val := row[h.Name] 102 | values[i] = val.NativeType() 103 | } 104 | return values 105 | } 106 | -------------------------------------------------------------------------------- /adapters/struct_load.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AdikaStyle/go-df/backend" 6 | "github.com/AdikaStyle/go-df/dataframe" 7 | "github.com/AdikaStyle/go-df/types" 8 | "reflect" 9 | ) 10 | 11 | func LoadStructs(structsArray interface{}, tag string) dataframe.Dataframe { 12 | structs := reflect.ValueOf(structsArray) 13 | headers := analyzeStruct(structs.Index(0).Type(), tag) 14 | be := backend.New(headers) 15 | populateStructs(be, structs, tag) 16 | return dataframe.New(be) 17 | } 18 | 19 | func populateStructs(be backend.Backend, structs reflect.Value, tag string) { 20 | len := structs.Len() 21 | for i := 0; i < len; i++ { 22 | val := structs.Index(i) 23 | be.AppendRow(structToRow(val, tag)) 24 | } 25 | } 26 | 27 | func structToRow(valOf reflect.Value, tag string) backend.Row { 28 | row := make(backend.Row) 29 | for i := 0; i < valOf.NumField(); i++ { 30 | key := extractFieldName(valOf.Type().Field(i), tag) 31 | kind := types.MatchKind(valOf.Field(i).Interface()) 32 | val := types.Convert(valOf.Field(i).Interface(), kind) 33 | row[key] = val 34 | } 35 | return row 36 | } 37 | 38 | func analyzeStruct(tpe reflect.Type, tag string) backend.Headers { 39 | var out backend.Headers 40 | fieldsCount := tpe.NumField() 41 | 42 | for i := 0; i < fieldsCount; i++ { 43 | field := tpe.Field(i) 44 | tagVal := extractFieldName(field, tag) 45 | 46 | if tagVal == "-" { 47 | continue 48 | } 49 | 50 | out = append(out, backend.Header{ 51 | Name: tagVal, 52 | Kind: goTypeToKind(field.Type), 53 | Visible: true, 54 | }) 55 | } 56 | 57 | return out 58 | } 59 | 60 | func extractFieldName(field reflect.StructField, tag string) string { 61 | tagVal, found := field.Tag.Lookup(tag) 62 | if !found { 63 | tagVal = field.Name 64 | } 65 | 66 | return tagVal 67 | } 68 | 69 | func goTypeToKind(tpe reflect.Type) types.TypeKind { 70 | if tpe.Name() == "Time" { 71 | return types.KindDatetime 72 | } 73 | 74 | switch tpe.Kind() { 75 | case reflect.Bool: 76 | return types.KindBoolean 77 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 78 | return types.KindInteger 79 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 80 | return types.KindInteger 81 | case reflect.Float32, reflect.Float64: 82 | return types.KindDecimal 83 | case reflect.String: 84 | return types.KindString 85 | default: 86 | panic(fmt.Errorf("failed to convert go type: %d to dataframe type", tpe.Kind())) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /adapters/struct_load_test.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestLoadStructs(t *testing.T) { 11 | structs := getStructs() 12 | df := LoadStructs(structs, "df") 13 | 14 | assert.NotNil(t, df) 15 | assert.EqualValues(t, 5, len(df.GetHeaders())) 16 | assert.EqualValues(t, "name", df.GetHeaders()[0].Name) 17 | assert.EqualValues(t, "age", df.GetHeaders()[1].Name) 18 | assert.EqualValues(t, "salary", df.GetHeaders()[2].Name) 19 | assert.EqualValues(t, "join_date", df.GetHeaders()[3].Name) 20 | assert.EqualValues(t, "is_active", df.GetHeaders()[4].Name) 21 | 22 | assert.EqualValues(t, 2, df.GetRowCount()) 23 | df.VisitRows(func(id int, row backend.Row) { 24 | expected := structs[id] 25 | assert.EqualValues(t, expected.Name, row["name"].NativeType()) 26 | assert.EqualValues(t, expected.Age, row["age"].NativeType()) 27 | assert.EqualValues(t, expected.Salary, row["salary"].NativeType()) 28 | assert.EqualValues(t, expected.JoinDate, row["join_date"].NativeType()) 29 | assert.EqualValues(t, expected.IsActive, row["is_active"].NativeType()) 30 | }) 31 | } 32 | 33 | func getStructs() []demoStruct { 34 | return []demoStruct{ 35 | { 36 | Name: "User1", 37 | Age: 10, 38 | Salary: 1203.3, 39 | JoinDate: time.Date(2020, time.May, 28, 20, 00, 00, 00, time.UTC), 40 | IsActive: true, 41 | }, 42 | { 43 | Name: "User2", 44 | Age: 20, 45 | Salary: 1203.311, 46 | JoinDate: time.Date(2020, time.May, 28, 20, 00, 00, 00, time.UTC), 47 | IsActive: false, 48 | }, 49 | } 50 | } 51 | 52 | type demoStruct struct { 53 | Name string `df:"name"` 54 | Age int `df:"age"` 55 | Salary float32 `df:"salary"` 56 | JoinDate time.Time `df:"join_date"` 57 | IsActive bool `df:"is_active"` 58 | Ignore int `df:"-"` 59 | } 60 | -------------------------------------------------------------------------------- /adapters/utils.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/types" 6 | "github.com/cstockton/go-conv" 7 | "log" 8 | "math/rand" 9 | "time" 10 | ) 11 | 12 | func AnalyzeDataset(headers []string, data [][]string) backend.Headers { 13 | tpes := make(map[string]types.TypedValue) 14 | sample := sampleRows(data, 0.1) 15 | 16 | for _, row := range sample { 17 | for hid, header := range headers { 18 | new := parseString(row[hid]) 19 | old := tpes[header] 20 | if shouldOverrideType(old, new) { 21 | tpes[header] = new 22 | } 23 | } 24 | } 25 | 26 | var out backend.Headers 27 | for _, h := range headers { 28 | out = append(out, backend.Header{ 29 | Name: h, 30 | Kind: tpes[h].Kind(), 31 | Visible: false, 32 | }) 33 | } 34 | 35 | return out 36 | } 37 | 38 | func shouldOverrideType(old types.TypedValue, new types.TypedValue) bool { 39 | if old == nil { 40 | return true 41 | } 42 | 43 | if new == nil { 44 | return false 45 | } 46 | 47 | return new.Precedence() > old.Precedence() 48 | } 49 | 50 | func sampleRows(data [][]string, percentage float32) [][]string { 51 | rowsCount := len(data) 52 | if rowsCount <= 0 { 53 | log.Fatalf("canno't sample data from empty array") 54 | } 55 | 56 | if rowsCount <= 100 { 57 | return data 58 | } 59 | 60 | sampleSize := int(percentage * float32(rowsCount)) 61 | 62 | out := make([][]string, sampleSize) 63 | for i := 0; i < sampleSize; i++ { 64 | sId := rand.Intn(rowsCount) 65 | out[i] = data[sId] 66 | } 67 | 68 | return out 69 | } 70 | 71 | func parseString(val string) types.TypedValue { 72 | if isMissing(val) { 73 | return types.Missing 74 | } 75 | 76 | /*if res, err := conv.Int64(val); err == nil { 77 | return types.Integer(res) 78 | }*/ 79 | 80 | if res, err := conv.Float64(val); err == nil { 81 | return types.Decimal(res) 82 | } 83 | 84 | if res, err := conv.Bool(val); err == nil { 85 | return types.Boolean(res) 86 | } 87 | 88 | if res, err := conv.Time(val); err == nil { 89 | return types.Datetime(res) 90 | } 91 | 92 | return types.String(val) 93 | } 94 | 95 | func string2TypedValueKind(val string, kind types.TypeKind) types.TypedValue { 96 | if isMissing(val) { 97 | return types.Missing 98 | } 99 | 100 | switch kind { 101 | case types.KindBoolean: 102 | var out bool 103 | types.PanicOnError(conv.Infer(&out, val)) 104 | return types.Boolean(out) 105 | case types.KindInteger: 106 | var out int64 107 | types.PanicOnError(conv.Infer(&out, val)) 108 | return types.Integer(out) 109 | case types.KindDecimal: 110 | var out float64 111 | types.PanicOnError(conv.Infer(&out, val)) 112 | return types.Decimal(out) 113 | case types.KindDatetime: 114 | var out time.Time 115 | err := conv.Infer(&out, val) 116 | if err != nil { 117 | return types.Missing 118 | } 119 | return types.Datetime(out) 120 | case types.KindString: 121 | var out string 122 | types.PanicOnError(conv.Infer(&out, val)) 123 | return types.String(out) 124 | case types.KindMissing: 125 | return types.Missing 126 | default: 127 | log.Fatalf("unable to convert string: %v to kind: %s", val, kind) 128 | return types.Missing 129 | 130 | } 131 | } 132 | 133 | func isMissing(val string) bool { 134 | return val == "null" || val == "" || val == "NULL" || val == "?" 135 | } 136 | -------------------------------------------------------------------------------- /aggs/agg_numeric.go: -------------------------------------------------------------------------------- 1 | package aggs 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/types" 6 | "math" 7 | ) 8 | 9 | func Sum() Aggregation { 10 | return func(column backend.Column) types.TypedValue { 11 | sum := float64(0) 12 | var value float64 13 | for idx := range column { 14 | column[idx].Cast(&value) 15 | sum += value 16 | } 17 | return types.Decimal(sum) 18 | } 19 | } 20 | 21 | func Avg() Aggregation { 22 | return func(column backend.Column) types.TypedValue { 23 | return Sum()(column).(types.Decimal) / types.Decimal(len(column)) 24 | } 25 | } 26 | 27 | func Min() Aggregation { 28 | return func(column backend.Column) types.TypedValue { 29 | min := math.Inf(1) 30 | for idx := range column { 31 | var val float64 32 | (column[idx]).Cast(&val) 33 | if val < min { 34 | min = val 35 | } 36 | } 37 | return types.Decimal(min) 38 | } 39 | } 40 | 41 | func Max() Aggregation { 42 | return func(column backend.Column) types.TypedValue { 43 | max := math.Inf(-1) 44 | for idx := range column { 45 | var val float64 46 | (column[idx]).Cast(&val) 47 | if val > max { 48 | max = val 49 | } 50 | } 51 | return types.Decimal(max) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /aggs/agg_others.go: -------------------------------------------------------------------------------- 1 | package aggs 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/types" 6 | ) 7 | 8 | func Count() Aggregation { 9 | return func(column backend.Column) types.TypedValue { 10 | return types.Integer(len(column)) 11 | } 12 | } 13 | 14 | func CountDistinct() Aggregation { 15 | return func(column backend.Column) types.TypedValue { 16 | dup := make(map[types.TypedValue]bool) 17 | for idx := range column { 18 | dup[column[idx]] = true 19 | } 20 | return types.Integer(len(dup)) 21 | } 22 | } 23 | 24 | func First() Aggregation { 25 | return func(column backend.Column) types.TypedValue { 26 | return column[0].TypedValue 27 | } 28 | } 29 | 30 | func Last() Aggregation { 31 | return func(column backend.Column) types.TypedValue { 32 | return column[len(column)-1].TypedValue 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aggs/index.go: -------------------------------------------------------------------------------- 1 | package aggs 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/types" 6 | ) 7 | 8 | type Aggregation func(column backend.Column) types.TypedValue 9 | -------------------------------------------------------------------------------- /backend/column.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | func (c *Columns) GetHeaders() Headers { 4 | var out Headers 5 | for k, v := range *c { 6 | out = append(out, Header{ 7 | Name: k, 8 | Kind: v[0].Kind(), 9 | Visible: true, 10 | }) 11 | } 12 | return out 13 | } 14 | -------------------------------------------------------------------------------- /backend/columnar_backend.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AdikaStyle/go-df/types" 6 | "github.com/cstockton/go-conv" 7 | "log" 8 | "math" 9 | "math/rand" 10 | ) 11 | 12 | type columnarBackend struct { 13 | headers Headers 14 | columns Columns 15 | row Row 16 | 17 | filtered int 18 | } 19 | 20 | func NewColumnarBackend(headers Headers) *columnarBackend { 21 | columns := make(Columns) 22 | for _, h := range headers { 23 | columns[h.Name] = nil 24 | } 25 | 26 | return &columnarBackend{ 27 | columns: columns, 28 | headers: headers, 29 | row: make(Row), 30 | filtered: 0, 31 | } 32 | } 33 | 34 | func (this *columnarBackend) GetHeaders() Headers { 35 | return this.headers 36 | } 37 | 38 | func (this *columnarBackend) GetRowCount() int { 39 | return len(this.columns[this.headers[0].Name]) - this.filtered 40 | } 41 | 42 | func (this *columnarBackend) GetRow(id int) Row { 43 | for _, header := range this.headers { 44 | if this.columns[header.Name][id].TypedValue == types.Missing { 45 | this.row[header.Name] = types.Missing 46 | } else { 47 | this.row[header.Name] = types.AutoCast(this.columns[header.Name][id].TypedValue, header.Kind) 48 | } 49 | } 50 | return this.row 51 | } 52 | 53 | func (this *columnarBackend) AppendRow(row ...Row) { 54 | for _, r := range row { 55 | for _, h := range this.headers { 56 | this.columns[h.Name] = append(this.columns[h.Name], Cell{ 57 | TypedValue: r[h.Name], 58 | filtered: false, 59 | }) 60 | } 61 | } 62 | } 63 | 64 | func (this *columnarBackend) ForEachRow(visitor RowVisitor) { 65 | for i := 0; i < this.getPhysicalCount(); i++ { 66 | if !this.columns[this.headers[0].Name][i].filtered { 67 | visitor(i, this.GetRow(i)) 68 | } 69 | } 70 | } 71 | 72 | func (this *columnarBackend) ForEachColumn(visitor ColumnVisitor) { 73 | for _, header := range this.headers { 74 | visitor(header.Name, this.columns[header.Name]) 75 | } 76 | } 77 | 78 | func (this *columnarBackend) RemoveRows(ids ...int) { 79 | for _, id := range ids { 80 | for _, header := range this.headers { 81 | this.columns[header.Name][id].filtered = true 82 | } 83 | this.filtered += 1 84 | } 85 | } 86 | 87 | func (this *columnarBackend) RemoveColumn(name string) { 88 | nameIdx := -1 89 | for idx := range this.headers { 90 | if this.headers[idx].Name == name { 91 | nameIdx = idx 92 | } 93 | } 94 | 95 | if nameIdx == -1 { 96 | log.Fatalf("failed to find column named %s while trying to remove column", name) 97 | } 98 | 99 | removeString(&this.headers, nameIdx) 100 | delete(this.columns, name) 101 | } 102 | 103 | func (this *columnarBackend) RenameColumn(old string, new string) { 104 | nameIdx := -1 105 | for idx := range this.headers { 106 | if this.headers[idx].Name == old { 107 | nameIdx = idx 108 | } 109 | } 110 | 111 | if nameIdx == -1 { 112 | log.Fatalf("failed to find column named %s while trying to rename column", old) 113 | } 114 | 115 | this.headers[nameIdx].Name = new 116 | 117 | this.columns[new] = this.columns[old] 118 | delete(this.columns, old) 119 | } 120 | 121 | func (this *columnarBackend) AddColumn(name string, kind types.TypeKind, mutateFn MutateFunction) { 122 | if _, found := this.columns[name]; found { 123 | log.Fatalf("failed to add column %s(%s) there is already a column named: %s", name, kind, name) 124 | } 125 | 126 | this.columns[name] = make(Column, this.GetRowCount()) 127 | 128 | this.ForEachRow(func(id int, row Row) { 129 | newValue := mutateFn(id, row) 130 | this.columns[name][id] = Cell{ 131 | TypedValue: newValue, 132 | filtered: false, 133 | } 134 | }) 135 | 136 | this.headers = append(this.headers, Header{ 137 | Name: name, 138 | Kind: kind, 139 | Visible: true, 140 | }) 141 | } 142 | 143 | func (this *columnarBackend) UpdateColumn(name string, mutateFn MutateFunction) { 144 | if _, found := this.columns[name]; !found { 145 | log.Fatalf("failed to update column %s, there is no column named: %s", name, name) 146 | } 147 | 148 | this.ForEachRow(func(id int, row Row) { 149 | newValue := mutateFn(id, row) 150 | this.columns[name][id].TypedValue = newValue 151 | }) 152 | } 153 | 154 | func (this *columnarBackend) SortByColumn(column string, order Ordering) { 155 | sortCol := this.columns[column] 156 | 157 | this.quickSort(sortCol[:], 0, bool(order), func(idx1, idx2 int) { 158 | for _, k := range this.headers { 159 | this.columns[k.Name][idx1], this.columns[k.Name][idx2] = 160 | this.columns[k.Name][idx2], this.columns[k.Name][idx1] 161 | } 162 | }) 163 | 164 | return 165 | } 166 | 167 | func (this *columnarBackend) ConstructNew(headers Headers) Backend { 168 | return NewColumnarBackend(headers) 169 | } 170 | 171 | func (this *columnarBackend) SetColumns(columns Columns) { 172 | this.columns = columns 173 | this.headers = columns.GetHeaders() 174 | this.filtered = 0 175 | } 176 | 177 | func (this *columnarBackend) CastColumn(name string, toKind types.TypeKind) { 178 | col, found := this.columns[name] 179 | if !found { 180 | log.Fatalf("canno't find column with name: %s when trying to cast column to: %s", name, toKind) 181 | } 182 | 183 | for idx := range col { 184 | col[idx].TypedValue = types.AutoCast(col[idx].TypedValue, toKind) 185 | } 186 | 187 | for idx := range this.headers { 188 | if this.headers[idx].Name == name { 189 | this.headers[idx].Kind = toKind 190 | break 191 | } 192 | } 193 | } 194 | 195 | func (this *columnarBackend) ApplyOnColumn(name string, fn ApplyFunction) { 196 | col, found := this.columns[name] 197 | if !found { 198 | log.Fatalf("canno't apply fn to column with name: %s, column does not exists", name) 199 | } 200 | 201 | for idx := range col { 202 | col[idx].TypedValue = fn(col[idx].TypedValue) 203 | } 204 | } 205 | 206 | func (this *columnarBackend) quickSort(a Column, offset int, order bool, fn swapFunc) { 207 | if len(a) < 2 { 208 | return 209 | } 210 | 211 | left, right := 0, len(a)-1 212 | pivot := rand.Int() % len(a) 213 | 214 | fn(pivot+offset, right+offset) 215 | 216 | for i := range a { 217 | var aistr = a[i].String() 218 | if aistr == "null" { 219 | aistr = fmt.Sprintf("%d", int64(math.Inf(-1))) 220 | } 221 | ai, err := conv.Int64(aistr) 222 | if err != nil { 223 | panic(err) 224 | } 225 | 226 | var arstr = a[right].String() 227 | if arstr == "null" { 228 | arstr = fmt.Sprintf("%d", int64(math.Inf(-1))) 229 | } 230 | aright, err := conv.Int64(arstr) 231 | if err != nil { 232 | panic(err) 233 | } 234 | 235 | if order { 236 | if ai < aright { 237 | fn(left+offset, i+offset) 238 | left++ 239 | } 240 | } else { 241 | if ai > aright { 242 | fn(left+offset, i+offset) 243 | left++ 244 | } 245 | } 246 | } 247 | 248 | fn(left+offset, right+offset) 249 | 250 | this.quickSort(a[:left], offset, order, fn) 251 | this.quickSort(a[left+1:], offset+left+1, order, fn) 252 | return 253 | } 254 | 255 | type swapFunc = func(idx1, idx2 int) 256 | 257 | func (this *columnarBackend) getPhysicalCount() int { 258 | return len(this.columns[this.headers[0].Name]) 259 | } 260 | 261 | func removeString(a *Headers, i int) { 262 | (*a)[i] = (*a)[len(*a)-1] 263 | (*a)[len(*a)-1] = Header{} 264 | *a = (*a)[:len(*a)-1] 265 | } 266 | -------------------------------------------------------------------------------- /backend/index.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import "github.com/AdikaStyle/go-df/types" 4 | 5 | func New(headers Headers) Backend { 6 | return NewColumnarBackend(headers) 7 | } 8 | 9 | type Backend interface { 10 | GetHeaders() Headers 11 | GetRowCount() int 12 | GetRow(id int) Row 13 | AppendRow(row ...Row) 14 | ForEachRow(visitor RowVisitor) 15 | ForEachColumn(visitor ColumnVisitor) 16 | AddColumn(name string, kind types.TypeKind, mutateFn MutateFunction) 17 | UpdateColumn(name string, mutateFn MutateFunction) 18 | RemoveRows(ids ...int) 19 | RemoveColumn(name string) 20 | RenameColumn(old string, new string) 21 | SortByColumn(column string, order Ordering) 22 | ConstructNew(headers Headers) Backend 23 | SetColumns(columns Columns) 24 | CastColumn(name string, toKind types.TypeKind) 25 | ApplyOnColumn(name string, fn ApplyFunction) 26 | } 27 | 28 | type RowVisitor func(id int, row Row) 29 | 30 | type ColumnVisitor func(header string, values Column) 31 | 32 | type MutateFunction func(id int, row Row) types.TypedValue 33 | 34 | type ApplyFunction func(value types.TypedValue) types.TypedValue 35 | 36 | type Row map[string]types.TypedValue 37 | 38 | type Cell struct { 39 | types.TypedValue 40 | filtered bool 41 | } 42 | 43 | type Column []Cell 44 | 45 | type Columns map[string]Column 46 | 47 | type Headers []Header 48 | 49 | type Header struct { 50 | Name string 51 | Kind types.TypeKind 52 | Visible bool 53 | } 54 | 55 | type Ordering bool 56 | 57 | const ( 58 | Asc Ordering = true 59 | Desc Ordering = false 60 | ) 61 | -------------------------------------------------------------------------------- /conds/comaparision_test.go: -------------------------------------------------------------------------------- 1 | package conds 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/types" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestEq(t *testing.T) { 11 | row := testRow() 12 | 13 | assert.True(t, Eq("bool", types.Boolean(true))(row)) 14 | assert.False(t, Eq("bool", types.Boolean(false))(row)) 15 | assert.True(t, Eq("int64", types.Integer(2200000))(row)) 16 | assert.False(t, Eq("int64", types.Integer(1))(row)) 17 | assert.True(t, Eq("float32", types.Decimal(22.33))(row)) 18 | assert.False(t, Eq("float32", types.Decimal(22.34))(row)) 19 | assert.True(t, Eq("float64", types.Decimal(40.2))(row)) 20 | assert.False(t, Eq("float64", types.Decimal(40.3))(row)) 21 | assert.True(t, Eq("string", types.String("Hello World"))(row)) 22 | assert.False(t, Eq("string", types.String("Hello Worldq"))(row)) 23 | } 24 | 25 | func TestGt(t *testing.T) { 26 | row := testRow() 27 | 28 | assert.True(t, Gt("int64", types.Integer(10))(row)) 29 | assert.True(t, Gte("int64", types.Integer(10))(row)) 30 | assert.True(t, Gte("int64", types.Integer(2200000))(row)) 31 | assert.False(t, Gte("int64", types.Integer(2200001))(row)) 32 | } 33 | 34 | func TestLt(t *testing.T) { 35 | row := testRow() 36 | 37 | assert.False(t, Lt("int64", types.Integer(10))(row)) 38 | assert.True(t, Lt("int8", types.Integer(23))(row)) 39 | assert.False(t, Lte("int64", types.Integer(10))(row)) 40 | assert.True(t, Lte("int64", types.Integer(2200000))(row)) 41 | assert.True(t, Lte("int64", types.Integer(2200001))(row)) 42 | } 43 | 44 | func TestIn(t *testing.T) { 45 | row := testRow() 46 | 47 | assert.True(t, In("int8", types.Integer(1), types.Integer(2), types.Integer(12))(row)) 48 | assert.True(t, In("int8", types.Integer(80), types.Integer(12), types.Integer(-12))(row)) 49 | assert.False(t, In("int8", types.Integer(1), types.Integer(2), types.Integer(3))(row)) 50 | } 51 | 52 | func testRow() backend.Row { 53 | row := make(backend.Row) 54 | row["bool"] = types.Boolean(true) 55 | row["int8"] = types.Integer(12) 56 | row["int16"] = types.Integer(22) 57 | row["int32"] = types.Integer(1002) 58 | row["int64"] = types.Integer(2200000) 59 | row["float32"] = types.Decimal(22.33) 60 | row["float64"] = types.Decimal((40.2)) 61 | row["string"] = types.String("Hello World") 62 | return row 63 | } 64 | -------------------------------------------------------------------------------- /conds/comparision.go: -------------------------------------------------------------------------------- 1 | package conds 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/types" 6 | ) 7 | 8 | func Eq(columnName string, value types.TypedValue) Condition { 9 | return func(row backend.Row) bool { 10 | return value.Equals(row[columnName]) 11 | } 12 | } 13 | 14 | func NotEq(columnName string, value types.TypedValue) Condition { 15 | return func(row backend.Row) bool { 16 | return !value.Equals(row[columnName]) 17 | } 18 | } 19 | 20 | func Gt(columnName string, value types.TypedValue) Condition { 21 | return func(row backend.Row) bool { 22 | left := row[columnName] 23 | return left.Compare(value) == types.LeftIsBigger 24 | } 25 | } 26 | 27 | func Gte(columnName string, value types.TypedValue) Condition { 28 | return func(row backend.Row) bool { 29 | left := row[columnName] 30 | 31 | cmp := left.Compare(value) 32 | return cmp == types.Equals || cmp == types.LeftIsBigger 33 | } 34 | } 35 | 36 | func Lt(columnName string, value types.TypedValue) Condition { 37 | return func(row backend.Row) bool { 38 | left := row[columnName] 39 | return left.Compare(value) == types.RightIsBigger 40 | } 41 | } 42 | 43 | func Lte(columnName string, value types.TypedValue) Condition { 44 | return func(row backend.Row) bool { 45 | left := row[columnName] 46 | 47 | cmp := left.Compare(value) 48 | return cmp == types.Equals || cmp == types.RightIsBigger 49 | } 50 | } 51 | 52 | func In(columnName string, inValues ...types.TypedValue) Condition { 53 | return func(row backend.Row) bool { 54 | for _, val := range inValues { 55 | if val.Equals(row[columnName]) { 56 | return true 57 | } 58 | } 59 | return false 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /conds/cond_joins.go: -------------------------------------------------------------------------------- 1 | package conds 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "strings" 6 | ) 7 | 8 | func On(column string, moreColumns ...string) JoinCondition { 9 | return newOnJoinCondition(append(moreColumns, column)) 10 | } 11 | 12 | type onJoinCondition struct { 13 | columns []string 14 | } 15 | 16 | func newOnJoinCondition(columns []string) *onJoinCondition { 17 | return &onJoinCondition{columns: columns} 18 | } 19 | 20 | func (o onJoinCondition) Match(left backend.Row, right backend.Row) bool { 21 | var out = true 22 | for _, col := range o.columns { 23 | out = out && (left[col] == right[col]) 24 | } 25 | return out 26 | } 27 | 28 | func (o onJoinCondition) ColumnsHint(row backend.Row) string { 29 | sb := &strings.Builder{} 30 | for _, col := range o.columns { 31 | sb.WriteString(row[col].String()) 32 | } 33 | return sb.String() 34 | } 35 | -------------------------------------------------------------------------------- /conds/index.go: -------------------------------------------------------------------------------- 1 | package conds 2 | 3 | import "github.com/AdikaStyle/go-df/backend" 4 | 5 | type Condition func(row backend.Row) bool 6 | 7 | type JoinCondition interface { 8 | Match(left backend.Row, right backend.Row) bool 9 | ColumnsHint(row backend.Row) string 10 | } 11 | -------------------------------------------------------------------------------- /conds/logical.go: -------------------------------------------------------------------------------- 1 | package conds 2 | 3 | import "github.com/AdikaStyle/go-df/backend" 4 | 5 | func Not(val Condition) Condition { 6 | return func(row backend.Row) bool { 7 | return !val(row) 8 | } 9 | } 10 | 11 | func And(left Condition, right Condition) Condition { 12 | return func(row backend.Row) bool { 13 | return left(row) && right(row) 14 | } 15 | } 16 | 17 | func Or(left Condition, right Condition) Condition { 18 | return func(row backend.Row) bool { 19 | return left(row) || right(row) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /conds/logical_test.go: -------------------------------------------------------------------------------- 1 | package conds 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/types" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestAnd(t *testing.T) { 10 | row := testRow() 11 | 12 | assert.True(t, And( 13 | Eq("int8", types.Integer(12)), 14 | Eq("int16", types.Integer(22)), 15 | )(row)) 16 | 17 | assert.False(t, And( 18 | Eq("int8", types.Integer(13)), 19 | Eq("int16", types.Integer(22)), 20 | )(row)) 21 | 22 | assert.False(t, And( 23 | Eq("int8", types.Integer(12)), 24 | Eq("int16", types.Integer(23)), 25 | )(row)) 26 | 27 | assert.False(t, And( 28 | Eq("int8", types.Integer(13)), 29 | Eq("int16", types.Integer(23)), 30 | )(row)) 31 | } 32 | 33 | func TestOr(t *testing.T) { 34 | row := testRow() 35 | 36 | assert.True(t, Or( 37 | Eq("int8", types.Integer(12)), 38 | Eq("int16", types.Integer(22)), 39 | )(row)) 40 | 41 | assert.True(t, Or( 42 | Eq("int8", types.Integer(13)), 43 | Eq("int16", types.Integer(22)), 44 | )(row)) 45 | 46 | assert.True(t, Or( 47 | Eq("int8", types.Integer(12)), 48 | Eq("int16", types.Integer(23)), 49 | )(row)) 50 | 51 | assert.False(t, Or( 52 | Eq("int8", types.Integer(13)), 53 | Eq("int16", types.Integer(23)), 54 | )(row)) 55 | } 56 | 57 | func TestNot(t *testing.T) { 58 | row := testRow() 59 | 60 | assert.True(t, Not(Eq("int8", types.Integer(13)))(row)) 61 | assert.False(t, Not(Eq("int8", types.Integer(12)))(row)) 62 | } 63 | -------------------------------------------------------------------------------- /dataframe/columnar_dataframe.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | ) 6 | 7 | type columnarDataframe struct { 8 | backend backend.Backend 9 | Filterable 10 | Transformable 11 | Aggregatable 12 | Joinable 13 | Printable 14 | } 15 | 16 | func newColumnarDataframe(backend backend.Backend) Dataframe { 17 | df := &columnarDataframe{backend: backend} 18 | df.Filterable = newDefaultFilterable(df) 19 | df.Transformable = newDefaultTransformable(df) 20 | df.Aggregatable = newDefaultAggregatable(df) 21 | df.Joinable = newDefaultJoinable(df) 22 | df.Printable = newDefaultPrintable(df) 23 | return df 24 | } 25 | 26 | func (this *columnarDataframe) VisitRows(visitor backend.RowVisitor) Dataframe { 27 | this.backend.ForEachRow(visitor) 28 | return this 29 | } 30 | 31 | func (this *columnarDataframe) VisitColumn(visitor backend.ColumnVisitor) { 32 | this.backend.ForEachColumn(visitor) 33 | } 34 | 35 | func (this *columnarDataframe) GetRowCount() int { 36 | return this.backend.GetRowCount() 37 | } 38 | 39 | func (this *columnarDataframe) GetHeaders() backend.Headers { 40 | return this.backend.GetHeaders() 41 | } 42 | 43 | func (this *columnarDataframe) getBackend() backend.Backend { 44 | return this.backend 45 | } 46 | 47 | func (this *columnarDataframe) constructNew(headers backend.Headers) Dataframe { 48 | return newColumnarDataframe(this.backend.ConstructNew(headers)) 49 | } 50 | -------------------------------------------------------------------------------- /dataframe/default_aggregatable.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AdikaStyle/go-df/aggs" 6 | "github.com/AdikaStyle/go-df/backend" 7 | "log" 8 | "strings" 9 | ) 10 | 11 | type defaultAggregatable struct { 12 | df Dataframe 13 | } 14 | 15 | func newDefaultAggregatable(df Dataframe) *defaultAggregatable { 16 | return &defaultAggregatable{df: df} 17 | } 18 | 19 | type group map[string]backend.Columns 20 | 21 | func (this *defaultAggregatable) Group(columns []string, aggregations map[string]aggs.Aggregation) Dataframe { 22 | groups := make(group) 23 | 24 | this.df.VisitRows(func(id int, row backend.Row) { 25 | groupKey := encodeGroupKey(columns, row) 26 | if groups[groupKey] == nil { 27 | groups[groupKey] = make(backend.Columns) 28 | } 29 | 30 | for k, v := range row { 31 | groups[groupKey][k] = append(groups[groupKey][k], backend.Cell{TypedValue: v}) 32 | } 33 | }) 34 | 35 | outCols := make(backend.Columns) 36 | for _, v := range groups { 37 | for col, agg := range aggregations { 38 | aggVal := agg(v[col]) 39 | outCols[col] = append(outCols[col], backend.Cell{TypedValue: aggVal}) 40 | } 41 | 42 | for _, c := range columns { 43 | if v[c] == nil { 44 | log.Fatalf("failed to find column named: %s in dataframe", c) 45 | } 46 | outCols[c] = append(outCols[c], backend.Cell{TypedValue: aggs.First()(v[c])}) 47 | } 48 | } 49 | 50 | out := this.df.constructNew(outCols.GetHeaders()) 51 | out.getBackend().SetColumns(outCols) 52 | return out 53 | } 54 | 55 | func (this *defaultAggregatable) OrderBy(columns string, order backend.Ordering) Dataframe { 56 | this.df.getBackend().SortByColumn(columns, order) 57 | return this.df 58 | } 59 | 60 | func encodeGroupKey(cols []string, row backend.Row) string { 61 | b := strings.Builder{} 62 | for _, c := range cols { 63 | b.WriteString(fmt.Sprintf("%v ", row[c])) 64 | } 65 | return b.String() 66 | } 67 | -------------------------------------------------------------------------------- /dataframe/default_aggregatable_test.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/aggs" 5 | "github.com/AdikaStyle/go-df/backend" 6 | "github.com/AdikaStyle/go-df/types" 7 | "github.com/stretchr/testify/assert" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestDefaultAggregatable_GroupBy_OrderBy(t *testing.T) { 13 | df := newColumnarDataframe(aggBackend()) 14 | gdf := df.Group( 15 | By{"SKU", "zone"}, 16 | Aggs{"quantity": aggs.Sum(), "income": aggs.Sum()}, 17 | ).OrderBy("income", backend.Desc) 18 | 19 | assert.EqualValues(t, gdf.GetRowCount(), 5) 20 | 21 | gdf.Print(os.Stdout) 22 | } 23 | 24 | func aggBackend() backend.Backend { 25 | headers := backend.Headers{ 26 | {Name: "SKU", Kind: types.KindString}, 27 | {Name: "zone", Kind: types.KindString}, 28 | {Name: "quantity", Kind: types.KindInteger}, 29 | {Name: "income", Kind: types.KindDecimal}, 30 | } 31 | be := backend.NewColumnarBackend(headers) 32 | be.AppendRow( 33 | backend.Row{"SKU": types.String("A0"), "zone": types.String("IL"), "quantity": types.Integer(2), "income": types.Decimal(200)}, 34 | backend.Row{"SKU": types.String("A0"), "zone": types.String("IL"), "quantity": types.Integer(1), "income": types.Decimal(100)}, 35 | backend.Row{"SKU": types.String("A0"), "zone": types.String("US"), "quantity": types.Integer(1), "income": types.Decimal(100)}, 36 | backend.Row{"SKU": types.String("A1"), "zone": types.String("IL"), "quantity": types.Integer(3), "income": types.Decimal(600)}, 37 | backend.Row{"SKU": types.String("A1"), "zone": types.String("IL"), "quantity": types.Integer(4), "income": types.Decimal(800)}, 38 | backend.Row{"SKU": types.String("A1"), "zone": types.String("EU"), "quantity": types.Integer(20), "income": types.Decimal(4000)}, 39 | backend.Row{"SKU": types.String("A1"), "zone": types.String("EU"), "quantity": types.Integer(1), "income": types.Decimal(200)}, 40 | backend.Row{"SKU": types.String("A1"), "zone": types.String("EU"), "quantity": types.Integer(3), "income": types.Decimal(600)}, 41 | backend.Row{"SKU": types.String("A1"), "zone": types.String("US"), "quantity": types.Integer(1), "income": types.Decimal(200)}, 42 | backend.Row{"SKU": types.String("A1"), "zone": types.String("US"), "quantity": types.Integer(1), "income": types.Decimal(200)}, 43 | backend.Row{"SKU": types.String("A1"), "zone": types.String("US"), "quantity": types.Integer(322), "income": types.Decimal(322 * 200)}, 44 | backend.Row{"SKU": types.String("A1"), "zone": types.String("US"), "quantity": types.Integer(1), "income": types.Decimal(200)}, 45 | ) 46 | return be 47 | } 48 | -------------------------------------------------------------------------------- /dataframe/default_filterable.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/conds" 6 | ) 7 | 8 | type defaultFilterable struct { 9 | df Dataframe 10 | } 11 | 12 | func newDefaultFilterable(dataframe Dataframe) *defaultFilterable { 13 | return &defaultFilterable{df: dataframe} 14 | } 15 | 16 | func (this *defaultFilterable) Select(columns ...string) Dataframe { 17 | selection := make(map[string]bool) 18 | for _, col := range columns { 19 | selection[col] = true 20 | } 21 | 22 | for _, h := range this.df.getBackend().GetHeaders() { 23 | if _, found := selection[h.Name]; !found { 24 | selection[h.Name] = false 25 | } 26 | } 27 | 28 | for k, keep := range selection { 29 | if !keep { 30 | this.df.getBackend().RemoveColumn(k) 31 | } 32 | } 33 | 34 | return this.df 35 | } 36 | 37 | func (this *defaultFilterable) Filter(cond conds.Condition) Dataframe { 38 | this.df.getBackend().ForEachRow(func(id int, row backend.Row) { 39 | if !cond(row) { 40 | this.df.getBackend().RemoveRows(id) 41 | } 42 | }) 43 | 44 | return this.df 45 | } 46 | -------------------------------------------------------------------------------- /dataframe/default_filterable_test.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/conds" 6 | "github.com/AdikaStyle/go-df/types" 7 | "github.com/stretchr/testify/assert" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestDefaultFilterable_Select(t *testing.T) { 13 | df := newColumnarDataframe(filterBackend()) 14 | df.getBackend().AppendRow( 15 | backend.Row{"name": types.String("Abraham"), "age": types.Integer(102), "brit": types.Boolean(true)}, 16 | backend.Row{"name": types.String("Yitzack"), "age": types.Integer(203), "brit": types.Boolean(true)}, 17 | backend.Row{"name": types.String("Yaakov"), "age": types.Integer(99), "brit": types.Boolean(true)}, 18 | ) 19 | 20 | df = df.Select("name", "brit") 21 | headers := df.GetHeaders() 22 | 23 | assert.EqualValues(t, 2, len(headers)) 24 | assert.EqualValues(t, "name", headers[0].Name) 25 | assert.EqualValues(t, "brit", headers[1].Name) 26 | 27 | df.Print(os.Stdout) 28 | } 29 | 30 | func TestDefaultFilterable_Filter(t *testing.T) { 31 | df := newColumnarDataframe(filterBackend()) 32 | df.getBackend().AppendRow( 33 | backend.Row{"name": types.String("Abraham"), "age": types.Integer(102), "brit": types.Boolean(true)}, 34 | backend.Row{"name": types.String("Yitzack"), "age": types.Integer(203), "brit": types.Boolean(true)}, 35 | backend.Row{"name": types.String("Yaakov"), "age": types.Integer(99), "brit": types.Boolean(true)}, 36 | ) 37 | 38 | df = df.Filter(conds.Gt("age", types.Integer(100))) 39 | 40 | assert.EqualValues(t, 2, df.GetRowCount()) 41 | df.VisitRows(func(id int, row backend.Row) { 42 | assert.NotEqualValues(t, "Yaakov", row["name"]) 43 | }) 44 | 45 | df.Print(os.Stdout) 46 | } 47 | 48 | func filterBackend() backend.Backend { 49 | headers := backend.Headers{ 50 | {Name: "name", Kind: types.KindString}, 51 | {Name: "age", Kind: types.KindInteger}, 52 | {Name: "brit", Kind: types.KindBoolean}, 53 | } 54 | be := backend.NewColumnarBackend(headers) 55 | return be 56 | } 57 | -------------------------------------------------------------------------------- /dataframe/default_joinable.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/conds" 6 | "github.com/AdikaStyle/go-df/types" 7 | ) 8 | 9 | type defaultJoinable struct { 10 | df Dataframe 11 | } 12 | 13 | func newDefaultJoinable(df Dataframe) *defaultJoinable { 14 | return &defaultJoinable{df: df} 15 | } 16 | 17 | func (this *defaultJoinable) LeftJoin(with Dataframe, on conds.JoinCondition) Dataframe { 18 | newHeaders := combineHeaders(this.df, with) 19 | joinedDf := this.df.constructNew(newHeaders) 20 | 21 | index := make(map[string][]int) 22 | with.VisitRows(func(id int, row backend.Row) { 23 | indexKey := on.ColumnsHint(row) 24 | index[indexKey] = append(index[indexKey], id) 25 | }) 26 | 27 | this.df.VisitRows(func(id int, left backend.Row) { 28 | indexKey := on.ColumnsHint(left) 29 | 30 | matches, found := index[indexKey] 31 | if found { 32 | for _, match := range matches { 33 | right := with.getBackend().GetRow(match) 34 | if on.Match(left, right) { 35 | joinedRow := combineRows(left, right, false) 36 | joinedDf.getBackend().AppendRow(joinedRow) 37 | } 38 | } 39 | } else { 40 | row := combineRows(left, with.getBackend().GetRow(0), true) 41 | joinedDf.getBackend().AppendRow(row) 42 | } 43 | }) 44 | 45 | return joinedDf 46 | } 47 | 48 | func (this *defaultJoinable) RightJoin(with Dataframe, on conds.JoinCondition) Dataframe { 49 | return with.LeftJoin(this.df, on) 50 | } 51 | 52 | func (this *defaultJoinable) InnerJoin(with Dataframe, on conds.JoinCondition) Dataframe { 53 | newHeaders := combineHeaders(this.df, with) 54 | joinedDf := this.df.constructNew(newHeaders) 55 | 56 | var small, big Dataframe 57 | if this.df.GetRowCount() > with.GetRowCount() { 58 | big = this.df 59 | small = with 60 | } else { 61 | big = with 62 | small = this.df 63 | } 64 | 65 | index := make(map[string][]int) 66 | small.VisitRows(func(id int, row backend.Row) { 67 | indexKey := on.ColumnsHint(row) 68 | index[indexKey] = append(index[indexKey], id) 69 | }) 70 | 71 | big.VisitRows(func(id int, row backend.Row) { 72 | indexKey := on.ColumnsHint(row) 73 | matches, found := index[indexKey] 74 | if found { 75 | for _, match := range matches { 76 | row2 := small.getBackend().GetRow(match) 77 | if on.Match(row, row2) { 78 | joinedRow := combineRows(row, row2, false) 79 | joinedDf.getBackend().AppendRow(joinedRow) 80 | } 81 | } 82 | } 83 | }) 84 | 85 | return joinedDf 86 | } 87 | 88 | func (this *defaultJoinable) OuterJoin(with Dataframe, on conds.JoinCondition) Dataframe { 89 | panic("unimplemented") 90 | } 91 | 92 | func combineHeaders(left Dataframe, right Dataframe) backend.Headers { 93 | var newHeaders backend.Headers 94 | dup := make(map[string]bool) 95 | 96 | for _, h := range left.GetHeaders() { 97 | newHeaders = append(newHeaders, h) 98 | dup[h.Name] = true 99 | } 100 | 101 | for _, h := range right.GetHeaders() { 102 | if _, found := dup[h.Name]; !found { 103 | newHeaders = append(newHeaders, h) 104 | } 105 | } 106 | 107 | return newHeaders 108 | } 109 | 110 | func combineRows(left backend.Row, right backend.Row, rightMissing bool) backend.Row { 111 | dup := make(map[string]bool) 112 | row := make(backend.Row) 113 | 114 | for k, v := range left { 115 | dup[k] = true 116 | row[k] = v 117 | } 118 | 119 | for k, v := range right { 120 | if _, found := dup[k]; found { 121 | continue 122 | } 123 | if rightMissing { 124 | row[k] = types.Missing 125 | } else { 126 | row[k] = v 127 | } 128 | } 129 | 130 | return row 131 | } 132 | -------------------------------------------------------------------------------- /dataframe/default_joinable_test.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/conds" 6 | "github.com/AdikaStyle/go-df/types" 7 | "github.com/stretchr/testify/assert" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestDefaultJoinable_InnerJoin(t *testing.T) { 13 | sales := newColumnarDataframe(aggBackend()) 14 | catalog := newColumnarDataframe(transBackend()) 15 | 16 | joined := sales.InnerJoin(catalog, conds.On("SKU")) 17 | 18 | assert.EqualValues(t, aggBackend().GetRowCount(), joined.GetRowCount()) 19 | assert.EqualValues(t, aggBackend().GetRowCount(), joined.GetRowCount()) 20 | assert.EqualValues(t, len(sales.GetHeaders())+len(catalog.GetHeaders())-1, len(joined.GetHeaders())) 21 | 22 | joined.VisitRows(func(id int, row backend.Row) { 23 | if row["SKU"] == types.String("A0") { 24 | l := sales.getBackend().GetRow(id) 25 | r := catalog.getBackend().GetRow(0) 26 | c := combineRows(l, r, false) 27 | assert.EqualValues(t, c, row) 28 | } else if row["SKU"] == types.String("A1") { 29 | l := sales.getBackend().GetRow(id) 30 | r := catalog.getBackend().GetRow(1) 31 | c := combineRows(l, r, false) 32 | assert.EqualValues(t, c, row) 33 | } else { 34 | assert.FailNow(t, "") 35 | } 36 | }) 37 | 38 | joined.Print(os.Stdout) 39 | } 40 | 41 | func TestDefaultJoinable_LeftJoin(t *testing.T) { 42 | sales := newColumnarDataframe(aggBackend()) 43 | catalog := newColumnarDataframe(transBackend()).Filter(conds.Eq("SKU", types.String("A1"))) 44 | 45 | joined := sales.LeftJoin(catalog, conds.On("SKU")) 46 | 47 | assert.EqualValues(t, aggBackend().GetRowCount(), joined.GetRowCount()) 48 | assert.EqualValues(t, aggBackend().GetRowCount(), joined.GetRowCount()) 49 | assert.EqualValues(t, len(sales.GetHeaders())+len(catalog.GetHeaders())-1, len(joined.GetHeaders())) 50 | 51 | joined.VisitRows(func(id int, row backend.Row) { 52 | if row["SKU"] == types.String("A0") { 53 | l := sales.getBackend().GetRow(id) 54 | assert.EqualValues(t, types.Missing, row["inventory"]) 55 | assert.EqualValues(t, types.Missing, row["name"]) 56 | delete(row, "inventory") 57 | delete(row, "name") 58 | assert.EqualValues(t, l, row) 59 | } else if row["SKU"] == types.String("A1") { 60 | l := sales.getBackend().GetRow(id) 61 | r := catalog.getBackend().GetRow(1) 62 | c := combineRows(l, r, false) 63 | assert.EqualValues(t, c, row) 64 | } else { 65 | assert.FailNow(t, "") 66 | } 67 | }) 68 | 69 | joined.Print(os.Stdout) 70 | } 71 | 72 | func TestDefaultJoinable_OuterJoin(t *testing.T) { 73 | 74 | } 75 | -------------------------------------------------------------------------------- /dataframe/default_printable.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AdikaStyle/go-df/backend" 6 | "github.com/olekukonko/tablewriter" 7 | "io" 8 | ) 9 | 10 | type defaultPrintable struct { 11 | df Dataframe 12 | } 13 | 14 | func newDefaultPrintable(df Dataframe) *defaultPrintable { 15 | return &defaultPrintable{df: df} 16 | } 17 | 18 | func (this *defaultPrintable) Print(w io.Writer) Dataframe { 19 | tw := tablewriter.NewWriter(w) 20 | headers := this.df.GetHeaders() 21 | names := onlyNames(headers) 22 | 23 | tw.SetHeader(names) 24 | tw.SetAutoFormatHeaders(false) 25 | 26 | printRow := make([]string, len(headers)) 27 | 28 | this.df.VisitRows(func(id int, row backend.Row) { 29 | if tw.NumLines() > 30 { 30 | return 31 | } 32 | 33 | for idx, h := range names { 34 | if row[h] != nil { 35 | printRow[idx] = row[h].String() 36 | } else { 37 | printRow[idx] = "?" 38 | } 39 | } 40 | tw.Append(printRow) 41 | }) 42 | 43 | tw.Render() 44 | fmt.Printf("Dataframe has %d columns and %d rows in total.\n", len(names), this.df.GetRowCount()) 45 | return this.df 46 | } 47 | 48 | func (this *defaultPrintable) Describe(w io.Writer) Dataframe { 49 | tw := tablewriter.NewWriter(w) 50 | tw.SetHeader([]string{"Num", "Name", "Type"}) 51 | 52 | for i, h := range this.df.GetHeaders() { 53 | tw.Append([]string{fmt.Sprintf("%d", i), h.Name, string(h.Kind)}) 54 | } 55 | tw.Render() 56 | fmt.Printf("Dataframe has %d columns and %d rows in total.\n", len(this.df.GetHeaders()), this.df.GetRowCount()) 57 | return this.df 58 | } 59 | 60 | func onlyNames(headers backend.Headers) []string { 61 | var headerNames []string 62 | for _, h := range headers { 63 | headerNames = append(headerNames, h.Name) 64 | } 65 | return headerNames 66 | } 67 | -------------------------------------------------------------------------------- /dataframe/default_transformable.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/conds" 6 | "github.com/AdikaStyle/go-df/types" 7 | ) 8 | 9 | type defaultTransformable struct { 10 | df Dataframe 11 | } 12 | 13 | func newDefaultTransformable(df Dataframe) *defaultTransformable { 14 | return &defaultTransformable{df: df} 15 | } 16 | 17 | func (this *defaultTransformable) UpdateColumn(name string, fn backend.MutateFunction) Dataframe { 18 | this.df.getBackend().UpdateColumn(name, fn) 19 | return this.df 20 | } 21 | 22 | func (this *defaultTransformable) AddColumn(name string, kind types.TypeKind, fn backend.MutateFunction) Dataframe { 23 | this.df.getBackend().AddColumn(name, kind, fn) 24 | return this.df 25 | } 26 | 27 | func (this *defaultTransformable) Concat(with Dataframe) Dataframe { 28 | with.VisitRows(func(id int, row backend.Row) { 29 | this.df.getBackend().AppendRow(row) 30 | }) 31 | return this.df 32 | } 33 | 34 | func (this *defaultTransformable) Split(cond conds.Condition) (onTrue Dataframe, onFalse Dataframe) { 35 | onTrue = this.df.constructNew(this.df.GetHeaders()) 36 | onFalse = this.df.constructNew(this.df.GetHeaders()) 37 | 38 | this.df.VisitRows(func(id int, row backend.Row) { 39 | if cond(row) { 40 | onTrue.getBackend().AppendRow(row) 41 | } else { 42 | onFalse.getBackend().AppendRow(row) 43 | } 44 | }) 45 | 46 | return onTrue, onFalse 47 | } 48 | 49 | func (this *defaultTransformable) CastColumn(name string, to types.TypeKind) Dataframe { 50 | this.df.getBackend().CastColumn(name, to) 51 | return this.df 52 | } 53 | 54 | func (this *defaultTransformable) Apply(name string, fn backend.ApplyFunction) Dataframe { 55 | this.df.getBackend().ApplyOnColumn(name, fn) 56 | return this.df 57 | } 58 | -------------------------------------------------------------------------------- /dataframe/default_transformable_test.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/conds" 6 | "github.com/AdikaStyle/go-df/types" 7 | "github.com/stretchr/testify/assert" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestDefaultTransformable_UpdateColumn(t *testing.T) { 13 | df := newColumnarDataframe(transBackend()) 14 | 15 | df.UpdateColumn("inventory", func(id int, row backend.Row) types.TypedValue { 16 | return 1000 * row["inventory"].(types.Integer) 17 | }) 18 | 19 | df.VisitRows(func(id int, row backend.Row) { 20 | assert.True(t, row["inventory"].(types.Integer) > 10000) 21 | }) 22 | 23 | df.Print(os.Stdout) 24 | } 25 | 26 | func TestDefaultTransformable_AddColumn(t *testing.T) { 27 | df := newColumnarDataframe(transBackend()) 28 | 29 | df.AddColumn("isApple", types.KindBoolean, func(id int, row backend.Row) types.TypedValue { 30 | pname := row["name"].String() 31 | if pname == "Macbook" || pname == "iPad" { 32 | return types.Boolean(true) 33 | } 34 | return types.Boolean(false) 35 | }) 36 | 37 | df.VisitRows(func(id int, row backend.Row) { 38 | pname := row["name"].String() 39 | if pname == "Macbook" || pname == "iPad" { 40 | assert.True(t, row["isApple"].Equals(types.Boolean(true))) 41 | } else { 42 | assert.True(t, row["isApple"].Equals(types.Boolean(false))) 43 | } 44 | }) 45 | 46 | df.Print(os.Stdout) 47 | } 48 | 49 | func TestDefaultTransformable_Concat(t *testing.T) { 50 | df := newColumnarDataframe(transBackend()) 51 | 52 | a0a1, a2a3 := df.Split(conds.In("SKU", types.String("A0"), types.String("A1"))) 53 | 54 | a0a1.Concat(a2a3) 55 | 56 | a0a1.VisitRows(func(id int, row backend.Row) { 57 | assert.EqualValues(t, a0a1.getBackend().GetRow(id), df.getBackend().GetRow(id)) 58 | }) 59 | 60 | a0a1.Print(os.Stdout) 61 | } 62 | 63 | func TestDefaultTransformable_Split(t *testing.T) { 64 | df := newColumnarDataframe(transBackend()) 65 | 66 | a0a1, a2a3 := df.Split(conds.In("SKU", types.String("A0"), types.String("A1"))) 67 | 68 | assert.EqualValues(t, 3, len(a0a1.GetHeaders())) 69 | assert.EqualValues(t, 2, a0a1.GetRowCount()) 70 | assert.EqualValues(t, "A0", a0a1.getBackend().GetRow(0)["SKU"]) 71 | assert.EqualValues(t, "A1", a0a1.getBackend().GetRow(1)["SKU"]) 72 | 73 | assert.EqualValues(t, 3, len(a2a3.GetHeaders())) 74 | assert.EqualValues(t, 2, a2a3.GetRowCount()) 75 | assert.EqualValues(t, "A2", a2a3.getBackend().GetRow(0)["SKU"]) 76 | assert.EqualValues(t, "A3", a2a3.getBackend().GetRow(1)["SKU"]) 77 | } 78 | 79 | func transBackend() backend.Backend { 80 | headers := backend.Headers{ 81 | {Name: "SKU", Kind: types.KindString}, 82 | {Name: "name", Kind: types.KindString}, 83 | {Name: "inventory", Kind: types.KindInteger}, 84 | } 85 | be := backend.NewColumnarBackend(headers) 86 | 87 | be.AppendRow( 88 | backend.Row{"SKU": types.String("A0"), "name": types.String("Macbook"), "inventory": types.Integer(323)}, 89 | backend.Row{"SKU": types.String("A1"), "name": types.String("PC"), "inventory": types.Integer(1000)}, 90 | backend.Row{"SKU": types.String("A2"), "name": types.String("PSP"), "inventory": types.Integer(203)}, 91 | backend.Row{"SKU": types.String("A3"), "name": types.String("iPad"), "inventory": types.Integer(120)}, 92 | ) 93 | return be 94 | } 95 | -------------------------------------------------------------------------------- /dataframe/functions.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/backend" 5 | "github.com/AdikaStyle/go-df/types" 6 | ) 7 | 8 | func ReplaceMissingValues(defaultValue types.TypedValue) backend.ApplyFunction { 9 | return func(value types.TypedValue) types.TypedValue { 10 | if value == types.Missing { 11 | return defaultValue 12 | } 13 | return value 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dataframe/index.go: -------------------------------------------------------------------------------- 1 | package dataframe 2 | 3 | import ( 4 | "github.com/AdikaStyle/go-df/aggs" 5 | "github.com/AdikaStyle/go-df/backend" 6 | "github.com/AdikaStyle/go-df/conds" 7 | "github.com/AdikaStyle/go-df/types" 8 | "io" 9 | ) 10 | 11 | func New(be backend.Backend) Dataframe { 12 | return newColumnarDataframe(be) 13 | } 14 | 15 | type Dataframe interface { 16 | Filterable 17 | Transformable 18 | Aggregatable 19 | Joinable 20 | 21 | VisitRows(visitor backend.RowVisitor) Dataframe 22 | VisitColumn(visitor backend.ColumnVisitor) 23 | GetRowCount() int 24 | GetHeaders() backend.Headers 25 | 26 | constructNew(headers backend.Headers) Dataframe 27 | getBackend() backend.Backend 28 | 29 | Printable 30 | } 31 | 32 | type Filterable interface { 33 | Select(columns ...string) Dataframe 34 | Filter(cond conds.Condition) Dataframe 35 | } 36 | 37 | type Transformable interface { 38 | UpdateColumn(name string, fn backend.MutateFunction) Dataframe 39 | AddColumn(name string, kind types.TypeKind, fn backend.MutateFunction) Dataframe 40 | Concat(with Dataframe) Dataframe 41 | Split(cond conds.Condition) (onTrue Dataframe, onFalse Dataframe) 42 | CastColumn(name string, to types.TypeKind) Dataframe 43 | Apply(name string, fn backend.ApplyFunction) Dataframe 44 | } 45 | 46 | type Aggregatable interface { 47 | Group(columns []string, aggregations map[string]aggs.Aggregation) Dataframe 48 | OrderBy(columns string, order backend.Ordering) Dataframe 49 | } 50 | 51 | type Joinable interface { 52 | LeftJoin(with Dataframe, on conds.JoinCondition) Dataframe 53 | RightJoin(with Dataframe, on conds.JoinCondition) Dataframe 54 | InnerJoin(with Dataframe, on conds.JoinCondition) Dataframe 55 | OuterJoin(with Dataframe, on conds.JoinCondition) Dataframe 56 | } 57 | 58 | type Printable interface { 59 | Print(w io.Writer) Dataframe 60 | Describe(w io.Writer) Dataframe 61 | } 62 | 63 | type By []string 64 | type Aggs map[string]aggs.Aggregation 65 | -------------------------------------------------------------------------------- /example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AdikaStyle/go-df/adapters" 6 | . "github.com/AdikaStyle/go-df/aggs" 7 | "github.com/AdikaStyle/go-df/backend" 8 | . "github.com/AdikaStyle/go-df/conds" 9 | . "github.com/AdikaStyle/go-df/dataframe" 10 | . "github.com/AdikaStyle/go-df/types" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | orders := adapters.LoadCSV("examples/order_details.csv", '|') 16 | 17 | products := adapters.LoadCSV("examples/products.csv", '|'). 18 | Select("ProductID", "ProductName") 19 | 20 | view := orders. 21 | Group(By{"ProductID"}, Aggs{"UnitPrice": Sum(), "Quantity": Sum()}). 22 | OrderBy("ProductID", backend.Asc). 23 | InnerJoin(products, On("ProductID")). 24 | Filter(And( 25 | Gte("UnitPrice", Decimal(600)), 26 | Gt("Quantity", Decimal(1000))), 27 | ). 28 | Print(os.Stdout) 29 | 30 | left, right := view.Split(Gte("ProductID", Decimal(30))) 31 | 32 | fmt.Printf("\nLeft side of split:\n") 33 | left.Print(os.Stdout) 34 | 35 | fmt.Printf("\nRight side of split:\n") 36 | right.Print(os.Stdout) 37 | } 38 | -------------------------------------------------------------------------------- /examples/order_details.csv: -------------------------------------------------------------------------------- 1 | OrderID|ProductID|UnitPrice|Quantity|Discount 2 | 10248|11|14.00|12|0 3 | 10248|42|9.80|10|0 4 | 10248|72|34.80|5|0 5 | 10249|14|18.60|9|0 6 | 10249|51|42.40|40|0 7 | 10250|41|7.70|10|0 8 | 10250|51|42.40|35|0.15 9 | 10250|65|16.80|15|0.15 10 | 10251|22|16.80|6|0.05 11 | 10251|57|15.60|15|0.05 12 | 10251|65|16.80|20|0 13 | 10252|20|64.80|40|0.05 14 | 10252|33|2.00|25|0.05 15 | 10252|60|27.20|40|0 16 | 10253|31|10.00|20|0 17 | 10253|39|14.40|42|0 18 | 10253|49|16.00|40|0 19 | 10254|24|3.60|15|0.15 20 | 10254|55|19.20|21|0.15 21 | 10254|74|8.00|21|0 22 | 10255|2|15.20|20|0 23 | 10255|16|13.90|35|0 24 | 10255|36|15.20|25|0 25 | 10255|59|44.00|30|0 26 | 10256|53|26.20|15|0 27 | 10256|77|10.40|12|0 28 | 10257|27|35.10|25|0 29 | 10257|39|14.40|6|0 30 | 10257|77|10.40|15|0 31 | 10258|2|15.20|50|0.2 32 | 10258|5|17.00|65|0.2 33 | 10258|32|25.60|6|0.2 34 | 10259|21|8.00|10|0 35 | 10259|37|20.80|1|0 36 | 10260|41|7.70|16|0.25 37 | 10260|57|15.60|50|0 38 | 10260|62|39.40|15|0.25 39 | 10260|70|12.00|21|0.25 40 | 10261|21|8.00|20|0 41 | 10261|35|14.40|20|0 42 | 10262|5|17.00|12|0.2 43 | 10262|7|24.00|15|0 44 | 10262|56|30.40|2|0 45 | 10263|16|13.90|60|0.25 46 | 10263|24|3.60|28|0 47 | 10263|30|20.70|60|0.25 48 | 10263|74|8.00|36|0.25 49 | 10264|2|15.20|35|0 50 | 10264|41|7.70|25|0.15 51 | 10265|17|31.20|30|0 52 | 10265|70|12.00|20|0 53 | 10266|12|30.40|12|0.05 54 | 10267|40|14.70|50|0 55 | 10267|59|44.00|70|0.15 56 | 10267|76|14.40|15|0.15 57 | 10268|29|99.00|10|0 58 | 10268|72|27.80|4|0 59 | 10269|33|2.00|60|0.05 60 | 10269|72|27.80|20|0.05 61 | 10270|36|15.20|30|0 62 | 10270|43|36.80|25|0 63 | 10271|33|2.00|24|0 64 | 10272|20|64.80|6|0 65 | 10272|31|10.00|40|0 66 | 10272|72|27.80|24|0 67 | 10273|10|24.80|24|0.05 68 | 10273|31|10.00|15|0.05 69 | 10273|33|2.00|20|0 70 | 10273|40|14.70|60|0.05 71 | 10273|76|14.40|33|0.05 72 | 10274|71|17.20|20|0 73 | 10274|72|27.80|7|0 74 | 10275|24|3.60|12|0.05 75 | 10275|59|44.00|6|0.05 76 | 10276|10|24.80|15|0 77 | 10276|13|4.80|10|0 78 | 10277|28|36.40|20|0 79 | 10277|62|39.40|12|0 80 | 10278|44|15.50|16|0 81 | 10278|59|44.00|15|0 82 | 10278|63|35.10|8|0 83 | 10278|73|12.00|25|0 84 | 10279|17|31.20|15|0.25 85 | 10280|24|3.60|12|0 86 | 10280|55|19.20|20|0 87 | 10280|75|6.20|30|0 88 | 10281|19|7.30|1|0 89 | 10281|24|3.60|6|0 90 | 10281|35|14.40|4|0 91 | 10282|30|20.70|6|0 92 | 10282|57|15.60|2|0 93 | 10283|15|12.40|20|0 94 | 10283|19|7.30|18|0 95 | 10283|60|27.20|35|0 96 | 10283|72|27.80|3|0 97 | 10284|27|35.10|15|0.25 98 | 10284|44|15.50|21|0 99 | 10284|60|27.20|20|0.25 100 | 10284|67|11.20|5|0.25 101 | 10285|1|14.40|45|0.2 102 | 10285|40|14.70|40|0.2 103 | 10285|53|26.20|36|0.2 104 | 10286|35|14.40|100|0 105 | 10286|62|39.40|40|0 106 | 10287|16|13.90|40|0.15 107 | 10287|34|11.20|20|0 108 | 10287|46|9.60|15|0.15 109 | 10288|54|5.90|10|0.1 110 | 10288|68|10.00|3|0.1 111 | 10289|3|8.00|30|0 112 | 10289|64|26.60|9|0 113 | 10290|5|17.00|20|0 114 | 10290|29|99.00|15|0 115 | 10290|49|16.00|15|0 116 | 10290|77|10.40|10|0 117 | 10291|13|4.80|20|0.1 118 | 10291|44|15.50|24|0.1 119 | 10291|51|42.40|2|0.1 120 | 10292|20|64.80|20|0 121 | 10293|18|50.00|12|0 122 | 10293|24|3.60|10|0 123 | 10293|63|35.10|5|0 124 | 10293|75|6.20|6|0 125 | 10294|1|14.40|18|0 126 | 10294|17|31.20|15|0 127 | 10294|43|36.80|15|0 128 | 10294|60|27.20|21|0 129 | 10294|75|6.20|6|0 130 | 10295|56|30.40|4|0 131 | 10296|11|16.80|12|0 132 | 10296|16|13.90|30|0 133 | 10296|69|28.80|15|0 134 | 10297|39|14.40|60|0 135 | 10297|72|27.80|20|0 136 | 10298|2|15.20|40|0 137 | 10298|36|15.20|40|0.25 138 | 10298|59|44.00|30|0.25 139 | 10298|62|39.40|15|0 140 | 10299|19|7.30|15|0 141 | 10299|70|12.00|20|0 142 | 10300|66|13.60|30|0 143 | 10300|68|10.00|20|0 144 | 10301|40|14.70|10|0 145 | 10301|56|30.40|20|0 146 | 10302|17|31.20|40|0 147 | 10302|28|36.40|28|0 148 | 10302|43|36.80|12|0 149 | 10303|40|14.70|40|0.1 150 | 10303|65|16.80|30|0.1 151 | 10303|68|10.00|15|0.1 152 | 10304|49|16.00|30|0 153 | 10304|59|44.00|10|0 154 | 10304|71|17.20|2|0 155 | 10305|18|50.00|25|0.1 156 | 10305|29|99.00|25|0.1 157 | 10305|39|14.40|30|0.1 158 | 10306|30|20.70|10|0 159 | 10306|53|26.20|10|0 160 | 10306|54|5.90|5|0 161 | 10307|62|39.40|10|0 162 | 10307|68|10.00|3|0 163 | 10308|69|28.80|1|0 164 | 10308|70|12.00|5|0 165 | 10309|4|17.60|20|0 166 | 10309|6|20.00|30|0 167 | 10309|42|11.20|2|0 168 | 10309|43|36.80|20|0 169 | 10309|71|17.20|3|0 170 | 10310|16|13.90|10|0 171 | 10310|62|39.40|5|0 172 | 10311|42|11.20|6|0 173 | 10311|69|28.80|7|0 174 | 10312|28|36.40|4|0 175 | 10312|43|36.80|24|0 176 | 10312|53|26.20|20|0 177 | 10312|75|6.20|10|0 178 | 10313|36|15.20|12|0 179 | 10314|32|25.60|40|0.1 180 | 10314|58|10.60|30|0.1 181 | 10314|62|39.40|25|0.1 182 | 10315|34|11.20|14|0 183 | 10315|70|12.00|30|0 184 | 10316|41|7.70|10|0 185 | 10316|62|39.40|70|0 186 | 10317|1|14.40|20|0 187 | 10318|41|7.70|20|0 188 | 10318|76|14.40|6|0 189 | 10319|17|31.20|8|0 190 | 10319|28|36.40|14|0 191 | 10319|76|14.40|30|0 192 | 10320|71|17.20|30|0 193 | 10321|35|14.40|10|0 194 | 10322|52|5.60|20|0 195 | 10323|15|12.40|5|0 196 | 10323|25|11.20|4|0 197 | 10323|39|14.40|4|0 198 | 10324|16|13.90|21|0.15 199 | 10324|35|14.40|70|0.15 200 | 10324|46|9.60|30|0 201 | 10324|59|44.00|40|0.15 202 | 10324|63|35.10|80|0.15 203 | 10325|6|20.00|6|0 204 | 10325|13|4.80|12|0 205 | 10325|14|18.60|9|0 206 | 10325|31|10.00|4|0 207 | 10325|72|27.80|40|0 208 | 10326|4|17.60|24|0 209 | 10326|57|15.60|16|0 210 | 10326|75|6.20|50|0 211 | 10327|2|15.20|25|0.2 212 | 10327|11|16.80|50|0.2 213 | 10327|30|20.70|35|0.2 214 | 10327|58|10.60|30|0.2 215 | 10328|59|44.00|9|0 216 | 10328|65|16.80|40|0 217 | 10328|68|10.00|10|0 218 | 10329|19|7.30|10|0.05 219 | 10329|30|20.70|8|0.05 220 | 10329|38|210.80|20|0.05 221 | 10329|56|30.40|12|0.05 222 | 10330|26|24.90|50|0.15 223 | 10330|72|27.80|25|0.15 224 | 10331|54|5.90|15|0 225 | 10332|18|50.00|40|0.2 226 | 10332|42|11.20|10|0.2 227 | 10332|47|7.60|16|0.2 228 | 10333|14|18.60|10|0 229 | 10333|21|8.00|10|0.1 230 | 10333|71|17.20|40|0.1 231 | 10334|52|5.60|8|0 232 | 10334|68|10.00|10|0 233 | 10335|2|15.20|7|0.2 234 | 10335|31|10.00|25|0.2 235 | 10335|32|25.60|6|0.2 236 | 10335|51|42.40|48|0.2 237 | 10336|4|17.60|18|0.1 238 | 10337|23|7.20|40|0 239 | 10337|26|24.90|24|0 240 | 10337|36|15.20|20|0 241 | 10337|37|20.80|28|0 242 | 10337|72|27.80|25|0 243 | 10338|17|31.20|20|0 244 | 10338|30|20.70|15|0 245 | 10339|4|17.60|10|0 246 | 10339|17|31.20|70|0.05 247 | 10339|62|39.40|28|0 248 | 10340|18|50.00|20|0.05 249 | 10340|41|7.70|12|0.05 250 | 10340|43|36.80|40|0.05 251 | 10341|33|2.00|8|0 252 | 10341|59|44.00|9|0.15 253 | 10342|2|15.20|24|0.2 254 | 10342|31|10.00|56|0.2 255 | 10342|36|15.20|40|0.2 256 | 10342|55|19.20|40|0.2 257 | 10343|64|26.60|50|0 258 | 10343|68|10.00|4|0.05 259 | 10343|76|14.40|15|0 260 | 10344|4|17.60|35|0 261 | 10344|8|32.00|70|0.25 262 | 10345|8|32.00|70|0 263 | 10345|19|7.30|80|0 264 | 10345|42|11.20|9|0 265 | 10346|17|31.20|36|0.1 266 | 10346|56|30.40|20|0 267 | 10347|25|11.20|10|0 268 | 10347|39|14.40|50|0.15 269 | 10347|40|14.70|4|0 270 | 10347|75|6.20|6|0.15 271 | 10348|1|14.40|15|0.15 272 | 10348|23|7.20|25|0 273 | 10349|54|5.90|24|0 274 | 10350|50|13.00|15|0.1 275 | 10350|69|28.80|18|0.1 276 | 10351|38|210.80|20|0.05 277 | 10351|41|7.70|13|0 278 | 10351|44|15.50|77|0.05 279 | 10351|65|16.80|10|0.05 280 | 10352|24|3.60|10|0 281 | 10352|54|5.90|20|0.15 282 | 10353|11|16.80|12|0.2 283 | 10353|38|210.80|50|0.2 284 | 10354|1|14.40|12|0 285 | 10354|29|99.00|4|0 286 | 10355|24|3.60|25|0 287 | 10355|57|15.60|25|0 288 | 10356|31|10.00|30|0 289 | 10356|55|19.20|12|0 290 | 10356|69|28.80|20|0 291 | 10357|10|24.80|30|0.2 292 | 10357|26|24.90|16|0 293 | 10357|60|27.20|8|0.2 294 | 10358|24|3.60|10|0.05 295 | 10358|34|11.20|10|0.05 296 | 10358|36|15.20|20|0.05 297 | 10359|16|13.90|56|0.05 298 | 10359|31|10.00|70|0.05 299 | 10359|60|27.20|80|0.05 300 | 10360|28|36.40|30|0 301 | 10360|29|99.00|35|0 302 | 10360|38|210.80|10|0 303 | 10360|49|16.00|35|0 304 | 10360|54|5.90|28|0 305 | 10361|39|14.40|54|0.1 306 | 10361|60|27.20|55|0.1 307 | 10362|25|11.20|50|0 308 | 10362|51|42.40|20|0 309 | 10362|54|5.90|24|0 310 | 10363|31|10.00|20|0 311 | 10363|75|6.20|12|0 312 | 10363|76|14.40|12|0 313 | 10364|69|28.80|30|0 314 | 10364|71|17.20|5|0 315 | 10365|11|16.80|24|0 316 | 10366|65|16.80|5|0 317 | 10366|77|10.40|5|0 318 | 10367|34|11.20|36|0 319 | 10367|54|5.90|18|0 320 | 10367|65|16.80|15|0 321 | 10367|77|10.40|7|0 322 | 10368|21|8.00|5|0.1 323 | 10368|28|36.40|13|0.1 324 | 10368|57|15.60|25|0 325 | 10368|64|26.60|35|0.1 326 | 10369|29|99.00|20|0 327 | 10369|56|30.40|18|0.25 328 | 10370|1|14.40|15|0.15 329 | 10370|64|26.60|30|0 330 | 10370|74|8.00|20|0.15 331 | 10371|36|15.20|6|0.2 332 | 10372|20|64.80|12|0.25 333 | 10372|38|210.80|40|0.25 334 | 10372|60|27.20|70|0.25 335 | 10372|72|27.80|42|0.25 336 | 10373|58|10.60|80|0.2 337 | 10373|71|17.20|50|0.2 338 | 10374|31|10.00|30|0 339 | 10374|58|10.60|15|0 340 | 10375|14|18.60|15|0 341 | 10375|54|5.90|10|0 342 | 10376|31|10.00|42|0.05 343 | 10377|28|36.40|20|0.15 344 | 10377|39|14.40|20|0.15 345 | 10378|71|17.20|6|0 346 | 10379|41|7.70|8|0.1 347 | 10379|63|35.10|16|0.1 348 | 10379|65|16.80|20|0.1 349 | 10380|30|20.70|18|0.1 350 | 10380|53|26.20|20|0.1 351 | 10380|60|27.20|6|0.1 352 | 10380|70|12.00|30|0 353 | 10381|74|8.00|14|0 354 | 10382|5|17.00|32|0 355 | 10382|18|50.00|9|0 356 | 10382|29|99.00|14|0 357 | 10382|33|2.00|60|0 358 | 10382|74|8.00|50|0 359 | 10383|13|4.80|20|0 360 | 10383|50|13.00|15|0 361 | 10383|56|30.40|20|0 362 | 10384|20|64.80|28|0 363 | 10384|60|27.20|15|0 364 | 10385|7|24.00|10|0.2 365 | 10385|60|27.20|20|0.2 366 | 10385|68|10.00|8|0.2 367 | 10386|24|3.60|15|0 368 | 10386|34|11.20|10|0 369 | 10387|24|3.60|15|0 370 | 10387|28|36.40|6|0 371 | 10387|59|44.00|12|0 372 | 10387|71|17.20|15|0 373 | 10388|45|7.60|15|0.2 374 | 10388|52|5.60|20|0.2 375 | 10388|53|26.20|40|0 376 | 10389|10|24.80|16|0 377 | 10389|55|19.20|15|0 378 | 10389|62|39.40|20|0 379 | 10389|70|12.00|30|0 380 | 10390|31|10.00|60|0.1 381 | 10390|35|14.40|40|0.1 382 | 10390|46|9.60|45|0 383 | 10390|72|27.80|24|0.1 384 | 10391|13|4.80|18|0 385 | 10392|69|28.80|50|0 386 | 10393|2|15.20|25|0.25 387 | 10393|14|18.60|42|0.25 388 | 10393|25|11.20|7|0.25 389 | 10393|26|24.90|70|0.25 390 | 10393|31|10.00|32|0 391 | 10394|13|4.80|10|0 392 | 10394|62|39.40|10|0 393 | 10395|46|9.60|28|0.1 394 | 10395|53|26.20|70|0.1 395 | 10395|69|28.80|8|0 396 | 10396|23|7.20|40|0 397 | 10396|71|17.20|60|0 398 | 10396|72|27.80|21|0 399 | 10397|21|8.00|10|0.15 400 | 10397|51|42.40|18|0.15 401 | 10398|35|14.40|30|0 402 | 10398|55|19.20|120|0.1 403 | 10399|68|10.00|60|0 404 | 10399|71|17.20|30|0 405 | 10399|76|14.40|35|0 406 | 10399|77|10.40|14|0 407 | 10400|29|99.00|21|0 408 | 10400|35|14.40|35|0 409 | 10400|49|16.00|30|0 410 | 10401|30|20.70|18|0 411 | 10401|56|30.40|70|0 412 | 10401|65|16.80|20|0 413 | 10401|71|17.20|60|0 414 | 10402|23|7.20|60|0 415 | 10402|63|35.10|65|0 416 | 10403|16|13.90|21|0.15 417 | 10403|48|10.20|70|0.15 418 | 10404|26|24.90|30|0.05 419 | 10404|42|11.20|40|0.05 420 | 10404|49|16.00|30|0.05 421 | 10405|3|8.00|50|0 422 | 10406|1|14.40|10|0 423 | 10406|21|8.00|30|0.1 424 | 10406|28|36.40|42|0.1 425 | 10406|36|15.20|5|0.1 426 | 10406|40|14.70|2|0.1 427 | 10407|11|16.80|30|0 428 | 10407|69|28.80|15|0 429 | 10407|71|17.20|15|0 430 | 10408|37|20.80|10|0 431 | 10408|54|5.90|6|0 432 | 10408|62|39.40|35|0 433 | 10409|14|18.60|12|0 434 | 10409|21|8.00|12|0 435 | 10410|33|2.00|49|0 436 | 10410|59|44.00|16|0 437 | 10411|41|7.70|25|0.2 438 | 10411|44|15.50|40|0.2 439 | 10411|59|44.00|9|0.2 440 | 10412|14|18.60|20|0.1 441 | 10413|1|14.40|24|0 442 | 10413|62|39.40|40|0 443 | 10413|76|14.40|14|0 444 | 10414|19|7.30|18|0.05 445 | 10414|33|2.00|50|0 446 | 10415|17|31.20|2|0 447 | 10415|33|2.00|20|0 448 | 10416|19|7.30|20|0 449 | 10416|53|26.20|10|0 450 | 10416|57|15.60|20|0 451 | 10417|38|210.80|50|0 452 | 10417|46|9.60|2|0.25 453 | 10417|68|10.00|36|0.25 454 | 10417|77|10.40|35|0 455 | 10418|2|15.20|60|0 456 | 10418|47|7.60|55|0 457 | 10418|61|22.80|16|0 458 | 10418|74|8.00|15|0 459 | 10419|60|27.20|60|0.05 460 | 10419|69|28.80|20|0.05 461 | 10420|9|77.60|20|0.1 462 | 10420|13|4.80|2|0.1 463 | 10420|70|12.00|8|0.1 464 | 10420|73|12.00|20|0.1 465 | 10421|19|7.30|4|0.15 466 | 10421|26|24.90|30|0 467 | 10421|53|26.20|15|0.15 468 | 10421|77|10.40|10|0.15 469 | 10422|26|24.90|2|0 470 | 10423|31|10.00|14|0 471 | 10423|59|44.00|20|0 472 | 10424|35|14.40|60|0.2 473 | 10424|38|210.80|49|0.2 474 | 10424|68|10.00|30|0.2 475 | 10425|55|19.20|10|0.25 476 | 10425|76|14.40|20|0.25 477 | 10426|56|30.40|5|0 478 | 10426|64|26.60|7|0 479 | 10427|14|18.60|35|0 480 | 10428|46|9.60|20|0 481 | 10429|50|13.00|40|0 482 | 10429|63|35.10|35|0.25 483 | 10430|17|31.20|45|0.2 484 | 10430|21|8.00|50|0 485 | 10430|56|30.40|30|0 486 | 10430|59|44.00|70|0.2 487 | 10431|17|31.20|50|0.25 488 | 10431|40|14.70|50|0.25 489 | 10431|47|7.60|30|0.25 490 | 10432|26|24.90|10|0 491 | 10432|54|5.90|40|0 492 | 10433|56|30.40|28|0 493 | 10434|11|16.80|6|0 494 | 10434|76|14.40|18|0.15 495 | 10435|2|15.20|10|0 496 | 10435|22|16.80|12|0 497 | 10435|72|27.80|10|0 498 | 10436|46|9.60|5|0 499 | 10436|56|30.40|40|0.1 500 | 10436|64|26.60|30|0.1 501 | 10436|75|6.20|24|0.1 502 | 10437|53|26.20|15|0 503 | 10438|19|7.30|15|0.2 504 | 10438|34|11.20|20|0.2 505 | 10438|57|15.60|15|0.2 506 | 10439|12|30.40|15|0 507 | 10439|16|13.90|16|0 508 | 10439|64|26.60|6|0 509 | 10439|74|8.00|30|0 510 | 10440|2|15.20|45|0.15 511 | 10440|16|13.90|49|0.15 512 | 10440|29|99.00|24|0.15 513 | 10440|61|22.80|90|0.15 514 | 10441|27|35.10|50|0 515 | 10442|11|16.80|30|0 516 | 10442|54|5.90|80|0 517 | 10442|66|13.60|60|0 518 | 10443|11|16.80|6|0.2 519 | 10443|28|36.40|12|0 520 | 10444|17|31.20|10|0 521 | 10444|26|24.90|15|0 522 | 10444|35|14.40|8|0 523 | 10444|41|7.70|30|0 524 | 10445|39|14.40|6|0 525 | 10445|54|5.90|15|0 526 | 10446|19|7.30|12|0.1 527 | 10446|24|3.60|20|0.1 528 | 10446|31|10.00|3|0.1 529 | 10446|52|5.60|15|0.1 530 | 10447|19|7.30|40|0 531 | 10447|65|16.80|35|0 532 | 10447|71|17.20|2|0 533 | 10448|26|24.90|6|0 534 | 10448|40|14.70|20|0 535 | 10449|10|24.80|14|0 536 | 10449|52|5.60|20|0 537 | 10449|62|39.40|35|0 538 | 10450|10|24.80|20|0.2 539 | 10450|54|5.90|6|0.2 540 | 10451|55|19.20|120|0.1 541 | 10451|64|26.60|35|0.1 542 | 10451|65|16.80|28|0.1 543 | 10451|77|10.40|55|0.1 544 | 10452|28|36.40|15|0 545 | 10452|44|15.50|100|0.05 546 | 10453|48|10.20|15|0.1 547 | 10453|70|12.00|25|0.1 548 | 10454|16|13.90|20|0.2 549 | 10454|33|2.00|20|0.2 550 | 10454|46|9.60|10|0.2 551 | 10455|39|14.40|20|0 552 | 10455|53|26.20|50|0 553 | 10455|61|22.80|25|0 554 | 10455|71|17.20|30|0 555 | 10456|21|8.00|40|0.15 556 | 10456|49|16.00|21|0.15 557 | 10457|59|44.00|36|0 558 | 10458|26|24.90|30|0 559 | 10458|28|36.40|30|0 560 | 10458|43|36.80|20|0 561 | 10458|56|30.40|15|0 562 | 10458|71|17.20|50|0 563 | 10459|7|24.00|16|0.05 564 | 10459|46|9.60|20|0.05 565 | 10459|72|27.80|40|0 566 | 10460|68|10.00|21|0.25 567 | 10460|75|6.20|4|0.25 568 | 10461|21|8.00|40|0.25 569 | 10461|30|20.70|28|0.25 570 | 10461|55|19.20|60|0.25 571 | 10462|13|4.80|1|0 572 | 10462|23|7.20|21|0 573 | 10463|19|7.30|21|0 574 | 10463|42|11.20|50|0 575 | 10464|4|17.60|16|0.2 576 | 10464|43|36.80|3|0 577 | 10464|56|30.40|30|0.2 578 | 10464|60|27.20|20|0 579 | 10465|24|3.60|25|0 580 | 10465|29|99.00|18|0.1 581 | 10465|40|14.70|20|0 582 | 10465|45|7.60|30|0.1 583 | 10465|50|13.00|25|0 584 | 10466|11|16.80|10|0 585 | 10466|46|9.60|5|0 586 | 10467|24|3.60|28|0 587 | 10467|25|11.20|12|0 588 | 10468|30|20.70|8|0 589 | 10468|43|36.80|15|0 590 | 10469|2|15.20|40|0.15 591 | 10469|16|13.90|35|0.15 592 | 10469|44|15.50|2|0.15 593 | 10470|18|50.00|30|0 594 | 10470|23|7.20|15|0 595 | 10470|64|26.60|8|0 596 | 10471|7|24.00|30|0 597 | 10471|56|30.40|20|0 598 | 10472|24|3.60|80|0.05 599 | 10472|51|42.40|18|0 600 | 10473|33|2.00|12|0 601 | 10473|71|17.20|12|0 602 | 10474|14|18.60|12|0 603 | 10474|28|36.40|18|0 604 | 10474|40|14.70|21|0 605 | 10474|75|6.20|10|0 606 | 10475|31|10.00|35|0.15 607 | 10475|66|13.60|60|0.15 608 | 10475|76|14.40|42|0.15 609 | 10476|55|19.20|2|0.05 610 | 10476|70|12.00|12|0 611 | 10477|1|14.40|15|0 612 | 10477|21|8.00|21|0.25 613 | 10477|39|14.40|20|0.25 614 | 10478|10|24.80|20|0.05 615 | 10479|38|210.80|30|0 616 | 10479|53|26.20|28|0 617 | 10479|59|44.00|60|0 618 | 10479|64|26.60|30|0 619 | 10480|47|7.60|30|0 620 | 10480|59|44.00|12|0 621 | 10481|49|16.00|24|0 622 | 10481|60|27.20|40|0 623 | 10482|40|14.70|10|0 624 | 10483|34|11.20|35|0.05 625 | 10483|77|10.40|30|0.05 626 | 10484|21|8.00|14|0 627 | 10484|40|14.70|10|0 628 | 10484|51|42.40|3|0 629 | 10485|2|15.20|20|0.1 630 | 10485|3|8.00|20|0.1 631 | 10485|55|19.20|30|0.1 632 | 10485|70|12.00|60|0.1 633 | 10486|11|16.80|5|0 634 | 10486|51|42.40|25|0 635 | 10486|74|8.00|16|0 636 | 10487|19|7.30|5|0 637 | 10487|26|24.90|30|0 638 | 10487|54|5.90|24|0.25 639 | 10488|59|44.00|30|0 640 | 10488|73|12.00|20|0.2 641 | 10489|11|16.80|15|0.25 642 | 10489|16|13.90|18|0 643 | 10490|59|44.00|60|0 644 | 10490|68|10.00|30|0 645 | 10490|75|6.20|36|0 646 | 10491|44|15.50|15|0.15 647 | 10491|77|10.40|7|0.15 648 | 10492|25|11.20|60|0.05 649 | 10492|42|11.20|20|0.05 650 | 10493|65|16.80|15|0.1 651 | 10493|66|13.60|10|0.1 652 | 10493|69|28.80|10|0.1 653 | 10494|56|30.40|30|0 654 | 10495|23|7.20|10|0 655 | 10495|41|7.70|20|0 656 | 10495|77|10.40|5|0 657 | 10496|31|10.00|20|0.05 658 | 10497|56|30.40|14|0 659 | 10497|72|27.80|25|0 660 | 10497|77|10.40|25|0 661 | 10498|24|4.50|14|0 662 | 10498|40|18.40|5|0 663 | 10498|42|14.00|30|0 664 | 10499|28|45.60|20|0 665 | 10499|49|20.00|25|0 666 | 10500|15|15.50|12|0.05 667 | 10500|28|45.60|8|0.05 668 | 10501|54|7.45|20|0 669 | 10502|45|9.50|21|0 670 | 10502|53|32.80|6|0 671 | 10502|67|14.00|30|0 672 | 10503|14|23.25|70|0 673 | 10503|65|21.05|20|0 674 | 10504|2|19.00|12|0 675 | 10504|21|10.00|12|0 676 | 10504|53|32.80|10|0 677 | 10504|61|28.50|25|0 678 | 10505|62|49.30|3|0 679 | 10506|25|14.00|18|0.1 680 | 10506|70|15.00|14|0.1 681 | 10507|43|46.00|15|0.15 682 | 10507|48|12.75|15|0.15 683 | 10508|13|6.00|10|0 684 | 10508|39|18.00|10|0 685 | 10509|28|45.60|3|0 686 | 10510|29|123.79|36|0 687 | 10510|75|7.75|36|0.1 688 | 10511|4|22.00|50|0.15 689 | 10511|7|30.00|50|0.15 690 | 10511|8|40.00|10|0.15 691 | 10512|24|4.50|10|0.15 692 | 10512|46|12.00|9|0.15 693 | 10512|47|9.50|6|0.15 694 | 10512|60|34.00|12|0.15 695 | 10513|21|10.00|40|0.2 696 | 10513|32|32.00|50|0.2 697 | 10513|61|28.50|15|0.2 698 | 10514|20|81.00|39|0 699 | 10514|28|45.60|35|0 700 | 10514|56|38.00|70|0 701 | 10514|65|21.05|39|0 702 | 10514|75|7.75|50|0 703 | 10515|9|97.00|16|0.15 704 | 10515|16|17.45|50|0 705 | 10515|27|43.90|120|0 706 | 10515|33|2.50|16|0.15 707 | 10515|60|34.00|84|0.15 708 | 10516|18|62.50|25|0.1 709 | 10516|41|9.65|80|0.1 710 | 10516|42|14.00|20|0 711 | 10517|52|7.00|6|0 712 | 10517|59|55.00|4|0 713 | 10517|70|15.00|6|0 714 | 10518|24|4.50|5|0 715 | 10518|38|263.50|15|0 716 | 10518|44|19.45|9|0 717 | 10519|10|31.00|16|0.05 718 | 10519|56|38.00|40|0 719 | 10519|60|34.00|10|0.05 720 | 10520|24|4.50|8|0 721 | 10520|53|32.80|5|0 722 | 10521|35|18.00|3|0 723 | 10521|41|9.65|10|0 724 | 10521|68|12.50|6|0 725 | 10522|1|18.00|40|0.2 726 | 10522|8|40.00|24|0 727 | 10522|30|25.89|20|0.2 728 | 10522|40|18.40|25|0.2 729 | 10523|17|39.00|25|0.1 730 | 10523|20|81.00|15|0.1 731 | 10523|37|26.00|18|0.1 732 | 10523|41|9.65|6|0.1 733 | 10524|10|31.00|2|0 734 | 10524|30|25.89|10|0 735 | 10524|43|46.00|60|0 736 | 10524|54|7.45|15|0 737 | 10525|36|19.00|30|0 738 | 10525|40|18.40|15|0.1 739 | 10526|1|18.00|8|0.15 740 | 10526|13|6.00|10|0 741 | 10526|56|38.00|30|0.15 742 | 10527|4|22.00|50|0.1 743 | 10527|36|19.00|30|0.1 744 | 10528|11|21.00|3|0 745 | 10528|33|2.50|8|0.2 746 | 10528|72|34.80|9|0 747 | 10529|55|24.00|14|0 748 | 10529|68|12.50|20|0 749 | 10529|69|36.00|10|0 750 | 10530|17|39.00|40|0 751 | 10530|43|46.00|25|0 752 | 10530|61|28.50|20|0 753 | 10530|76|18.00|50|0 754 | 10531|59|55.00|2|0 755 | 10532|30|25.89|15|0 756 | 10532|66|17.00|24|0 757 | 10533|4|22.00|50|0.05 758 | 10533|72|34.80|24|0 759 | 10533|73|15.00|24|0.05 760 | 10534|30|25.89|10|0 761 | 10534|40|18.40|10|0.2 762 | 10534|54|7.45|10|0.2 763 | 10535|11|21.00|50|0.1 764 | 10535|40|18.40|10|0.1 765 | 10535|57|19.50|5|0.1 766 | 10535|59|55.00|15|0.1 767 | 10536|12|38.00|15|0.25 768 | 10536|31|12.50|20|0 769 | 10536|33|2.50|30|0 770 | 10536|60|34.00|35|0.25 771 | 10537|31|12.50|30|0 772 | 10537|51|53.00|6|0 773 | 10537|58|13.25|20|0 774 | 10537|72|34.80|21|0 775 | 10537|73|15.00|9|0 776 | 10538|70|15.00|7|0 777 | 10538|72|34.80|1|0 778 | 10539|13|6.00|8|0 779 | 10539|21|10.00|15|0 780 | 10539|33|2.50|15|0 781 | 10539|49|20.00|6|0 782 | 10540|3|10.00|60|0 783 | 10540|26|31.23|40|0 784 | 10540|38|263.50|30|0 785 | 10540|68|12.50|35|0 786 | 10541|24|4.50|35|0.1 787 | 10541|38|263.50|4|0.1 788 | 10541|65|21.05|36|0.1 789 | 10541|71|21.50|9|0.1 790 | 10542|11|21.00|15|0.05 791 | 10542|54|7.45|24|0.05 792 | 10543|12|38.00|30|0.15 793 | 10543|23|9.00|70|0.15 794 | 10544|28|45.60|7|0 795 | 10544|67|14.00|7|0 796 | 10545|11|21.00|10|0 797 | 10546|7|30.00|10|0 798 | 10546|35|18.00|30|0 799 | 10546|62|49.30|40|0 800 | 10547|32|32.00|24|0.15 801 | 10547|36|19.00|60|0 802 | 10548|34|14.00|10|0.25 803 | 10548|41|9.65|14|0 804 | 10549|31|12.50|55|0.15 805 | 10549|45|9.50|100|0.15 806 | 10549|51|53.00|48|0.15 807 | 10550|17|39.00|8|0.1 808 | 10550|19|9.20|10|0 809 | 10550|21|10.00|6|0.1 810 | 10550|61|28.50|10|0.1 811 | 10551|16|17.45|40|0.15 812 | 10551|35|18.00|20|0.15 813 | 10551|44|19.45|40|0 814 | 10552|69|36.00|18|0 815 | 10552|75|7.75|30|0 816 | 10553|11|21.00|15|0 817 | 10553|16|17.45|14|0 818 | 10553|22|21.00|24|0 819 | 10553|31|12.50|30|0 820 | 10553|35|18.00|6|0 821 | 10554|16|17.45|30|0.05 822 | 10554|23|9.00|20|0.05 823 | 10554|62|49.30|20|0.05 824 | 10554|77|13.00|10|0.05 825 | 10555|14|23.25|30|0.2 826 | 10555|19|9.20|35|0.2 827 | 10555|24|4.50|18|0.2 828 | 10555|51|53.00|20|0.2 829 | 10555|56|38.00|40|0.2 830 | 10556|72|34.80|24|0 831 | 10557|64|33.25|30|0 832 | 10557|75|7.75|20|0 833 | 10558|47|9.50|25|0 834 | 10558|51|53.00|20|0 835 | 10558|52|7.00|30|0 836 | 10558|53|32.80|18|0 837 | 10558|73|15.00|3|0 838 | 10559|41|9.65|12|0.05 839 | 10559|55|24.00|18|0.05 840 | 10560|30|25.89|20|0 841 | 10560|62|49.30|15|0.25 842 | 10561|44|19.45|10|0 843 | 10561|51|53.00|50|0 844 | 10562|33|2.50|20|0.1 845 | 10562|62|49.30|10|0.1 846 | 10563|36|19.00|25|0 847 | 10563|52|7.00|70|0 848 | 10564|17|39.00|16|0.05 849 | 10564|31|12.50|6|0.05 850 | 10564|55|24.00|25|0.05 851 | 10565|24|4.50|25|0.1 852 | 10565|64|33.25|18|0.1 853 | 10566|11|21.00|35|0.15 854 | 10566|18|62.50|18|0.15 855 | 10566|76|18.00|10|0 856 | 10567|31|12.50|60|0.2 857 | 10567|51|53.00|3|0 858 | 10567|59|55.00|40|0.2 859 | 10568|10|31.00|5|0 860 | 10569|31|12.50|35|0.2 861 | 10569|76|18.00|30|0 862 | 10570|11|21.00|15|0.05 863 | 10570|56|38.00|60|0.05 864 | 10571|14|23.25|11|0.15 865 | 10571|42|14.00|28|0.15 866 | 10572|16|17.45|12|0.1 867 | 10572|32|32.00|10|0.1 868 | 10572|40|18.40|50|0 869 | 10572|75|7.75|15|0.1 870 | 10573|17|39.00|18|0 871 | 10573|34|14.00|40|0 872 | 10573|53|32.80|25|0 873 | 10574|33|2.50|14|0 874 | 10574|40|18.40|2|0 875 | 10574|62|49.30|10|0 876 | 10574|64|33.25|6|0 877 | 10575|59|55.00|12|0 878 | 10575|63|43.90|6|0 879 | 10575|72|34.80|30|0 880 | 10575|76|18.00|10|0 881 | 10576|1|18.00|10|0 882 | 10576|31|12.50|20|0 883 | 10576|44|19.45|21|0 884 | 10577|39|18.00|10|0 885 | 10577|75|7.75|20|0 886 | 10577|77|13.00|18|0 887 | 10578|35|18.00|20|0 888 | 10578|57|19.50|6|0 889 | 10579|15|15.50|10|0 890 | 10579|75|7.75|21|0 891 | 10580|14|23.25|15|0.05 892 | 10580|41|9.65|9|0.05 893 | 10580|65|21.05|30|0.05 894 | 10581|75|7.75|50|0.2 895 | 10582|57|19.50|4|0 896 | 10582|76|18.00|14|0 897 | 10583|29|123.79|10|0 898 | 10583|60|34.00|24|0.15 899 | 10583|69|36.00|10|0.15 900 | 10584|31|12.50|50|0.05 901 | 10585|47|9.50|15|0 902 | 10586|52|7.00|4|0.15 903 | 10587|26|31.23|6|0 904 | 10587|35|18.00|20|0 905 | 10587|77|13.00|20|0 906 | 10588|18|62.50|40|0.2 907 | 10588|42|14.00|100|0.2 908 | 10589|35|18.00|4|0 909 | 10590|1|18.00|20|0 910 | 10590|77|13.00|60|0.05 911 | 10591|3|10.00|14|0 912 | 10591|7|30.00|10|0 913 | 10591|54|7.45|50|0 914 | 10592|15|15.50|25|0.05 915 | 10592|26|31.23|5|0.05 916 | 10593|20|81.00|21|0.2 917 | 10593|69|36.00|20|0.2 918 | 10593|76|18.00|4|0.2 919 | 10594|52|7.00|24|0 920 | 10594|58|13.25|30|0 921 | 10595|35|18.00|30|0.25 922 | 10595|61|28.50|120|0.25 923 | 10595|69|36.00|65|0.25 924 | 10596|56|38.00|5|0.2 925 | 10596|63|43.90|24|0.2 926 | 10596|75|7.75|30|0.2 927 | 10597|24|4.50|35|0.2 928 | 10597|57|19.50|20|0 929 | 10597|65|21.05|12|0.2 930 | 10598|27|43.90|50|0 931 | 10598|71|21.50|9|0 932 | 10599|62|49.30|10|0 933 | 10600|54|7.45|4|0 934 | 10600|73|15.00|30|0 935 | 10601|13|6.00|60|0 936 | 10601|59|55.00|35|0 937 | 10602|77|13.00|5|0.25 938 | 10603|22|21.00|48|0 939 | 10603|49|20.00|25|0.05 940 | 10604|48|12.75|6|0.1 941 | 10604|76|18.00|10|0.1 942 | 10605|16|17.45|30|0.05 943 | 10605|59|55.00|20|0.05 944 | 10605|60|34.00|70|0.05 945 | 10605|71|21.50|15|0.05 946 | 10606|4|22.00|20|0.2 947 | 10606|55|24.00|20|0.2 948 | 10606|62|49.30|10|0.2 949 | 10607|7|30.00|45|0 950 | 10607|17|39.00|100|0 951 | 10607|33|2.50|14|0 952 | 10607|40|18.40|42|0 953 | 10607|72|34.80|12|0 954 | 10608|56|38.00|28|0 955 | 10609|1|18.00|3|0 956 | 10609|10|31.00|10|0 957 | 10609|21|10.00|6|0 958 | 10610|36|19.00|21|0.25 959 | 10611|1|18.00|6|0 960 | 10611|2|19.00|10|0 961 | 10611|60|34.00|15|0 962 | 10612|10|31.00|70|0 963 | 10612|36|19.00|55|0 964 | 10612|49|20.00|18|0 965 | 10612|60|34.00|40|0 966 | 10612|76|18.00|80|0 967 | 10613|13|6.00|8|0.1 968 | 10613|75|7.75|40|0 969 | 10614|11|21.00|14|0 970 | 10614|21|10.00|8|0 971 | 10614|39|18.00|5|0 972 | 10615|55|24.00|5|0 973 | 10616|38|263.50|15|0.05 974 | 10616|56|38.00|14|0 975 | 10616|70|15.00|15|0.05 976 | 10616|71|21.50|15|0.05 977 | 10617|59|55.00|30|0.15 978 | 10618|6|25.00|70|0 979 | 10618|56|38.00|20|0 980 | 10618|68|12.50|15|0 981 | 10619|21|10.00|42|0 982 | 10619|22|21.00|40|0 983 | 10620|24|4.50|5|0 984 | 10620|52|7.00|5|0 985 | 10621|19|9.20|5|0 986 | 10621|23|9.00|10|0 987 | 10621|70|15.00|20|0 988 | 10621|71|21.50|15|0 989 | 10622|2|19.00|20|0 990 | 10622|68|12.50|18|0.2 991 | 10623|14|23.25|21|0 992 | 10623|19|9.20|15|0.1 993 | 10623|21|10.00|25|0.1 994 | 10623|24|4.50|3|0 995 | 10623|35|18.00|30|0.1 996 | 10624|28|45.60|10|0 997 | 10624|29|123.79|6|0 998 | 10624|44|19.45|10|0 999 | 10625|14|23.25|3|0 1000 | 10625|42|14.00|5|0 1001 | 10625|60|34.00|10|0 1002 | 10626|53|32.80|12|0 1003 | 10626|60|34.00|20|0 1004 | 10626|71|21.50|20|0 1005 | 10627|62|49.30|15|0 1006 | 10627|73|15.00|35|0.15 1007 | 10628|1|18.00|25|0 1008 | 10629|29|123.79|20|0 1009 | 10629|64|33.25|9|0 1010 | 10630|55|24.00|12|0.05 1011 | 10630|76|18.00|35|0 1012 | 10631|75|7.75|8|0.1 1013 | 10632|2|19.00|30|0.05 1014 | 10632|33|2.50|20|0.05 1015 | 10633|12|38.00|36|0.15 1016 | 10633|13|6.00|13|0.15 1017 | 10633|26|31.23|35|0.15 1018 | 10633|62|49.30|80|0.15 1019 | 10634|7|30.00|35|0 1020 | 10634|18|62.50|50|0 1021 | 10634|51|53.00|15|0 1022 | 10634|75|7.75|2|0 1023 | 10635|4|22.00|10|0.1 1024 | 10635|5|21.35|15|0.1 1025 | 10635|22|21.00|40|0 1026 | 10636|4|22.00|25|0 1027 | 10636|58|13.25|6|0 1028 | 10637|11|21.00|10|0 1029 | 10637|50|16.25|25|0.05 1030 | 10637|56|38.00|60|0.05 1031 | 10638|45|9.50|20|0 1032 | 10638|65|21.05|21|0 1033 | 10638|72|34.80|60|0 1034 | 10639|18|62.50|8|0 1035 | 10640|69|36.00|20|0.25 1036 | 10640|70|15.00|15|0.25 1037 | 10641|2|19.00|50|0 1038 | 10641|40|18.40|60|0 1039 | 10642|21|10.00|30|0.2 1040 | 10642|61|28.50|20|0.2 1041 | 10643|28|45.60|15|0.25 1042 | 10643|39|18.00|21|0.25 1043 | 10643|46|12.00|2|0.25 1044 | 10644|18|62.50|4|0.1 1045 | 10644|43|46.00|20|0 1046 | 10644|46|12.00|21|0.1 1047 | 10645|18|62.50|20|0 1048 | 10645|36|19.00|15|0 1049 | 10646|1|18.00|15|0.25 1050 | 10646|10|31.00|18|0.25 1051 | 10646|71|21.50|30|0.25 1052 | 10646|77|13.00|35|0.25 1053 | 10647|19|9.20|30|0 1054 | 10647|39|18.00|20|0 1055 | 10648|22|21.00|15|0 1056 | 10648|24|4.50|15|0.15 1057 | 10649|28|45.60|20|0 1058 | 10649|72|34.80|15|0 1059 | 10650|30|25.89|30|0 1060 | 10650|53|32.80|25|0.05 1061 | 10650|54|7.45|30|0 1062 | 10651|19|9.20|12|0.25 1063 | 10651|22|21.00|20|0.25 1064 | 10652|30|25.89|2|0.25 1065 | 10652|42|14.00|20|0 1066 | 10653|16|17.45|30|0.1 1067 | 10653|60|34.00|20|0.1 1068 | 10654|4|22.00|12|0.1 1069 | 10654|39|18.00|20|0.1 1070 | 10654|54|7.45|6|0.1 1071 | 10655|41|9.65|20|0.2 1072 | 10656|14|23.25|3|0.1 1073 | 10656|44|19.45|28|0.1 1074 | 10656|47|9.50|6|0.1 1075 | 10657|15|15.50|50|0 1076 | 10657|41|9.65|24|0 1077 | 10657|46|12.00|45|0 1078 | 10657|47|9.50|10|0 1079 | 10657|56|38.00|45|0 1080 | 10657|60|34.00|30|0 1081 | 10658|21|10.00|60|0 1082 | 10658|40|18.40|70|0.05 1083 | 10658|60|34.00|55|0.05 1084 | 10658|77|13.00|70|0.05 1085 | 10659|31|12.50|20|0.05 1086 | 10659|40|18.40|24|0.05 1087 | 10659|70|15.00|40|0.05 1088 | 10660|20|81.00|21|0 1089 | 10661|39|18.00|3|0.2 1090 | 10661|58|13.25|49|0.2 1091 | 10662|68|12.50|10|0 1092 | 10663|40|18.40|30|0.05 1093 | 10663|42|14.00|30|0.05 1094 | 10663|51|53.00|20|0.05 1095 | 10664|10|31.00|24|0.15 1096 | 10664|56|38.00|12|0.15 1097 | 10664|65|21.05|15|0.15 1098 | 10665|51|53.00|20|0 1099 | 10665|59|55.00|1|0 1100 | 10665|76|18.00|10|0 1101 | 10666|29|123.79|36|0 1102 | 10666|65|21.05|10|0 1103 | 10667|69|36.00|45|0.2 1104 | 10667|71|21.50|14|0.2 1105 | 10668|31|12.50|8|0.1 1106 | 10668|55|24.00|4|0.1 1107 | 10668|64|33.25|15|0.1 1108 | 10669|36|19.00|30|0 1109 | 10670|23|9.00|32|0 1110 | 10670|46|12.00|60|0 1111 | 10670|67|14.00|25|0 1112 | 10670|73|15.00|50|0 1113 | 10670|75|7.75|25|0 1114 | 10671|16|17.45|10|0 1115 | 10671|62|49.30|10|0 1116 | 10671|65|21.05|12|0 1117 | 10672|38|263.50|15|0.1 1118 | 10672|71|21.50|12|0 1119 | 10673|16|17.45|3|0 1120 | 10673|42|14.00|6|0 1121 | 10673|43|46.00|6|0 1122 | 10674|23|9.00|5|0 1123 | 10675|14|23.25|30|0 1124 | 10675|53|32.80|10|0 1125 | 10675|58|13.25|30|0 1126 | 10676|10|31.00|2|0 1127 | 10676|19|9.20|7|0 1128 | 10676|44|19.45|21|0 1129 | 10677|26|31.23|30|0.15 1130 | 10677|33|2.50|8|0.15 1131 | 10678|12|38.00|100|0 1132 | 10678|33|2.50|30|0 1133 | 10678|41|9.65|120|0 1134 | 10678|54|7.45|30|0 1135 | 10679|59|55.00|12|0 1136 | 10680|16|17.45|50|0.25 1137 | 10680|31|12.50|20|0.25 1138 | 10680|42|14.00|40|0.25 1139 | 10681|19|9.20|30|0.1 1140 | 10681|21|10.00|12|0.1 1141 | 10681|64|33.25|28|0 1142 | 10682|33|2.50|30|0 1143 | 10682|66|17.00|4|0 1144 | 10682|75|7.75|30|0 1145 | 10683|52|7.00|9|0 1146 | 10684|40|18.40|20|0 1147 | 10684|47|9.50|40|0 1148 | 10684|60|34.00|30|0 1149 | 10685|10|31.00|20|0 1150 | 10685|41|9.65|4|0 1151 | 10685|47|9.50|15|0 1152 | 10686|17|39.00|30|0.2 1153 | 10686|26|31.23|15|0 1154 | 10687|9|97.00|50|0.25 1155 | 10687|29|123.79|10|0 1156 | 10687|36|19.00|6|0.25 1157 | 10688|10|31.00|18|0.1 1158 | 10688|28|45.60|60|0.1 1159 | 10688|34|14.00|14|0 1160 | 10689|1|18.00|35|0.25 1161 | 10690|56|38.00|20|0.25 1162 | 10690|77|13.00|30|0.25 1163 | 10691|1|18.00|30|0 1164 | 10691|29|123.79|40|0 1165 | 10691|43|46.00|40|0 1166 | 10691|44|19.45|24|0 1167 | 10691|62|49.30|48|0 1168 | 10692|63|43.90|20|0 1169 | 10693|9|97.00|6|0 1170 | 10693|54|7.45|60|0.15 1171 | 10693|69|36.00|30|0.15 1172 | 10693|73|15.00|15|0.15 1173 | 10694|7|30.00|90|0 1174 | 10694|59|55.00|25|0 1175 | 10694|70|15.00|50|0 1176 | 10695|8|40.00|10|0 1177 | 10695|12|38.00|4|0 1178 | 10695|24|4.50|20|0 1179 | 10696|17|39.00|20|0 1180 | 10696|46|12.00|18|0 1181 | 10697|19|9.20|7|0.25 1182 | 10697|35|18.00|9|0.25 1183 | 10697|58|13.25|30|0.25 1184 | 10697|70|15.00|30|0.25 1185 | 10698|11|21.00|15|0 1186 | 10698|17|39.00|8|0.05 1187 | 10698|29|123.79|12|0.05 1188 | 10698|65|21.05|65|0.05 1189 | 10698|70|15.00|8|0.05 1190 | 10699|47|9.50|12|0 1191 | 10700|1|18.00|5|0.2 1192 | 10700|34|14.00|12|0.2 1193 | 10700|68|12.50|40|0.2 1194 | 10700|71|21.50|60|0.2 1195 | 10701|59|55.00|42|0.15 1196 | 10701|71|21.50|20|0.15 1197 | 10701|76|18.00|35|0.15 1198 | 10702|3|10.00|6|0 1199 | 10702|76|18.00|15|0 1200 | 10703|2|19.00|5|0 1201 | 10703|59|55.00|35|0 1202 | 10703|73|15.00|35|0 1203 | 10704|4|22.00|6|0 1204 | 10704|24|4.50|35|0 1205 | 10704|48|12.75|24|0 1206 | 10705|31|12.50|20|0 1207 | 10705|32|32.00|4|0 1208 | 10706|16|17.45|20|0 1209 | 10706|43|46.00|24|0 1210 | 10706|59|55.00|8|0 1211 | 10707|55|24.00|21|0 1212 | 10707|57|19.50|40|0 1213 | 10707|70|15.00|28|0.15 1214 | 10708|5|21.35|4|0 1215 | 10708|36|19.00|5|0 1216 | 10709|8|40.00|40|0 1217 | 10709|51|53.00|28|0 1218 | 10709|60|34.00|10|0 1219 | 10710|19|9.20|5|0 1220 | 10710|47|9.50|5|0 1221 | 10711|19|9.20|12|0 1222 | 10711|41|9.65|42|0 1223 | 10711|53|32.80|120|0 1224 | 10712|53|32.80|3|0.05 1225 | 10712|56|38.00|30|0 1226 | 10713|10|31.00|18|0 1227 | 10713|26|31.23|30|0 1228 | 10713|45|9.50|110|0 1229 | 10713|46|12.00|24|0 1230 | 10714|2|19.00|30|0.25 1231 | 10714|17|39.00|27|0.25 1232 | 10714|47|9.50|50|0.25 1233 | 10714|56|38.00|18|0.25 1234 | 10714|58|13.25|12|0.25 1235 | 10715|10|31.00|21|0 1236 | 10715|71|21.50|30|0 1237 | 10716|21|10.00|5|0 1238 | 10716|51|53.00|7|0 1239 | 10716|61|28.50|10|0 1240 | 10717|21|10.00|32|0.05 1241 | 10717|54|7.45|15|0 1242 | 10717|69|36.00|25|0.05 1243 | 10718|12|38.00|36|0 1244 | 10718|16|17.45|20|0 1245 | 10718|36|19.00|40|0 1246 | 10718|62|49.30|20|0 1247 | 10719|18|62.50|12|0.25 1248 | 10719|30|25.89|3|0.25 1249 | 10719|54|7.45|40|0.25 1250 | 10720|35|18.00|21|0 1251 | 10720|71|21.50|8|0 1252 | 10721|44|19.45|50|0.05 1253 | 10722|2|19.00|3|0 1254 | 10722|31|12.50|50|0 1255 | 10722|68|12.50|45|0 1256 | 10722|75|7.75|42|0 1257 | 10723|26|31.23|15|0 1258 | 10724|10|31.00|16|0 1259 | 10724|61|28.50|5|0 1260 | 10725|41|9.65|12|0 1261 | 10725|52|7.00|4|0 1262 | 10725|55|24.00|6|0 1263 | 10726|4|22.00|25|0 1264 | 10726|11|21.00|5|0 1265 | 10727|17|39.00|20|0.05 1266 | 10727|56|38.00|10|0.05 1267 | 10727|59|55.00|10|0.05 1268 | 10728|30|25.89|15|0 1269 | 10728|40|18.40|6|0 1270 | 10728|55|24.00|12|0 1271 | 10728|60|34.00|15|0 1272 | 10729|1|18.00|50|0 1273 | 10729|21|10.00|30|0 1274 | 10729|50|16.25|40|0 1275 | 10730|16|17.45|15|0.05 1276 | 10730|31|12.50|3|0.05 1277 | 10730|65|21.05|10|0.05 1278 | 10731|21|10.00|40|0.05 1279 | 10731|51|53.00|30|0.05 1280 | 10732|76|18.00|20|0 1281 | 10733|14|23.25|16|0 1282 | 10733|28|45.60|20|0 1283 | 10733|52|7.00|25|0 1284 | 10734|6|25.00|30|0 1285 | 10734|30|25.89|15|0 1286 | 10734|76|18.00|20|0 1287 | 10735|61|28.50|20|0.1 1288 | 10735|77|13.00|2|0.1 1289 | 10736|65|21.05|40|0 1290 | 10736|75|7.75|20|0 1291 | 10737|13|6.00|4|0 1292 | 10737|41|9.65|12|0 1293 | 10738|16|17.45|3|0 1294 | 10739|36|19.00|6|0 1295 | 10739|52|7.00|18|0 1296 | 10740|28|45.60|5|0.2 1297 | 10740|35|18.00|35|0.2 1298 | 10740|45|9.50|40|0.2 1299 | 10740|56|38.00|14|0.2 1300 | 10741|2|19.00|15|0.2 1301 | 10742|3|10.00|20|0 1302 | 10742|60|34.00|50|0 1303 | 10742|72|34.80|35|0 1304 | 10743|46|12.00|28|0.05 1305 | 10744|40|18.40|50|0.2 1306 | 10745|18|62.50|24|0 1307 | 10745|44|19.45|16|0 1308 | 10745|59|55.00|45|0 1309 | 10745|72|34.80|7|0 1310 | 10746|13|6.00|6|0 1311 | 10746|42|14.00|28|0 1312 | 10746|62|49.30|9|0 1313 | 10746|69|36.00|40|0 1314 | 10747|31|12.50|8|0 1315 | 10747|41|9.65|35|0 1316 | 10747|63|43.90|9|0 1317 | 10747|69|36.00|30|0 1318 | 10748|23|9.00|44|0 1319 | 10748|40|18.40|40|0 1320 | 10748|56|38.00|28|0 1321 | 10749|56|38.00|15|0 1322 | 10749|59|55.00|6|0 1323 | 10749|76|18.00|10|0 1324 | 10750|14|23.25|5|0.15 1325 | 10750|45|9.50|40|0.15 1326 | 10750|59|55.00|25|0.15 1327 | 10751|26|31.23|12|0.1 1328 | 10751|30|25.89|30|0 1329 | 10751|50|16.25|20|0.1 1330 | 10751|73|15.00|15|0 1331 | 10752|1|18.00|8|0 1332 | 10752|69|36.00|3|0 1333 | 10753|45|9.50|4|0 1334 | 10753|74|10.00|5|0 1335 | 10754|40|18.40|3|0 1336 | 10755|47|9.50|30|0.25 1337 | 10755|56|38.00|30|0.25 1338 | 10755|57|19.50|14|0.25 1339 | 10755|69|36.00|25|0.25 1340 | 10756|18|62.50|21|0.2 1341 | 10756|36|19.00|20|0.2 1342 | 10756|68|12.50|6|0.2 1343 | 10756|69|36.00|20|0.2 1344 | 10757|34|14.00|30|0 1345 | 10757|59|55.00|7|0 1346 | 10757|62|49.30|30|0 1347 | 10757|64|33.25|24|0 1348 | 10758|26|31.23|20|0 1349 | 10758|52|7.00|60|0 1350 | 10758|70|15.00|40|0 1351 | 10759|32|32.00|10|0 1352 | 10760|25|14.00|12|0.25 1353 | 10760|27|43.90|40|0 1354 | 10760|43|46.00|30|0.25 1355 | 10761|25|14.00|35|0.25 1356 | 10761|75|7.75|18|0 1357 | 10762|39|18.00|16|0 1358 | 10762|47|9.50|30|0 1359 | 10762|51|53.00|28|0 1360 | 10762|56|38.00|60|0 1361 | 10763|21|10.00|40|0 1362 | 10763|22|21.00|6|0 1363 | 10763|24|4.50|20|0 1364 | 10764|3|10.00|20|0.1 1365 | 10764|39|18.00|130|0.1 1366 | 10765|65|21.05|80|0.1 1367 | 10766|2|19.00|40|0 1368 | 10766|7|30.00|35|0 1369 | 10766|68|12.50|40|0 1370 | 10767|42|14.00|2|0 1371 | 10768|22|21.00|4|0 1372 | 10768|31|12.50|50|0 1373 | 10768|60|34.00|15|0 1374 | 10768|71|21.50|12|0 1375 | 10769|41|9.65|30|0.05 1376 | 10769|52|7.00|15|0.05 1377 | 10769|61|28.50|20|0 1378 | 10769|62|49.30|15|0 1379 | 10770|11|21.00|15|0.25 1380 | 10771|71|21.50|16|0 1381 | 10772|29|123.79|18|0 1382 | 10772|59|55.00|25|0 1383 | 10773|17|39.00|33|0 1384 | 10773|31|12.50|70|0.2 1385 | 10773|75|7.75|7|0.2 1386 | 10774|31|12.50|2|0.25 1387 | 10774|66|17.00|50|0 1388 | 10775|10|31.00|6|0 1389 | 10775|67|14.00|3|0 1390 | 10776|31|12.50|16|0.05 1391 | 10776|42|14.00|12|0.05 1392 | 10776|45|9.50|27|0.05 1393 | 10776|51|53.00|120|0.05 1394 | 10777|42|14.00|20|0.2 1395 | 10778|41|9.65|10|0 1396 | 10779|16|17.45|20|0 1397 | 10779|62|49.30|20|0 1398 | 10780|70|15.00|35|0 1399 | 10780|77|13.00|15|0 1400 | 10781|54|7.45|3|0.2 1401 | 10781|56|38.00|20|0.2 1402 | 10781|74|10.00|35|0 1403 | 10782|31|12.50|1|0 1404 | 10783|31|12.50|10|0 1405 | 10783|38|263.50|5|0 1406 | 10784|36|19.00|30|0 1407 | 10784|39|18.00|2|0.15 1408 | 10784|72|34.80|30|0.15 1409 | 10785|10|31.00|10|0 1410 | 10785|75|7.75|10|0 1411 | 10786|8|40.00|30|0.2 1412 | 10786|30|25.89|15|0.2 1413 | 10786|75|7.75|42|0.2 1414 | 10787|2|19.00|15|0.05 1415 | 10787|29|123.79|20|0.05 1416 | 10788|19|9.20|50|0.05 1417 | 10788|75|7.75|40|0.05 1418 | 10789|18|62.50|30|0 1419 | 10789|35|18.00|15|0 1420 | 10789|63|43.90|30|0 1421 | 10789|68|12.50|18|0 1422 | 10790|7|30.00|3|0.15 1423 | 10790|56|38.00|20|0.15 1424 | 10791|29|123.79|14|0.05 1425 | 10791|41|9.65|20|0.05 1426 | 10792|2|19.00|10|0 1427 | 10792|54|7.45|3|0 1428 | 10792|68|12.50|15|0 1429 | 10793|41|9.65|14|0 1430 | 10793|52|7.00|8|0 1431 | 10794|14|23.25|15|0.2 1432 | 10794|54|7.45|6|0.2 1433 | 10795|16|17.45|65|0 1434 | 10795|17|39.00|35|0.25 1435 | 10796|26|31.23|21|0.2 1436 | 10796|44|19.45|10|0 1437 | 10796|64|33.25|35|0.2 1438 | 10796|69|36.00|24|0.2 1439 | 10797|11|21.00|20|0 1440 | 10798|62|49.30|2|0 1441 | 10798|72|34.80|10|0 1442 | 10799|13|6.00|20|0.15 1443 | 10799|24|4.50|20|0.15 1444 | 10799|59|55.00|25|0 1445 | 10800|11|21.00|50|0.1 1446 | 10800|51|53.00|10|0.1 1447 | 10800|54|7.45|7|0.1 1448 | 10801|17|39.00|40|0.25 1449 | 10801|29|123.79|20|0.25 1450 | 10802|30|25.89|25|0.25 1451 | 10802|51|53.00|30|0.25 1452 | 10802|55|24.00|60|0.25 1453 | 10802|62|49.30|5|0.25 1454 | 10803|19|9.20|24|0.05 1455 | 10803|25|14.00|15|0.05 1456 | 10803|59|55.00|15|0.05 1457 | 10804|10|31.00|36|0 1458 | 10804|28|45.60|24|0 1459 | 10804|49|20.00|4|0.15 1460 | 10805|34|14.00|10|0 1461 | 10805|38|263.50|10|0 1462 | 10806|2|19.00|20|0.25 1463 | 10806|65|21.05|2|0 1464 | 10806|74|10.00|15|0.25 1465 | 10807|40|18.40|1|0 1466 | 10808|56|38.00|20|0.15 1467 | 10808|76|18.00|50|0.15 1468 | 10809|52|7.00|20|0 1469 | 10810|13|6.00|7|0 1470 | 10810|25|14.00|5|0 1471 | 10810|70|15.00|5|0 1472 | 10811|19|9.20|15|0 1473 | 10811|23|9.00|18|0 1474 | 10811|40|18.40|30|0 1475 | 10812|31|12.50|16|0.1 1476 | 10812|72|34.80|40|0.1 1477 | 10812|77|13.00|20|0 1478 | 10813|2|19.00|12|0.2 1479 | 10813|46|12.00|35|0 1480 | 10814|41|9.65|20|0 1481 | 10814|43|46.00|20|0.15 1482 | 10814|48|12.75|8|0.15 1483 | 10814|61|28.50|30|0.15 1484 | 10815|33|2.50|16|0 1485 | 10816|38|263.50|30|0.05 1486 | 10816|62|49.30|20|0.05 1487 | 10817|26|31.23|40|0.15 1488 | 10817|38|263.50|30|0 1489 | 10817|40|18.40|60|0.15 1490 | 10817|62|49.30|25|0.15 1491 | 10818|32|32.00|20|0 1492 | 10818|41|9.65|20|0 1493 | 10819|43|46.00|7|0 1494 | 10819|75|7.75|20|0 1495 | 10820|56|38.00|30|0 1496 | 10821|35|18.00|20|0 1497 | 10821|51|53.00|6|0 1498 | 10822|62|49.30|3|0 1499 | 10822|70|15.00|6|0 1500 | 10823|11|21.00|20|0.1 1501 | 10823|57|19.50|15|0 1502 | 10823|59|55.00|40|0.1 1503 | 10823|77|13.00|15|0.1 1504 | 10824|41|9.65|12|0 1505 | 10824|70|15.00|9|0 1506 | 10825|26|31.23|12|0 1507 | 10825|53|32.80|20|0 1508 | 10826|31|12.50|35|0 1509 | 10826|57|19.50|15|0 1510 | 10827|10|31.00|15|0 1511 | 10827|39|18.00|21|0 1512 | 10828|20|81.00|5|0 1513 | 10828|38|263.50|2|0 1514 | 10829|2|19.00|10|0 1515 | 10829|8|40.00|20|0 1516 | 10829|13|6.00|10|0 1517 | 10829|60|34.00|21|0 1518 | 10830|6|25.00|6|0 1519 | 10830|39|18.00|28|0 1520 | 10830|60|34.00|30|0 1521 | 10830|68|12.50|24|0 1522 | 10831|19|9.20|2|0 1523 | 10831|35|18.00|8|0 1524 | 10831|38|263.50|8|0 1525 | 10831|43|46.00|9|0 1526 | 10832|13|6.00|3|0.2 1527 | 10832|25|14.00|10|0.2 1528 | 10832|44|19.45|16|0.2 1529 | 10832|64|33.25|3|0 1530 | 10833|7|30.00|20|0.1 1531 | 10833|31|12.50|9|0.1 1532 | 10833|53|32.80|9|0.1 1533 | 10834|29|123.79|8|0.05 1534 | 10834|30|25.89|20|0.05 1535 | 10835|59|55.00|15|0 1536 | 10835|77|13.00|2|0.2 1537 | 10836|22|21.00|52|0 1538 | 10836|35|18.00|6|0 1539 | 10836|57|19.50|24|0 1540 | 10836|60|34.00|60|0 1541 | 10836|64|33.25|30|0 1542 | 10837|13|6.00|6|0 1543 | 10837|40|18.40|25|0 1544 | 10837|47|9.50|40|0.25 1545 | 10837|76|18.00|21|0.25 1546 | 10838|1|18.00|4|0.25 1547 | 10838|18|62.50|25|0.25 1548 | 10838|36|19.00|50|0.25 1549 | 10839|58|13.25|30|0.1 1550 | 10839|72|34.80|15|0.1 1551 | 10840|25|14.00|6|0.2 1552 | 10840|39|18.00|10|0.2 1553 | 10841|10|31.00|16|0 1554 | 10841|56|38.00|30|0 1555 | 10841|59|55.00|50|0 1556 | 10841|77|13.00|15|0 1557 | 10842|11|21.00|15|0 1558 | 10842|43|46.00|5|0 1559 | 10842|68|12.50|20|0 1560 | 10842|70|15.00|12|0 1561 | 10843|51|53.00|4|0.25 1562 | 10844|22|21.00|35|0 1563 | 10845|23|9.00|70|0.1 1564 | 10845|35|18.00|25|0.1 1565 | 10845|42|14.00|42|0.1 1566 | 10845|58|13.25|60|0.1 1567 | 10845|64|33.25|48|0 1568 | 10846|4|22.00|21|0 1569 | 10846|70|15.00|30|0 1570 | 10846|74|10.00|20|0 1571 | 10847|1|18.00|80|0.2 1572 | 10847|19|9.20|12|0.2 1573 | 10847|37|26.00|60|0.2 1574 | 10847|45|9.50|36|0.2 1575 | 10847|60|34.00|45|0.2 1576 | 10847|71|21.50|55|0.2 1577 | 10848|5|21.35|30|0 1578 | 10848|9|97.00|3|0 1579 | 10849|3|10.00|49|0 1580 | 10849|26|31.23|18|0.15 1581 | 10850|25|14.00|20|0.15 1582 | 10850|33|2.50|4|0.15 1583 | 10850|70|15.00|30|0.15 1584 | 10851|2|19.00|5|0.05 1585 | 10851|25|14.00|10|0.05 1586 | 10851|57|19.50|10|0.05 1587 | 10851|59|55.00|42|0.05 1588 | 10852|2|19.00|15|0 1589 | 10852|17|39.00|6|0 1590 | 10852|62|49.30|50|0 1591 | 10853|18|62.50|10|0 1592 | 10854|10|31.00|100|0.15 1593 | 10854|13|6.00|65|0.15 1594 | 10855|16|17.45|50|0 1595 | 10855|31|12.50|14|0 1596 | 10855|56|38.00|24|0 1597 | 10855|65|21.05|15|0.15 1598 | 10856|2|19.00|20|0 1599 | 10856|42|14.00|20|0 1600 | 10857|3|10.00|30|0 1601 | 10857|26|31.23|35|0.25 1602 | 10857|29|123.79|10|0.25 1603 | 10858|7|30.00|5|0 1604 | 10858|27|43.90|10|0 1605 | 10858|70|15.00|4|0 1606 | 10859|24|4.50|40|0.25 1607 | 10859|54|7.45|35|0.25 1608 | 10859|64|33.25|30|0.25 1609 | 10860|51|53.00|3|0 1610 | 10860|76|18.00|20|0 1611 | 10861|17|39.00|42|0 1612 | 10861|18|62.50|20|0 1613 | 10861|21|10.00|40|0 1614 | 10861|33|2.50|35|0 1615 | 10861|62|49.30|3|0 1616 | 10862|11|21.00|25|0 1617 | 10862|52|7.00|8|0 1618 | 10863|1|18.00|20|0.15 1619 | 10863|58|13.25|12|0.15 1620 | 10864|35|18.00|4|0 1621 | 10864|67|14.00|15|0 1622 | 10865|38|263.50|60|0.05 1623 | 10865|39|18.00|80|0.05 1624 | 10866|2|19.00|21|0.25 1625 | 10866|24|4.50|6|0.25 1626 | 10866|30|25.89|40|0.25 1627 | 10867|53|32.80|3|0 1628 | 10868|26|31.23|20|0 1629 | 10868|35|18.00|30|0 1630 | 10868|49|20.00|42|0.1 1631 | 10869|1|18.00|40|0 1632 | 10869|11|21.00|10|0 1633 | 10869|23|9.00|50|0 1634 | 10869|68|12.50|20|0 1635 | 10870|35|18.00|3|0 1636 | 10870|51|53.00|2|0 1637 | 10871|6|25.00|50|0.05 1638 | 10871|16|17.45|12|0.05 1639 | 10871|17|39.00|16|0.05 1640 | 10872|55|24.00|10|0.05 1641 | 10872|62|49.30|20|0.05 1642 | 10872|64|33.25|15|0.05 1643 | 10872|65|21.05|21|0.05 1644 | 10873|21|10.00|20|0 1645 | 10873|28|45.60|3|0 1646 | 10874|10|31.00|10|0 1647 | 10875|19|9.20|25|0 1648 | 10875|47|9.50|21|0.1 1649 | 10875|49|20.00|15|0 1650 | 10876|46|12.00|21|0 1651 | 10876|64|33.25|20|0 1652 | 10877|16|17.45|30|0.25 1653 | 10877|18|62.50|25|0 1654 | 10878|20|81.00|20|0.05 1655 | 10879|40|18.40|12|0 1656 | 10879|65|21.05|10|0 1657 | 10879|76|18.00|10|0 1658 | 10880|23|9.00|30|0.2 1659 | 10880|61|28.50|30|0.2 1660 | 10880|70|15.00|50|0.2 1661 | 10881|73|15.00|10|0 1662 | 10882|42|14.00|25|0 1663 | 10882|49|20.00|20|0.15 1664 | 10882|54|7.45|32|0.15 1665 | 10883|24|4.50|8|0 1666 | 10884|21|10.00|40|0.05 1667 | 10884|56|38.00|21|0.05 1668 | 10884|65|21.05|12|0.05 1669 | 10885|2|19.00|20|0 1670 | 10885|24|4.50|12|0 1671 | 10885|70|15.00|30|0 1672 | 10885|77|13.00|25|0 1673 | 10886|10|31.00|70|0 1674 | 10886|31|12.50|35|0 1675 | 10886|77|13.00|40|0 1676 | 10887|25|14.00|5|0 1677 | 10888|2|19.00|20|0 1678 | 10888|68|12.50|18|0 1679 | 10889|11|21.00|40|0 1680 | 10889|38|263.50|40|0 1681 | 10890|17|39.00|15|0 1682 | 10890|34|14.00|10|0 1683 | 10890|41|9.65|14|0 1684 | 10891|30|25.89|15|0.05 1685 | 10892|59|55.00|40|0.05 1686 | 10893|8|40.00|30|0 1687 | 10893|24|4.50|10|0 1688 | 10893|29|123.79|24|0 1689 | 10893|30|25.89|35|0 1690 | 10893|36|19.00|20|0 1691 | 10894|13|6.00|28|0.05 1692 | 10894|69|36.00|50|0.05 1693 | 10894|75|7.75|120|0.05 1694 | 10895|24|4.50|110|0 1695 | 10895|39|18.00|45|0 1696 | 10895|40|18.40|91|0 1697 | 10895|60|34.00|100|0 1698 | 10896|45|9.50|15|0 1699 | 10896|56|38.00|16|0 1700 | 10897|29|123.79|80|0 1701 | 10897|30|25.89|36|0 1702 | 10898|13|6.00|5|0 1703 | 10899|39|18.00|8|0.15 1704 | 10900|70|15.00|3|0.25 1705 | 10901|41|9.65|30|0 1706 | 10901|71|21.50|30|0 1707 | 10902|55|24.00|30|0.15 1708 | 10902|62|49.30|6|0.15 1709 | 10903|13|6.00|40|0 1710 | 10903|65|21.05|21|0 1711 | 10903|68|12.50|20|0 1712 | 10904|58|13.25|15|0 1713 | 10904|62|49.30|35|0 1714 | 10905|1|18.00|20|0.05 1715 | 10906|61|28.50|15|0 1716 | 10907|75|7.75|14|0 1717 | 10908|7|30.00|20|0.05 1718 | 10908|52|7.00|14|0.05 1719 | 10909|7|30.00|12|0 1720 | 10909|16|17.45|15|0 1721 | 10909|41|9.65|5|0 1722 | 10910|19|9.20|12|0 1723 | 10910|49|20.00|10|0 1724 | 10910|61|28.50|5|0 1725 | 10911|1|18.00|10|0 1726 | 10911|17|39.00|12|0 1727 | 10911|67|14.00|15|0 1728 | 10912|11|21.00|40|0.25 1729 | 10912|29|123.79|60|0.25 1730 | 10913|4|22.00|30|0.25 1731 | 10913|33|2.50|40|0.25 1732 | 10913|58|13.25|15|0 1733 | 10914|71|21.50|25|0 1734 | 10915|17|39.00|10|0 1735 | 10915|33|2.50|30|0 1736 | 10915|54|7.45|10|0 1737 | 10916|16|17.45|6|0 1738 | 10916|32|32.00|6|0 1739 | 10916|57|19.50|20|0 1740 | 10917|30|25.89|1|0 1741 | 10917|60|34.00|10|0 1742 | 10918|1|18.00|60|0.25 1743 | 10918|60|34.00|25|0.25 1744 | 10919|16|17.45|24|0 1745 | 10919|25|14.00|24|0 1746 | 10919|40|18.40|20|0 1747 | 10920|50|16.25|24|0 1748 | 10921|35|18.00|10|0 1749 | 10921|63|43.90|40|0 1750 | 10922|17|39.00|15|0 1751 | 10922|24|4.50|35|0 1752 | 10923|42|14.00|10|0.2 1753 | 10923|43|46.00|10|0.2 1754 | 10923|67|14.00|24|0.2 1755 | 10924|10|31.00|20|0.1 1756 | 10924|28|45.60|30|0.1 1757 | 10924|75|7.75|6|0 1758 | 10925|36|19.00|25|0.15 1759 | 10925|52|7.00|12|0.15 1760 | 10926|11|21.00|2|0 1761 | 10926|13|6.00|10|0 1762 | 10926|19|9.20|7|0 1763 | 10926|72|34.80|10|0 1764 | 10927|20|81.00|5|0 1765 | 10927|52|7.00|5|0 1766 | 10927|76|18.00|20|0 1767 | 10928|47|9.50|5|0 1768 | 10928|76|18.00|5|0 1769 | 10929|21|10.00|60|0 1770 | 10929|75|7.75|49|0 1771 | 10929|77|13.00|15|0 1772 | 10930|21|10.00|36|0 1773 | 10930|27|43.90|25|0 1774 | 10930|55|24.00|25|0.2 1775 | 10930|58|13.25|30|0.2 1776 | 10931|13|6.00|42|0.15 1777 | 10931|57|19.50|30|0 1778 | 10932|16|17.45|30|0.1 1779 | 10932|62|49.30|14|0.1 1780 | 10932|72|34.80|16|0 1781 | 10932|75|7.75|20|0.1 1782 | 10933|53|32.80|2|0 1783 | 10933|61|28.50|30|0 1784 | 10934|6|25.00|20|0 1785 | 10935|1|18.00|21|0 1786 | 10935|18|62.50|4|0.25 1787 | 10935|23|9.00|8|0.25 1788 | 10936|36|19.00|30|0.2 1789 | 10937|28|45.60|8|0 1790 | 10937|34|14.00|20|0 1791 | 10938|13|6.00|20|0.25 1792 | 10938|43|46.00|24|0.25 1793 | 10938|60|34.00|49|0.25 1794 | 10938|71|21.50|35|0.25 1795 | 10939|2|19.00|10|0.15 1796 | 10939|67|14.00|40|0.15 1797 | 10940|7|30.00|8|0 1798 | 10940|13|6.00|20|0 1799 | 10941|31|12.50|44|0.25 1800 | 10941|62|49.30|30|0.25 1801 | 10941|68|12.50|80|0.25 1802 | 10941|72|34.80|50|0 1803 | 10942|49|20.00|28|0 1804 | 10943|13|6.00|15|0 1805 | 10943|22|21.00|21|0 1806 | 10943|46|12.00|15|0 1807 | 10944|11|21.00|5|0.25 1808 | 10944|44|19.45|18|0.25 1809 | 10944|56|38.00|18|0 1810 | 10945|13|6.00|20|0 1811 | 10945|31|12.50|10|0 1812 | 10946|10|31.00|25|0 1813 | 10946|24|4.50|25|0 1814 | 10946|77|13.00|40|0 1815 | 10947|59|55.00|4|0 1816 | 10948|50|16.25|9|0 1817 | 10948|51|53.00|40|0 1818 | 10948|55|24.00|4|0 1819 | 10949|6|25.00|12|0 1820 | 10949|10|31.00|30|0 1821 | 10949|17|39.00|6|0 1822 | 10949|62|49.30|60|0 1823 | 10950|4|22.00|5|0 1824 | 10951|33|2.50|15|0.05 1825 | 10951|41|9.65|6|0.05 1826 | 10951|75|7.75|50|0.05 1827 | 10952|6|25.00|16|0.05 1828 | 10952|28|45.60|2|0 1829 | 10953|20|81.00|50|0.05 1830 | 10953|31|12.50|50|0.05 1831 | 10954|16|17.45|28|0.15 1832 | 10954|31|12.50|25|0.15 1833 | 10954|45|9.50|30|0 1834 | 10954|60|34.00|24|0.15 1835 | 10955|75|7.75|12|0.2 1836 | 10956|21|10.00|12|0 1837 | 10956|47|9.50|14|0 1838 | 10956|51|53.00|8|0 1839 | 10957|30|25.89|30|0 1840 | 10957|35|18.00|40|0 1841 | 10957|64|33.25|8|0 1842 | 10958|5|21.35|20|0 1843 | 10958|7|30.00|6|0 1844 | 10958|72|34.80|5|0 1845 | 10959|75|7.75|20|0.15 1846 | 10960|24|4.50|10|0.25 1847 | 10960|41|9.65|24|0 1848 | 10961|52|7.00|6|0.05 1849 | 10961|76|18.00|60|0 1850 | 10962|7|30.00|45|0 1851 | 10962|13|6.00|77|0 1852 | 10962|53|32.80|20|0 1853 | 10962|69|36.00|9|0 1854 | 10962|76|18.00|44|0 1855 | 10963|60|34.00|2|0.15 1856 | 10964|18|62.50|6|0 1857 | 10964|38|263.50|5|0 1858 | 10964|69|36.00|10|0 1859 | 10965|51|53.00|16|0 1860 | 10966|37|26.00|8|0 1861 | 10966|56|38.00|12|0.15 1862 | 10966|62|49.30|12|0.15 1863 | 10967|19|9.20|12|0 1864 | 10967|49|20.00|40|0 1865 | 10968|12|38.00|30|0 1866 | 10968|24|4.50|30|0 1867 | 10968|64|33.25|4|0 1868 | 10969|46|12.00|9|0 1869 | 10970|52|7.00|40|0.2 1870 | 10971|29|123.79|14|0 1871 | 10972|17|39.00|6|0 1872 | 10972|33|2.50|7|0 1873 | 10973|26|31.23|5|0 1874 | 10973|41|9.65|6|0 1875 | 10973|75|7.75|10|0 1876 | 10974|63|43.90|10|0 1877 | 10975|8|40.00|16|0 1878 | 10975|75|7.75|10|0 1879 | 10976|28|45.60|20|0 1880 | 10977|39|18.00|30|0 1881 | 10977|47|9.50|30|0 1882 | 10977|51|53.00|10|0 1883 | 10977|63|43.90|20|0 1884 | 10978|8|40.00|20|0.15 1885 | 10978|21|10.00|40|0.15 1886 | 10978|40|18.40|10|0 1887 | 10978|44|19.45|6|0.15 1888 | 10979|7|30.00|18|0 1889 | 10979|12|38.00|20|0 1890 | 10979|24|4.50|80|0 1891 | 10979|27|43.90|30|0 1892 | 10979|31|12.50|24|0 1893 | 10979|63|43.90|35|0 1894 | 10980|75|7.75|40|0.2 1895 | 10981|38|263.50|60|0 1896 | 10982|7|30.00|20|0 1897 | 10982|43|46.00|9|0 1898 | 10983|13|6.00|84|0.15 1899 | 10983|57|19.50|15|0 1900 | 10984|16|17.45|55|0 1901 | 10984|24|4.50|20|0 1902 | 10984|36|19.00|40|0 1903 | 10985|16|17.45|36|0.1 1904 | 10985|18|62.50|8|0.1 1905 | 10985|32|32.00|35|0.1 1906 | 10986|11|21.00|30|0 1907 | 10986|20|81.00|15|0 1908 | 10986|76|18.00|10|0 1909 | 10986|77|13.00|15|0 1910 | 10987|7|30.00|60|0 1911 | 10987|43|46.00|6|0 1912 | 10987|72|34.80|20|0 1913 | 10988|7|30.00|60|0 1914 | 10988|62|49.30|40|0.1 1915 | 10989|6|25.00|40|0 1916 | 10989|11|21.00|15|0 1917 | 10989|41|9.65|4|0 1918 | 10990|21|10.00|65|0 1919 | 10990|34|14.00|60|0.15 1920 | 10990|55|24.00|65|0.15 1921 | 10990|61|28.50|66|0.15 1922 | 10991|2|19.00|50|0.2 1923 | 10991|70|15.00|20|0.2 1924 | 10991|76|18.00|90|0.2 1925 | 10992|72|34.80|2|0 1926 | 10993|29|123.79|50|0.25 1927 | 10993|41|9.65|35|0.25 1928 | 10994|59|55.00|18|0.05 1929 | 10995|51|53.00|20|0 1930 | 10995|60|34.00|4|0 1931 | 10996|42|14.00|40|0 1932 | 10997|32|32.00|50|0 1933 | 10997|46|12.00|20|0.25 1934 | 10997|52|7.00|20|0.25 1935 | 10998|24|4.50|12|0 1936 | 10998|61|28.50|7|0 1937 | 10998|74|10.00|20|0 1938 | 10998|75|7.75|30|0 1939 | 10999|41|9.65|20|0.05 1940 | 10999|51|53.00|15|0.05 1941 | 10999|77|13.00|21|0.05 1942 | 11000|4|22.00|25|0.25 1943 | 11000|24|4.50|30|0.25 1944 | 11000|77|13.00|30|0 1945 | 11001|7|30.00|60|0 1946 | 11001|22|21.00|25|0 1947 | 11001|46|12.00|25|0 1948 | 11001|55|24.00|6|0 1949 | 11002|13|6.00|56|0 1950 | 11002|35|18.00|15|0.15 1951 | 11002|42|14.00|24|0.15 1952 | 11002|55|24.00|40|0 1953 | 11003|1|18.00|4|0 1954 | 11003|40|18.40|10|0 1955 | 11003|52|7.00|10|0 1956 | 11004|26|31.23|6|0 1957 | 11004|76|18.00|6|0 1958 | 11005|1|18.00|2|0 1959 | 11005|59|55.00|10|0 1960 | 11006|1|18.00|8|0 1961 | 11006|29|123.79|2|0.25 1962 | 11007|8|40.00|30|0 1963 | 11007|29|123.79|10|0 1964 | 11007|42|14.00|14|0 1965 | 11008|28|45.60|70|0.05 1966 | 11008|34|14.00|90|0.05 1967 | 11008|71|21.50|21|0 1968 | 11009|24|4.50|12|0 1969 | 11009|36|19.00|18|0.25 1970 | 11009|60|34.00|9|0 1971 | 11010|7|30.00|20|0 1972 | 11010|24|4.50|10|0 1973 | 11011|58|13.25|40|0.05 1974 | 11011|71|21.50|20|0 1975 | 11012|19|9.20|50|0.05 1976 | 11012|60|34.00|36|0.05 1977 | 11012|71|21.50|60|0.05 1978 | 11013|23|9.00|10|0 1979 | 11013|42|14.00|4|0 1980 | 11013|45|9.50|20|0 1981 | 11013|68|12.50|2|0 1982 | 11014|41|9.65|28|0.1 1983 | 11015|30|25.89|15|0 1984 | 11015|77|13.00|18|0 1985 | 11016|31|12.50|15|0 1986 | 11016|36|19.00|16|0 1987 | 11017|3|10.00|25|0 1988 | 11017|59|55.00|110|0 1989 | 11017|70|15.00|30|0 1990 | 11018|12|38.00|20|0 1991 | 11018|18|62.50|10|0 1992 | 11018|56|38.00|5|0 1993 | 11019|46|12.00|3|0 1994 | 11019|49|20.00|2|0 1995 | 11020|10|31.00|24|0.15 1996 | 11021|2|19.00|11|0.25 1997 | 11021|20|81.00|15|0 1998 | 11021|26|31.23|63|0 1999 | 11021|51|53.00|44|0.25 2000 | 11021|72|34.80|35|0 2001 | 11022|19|9.20|35|0 2002 | 11022|69|36.00|30|0 2003 | 11023|7|30.00|4|0 2004 | 11023|43|46.00|30|0 2005 | 11024|26|31.23|12|0 2006 | 11024|33|2.50|30|0 2007 | 11024|65|21.05|21|0 2008 | 11024|71|21.50|50|0 2009 | 11025|1|18.00|10|0.1 2010 | 11025|13|6.00|20|0.1 2011 | 11026|18|62.50|8|0 2012 | 11026|51|53.00|10|0 2013 | 11027|24|4.50|30|0.25 2014 | 11027|62|49.30|21|0.25 2015 | 11028|55|24.00|35|0 2016 | 11028|59|55.00|24|0 2017 | 11029|56|38.00|20|0 2018 | 11029|63|43.90|12|0 2019 | 11030|2|19.00|100|0.25 2020 | 11030|5|21.35|70|0 2021 | 11030|29|123.79|60|0.25 2022 | 11030|59|55.00|100|0.25 2023 | 11031|1|18.00|45|0 2024 | 11031|13|6.00|80|0 2025 | 11031|24|4.50|21|0 2026 | 11031|64|33.25|20|0 2027 | 11031|71|21.50|16|0 2028 | 11032|36|19.00|35|0 2029 | 11032|38|263.50|25|0 2030 | 11032|59|55.00|30|0 2031 | 11033|53|32.80|70|0.1 2032 | 11033|69|36.00|36|0.1 2033 | 11034|21|10.00|15|0.1 2034 | 11034|44|19.45|12|0 2035 | 11034|61|28.50|6|0 2036 | 11035|1|18.00|10|0 2037 | 11035|35|18.00|60|0 2038 | 11035|42|14.00|30|0 2039 | 11035|54|7.45|10|0 2040 | 11036|13|6.00|7|0 2041 | 11036|59|55.00|30|0 2042 | 11037|70|15.00|4|0 2043 | 11038|40|18.40|5|0.2 2044 | 11038|52|7.00|2|0 2045 | 11038|71|21.50|30|0 2046 | 11039|28|45.60|20|0 2047 | 11039|35|18.00|24|0 2048 | 11039|49|20.00|60|0 2049 | 11039|57|19.50|28|0 2050 | 11040|21|10.00|20|0 2051 | 11041|2|19.00|30|0.2 2052 | 11041|63|43.90|30|0 2053 | 11042|44|19.45|15|0 2054 | 11042|61|28.50|4|0 2055 | 11043|11|21.00|10|0 2056 | 11044|62|49.30|12|0 2057 | 11045|33|2.50|15|0 2058 | 11045|51|53.00|24|0 2059 | 11046|12|38.00|20|0.05 2060 | 11046|32|32.00|15|0.05 2061 | 11046|35|18.00|18|0.05 2062 | 11047|1|18.00|25|0.25 2063 | 11047|5|21.35|30|0.25 2064 | 11048|68|12.50|42|0 2065 | 11049|2|19.00|10|0.2 2066 | 11049|12|38.00|4|0.2 2067 | 11050|76|18.00|50|0.1 2068 | 11051|24|4.50|10|0.2 2069 | 11052|43|46.00|30|0.2 2070 | 11052|61|28.50|10|0.2 2071 | 11053|18|62.50|35|0.2 2072 | 11053|32|32.00|20|0 2073 | 11053|64|33.25|25|0.2 2074 | 11054|33|2.50|10|0 2075 | 11054|67|14.00|20|0 2076 | 11055|24|4.50|15|0 2077 | 11055|25|14.00|15|0 2078 | 11055|51|53.00|20|0 2079 | 11055|57|19.50|20|0 2080 | 11056|7|30.00|40|0 2081 | 11056|55|24.00|35|0 2082 | 11056|60|34.00|50|0 2083 | 11057|70|15.00|3|0 2084 | 11058|21|10.00|3|0 2085 | 11058|60|34.00|21|0 2086 | 11058|61|28.50|4|0 2087 | 11059|13|6.00|30|0 2088 | 11059|17|39.00|12|0 2089 | 11059|60|34.00|35|0 2090 | 11060|60|34.00|4|0 2091 | 11060|77|13.00|10|0 2092 | 11061|60|34.00|15|0 2093 | 11062|53|32.80|10|0.2 2094 | 11062|70|15.00|12|0.2 2095 | 11063|34|14.00|30|0 2096 | 11063|40|18.40|40|0.1 2097 | 11063|41|9.65|30|0.1 2098 | 11064|17|39.00|77|0.1 2099 | 11064|41|9.65|12|0 2100 | 11064|53|32.80|25|0.1 2101 | 11064|55|24.00|4|0.1 2102 | 11064|68|12.50|55|0 2103 | 11065|30|25.89|4|0.25 2104 | 11065|54|7.45|20|0.25 2105 | 11066|16|17.45|3|0 2106 | 11066|19|9.20|42|0 2107 | 11066|34|14.00|35|0 2108 | 11067|41|9.65|9|0 2109 | 11068|28|45.60|8|0.15 2110 | 11068|43|46.00|36|0.15 2111 | 11068|77|13.00|28|0.15 2112 | 11069|39|18.00|20|0 2113 | 11070|1|18.00|40|0.15 2114 | 11070|2|19.00|20|0.15 2115 | 11070|16|17.45|30|0.15 2116 | 11070|31|12.50|20|0 2117 | 11071|7|30.00|15|0.05 2118 | 11071|13|6.00|10|0.05 2119 | 11072|2|19.00|8|0 2120 | 11072|41|9.65|40|0 2121 | 11072|50|16.25|22|0 2122 | 11072|64|33.25|130|0 2123 | 11073|11|21.00|10|0 2124 | 11073|24|4.50|20|0 2125 | 11074|16|17.45|14|0.05 2126 | 11075|2|19.00|10|0.15 2127 | 11075|46|12.00|30|0.15 2128 | 11075|76|18.00|2|0.15 2129 | 11076|6|25.00|20|0.25 2130 | 11076|14|23.25|20|0.25 2131 | 11076|19|9.20|10|0.25 2132 | 11077|2|19.00|24|0.2 2133 | 11077|3|10.00|4|0 2134 | 11077|4|22.00|1|0 2135 | 11077|6|25.00|1|0.02 2136 | 11077|7|30.00|1|0.05 2137 | 11077|8|40.00|2|0.1 2138 | 11077|10|31.00|1|0 2139 | 11077|12|38.00|2|0.05 2140 | 11077|13|6.00|4|0 2141 | 11077|14|23.25|1|0.03 2142 | 11077|16|17.45|2|0.03 2143 | 11077|20|81.00|1|0.04 2144 | 11077|23|9.00|2|0 2145 | 11077|32|32.00|1|0 2146 | 11077|39|18.00|2|0.05 2147 | 11077|41|9.65|3|0 2148 | 11077|46|12.00|3|0.02 2149 | 11077|52|7.00|2|0 2150 | 11077|55|24.00|2|0 2151 | 11077|60|34.00|2|0.06 2152 | 11077|64|33.25|2|0.03 2153 | 11077|66|17.00|1|0 2154 | 11077|73|15.00|2|0.01 2155 | 11077|75|7.75|4|0 2156 | 11077|77|13.00|2|0 -------------------------------------------------------------------------------- /examples/products.csv: -------------------------------------------------------------------------------- 1 | ProductID|ProductName|SupplierID|CategoryID|QuantityPerUnit|UnitPrice|UnitsInStock|UnitsOnOrder|ReorderLevel|Discontinued 2 | 1|Chai|1|1|10 boxes x 20 bags|18.00|39|0|10|0 3 | 2|Chang|1|1|24 - 12 oz bottles|19.00|17|40|25|0 4 | 3|Aniseed Syrup|1|2|12 - 550 ml bottles|10.00|13|70|25|0 5 | 4|Chef Anton's Cajun Seasoning|2|2|48 - 6 oz jars|22.00|53|0|0|0 6 | 5|Chef Anton's Gumbo Mix|2|2|36 boxes|21.35|0|0|0|1 7 | 6|Grandma's Boysenberry Spread|3|2|12 - 8 oz jars|25.00|120|0|25|0 8 | 7|Uncle Bob's Organic Dried Pears|3|7|12 - 1 lb pkgs.|30.00|15|0|10|0 9 | 8|Northwoods Cranberry Sauce|3|2|12 - 12 oz jars|40.00|6|0|0|0 10 | 9|Mishi Kobe Niku|4|6|18 - 500 g pkgs.|97.00|29|0|0|1 11 | 10|Ikura|4|8|12 - 200 ml jars|31.00|31|0|0|0 12 | 11|Queso Cabrales|5|4|1 kg pkg.|21.00|22|30|30|0 13 | 12|Queso Manchego La Pastora|5|4|10 - 500 g pkgs.|38.00|86|0|0|0 14 | 13|Konbu|6|8|2 kg box|6.00|24|0|5|0 15 | 14|Tofu|6|7|40 - 100 g pkgs.|23.25|35|0|0|0 16 | 15|Genen Shouyu|6|2|24 - 250 ml bottles|15.50|39|0|5|0 17 | 16|Pavlova|7|3|32 - 500 g boxes|17.45|29|0|10|0 18 | 17|Alice Mutton|7|6|20 - 1 kg tins|39.00|0|0|0|1 19 | 18|Carnarvon Tigers|7|8|16 kg pkg.|62.50|42|0|0|0 20 | 19|Teatime Chocolate Biscuits|8|3|10 boxes x 12 pieces|9.20|25|0|5|0 21 | 20|Sir Rodney's Marmalade|8|3|30 gift boxes|81.00|40|0|0|0 22 | 21|Sir Rodney's Scones|8|3|24 pkgs. x 4 pieces|10.00|3|40|5|0 23 | 22|Gustaf's Knäckebröd|9|5|24 - 500 g pkgs.|21.00|104|0|25|0 24 | 23|Tunnbröd|9|5|12 - 250 g pkgs.|9.00|61|0|25|0 25 | 24|Guaraná Fantástica|10|1|12 - 355 ml cans|4.50|20|0|0|1 26 | 25|NuNuCa Nuß-Nougat-Creme|11|3|20 - 450 g glasses|14.00|76|0|30|0 27 | 26|Gumbär Gummibärchen|11|3|100 - 250 g bags|31.23|15|0|0|0 28 | 27|Schoggi Schokolade|11|3|100 - 100 g pieces|43.90|49|0|30|0 29 | 28|Rössle Sauerkraut|12|7|25 - 825 g cans|45.60|26|0|0|1 30 | 29|Thüringer Rostbratwurst|12|6|50 bags x 30 sausgs.|123.79|0|0|0|1 31 | 30|Nord-Ost Matjeshering|13|8|10 - 200 g glasses|25.89|10|0|15|0 32 | 31|Gorgonzola Telino|14|4|12 - 100 g pkgs|12.50|0|70|20|0 33 | 32|Mascarpone Fabioli|14|4|24 - 200 g pkgs.|32.00|9|40|25|0 34 | 33|Geitost|15|4|500 g|2.50|112|0|20|0 35 | 34|Sasquatch Ale|16|1|24 - 12 oz bottles|14.00|111|0|15|0 36 | 35|Steeleye Stout|16|1|24 - 12 oz bottles|18.00|20|0|15|0 37 | 36|Inlagd Sill|17|8|24 - 250 g jars|19.00|112|0|20|0 38 | 37|Gravad lax|17|8|12 - 500 g pkgs.|26.00|11|50|25|0 39 | 38|Côte de Blaye|18|1|12 - 75 cl bottles|263.50|17|0|15|0 40 | 39|Chartreuse verte|18|1|750 cc per bottle|18.00|69|0|5|0 41 | 40|Boston Crab Meat|19|8|24 - 4 oz tins|18.40|123|0|30|0 42 | 41|Jack's New England Clam Chowder|19|8|12 - 12 oz cans|9.65|85|0|10|0 43 | 42|Singaporean Hokkien Fried Mee|20|5|32 - 1 kg pkgs.|14.00|26|0|0|1 44 | 43|Ipoh Coffee|20|1|16 - 500 g tins|46.00|17|10|25|0 45 | 44|Gula Malacca|20|2|20 - 2 kg bags|19.45|27|0|15|0 46 | 45|Rogede sild|21|8|1k pkg.|9.50|5|70|15|0 47 | 46|Spegesild|21|8|4 - 450 g glasses|12.00|95|0|0|0 48 | 47|Zaanse koeken|22|3|10 - 4 oz boxes|9.50|36|0|0|0 49 | 48|Chocolade|22|3|10 pkgs.|12.75|15|70|25|0 50 | 49|Maxilaku|23|3|24 - 50 g pkgs.|20.00|10|60|15|0 51 | 50|Valkoinen suklaa|23|3|12 - 100 g bars|16.25|65|0|30|0 52 | 51|Manjimup Dried Apples|24|7|50 - 300 g pkgs.|53.00|20|0|10|0 53 | 52|Filo Mix|24|5|16 - 2 kg boxes|7.00|38|0|25|0 54 | 53|Perth Pasties|24|6|48 pieces|32.80|0|0|0|1 55 | 54|Tourtière|25|6|16 pies|7.45|21|0|10|0 56 | 55|Pâté chinois|25|6|24 boxes x 2 pies|24.00|115|0|20|0 57 | 56|Gnocchi di nonna Alice|26|5|24 - 250 g pkgs.|38.00|21|10|30|0 58 | 57|Ravioli Angelo|26|5|24 - 250 g pkgs.|19.50|36|0|20|0 59 | 58|Escargots de Bourgogne|27|8|24 pieces|13.25|62|0|20|0 60 | 59|Raclette Courdavault|28|4|5 kg pkg.|55.00|79|0|0|0 61 | 60|Camembert Pierrot|28|4|15 - 300 g rounds|34.00|19|0|0|0 62 | 61|Sirop d'érable|29|2|24 - 500 ml bottles|28.50|113|0|25|0 63 | 62|Tarte au sucre|29|3|48 pies|49.30|17|0|0|0 64 | 63|Vegie-spread|7|2|15 - 625 g jars|43.90|24|0|5|0 65 | 64|Wimmers gute Semmelknödel|12|5|20 bags x 4 pieces|33.25|22|80|30|0 66 | 65|Louisiana Fiery Hot Pepper Sauce|2|2|32 - 8 oz bottles|21.05|76|0|0|0 67 | 66|Louisiana Hot Spiced Okra|2|2|24 - 8 oz jars|17.00|4|100|20|0 68 | 67|Laughing Lumberjack Lager|16|1|24 - 12 oz bottles|14.00|52|0|10|0 69 | 68|Scottish Longbreads|8|3|10 boxes x 8 pieces|12.50|6|10|15|0 70 | 69|Gudbrandsdalsost|15|4|10 kg pkg.|36.00|26|0|15|0 71 | 70|Outback Lager|7|1|24 - 355 ml bottles|15.00|15|10|30|0 72 | 71|Flotemysost|15|4|10 - 500 g pkgs.|21.50|26|0|0|0 73 | 72|Mozzarella di Giovanni|14|4|24 - 200 g pkgs.|34.80|14|0|0|0 74 | 73|Röd Kaviar|17|8|24 - 150 g jars|15.00|101|0|5|0 75 | 74|Longlife Tofu|4|7|5 kg pkg.|10.00|4|20|5|0 76 | 75|Rhönbräu Klosterbier|12|1|24 - 0.5 l bottles|7.75|125|0|25|0 77 | 76|Lakkalikööri|23|1|500 ml|18.00|57|0|20|0 78 | 77|Original Frankfurter grüne Soße|12|2|12 boxes|13.00|32|0|15|0 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/AdikaStyle/go-df 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/cstockton/go-conv v0.0.0-20170524002450-66a2b2ba36e1 7 | github.com/olekukonko/tablewriter v0.0.4 8 | github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 9 | github.com/stretchr/testify v1.6.1 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cstockton/go-conv v0.0.0-20170524002450-66a2b2ba36e1 h1:h4OgDocdYHGiUh+zUEe4nFlb9ShoHUllqDefGaRoZFg= 2 | github.com/cstockton/go-conv v0.0.0-20170524002450-66a2b2ba36e1/go.mod h1:MBKpQ5HV5wcT/nQYoEqjSMiXwxPouaReOs2f4kj70SQ= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= 6 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 7 | github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= 8 | github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= 9 | github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 h1:nRlQD0u1871kaznCnn1EvYiMbum36v7hw1DLPEjds4o= 10 | github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177/go.mod h1:ao5zGxj8Z4x60IOVYZUbDSmt3R8Ddo080vEgPosHpak= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 15 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 18 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /types/boolean.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cstockton/go-conv" 6 | ) 7 | 8 | type Boolean bool 9 | 10 | func (b Boolean) String() string { 11 | if b { 12 | return "true" 13 | } else { 14 | return "false" 15 | } 16 | } 17 | 18 | func (b Boolean) Equals(other TypedValue) bool { 19 | if b.Kind() == other.Kind() { 20 | return b == other 21 | } 22 | return false 23 | } 24 | 25 | func (b Boolean) Compare(other TypedValue) TypeComparision { 26 | if b.Kind() != other.Kind() { 27 | panic(fmt.Sprintf( 28 | "couldn't compare between different kind of types where left is: %s(%v) and right is: %s(%v)", 29 | b.Kind(), b, other.Kind(), other), 30 | ) 31 | } 32 | 33 | var otherVal bool 34 | other.Cast(&otherVal) 35 | 36 | if b == other { 37 | return Equals 38 | } else if b == true && otherVal == false { 39 | return LeftIsBigger 40 | } else { 41 | return RightIsBigger 42 | } 43 | } 44 | 45 | func (b Boolean) Cast(toPtr interface{}) { 46 | if err := conv.Infer(toPtr, b); err != nil { 47 | panic(err) 48 | } 49 | } 50 | 51 | func (b Boolean) Kind() TypeKind { 52 | return KindBoolean 53 | } 54 | 55 | func (b Boolean) Ptr() TypedValue { 56 | return &b 57 | } 58 | 59 | func (b Boolean) NativeType() interface{} { 60 | return bool(b) 61 | } 62 | 63 | func (b Boolean) Precedence() int { 64 | return 0 65 | } 66 | -------------------------------------------------------------------------------- /types/boolean_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | const trueType = Boolean(true) 9 | const falseType = Boolean(false) 10 | 11 | func TestBooleanType_String(t *testing.T) { 12 | assert.EqualValues(t, "true", trueType.String()) 13 | assert.EqualValues(t, "false", falseType.String()) 14 | } 15 | 16 | func TestBooleanType_Kind(t *testing.T) { 17 | assert.EqualValues(t, KindBoolean, trueType.Kind()) 18 | assert.EqualValues(t, KindBoolean, falseType.Kind()) 19 | } 20 | 21 | func TestBooleanType_Equals(t *testing.T) { 22 | assert.True(t, trueType.Equals(trueType)) 23 | assert.True(t, falseType.Equals(falseType)) 24 | assert.False(t, trueType.Equals(falseType)) 25 | assert.False(t, falseType.Equals(trueType)) 26 | } 27 | 28 | func TestBooleanType_Compare(t *testing.T) { 29 | assert.EqualValues(t, Equals, trueType.Compare(trueType)) 30 | assert.EqualValues(t, LeftIsBigger, trueType.Compare(falseType)) 31 | assert.EqualValues(t, RightIsBigger, falseType.Compare(trueType)) 32 | } 33 | 34 | func TestBooleanType_Cast(t *testing.T) { 35 | var str string 36 | trueType.Cast(&str) 37 | assert.EqualValues(t, "true", str) 38 | 39 | var integer int64 40 | trueType.Cast(&integer) 41 | assert.EqualValues(t, 1, integer) 42 | 43 | var decimal float64 44 | trueType.Cast(&decimal) 45 | assert.EqualValues(t, 1, decimal) 46 | } 47 | -------------------------------------------------------------------------------- /types/datetime.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cstockton/go-conv" 6 | "time" 7 | ) 8 | 9 | type Datetime time.Time 10 | 11 | func (i Datetime) String() string { 12 | return time.Time(i).String() 13 | } 14 | 15 | func (i Datetime) Equals(other TypedValue) bool { 16 | if i.Kind() == other.Kind() { 17 | otherVal, err := conv.Time(other) 18 | PanicOnError(err) 19 | return time.Time(i).Equal(otherVal) 20 | } 21 | return false 22 | } 23 | 24 | func (i Datetime) Compare(other TypedValue) TypeComparision { 25 | if i.Kind() != other.Kind() { 26 | panic(fmt.Sprintf( 27 | "couldn't compare between different kind of types where left is: %s(%v) and right is: %s(%v)", 28 | i.Kind(), i, other.Kind(), other), 29 | ) 30 | } 31 | 32 | otherVal, err := conv.Time(other) 33 | PanicOnError(err) 34 | if time.Time(i).Equal(otherVal) { 35 | return Equals 36 | } else if time.Time(i).After(otherVal) { 37 | return LeftIsBigger 38 | } else { 39 | return RightIsBigger 40 | } 41 | } 42 | 43 | func (i Datetime) Cast(toPtr interface{}) { 44 | if err := conv.Infer(toPtr, i); err != nil { 45 | panic(err) 46 | } 47 | } 48 | 49 | func (i Datetime) Kind() TypeKind { 50 | return KindDatetime 51 | } 52 | 53 | func (i Datetime) Ptr() TypedValue { 54 | return &i 55 | } 56 | 57 | func (i Datetime) NativeType() interface{} { 58 | return time.Time(i) 59 | } 60 | 61 | func (i Datetime) Precedence() int { 62 | return 3 63 | } 64 | -------------------------------------------------------------------------------- /types/datetime_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var datetime1999 = Datetime(time.Date(1999, 1, 1, 10, 0, 0, 0, time.UTC)) 10 | var datetime1999_1 = Datetime(time.Date(1999, 1, 1, 10, 0, 0, 0, time.UTC)) 11 | var datetime2010 = Datetime(time.Date(2010, 1, 1, 10, 0, 0, 0, time.UTC)) 12 | 13 | func TestDateTimeType_String(t *testing.T) { 14 | assert.EqualValues(t, "1999-01-01 10:00:00 +0000 UTC", datetime1999.String()) 15 | assert.EqualValues(t, "2010-01-01 10:00:00 +0000 UTC", datetime2010.String()) 16 | } 17 | 18 | func TestDateTimeType_Kind(t *testing.T) { 19 | assert.EqualValues(t, KindDatetime, datetime1999.Kind()) 20 | assert.EqualValues(t, KindDatetime, datetime2010.Kind()) 21 | } 22 | 23 | func TestDateTimeType_Equals(t *testing.T) { 24 | assert.True(t, datetime1999.Equals(datetime1999_1)) 25 | assert.False(t, datetime1999.Equals(datetime2010)) 26 | assert.False(t, datetime2010.Equals(datetime1999)) 27 | } 28 | 29 | func TestDateTimeType_Compare(t *testing.T) { 30 | assert.EqualValues(t, Equals, datetime1999.Compare(datetime1999_1)) 31 | assert.EqualValues(t, LeftIsBigger, datetime2010.Compare(datetime1999)) 32 | assert.EqualValues(t, RightIsBigger, datetime1999.Compare(datetime2010)) 33 | } 34 | 35 | func TestDateTimeType_Cast(t *testing.T) { 36 | var str string 37 | datetime2010.Cast(&str) 38 | assert.EqualValues(t, "2010-01-01 10:00:00 +0000 UTC", str) 39 | } 40 | -------------------------------------------------------------------------------- /types/decimal.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cstockton/go-conv" 6 | "strconv" 7 | ) 8 | 9 | type Decimal float64 10 | 11 | func (i Decimal) String() string { 12 | str, err := conv.String(float64(i)) 13 | if err != nil { 14 | panic(err) 15 | } 16 | return str 17 | } 18 | 19 | func (i Decimal) Equals(other TypedValue) bool { 20 | if i.Kind() == other.Kind() { 21 | return i == other 22 | } 23 | return false 24 | } 25 | 26 | func (i Decimal) Compare(other TypedValue) TypeComparision { 27 | if i.Kind() != other.Kind() { 28 | panic(fmt.Sprintf( 29 | "couldn't compare between different kind of types where left is: %s(%v) and right is: %s(%v)", 30 | i.Kind(), i, other.Kind(), other), 31 | ) 32 | } 33 | 34 | otherVal, err := conv.Float64(other) 35 | PanicOnError(err) 36 | if float64(i) == otherVal { 37 | return Equals 38 | } else if float64(i) > otherVal { 39 | return LeftIsBigger 40 | } else { 41 | return RightIsBigger 42 | } 43 | } 44 | 45 | func (i Decimal) Cast(toPtr interface{}) { 46 | switch toPtr.(type) { 47 | case *string: 48 | temp := strconv.FormatFloat(i.NativeType().(float64), 'f', -1, 64) 49 | origin := toPtr.(*string) 50 | *origin = temp 51 | default: 52 | if err := conv.Infer(toPtr, i.NativeType()); err != nil { 53 | panic(err) 54 | } 55 | } 56 | } 57 | 58 | func (i Decimal) Kind() TypeKind { 59 | return KindDecimal 60 | } 61 | 62 | func (i Decimal) Ptr() TypedValue { 63 | return &i 64 | } 65 | 66 | func (i Decimal) NativeType() interface{} { 67 | return float64(i) 68 | } 69 | 70 | func (i Decimal) Precedence() int { 71 | return 2 72 | } 73 | -------------------------------------------------------------------------------- /types/decimal_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | const pi = Decimal(3.14) 9 | const pi_1 = Decimal(3.14) 10 | const e = Decimal(2.71) 11 | const long = Decimal(193655049651) 12 | 13 | func TestDecimalType_String(t *testing.T) { 14 | assert.EqualValues(t, "3.14", pi.String()) 15 | assert.EqualValues(t, "2.71", e.String()) 16 | } 17 | 18 | func TestDecimalType_Kind(t *testing.T) { 19 | assert.EqualValues(t, KindDecimal, pi.Kind()) 20 | assert.EqualValues(t, KindDecimal, e.Kind()) 21 | } 22 | 23 | func TestDecimalType_Equals(t *testing.T) { 24 | assert.True(t, pi.Equals(pi_1)) 25 | assert.False(t, pi.Equals(e)) 26 | assert.False(t, e.Equals(pi)) 27 | } 28 | 29 | func TestDecimalType_Compare(t *testing.T) { 30 | assert.EqualValues(t, Equals, pi.Compare(pi_1)) 31 | assert.EqualValues(t, LeftIsBigger, pi.Compare(e)) 32 | assert.EqualValues(t, RightIsBigger, e.Compare(pi)) 33 | } 34 | 35 | func TestDecimalType_Cast(t *testing.T) { 36 | var str string 37 | pi.Cast(&str) 38 | assert.EqualValues(t, "3.14", str) 39 | 40 | var strLong string 41 | long.Cast(&strLong) 42 | assert.EqualValues(t, "193655049651", strLong) 43 | 44 | var integer int64 45 | pi.Cast(&integer) 46 | assert.EqualValues(t, 3, integer) 47 | } 48 | -------------------------------------------------------------------------------- /types/index.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "fmt" 4 | 5 | type TypeComparision int 6 | 7 | const ( 8 | Equals TypeComparision = 0 9 | LeftIsBigger TypeComparision = 1 10 | RightIsBigger TypeComparision = -1 11 | ) 12 | 13 | type TypeKind string 14 | 15 | const ( 16 | KindBoolean TypeKind = "Boolean" 17 | KindInteger TypeKind = "Integer" 18 | KindDecimal TypeKind = "Decimal" 19 | KindString TypeKind = "String" 20 | KindDatetime TypeKind = "Datetime" 21 | 22 | KindMissing TypeKind = "KindMissing" 23 | KindUnknown TypeKind = "KindUnknown" 24 | ) 25 | 26 | type TypedValue interface { 27 | fmt.Stringer 28 | Equals(other TypedValue) bool 29 | Compare(other TypedValue) TypeComparision 30 | Cast(toPtr interface{}) 31 | Kind() TypeKind 32 | Ptr() TypedValue 33 | NativeType() interface{} 34 | Precedence() int 35 | } 36 | -------------------------------------------------------------------------------- /types/integer.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cstockton/go-conv" 6 | ) 7 | 8 | type Integer int64 9 | 10 | func (i Integer) String() string { 11 | str, err := conv.String(int64(i)) 12 | if err != nil { 13 | panic(err) 14 | } 15 | return str 16 | } 17 | 18 | func (i Integer) Equals(other TypedValue) bool { 19 | if i.Kind() == other.Kind() { 20 | return i == other 21 | } 22 | return false 23 | } 24 | 25 | func (i Integer) Compare(other TypedValue) TypeComparision { 26 | if i.Kind() != other.Kind() { 27 | panic(fmt.Sprintf( 28 | "couldn't compare between different kind of types where left is: %s(%v) and right is: %s(%v)", 29 | i.Kind(), i, other.Kind(), other), 30 | ) 31 | } 32 | 33 | otherVal, err := conv.Int64(other) 34 | PanicOnError(err) 35 | if int64(i) == otherVal { 36 | return Equals 37 | } else if int64(i) > otherVal { 38 | return LeftIsBigger 39 | } else { 40 | return RightIsBigger 41 | } 42 | } 43 | 44 | func (i Integer) Cast(toPtr interface{}) { 45 | if err := conv.Infer(toPtr, i); err != nil { 46 | panic(err) 47 | } 48 | } 49 | 50 | func (i Integer) Kind() TypeKind { 51 | return KindInteger 52 | } 53 | 54 | func (i Integer) Ptr() TypedValue { 55 | return &i 56 | } 57 | 58 | func (i Integer) NativeType() interface{} { 59 | return int64(i) 60 | } 61 | 62 | func (i Integer) Precedence() int { 63 | return 1 64 | } 65 | -------------------------------------------------------------------------------- /types/integer_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | const one = Integer(1) 9 | const one_1 = Integer(1) 10 | const two = Integer(2) 11 | 12 | func TestIntegerType_String(t *testing.T) { 13 | assert.EqualValues(t, "1", one.String()) 14 | assert.EqualValues(t, "2", two.String()) 15 | } 16 | 17 | func TestIntegerType_Kind(t *testing.T) { 18 | assert.EqualValues(t, KindInteger, one.Kind()) 19 | assert.EqualValues(t, KindInteger, two.Kind()) 20 | } 21 | 22 | func TestIntegerType_Equals(t *testing.T) { 23 | assert.True(t, one.Equals(one_1)) 24 | assert.False(t, one.Equals(two)) 25 | assert.False(t, two.Equals(one)) 26 | } 27 | 28 | func TestIntegerType_Compare(t *testing.T) { 29 | assert.EqualValues(t, Equals, one.Compare(one_1)) 30 | assert.EqualValues(t, LeftIsBigger, two.Compare(one)) 31 | assert.EqualValues(t, RightIsBigger, one.Compare(two)) 32 | } 33 | 34 | func TestIntegerType_Cast(t *testing.T) { 35 | var str string 36 | two.Cast(&str) 37 | assert.EqualValues(t, "2", str) 38 | 39 | var decimal float64 40 | two.Cast(&decimal) 41 | assert.EqualValues(t, 2, decimal) 42 | } 43 | -------------------------------------------------------------------------------- /types/missing.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type MissingValue struct{} 4 | 5 | var Missing MissingValue 6 | 7 | func (m MissingValue) String() string { 8 | return "null" 9 | } 10 | 11 | func (m MissingValue) Equals(other TypedValue) bool { 12 | if other == nil { 13 | return true 14 | } else { 15 | return false 16 | } 17 | } 18 | 19 | func (m MissingValue) Compare(other TypedValue) TypeComparision { 20 | if other == nil { 21 | return Equals 22 | } else { 23 | return RightIsBigger 24 | } 25 | } 26 | 27 | func (m MissingValue) Cast(toPtr interface{}) { 28 | toPtr = nil 29 | } 30 | 31 | func (m MissingValue) Kind() TypeKind { 32 | return KindMissing 33 | } 34 | 35 | func (m MissingValue) Ptr() TypedValue { 36 | return nil 37 | } 38 | 39 | func (b MissingValue) NativeType() interface{} { 40 | return nil 41 | } 42 | 43 | func (m MissingValue) Precedence() int { 44 | return -1 45 | } 46 | -------------------------------------------------------------------------------- /types/string.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cstockton/go-conv" 6 | ) 7 | 8 | type String string 9 | 10 | func (i String) String() string { 11 | return string(i) 12 | } 13 | 14 | func (i String) Equals(other TypedValue) bool { 15 | if i.Kind() == other.Kind() { 16 | return i == other 17 | } 18 | return false 19 | } 20 | 21 | func (i String) Compare(other TypedValue) TypeComparision { 22 | if i.Kind() != other.Kind() { 23 | panic(fmt.Sprintf( 24 | "couldn't compare between different kind of types where left is: %s(%v) and right is: %s(%v)", 25 | i.Kind(), i, other.Kind(), other), 26 | ) 27 | } 28 | 29 | otherVal, err := conv.String(other) 30 | PanicOnError(err) 31 | if string(i) == otherVal { 32 | return Equals 33 | } else if string(i) > otherVal { 34 | return LeftIsBigger 35 | } else { 36 | return RightIsBigger 37 | } 38 | } 39 | 40 | func (i String) Cast(toPtr interface{}) { 41 | if err := conv.Infer(toPtr, i); err != nil { 42 | panic(err) 43 | } 44 | } 45 | 46 | func (i String) Kind() TypeKind { 47 | return KindString 48 | } 49 | 50 | func (i String) Ptr() TypedValue { 51 | return &i 52 | } 53 | 54 | func (i String) NativeType() interface{} { 55 | return string(i) 56 | } 57 | 58 | func (i String) Precedence() int { 59 | return 4 60 | } 61 | -------------------------------------------------------------------------------- /types/utils.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "reflect" 7 | "time" 8 | ) 9 | 10 | func PanicOnError(err error) { 11 | if err != nil { 12 | panic(err) 13 | } 14 | } 15 | 16 | func MatchKind(value interface{}) TypeKind { 17 | if value == nil { 18 | return KindMissing 19 | } 20 | 21 | switch tp := value.(type) { 22 | case bool: 23 | return KindBoolean 24 | case uint8, uint16, uint32, uint64, uint, int8, int16, int32, int64, int: 25 | return KindInteger 26 | case float32, float64: 27 | return KindDecimal 28 | case string: 29 | return KindString 30 | case time.Time: 31 | return KindDatetime 32 | default: 33 | panic(fmt.Sprintf("type: %v isn't supported", tp)) 34 | } 35 | } 36 | 37 | func Convert(value interface{}, kind TypeKind) TypedValue { 38 | v := reflect.ValueOf(value) 39 | switch tp := value.(type) { 40 | case bool: 41 | return Boolean(v.Bool()) 42 | case uint8, uint16, uint32, uint64, uint, int8, int16, int32, int64, int: 43 | return Integer(v.Int()) 44 | case float32, float64: 45 | return Decimal(v.Float()) 46 | case string: 47 | return String(v.String()) 48 | case time.Time: 49 | return Datetime(value.(time.Time)) 50 | default: 51 | panic(fmt.Sprintf("type: %v isn't supported", tp)) 52 | } 53 | } 54 | 55 | func AutoCast(from TypedValue, to TypeKind) TypedValue { 56 | 57 | if from == nil { 58 | return Missing 59 | } 60 | 61 | switch to { 62 | case KindBoolean: 63 | var out bool 64 | from.Cast(&out) 65 | return Boolean(out) 66 | case KindInteger: 67 | var out int64 68 | from.Cast(&out) 69 | return Integer(out) 70 | case KindDecimal: 71 | var out float64 72 | from.Cast(&out) 73 | return Decimal(out) 74 | case KindDatetime: 75 | var out time.Time 76 | from.Cast(&out) 77 | return Datetime(out) 78 | case KindString: 79 | var out string 80 | from.Cast(&out) 81 | return String(out) 82 | case KindMissing: 83 | return Missing 84 | default: 85 | log.Fatalf("unable to auto cast type from: %v to kind: %s", from, to) 86 | return Missing 87 | } 88 | } 89 | --------------------------------------------------------------------------------