././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5677354 datrie-0.8.3/0000755000175100017510000000000015054046505013256 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/.gitignore0000644000175100017510000000020715054046003015236 0ustar00tcaswelltcaswell*.pyc src/*.html .hypothesis/ .eggs/ .tox .cache *.egg-info/* dist/ *.so build/ src/datrie.c src/cdatrie.c src/stdio_ext.c wheelhouse././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/.gitmodules0000644000175100017510000000012715054046003015424 0ustar00tcaswelltcaswell[submodule "libdatrie"] path = libdatrie url = https://github.com/tlwg/libdatrie.git ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/.travis.yml0000644000175100017510000000024715054046003015363 0ustar00tcaswelltcaswelldist: xenial cache: pip language: python python: - "2.7" - "3.4" - "3.5" - "3.6" - "3.7" - "3.8" install: - pip install tox-travis cython script: tox ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/CHANGES.rst0000644000175100017510000000615415054046003015057 0ustar00tcaswelltcaswellCHANGES ======= 0.8.2 (2020-03-25) ------------------ * Future-proof Python support by making cython a build time dependency and removing cython generated c files from the repo (and sdist). * Fix collections.abc.MutableMapping import * CI and test updates * Adjust library name to unbreak some linkers 0.8.1 (skipped) --------------- This version intentionally skipped 0.8 (2019-07-03) ---------------- * Python 3.7 compatibility; extension is rebuilt with Cython 0.29.11. * Trie.get function; * Python 2.6 and 3.3 support is dropped; * removed patch to libdatrie which is no longer required; * testing and CI fixes. 0.7.1 (2016-03-12) ------------------ * updated the bundled C library to version 0.2.9; * implemented ``Trie.__len__`` in terms of ``trie_enumerate``; * rebuilt Cython wrapper with Cython 0.23.4; * changed ``Trie`` to implement ``collections.abc.MutableMapping``; * fixed ``Trie`` pickling, which segfaulted on Python2.X. 0.7 (2014-02-18) ---------------- * bundled libdatrie C library is updated to version 0.2.8; * new `.suffixes()` method (thanks Ahmed T. Youssef); * wrapper is rebuilt with Cython 0.20.1. 0.6.1 (2013-09-21) ------------------ * fixed build for Visual Studio (thanks Gabi Davar). 0.6 (2013-07-09) ---------------- * datrie is rebuilt with Cython 0.19.1; * ``iter_prefix_values``, ``prefix_values`` and ``longest_prefix_value`` methods for ``datrie.BaseTrie`` and ``datrie.Trie`` (thanks Jared Suttles). 0.5.1 (2013-01-30) ------------------ * Recently introduced memory leak in ``longest_prefix`` and ``longest_prefix_item`` is fixed. 0.5 (2013-01-29) ---------------- * ``longest_prefix`` and ``longest_prefix_item`` methods are fixed; * datrie is rebuilt with Cython 0.18; * misleading benchmark results in README are fixed; * State._walk is renamed to State.walk_char. 0.4.2 (2012-09-02) ------------------ * Update to latest libdatrie; this makes ``.keys()`` method a bit slower but removes a keys length limitation. 0.4.1 (2012-07-29) ------------------ * cPickle is used for saving/loading ``datrie.Trie`` if it is available. 0.4 (2012-07-27) ---------------- * ``libdatrie`` improvements and bugfixes, including C iterator API support; * custom iteration support using ``datrie.State`` and ``datrie.Iterator``. * speed improvements: ``__length__``, ``keys``, ``values`` and ``items`` methods should be up to 2x faster. * keys longer than 32768 are not supported in this release. 0.3 (2012-07-21) ---------------- There are no new features or speed improvements in this release. * ``datrie.new`` is deprecated; use ``datrie.Trie`` with the same arguments; * small test & benchmark improvements. 0.2 (2012-07-16) ---------------- * ``datrie.Trie`` items can have any Python object as a value (``Trie`` from 0.1.x becomes ``datrie.BaseTrie``); * ``longest_prefix`` and ``longest_prefix_items`` are fixed; * ``save`` & ``load`` are rewritten; * ``setdefault`` method. 0.1.1 (2012-07-13) ------------------ * Windows support (upstream libdatrie changes are merged); * license is changed from LGPL v3 to LGPL v2.1 to match the libdatrie license. 0.1 (2012-07-12) ---------------- Initial release. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/COPYING0000644000175100017510000006363415054046003014316 0ustar00tcaswelltcaswell GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/MANIFEST.in0000644000175100017510000000051415054046003015005 0ustar00tcaswelltcaswellinclude README.rst include CHANGES.rst include COPYING include tox.ini include tox-bench.ini include update_c.sh recursive-include libdatrie *.h recursive-include libdatrie *.c include tests/words100k.txt.zip recursive-include tests *.py include src/datrie.pyx include src/cdatrie.pxd include src/stdio_ext.pxd exclude src/datrie.c././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5677168 datrie-0.8.3/PKG-INFO0000644000175100017510000003600415054046505014356 0ustar00tcaswelltcaswellMetadata-Version: 2.4 Name: datrie Version: 0.8.3 Summary: Super-fast, efficiently stored Trie for Python. Home-page: https://github.com/kmike/datrie Author: Mikhail Korobov Author-email: kmike84@gmail.com License: LGPLv2+ Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) Classifier: Programming Language :: Cython Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Scientific/Engineering :: Information Analysis Classifier: Topic :: Text Processing :: Linguistic Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* License-File: COPYING Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: license Dynamic: license-file Dynamic: requires-python Dynamic: summary datrie |travis| |appveyor| ========================== .. |travis| image:: https://travis-ci.org/pytries/datrie.svg :target: https://travis-ci.org/pytries/datrie .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/6bpvhllpjhlau7x0?svg=true :target: https://ci.appveyor.com/project/superbobry/datrie Super-fast, efficiently stored Trie for Python (2.x and 3.x). Uses `libdatrie`_. .. _libdatrie: https://linux.thai.net/~thep/datrie/datrie.html Installation ============ :: pip install datrie Usage ===== Create a new trie capable of storing items with lower-case ascii keys:: >>> import string >>> import datrie >>> trie = datrie.Trie(string.ascii_lowercase) ``trie`` variable is a dict-like object that can have unicode keys of certain ranges and Python objects as values. In addition to implementing the mapping interface, tries facilitate finding the items for a given prefix, and vice versa, finding the items whose keys are prefixes of a given string. As a common special case, finding the longest-prefix item is also supported. .. warning:: For efficiency you must define allowed character range(s) while creating trie. ``datrie`` doesn't check if keys are in allowed ranges at runtime, so be careful! Invalid keys are OK at lookup time but values won't be stored correctly for such keys. Add some values to it (datrie keys must be unicode; the examples are for Python 2.x):: >>> trie[u'foo'] = 5 >>> trie[u'foobar'] = 10 >>> trie[u'bar'] = 'bar value' >>> trie.setdefault(u'foobar', 15) 10 Check if u'foo' is in trie:: >>> u'foo' in trie True Get a value:: >>> trie[u'foo'] 5 Find all prefixes of a word:: >>> trie.prefixes(u'foobarbaz') [u'foo', u'foobar'] >>> trie.prefix_items(u'foobarbaz') [(u'foo', 5), (u'foobar', 10)] >>> trie.iter_prefixes(u'foobarbaz') >>> trie.iter_prefix_items(u'foobarbaz') Find the longest prefix of a word:: >>> trie.longest_prefix(u'foo') u'foo' >>> trie.longest_prefix(u'foobarbaz') u'foobar' >>> trie.longest_prefix(u'gaz') KeyError: u'gaz' >>> trie.longest_prefix(u'gaz', default=u'vasia') u'vasia' >>> trie.longest_prefix_item(u'foobarbaz') (u'foobar', 10) Check if the trie has keys with a given prefix:: >>> trie.has_keys_with_prefix(u'fo') True >>> trie.has_keys_with_prefix(u'FO') False Get all items with a given prefix from a trie:: >>> trie.keys(u'fo') [u'foo', u'foobar'] >>> trie.items(u'ba') [(u'bar', 'bar value')] >>> trie.values(u'foob') [10] Get all suffixes of certain word starting with a given prefix from a trie:: >>> trie.suffixes() [u'pro', u'producer', u'producers', u'product', u'production', u'productivity', u'prof'] >>> trie.suffixes(u'prod') [u'ucer', u'ucers', u'uct', u'uction', u'uctivity'] Save & load a trie (values must be picklable):: >>> trie.save('my.trie') >>> trie2 = datrie.Trie.load('my.trie') Trie and BaseTrie ================= There are two Trie classes in datrie package: ``datrie.Trie`` and ``datrie.BaseTrie``. ``datrie.BaseTrie`` is slightly faster and uses less memory but it can store only integer numbers -2147483648 <= x <= 2147483647. ``datrie.Trie`` is a bit slower but can store any Python object as a value. If you don't need values or integer values are OK then use ``datrie.BaseTrie``:: import datrie import string trie = datrie.BaseTrie(string.ascii_lowercase) Custom iteration ================ If the built-in trie methods don't fit you can use ``datrie.State`` and ``datrie.Iterator`` to implement custom traversal. .. note:: If you use ``datrie.BaseTrie`` you need ``datrie.BaseState`` and ``datrie.BaseIterator`` for custom traversal. For example, let's find all suffixes of ``'fo'`` for our trie and get the values:: >>> state = datrie.State(trie) >>> state.walk(u'foo') >>> it = datrie.Iterator(state) >>> while it.next(): ... print(it.key()) ... print(it.data)) o 5 obar 10 Performance =========== Performance is measured for ``datrie.Trie`` against Python's dict with 100k unique unicode words (English and Russian) as keys and '1' numbers as values. ``datrie.Trie`` uses about 5M memory for 100k words; Python's dict uses about 22M for this according to my unscientific tests. This trie implementation is 2-6 times slower than python's dict on __getitem__. Benchmark results (macbook air i5 1.8GHz, "1.000M ops/sec" == "1 000 000 operations per second"):: Python 2.6: dict __getitem__: 7.107M ops/sec trie __getitem__: 2.478M ops/sec Python 2.7: dict __getitem__: 6.550M ops/sec trie __getitem__: 2.474M ops/sec Python 3.2: dict __getitem__: 8.185M ops/sec trie __getitem__: 2.684M ops/sec Python 3.3: dict __getitem__: 7.050M ops/sec trie __getitem__: 2.755M ops/sec Looking for prefixes of a given word is almost as fast as ``__getitem__`` (results are for Python 3.3):: trie.iter_prefix_items (hits): 0.461M ops/sec trie.prefix_items (hits): 0.743M ops/sec trie.prefix_items loop (hits): 0.629M ops/sec trie.iter_prefixes (hits): 0.759M ops/sec trie.iter_prefixes (misses): 1.538M ops/sec trie.iter_prefixes (mixed): 1.359M ops/sec trie.has_keys_with_prefix (hits): 1.896M ops/sec trie.has_keys_with_prefix (misses): 2.590M ops/sec trie.longest_prefix (hits): 1.710M ops/sec trie.longest_prefix (misses): 1.506M ops/sec trie.longest_prefix (mixed): 1.520M ops/sec trie.longest_prefix_item (hits): 1.276M ops/sec trie.longest_prefix_item (misses): 1.292M ops/sec trie.longest_prefix_item (mixed): 1.379M ops/sec Looking for all words starting with a given prefix is mostly limited by overall result count (this can be improved in future because a lot of time is spent decoding strings from utf_32_le to Python's unicode):: trie.items(prefix="xxx"), avg_len(res)==415: 0.609K ops/sec trie.keys(prefix="xxx"), avg_len(res)==415: 0.642K ops/sec trie.values(prefix="xxx"), avg_len(res)==415: 4.974K ops/sec trie.items(prefix="xxxxx"), avg_len(res)==17: 14.781K ops/sec trie.keys(prefix="xxxxx"), avg_len(res)==17: 15.766K ops/sec trie.values(prefix="xxxxx"), avg_len(res)==17: 96.456K ops/sec trie.items(prefix="xxxxxxxx"), avg_len(res)==3: 75.165K ops/sec trie.keys(prefix="xxxxxxxx"), avg_len(res)==3: 77.225K ops/sec trie.values(prefix="xxxxxxxx"), avg_len(res)==3: 320.755K ops/sec trie.items(prefix="xxxxx..xx"), avg_len(res)==1.4: 173.591K ops/sec trie.keys(prefix="xxxxx..xx"), avg_len(res)==1.4: 180.678K ops/sec trie.values(prefix="xxxxx..xx"), avg_len(res)==1.4: 503.392K ops/sec trie.items(prefix="xxx"), NON_EXISTING: 2023.647K ops/sec trie.keys(prefix="xxx"), NON_EXISTING: 1976.928K ops/sec trie.values(prefix="xxx"), NON_EXISTING: 2060.372K ops/sec Random insert time is very slow compared to dict, this is the limitation of double-array tries; updates are quite fast. If you want to build a trie, consider sorting keys before the insertion:: dict __setitem__ (updates): 6.497M ops/sec trie __setitem__ (updates): 2.633M ops/sec dict __setitem__ (inserts, random): 5.808M ops/sec trie __setitem__ (inserts, random): 0.053M ops/sec dict __setitem__ (inserts, sorted): 5.749M ops/sec trie __setitem__ (inserts, sorted): 0.624M ops/sec dict setdefault (updates): 3.455M ops/sec trie setdefault (updates): 1.910M ops/sec dict setdefault (inserts): 3.466M ops/sec trie setdefault (inserts): 0.053M ops/sec Other results (note that ``len(trie)`` is currently implemented using trie traversal):: dict __contains__ (hits): 6.801M ops/sec trie __contains__ (hits): 2.816M ops/sec dict __contains__ (misses): 5.470M ops/sec trie __contains__ (misses): 4.224M ops/sec dict __len__: 334336.269 ops/sec trie __len__: 22.900 ops/sec dict values(): 406.507 ops/sec trie values(): 20.864 ops/sec dict keys(): 189.298 ops/sec trie keys(): 2.773 ops/sec dict items(): 48.734 ops/sec trie items(): 2.611 ops/sec Please take this benchmark results with a grain of salt; this is a very simple benchmark and may not cover your use case. Current Limitations =================== * keys must be unicode (no implicit conversion for byte strings under Python 2.x, sorry); * there are no iterator versions of keys/values/items (this is not implemented yet); * it is painfully slow and maybe buggy under pypy; * library is not tested with narrow Python builds. Contributing ============ Development happens at github: https://github.com/pytries/datrie. Feel free to submit ideas, bugs, pull requests. Running tests and benchmarks ---------------------------- Make sure `tox`_ is installed and run :: $ tox from the source checkout. Tests should pass under Python 2.7 and 3.4+. :: $ tox -c tox-bench.ini runs benchmarks. If you've changed anything in the source code then make sure `cython`_ is installed and run :: $ update_c.sh before each ``tox`` command. Please note that benchmarks are not included in the release tar.gz's because benchmark data is large and this saves a lot of bandwidth; use source checkouts from github or bitbucket for the benchmarks. .. _cython: https://cython.org/ .. _tox: https://tox.readthedocs.io/ Authors & Contributors ---------------------- See https://github.com/pytries/datrie/graphs/contributors. This module is based on `libdatrie`_ C library by Theppitak Karoonboonyanan and is inspired by `fast_trie`_ Ruby bindings, `PyTrie`_ pure Python implementation and `Tree::Trie`_ Perl implementation; some docs and API ideas are borrowed from these projects. .. _fast_trie: https://github.com/tyler/trie .. _PyTrie: https://github.com/gsakkis/pytrie .. _Tree::Trie: https://metacpan.org/pod/release/AVIF/Tree-Trie-1.9/Trie.pm License ======= Licensed under LGPL v2.1. CHANGES ======= 0.8.2 (2020-03-25) ------------------ * Future-proof Python support by making cython a build time dependency and removing cython generated c files from the repo (and sdist). * Fix collections.abc.MutableMapping import * CI and test updates * Adjust library name to unbreak some linkers 0.8.1 (skipped) --------------- This version intentionally skipped 0.8 (2019-07-03) ---------------- * Python 3.7 compatibility; extension is rebuilt with Cython 0.29.11. * Trie.get function; * Python 2.6 and 3.3 support is dropped; * removed patch to libdatrie which is no longer required; * testing and CI fixes. 0.7.1 (2016-03-12) ------------------ * updated the bundled C library to version 0.2.9; * implemented ``Trie.__len__`` in terms of ``trie_enumerate``; * rebuilt Cython wrapper with Cython 0.23.4; * changed ``Trie`` to implement ``collections.abc.MutableMapping``; * fixed ``Trie`` pickling, which segfaulted on Python2.X. 0.7 (2014-02-18) ---------------- * bundled libdatrie C library is updated to version 0.2.8; * new `.suffixes()` method (thanks Ahmed T. Youssef); * wrapper is rebuilt with Cython 0.20.1. 0.6.1 (2013-09-21) ------------------ * fixed build for Visual Studio (thanks Gabi Davar). 0.6 (2013-07-09) ---------------- * datrie is rebuilt with Cython 0.19.1; * ``iter_prefix_values``, ``prefix_values`` and ``longest_prefix_value`` methods for ``datrie.BaseTrie`` and ``datrie.Trie`` (thanks Jared Suttles). 0.5.1 (2013-01-30) ------------------ * Recently introduced memory leak in ``longest_prefix`` and ``longest_prefix_item`` is fixed. 0.5 (2013-01-29) ---------------- * ``longest_prefix`` and ``longest_prefix_item`` methods are fixed; * datrie is rebuilt with Cython 0.18; * misleading benchmark results in README are fixed; * State._walk is renamed to State.walk_char. 0.4.2 (2012-09-02) ------------------ * Update to latest libdatrie; this makes ``.keys()`` method a bit slower but removes a keys length limitation. 0.4.1 (2012-07-29) ------------------ * cPickle is used for saving/loading ``datrie.Trie`` if it is available. 0.4 (2012-07-27) ---------------- * ``libdatrie`` improvements and bugfixes, including C iterator API support; * custom iteration support using ``datrie.State`` and ``datrie.Iterator``. * speed improvements: ``__length__``, ``keys``, ``values`` and ``items`` methods should be up to 2x faster. * keys longer than 32768 are not supported in this release. 0.3 (2012-07-21) ---------------- There are no new features or speed improvements in this release. * ``datrie.new`` is deprecated; use ``datrie.Trie`` with the same arguments; * small test & benchmark improvements. 0.2 (2012-07-16) ---------------- * ``datrie.Trie`` items can have any Python object as a value (``Trie`` from 0.1.x becomes ``datrie.BaseTrie``); * ``longest_prefix`` and ``longest_prefix_items`` are fixed; * ``save`` & ``load`` are rewritten; * ``setdefault`` method. 0.1.1 (2012-07-13) ------------------ * Windows support (upstream libdatrie changes are merged); * license is changed from LGPL v3 to LGPL v2.1 to match the libdatrie license. 0.1 (2012-07-12) ---------------- Initial release. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/README.rst0000644000175100017510000002434415054046003014745 0ustar00tcaswelltcaswelldatrie |travis| |appveyor| ========================== .. |travis| image:: https://travis-ci.org/pytries/datrie.svg :target: https://travis-ci.org/pytries/datrie .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/6bpvhllpjhlau7x0?svg=true :target: https://ci.appveyor.com/project/superbobry/datrie Super-fast, efficiently stored Trie for Python (2.x and 3.x). Uses `libdatrie`_. .. _libdatrie: https://linux.thai.net/~thep/datrie/datrie.html Installation ============ :: pip install datrie Usage ===== Create a new trie capable of storing items with lower-case ascii keys:: >>> import string >>> import datrie >>> trie = datrie.Trie(string.ascii_lowercase) ``trie`` variable is a dict-like object that can have unicode keys of certain ranges and Python objects as values. In addition to implementing the mapping interface, tries facilitate finding the items for a given prefix, and vice versa, finding the items whose keys are prefixes of a given string. As a common special case, finding the longest-prefix item is also supported. .. warning:: For efficiency you must define allowed character range(s) while creating trie. ``datrie`` doesn't check if keys are in allowed ranges at runtime, so be careful! Invalid keys are OK at lookup time but values won't be stored correctly for such keys. Add some values to it (datrie keys must be unicode; the examples are for Python 2.x):: >>> trie[u'foo'] = 5 >>> trie[u'foobar'] = 10 >>> trie[u'bar'] = 'bar value' >>> trie.setdefault(u'foobar', 15) 10 Check if u'foo' is in trie:: >>> u'foo' in trie True Get a value:: >>> trie[u'foo'] 5 Find all prefixes of a word:: >>> trie.prefixes(u'foobarbaz') [u'foo', u'foobar'] >>> trie.prefix_items(u'foobarbaz') [(u'foo', 5), (u'foobar', 10)] >>> trie.iter_prefixes(u'foobarbaz') >>> trie.iter_prefix_items(u'foobarbaz') Find the longest prefix of a word:: >>> trie.longest_prefix(u'foo') u'foo' >>> trie.longest_prefix(u'foobarbaz') u'foobar' >>> trie.longest_prefix(u'gaz') KeyError: u'gaz' >>> trie.longest_prefix(u'gaz', default=u'vasia') u'vasia' >>> trie.longest_prefix_item(u'foobarbaz') (u'foobar', 10) Check if the trie has keys with a given prefix:: >>> trie.has_keys_with_prefix(u'fo') True >>> trie.has_keys_with_prefix(u'FO') False Get all items with a given prefix from a trie:: >>> trie.keys(u'fo') [u'foo', u'foobar'] >>> trie.items(u'ba') [(u'bar', 'bar value')] >>> trie.values(u'foob') [10] Get all suffixes of certain word starting with a given prefix from a trie:: >>> trie.suffixes() [u'pro', u'producer', u'producers', u'product', u'production', u'productivity', u'prof'] >>> trie.suffixes(u'prod') [u'ucer', u'ucers', u'uct', u'uction', u'uctivity'] Save & load a trie (values must be picklable):: >>> trie.save('my.trie') >>> trie2 = datrie.Trie.load('my.trie') Trie and BaseTrie ================= There are two Trie classes in datrie package: ``datrie.Trie`` and ``datrie.BaseTrie``. ``datrie.BaseTrie`` is slightly faster and uses less memory but it can store only integer numbers -2147483648 <= x <= 2147483647. ``datrie.Trie`` is a bit slower but can store any Python object as a value. If you don't need values or integer values are OK then use ``datrie.BaseTrie``:: import datrie import string trie = datrie.BaseTrie(string.ascii_lowercase) Custom iteration ================ If the built-in trie methods don't fit you can use ``datrie.State`` and ``datrie.Iterator`` to implement custom traversal. .. note:: If you use ``datrie.BaseTrie`` you need ``datrie.BaseState`` and ``datrie.BaseIterator`` for custom traversal. For example, let's find all suffixes of ``'fo'`` for our trie and get the values:: >>> state = datrie.State(trie) >>> state.walk(u'foo') >>> it = datrie.Iterator(state) >>> while it.next(): ... print(it.key()) ... print(it.data)) o 5 obar 10 Performance =========== Performance is measured for ``datrie.Trie`` against Python's dict with 100k unique unicode words (English and Russian) as keys and '1' numbers as values. ``datrie.Trie`` uses about 5M memory for 100k words; Python's dict uses about 22M for this according to my unscientific tests. This trie implementation is 2-6 times slower than python's dict on __getitem__. Benchmark results (macbook air i5 1.8GHz, "1.000M ops/sec" == "1 000 000 operations per second"):: Python 2.6: dict __getitem__: 7.107M ops/sec trie __getitem__: 2.478M ops/sec Python 2.7: dict __getitem__: 6.550M ops/sec trie __getitem__: 2.474M ops/sec Python 3.2: dict __getitem__: 8.185M ops/sec trie __getitem__: 2.684M ops/sec Python 3.3: dict __getitem__: 7.050M ops/sec trie __getitem__: 2.755M ops/sec Looking for prefixes of a given word is almost as fast as ``__getitem__`` (results are for Python 3.3):: trie.iter_prefix_items (hits): 0.461M ops/sec trie.prefix_items (hits): 0.743M ops/sec trie.prefix_items loop (hits): 0.629M ops/sec trie.iter_prefixes (hits): 0.759M ops/sec trie.iter_prefixes (misses): 1.538M ops/sec trie.iter_prefixes (mixed): 1.359M ops/sec trie.has_keys_with_prefix (hits): 1.896M ops/sec trie.has_keys_with_prefix (misses): 2.590M ops/sec trie.longest_prefix (hits): 1.710M ops/sec trie.longest_prefix (misses): 1.506M ops/sec trie.longest_prefix (mixed): 1.520M ops/sec trie.longest_prefix_item (hits): 1.276M ops/sec trie.longest_prefix_item (misses): 1.292M ops/sec trie.longest_prefix_item (mixed): 1.379M ops/sec Looking for all words starting with a given prefix is mostly limited by overall result count (this can be improved in future because a lot of time is spent decoding strings from utf_32_le to Python's unicode):: trie.items(prefix="xxx"), avg_len(res)==415: 0.609K ops/sec trie.keys(prefix="xxx"), avg_len(res)==415: 0.642K ops/sec trie.values(prefix="xxx"), avg_len(res)==415: 4.974K ops/sec trie.items(prefix="xxxxx"), avg_len(res)==17: 14.781K ops/sec trie.keys(prefix="xxxxx"), avg_len(res)==17: 15.766K ops/sec trie.values(prefix="xxxxx"), avg_len(res)==17: 96.456K ops/sec trie.items(prefix="xxxxxxxx"), avg_len(res)==3: 75.165K ops/sec trie.keys(prefix="xxxxxxxx"), avg_len(res)==3: 77.225K ops/sec trie.values(prefix="xxxxxxxx"), avg_len(res)==3: 320.755K ops/sec trie.items(prefix="xxxxx..xx"), avg_len(res)==1.4: 173.591K ops/sec trie.keys(prefix="xxxxx..xx"), avg_len(res)==1.4: 180.678K ops/sec trie.values(prefix="xxxxx..xx"), avg_len(res)==1.4: 503.392K ops/sec trie.items(prefix="xxx"), NON_EXISTING: 2023.647K ops/sec trie.keys(prefix="xxx"), NON_EXISTING: 1976.928K ops/sec trie.values(prefix="xxx"), NON_EXISTING: 2060.372K ops/sec Random insert time is very slow compared to dict, this is the limitation of double-array tries; updates are quite fast. If you want to build a trie, consider sorting keys before the insertion:: dict __setitem__ (updates): 6.497M ops/sec trie __setitem__ (updates): 2.633M ops/sec dict __setitem__ (inserts, random): 5.808M ops/sec trie __setitem__ (inserts, random): 0.053M ops/sec dict __setitem__ (inserts, sorted): 5.749M ops/sec trie __setitem__ (inserts, sorted): 0.624M ops/sec dict setdefault (updates): 3.455M ops/sec trie setdefault (updates): 1.910M ops/sec dict setdefault (inserts): 3.466M ops/sec trie setdefault (inserts): 0.053M ops/sec Other results (note that ``len(trie)`` is currently implemented using trie traversal):: dict __contains__ (hits): 6.801M ops/sec trie __contains__ (hits): 2.816M ops/sec dict __contains__ (misses): 5.470M ops/sec trie __contains__ (misses): 4.224M ops/sec dict __len__: 334336.269 ops/sec trie __len__: 22.900 ops/sec dict values(): 406.507 ops/sec trie values(): 20.864 ops/sec dict keys(): 189.298 ops/sec trie keys(): 2.773 ops/sec dict items(): 48.734 ops/sec trie items(): 2.611 ops/sec Please take this benchmark results with a grain of salt; this is a very simple benchmark and may not cover your use case. Current Limitations =================== * keys must be unicode (no implicit conversion for byte strings under Python 2.x, sorry); * there are no iterator versions of keys/values/items (this is not implemented yet); * it is painfully slow and maybe buggy under pypy; * library is not tested with narrow Python builds. Contributing ============ Development happens at github: https://github.com/pytries/datrie. Feel free to submit ideas, bugs, pull requests. Running tests and benchmarks ---------------------------- Make sure `tox`_ is installed and run :: $ tox from the source checkout. Tests should pass under Python 2.7 and 3.4+. :: $ tox -c tox-bench.ini runs benchmarks. If you've changed anything in the source code then make sure `cython`_ is installed and run :: $ update_c.sh before each ``tox`` command. Please note that benchmarks are not included in the release tar.gz's because benchmark data is large and this saves a lot of bandwidth; use source checkouts from github or bitbucket for the benchmarks. .. _cython: https://cython.org/ .. _tox: https://tox.readthedocs.io/ Authors & Contributors ---------------------- See https://github.com/pytries/datrie/graphs/contributors. This module is based on `libdatrie`_ C library by Theppitak Karoonboonyanan and is inspired by `fast_trie`_ Ruby bindings, `PyTrie`_ pure Python implementation and `Tree::Trie`_ Perl implementation; some docs and API ideas are borrowed from these projects. .. _fast_trie: https://github.com/tyler/trie .. _PyTrie: https://github.com/gsakkis/pytrie .. _Tree::Trie: https://metacpan.org/pod/release/AVIF/Tree-Trie-1.9/Trie.pm License ======= Licensed under LGPL v2.1. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/appveyor.yml0000644000175100017510000000464615054046003015651 0ustar00tcaswelltcaswellversion: "{build}" clone_depth: 10 environment: global: # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the # /E:ON and /V:ON options are not enabled in the batch script intepreter # See: https://stackoverflow.com/questions/11267463/compiling-python-modules-on-windows-x64/13751649#13751649 CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\continuous_integration\\appveyor\\run_with_env.cmd" matrix: - PYTHON: "C:\\Python27" PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python27-x64" PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "64" - PYTHON: "C:\\Python34" PYTHON_VERSION: "3.4.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python34-x64" PYTHON_VERSION: "3.4.x" PYTHON_ARCH: "64" - PYTHON: "C:\\Python35" PYTHON_VERSION: "3.5.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python35-x64" PYTHON_VERSION: "3.5.x" PYTHON_ARCH: "64" - PYTHON: "C:\\Python36" PYTHON_VERSION: "3.6.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python36-x64" PYTHON_VERSION: "3.6.x" PYTHON_ARCH: "64" - PYTHON: "C:\\Python37" PYTHON_VERSION: "3.7.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7.x" PYTHON_ARCH: "64" - PYTHON: "C:\\Python38" PYTHON_VERSION: "3.8.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python38-x64" PYTHON_VERSION: "3.8.x" PYTHON_ARCH: "64" install: # Install Python (from the official .msi of https://www.python.org/) and pip when # not already installed. - "powershell ./continuous_integration/appveyor/install.ps1" - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "python -m pip install -U pip" # Install the build and runtime dependencies of the project. - "%CMD_IN_ENV% pip install --timeout=60 -r continuous_integration/appveyor/requirements.txt" - "%CMD_IN_ENV% python setup.py bdist_wheel bdist_wininst" # Install the genreated wheel package to test it - "pip install --pre --no-index --find-links dist/ datrie" build: false test_script: # Change to a non-source folder to make sure we run the tests on the # installed library. - "mkdir empty_folder" - "cd empty_folder" - "python -c \"import pytest; pytest.main()\" -s -v datrie" # Move back to the project folder - "cd .." artifacts: # Archive the generated wheel package in the ci.appveyor.com build report. - path: dist\* matrix: fast_finish: true ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5656843 datrie-0.8.3/bench/0000755000175100017510000000000015054046505014335 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/bench/__init__.py0000644000175100017510000000007615054046003016442 0ustar00tcaswelltcaswell# -*- coding: utf-8 -*- from __future__ import absolute_import././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/bench/speed.py0000644000175100017510000002216215054046003016003 0ustar00tcaswelltcaswell#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals, division import random import string import timeit import os import zipfile import datrie def words100k(): zip_name = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'words100k.txt.zip' ) zf = zipfile.ZipFile(zip_name) txt = zf.open(zf.namelist()[0]).read().decode('utf8') return txt.splitlines() def random_words(num): russian = 'абвгдеёжзиклмнопрстуфхцчъыьэюя' alphabet = russian + string.ascii_letters return [ "".join([random.choice(alphabet) for x in range(random.randint(1,15))]) for y in range(num) ] def truncated_words(words): return [word[:3] for word in words] def prefixes1k(words, prefix_len): words = [w for w in words if len(w) >= prefix_len] every_nth = int(len(words)/1000) _words = [w[:prefix_len] for w in words[::every_nth]] return _words[:1000] WORDS100k = words100k() MIXED_WORDS100k = truncated_words(WORDS100k) NON_WORDS100k = random_words(100000) PREFIXES_3_1k = prefixes1k(WORDS100k, 3) PREFIXES_5_1k = prefixes1k(WORDS100k, 5) PREFIXES_8_1k = prefixes1k(WORDS100k, 8) PREFIXES_15_1k = prefixes1k(WORDS100k, 15) def _alphabet(words): chars = set() for word in words: for ch in word: chars.add(ch) return "".join(sorted(list(chars))) ALPHABET = _alphabet(WORDS100k) def bench(name, timer, descr='M ops/sec', op_count=0.1, repeats=3, runs=5): times = [] for x in range(runs): times.append(timer.timeit(repeats)) def op_time(time): return op_count*repeats / time print("%55s: %0.3f%s" % ( name, op_time(min(times)), descr, )) def create_trie(): words = words100k() trie = datrie.Trie(ALPHABET) for word in words: trie[word] = 1 return trie def benchmark(): print('\n====== Benchmarks (100k unique unicode words) =======\n') tests = [ ('__getitem__ (hits)', "for word in words: data[word]", 'M ops/sec', 0.1, 3), ('__contains__ (hits)', "for word in words: word in data", 'M ops/sec', 0.1, 3), ('__contains__ (misses)', "for word in NON_WORDS100k: word in data", 'M ops/sec', 0.1, 3), ('__len__', 'len(data)', ' ops/sec', 1, 1), ('__setitem__ (updates)', 'for word in words: data[word]=1', 'M ops/sec', 0.1, 3), ('__setitem__ (inserts, random)', 'for word in NON_WORDS_10k: data[word]=1', 'M ops/sec',0.01, 3), ('__setitem__ (inserts, sorted)', 'for word in words: empty_data[word]=1', 'M ops/sec', 0.1, 3), ('setdefault (updates)', 'for word in words: data.setdefault(word, 1)', 'M ops/sec', 0.1, 3), ('setdefault (inserts)', 'for word in NON_WORDS_10k: data.setdefault(word, 1)', 'M ops/sec', 0.01, 3), ('values()', 'list(data.values())', ' ops/sec', 1, 1), ('keys()', 'list(data.keys())', ' ops/sec', 1, 1), ('items()', 'list(data.items())', ' ops/sec', 1, 1), ] common_setup = """ from __main__ import create_trie, WORDS100k, NON_WORDS100k, MIXED_WORDS100k, datrie from __main__ import PREFIXES_3_1k, PREFIXES_5_1k, PREFIXES_8_1k, PREFIXES_15_1k from __main__ import ALPHABET words = WORDS100k NON_WORDS_10k = NON_WORDS100k[:10000] NON_WORDS_1k = ['ыва', 'xyz', 'соы', 'Axx', 'avы']*200 """ dict_setup = common_setup + 'data = dict((word, 1) for word in words); empty_data=dict()' trie_setup = common_setup + 'data = create_trie(); empty_data = datrie.Trie(ALPHABET)' for test_name, test, descr, op_count, repeats in tests: t_dict = timeit.Timer(test, dict_setup) t_trie = timeit.Timer(test, trie_setup) bench('dict '+test_name, t_dict, descr, op_count, repeats) bench('trie '+test_name, t_trie, descr, op_count, repeats) # trie-specific benchmarks bench( 'trie.iter_prefix_values (hits)', timeit.Timer( "for word in words:\n" " for it in data.iter_prefix_values(word):\n" " pass", trie_setup ), ) bench( 'trie.prefix_values (hits)', timeit.Timer( "for word in words: data.prefix_values(word)", trie_setup ) ) bench( 'trie.prefix_values loop (hits)', timeit.Timer( "for word in words:\n" " for it in data.prefix_values(word):pass", trie_setup ) ) bench( 'trie.iter_prefix_items (hits)', timeit.Timer( "for word in words:\n" " for it in data.iter_prefix_items(word):\n" " pass", trie_setup ), ) bench( 'trie.prefix_items (hits)', timeit.Timer( "for word in words: data.prefix_items(word)", trie_setup ) ) bench( 'trie.prefix_items loop (hits)', timeit.Timer( "for word in words:\n" " for it in data.prefix_items(word):pass", trie_setup ) ) bench( 'trie.iter_prefixes (hits)', timeit.Timer( "for word in words:\n" " for it in data.iter_prefixes(word): pass", trie_setup ) ) bench( 'trie.iter_prefixes (misses)', timeit.Timer( "for word in NON_WORDS100k:\n" " for it in data.iter_prefixes(word): pass", trie_setup ) ) bench( 'trie.iter_prefixes (mixed)', timeit.Timer( "for word in MIXED_WORDS100k:\n" " for it in data.iter_prefixes(word): pass", trie_setup ) ) bench( 'trie.has_keys_with_prefix (hits)', timeit.Timer( "for word in words: data.has_keys_with_prefix(word)", trie_setup ) ) bench( 'trie.has_keys_with_prefix (misses)', timeit.Timer( "for word in NON_WORDS100k: data.has_keys_with_prefix(word)", trie_setup ) ) for meth in ('longest_prefix', 'longest_prefix_item', 'longest_prefix_value'): bench( 'trie.%s (hits)' % meth, timeit.Timer( "for word in words: data.%s(word)" % meth, trie_setup ) ) bench( 'trie.%s (misses)' % meth, timeit.Timer( "for word in NON_WORDS100k: data.%s(word, default=None)" % meth, trie_setup ) ) bench( 'trie.%s (mixed)' % meth, timeit.Timer( "for word in MIXED_WORDS100k: data.%s(word, default=None)" % meth, trie_setup ) ) prefix_data = [ ('xxx', 'avg_len(res)==415', 'PREFIXES_3_1k'), ('xxxxx', 'avg_len(res)==17', 'PREFIXES_5_1k'), ('xxxxxxxx', 'avg_len(res)==3', 'PREFIXES_8_1k'), ('xxxxx..xx', 'avg_len(res)==1.4', 'PREFIXES_15_1k'), ('xxx', 'NON_EXISTING', 'NON_WORDS_1k'), ] for xxx, avg, data in prefix_data: for meth in ('items', 'keys', 'values'): bench( 'trie.%s(prefix="%s"), %s' % (meth, xxx, avg), timeit.Timer( "for word in %s: data.%s(word)" % (data, meth), trie_setup ), 'K ops/sec', op_count=1, ) def profiling(): print('\n====== Profiling =======\n') def profile_yep(): import yep trie = create_trie() #WORDS = words100k() yep.start(b'output.prof') for x in range(100): trie.keys() # for x in range(1000): # for word in WORDS: # trie[word] yep.stop() def profile_cprofile(): import pstats import cProfile trie = create_trie() WORDS = words100k() def check_trie(trie, words): value = 0 for word in words: value += trie[word] if value != len(words): raise Exception() # def check_prefixes(trie, words): # for word in words: # trie.keys(word) # cProfile.runctx("check_prefixes(trie, NON_WORDS_1k)", globals(), locals(), "Profile.prof") cProfile.runctx("check_trie(trie, WORDS)", globals(), locals(), "Profile.prof") s = pstats.Stats("Profile.prof") s.strip_dirs().sort_stats("time").print_stats(20) #profile_cprofile() profile_yep() #def memory(): # gc.collect() # _memory = lambda: _get_memory(os.getpid()) # initial_memory = _memory() # trie = create_trie() # gc.collect() # trie_memory = _memory() # # del trie # gc.collect() # alphabet, words = words100k() # words_dict = dict((word, 1) for word in words) # del alphabet # del words # gc.collect() # # dict_memory = _memory() # print('initial: %s, trie: +%s, dict: +%s' % ( # initial_memory, # trie_memory-initial_memory, # dict_memory-initial_memory, # )) if __name__ == '__main__': benchmark() #profiling() #memory() print('\n~~~~~~~~~~~~~~\n')././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/bench/words100k.txt.zip0000644000175100017510000147443515054046003017444 0ustar00tcaswelltcaswellPKvx@ words100k.txtUX OoOd}ۖ۶{#uNrzHB"xQ7ϼ9ct&@q- _/x/Ζ쐆fHYNr،X1!ma4U"ݩ-eT7f 11 1q-{i]'Klȷ6s HdG5r /{ls>奀ihͥ1Ќs0%)6C3n$i#hJil!7s8kFBJZhj03sЗkfeU8YnCg24'=e4/@;ryi3l~iZsIoh(yv1,'%2tSϷm(_. y!=LeΫJ풐Iw)-allIP"ͤM8= P#iNvjzdMMDʵzDht#߭w7L`1pZXXK}ױ͒U#+8M )$X/QSƬ,OiVTW_ vv2YwE=y^E\SLMM$ʀ3uH` Ee@ǚGgW F?e7c/_ [đB4{7ƻOG^<7YO)d:JGKa ύ27H U)!}V忨. @=E!E/`]9xߡal]T:ϊ3/SJetk?qpB%;z(Hc4/4\Uïm`iϛS&\rGY}xS*躠0x:4*29lB71B P.س+1zb=5K{z55wR h҅)²O(s=4͑ʗ:M8 31C*x# Gmï*G5'_ ymV=2KШ1N"z]) jM5dMf 41Ͳ,O.uj:9uf!Tr71*ld3<ߋdZL71~Kh&6|Z%O`ÀGL5' ʌgہ8@ JA_WqLAsFfEF:N|LB_~{ۗ_| LJɉ 8%e7c\nIQ ]𕿑 aDŽ .F>Qi _1TEEÛ˕BS[<9+t!7zGX[c5+<#3+cS I0\3u=)h -M>8;H1DDې* 0}|PBc(C ceUַ1DMsWtubQs304K#6ka!|MhF.gVR"GOyj們mdo K W4-kF=/.ZM'b}@[Cz*#wev^&y¿I<[v z*6De .~H.K J!|K-8wo>; ;rHl :)VrG0IX귫gwYULV*wC'6q( (1;$?6'd~Bk;rM'h ,KӲd% @V˻4B'vw&%Cϧم'[%&N _m7k3,خbmj4/$k#Q4C ELX(!~zZ ;܌DFR6N7Np*(XPItjw}y !64jt ,YNwq` օƗf(B!"e9U2/~8OhDH?%:r1l8Vueúч˛W1mo`ؿ`}a?6X]LbŸo#ZtI4_]o=6I޾o2$)f?%HFX?%PNU^FلAd;1` @y @~.N5ꚯq)!H[ ٢gR!u<4FU˟I9qLlpN],n^ސ4*퐗x2Lb\m^ʋ1rc@OՒdY'ß?3_XFc.2]P5Jm (N=]I ܱvmUu ^ȯH7fЇ?bHgʐ{A2pv$XGEs*Yn#Q΂t"?)VL^uP >)q39?Ŷai ? ƕ?7Lhyx\zf46ƼHO4儕$Ʋh2?P'V@Q?44`,ϧR@;AG Wbc f5 쀐}^C0?}@sV*CJGP `D;PEYp-X*ӧj(g۴ׇ[}C WI@> 3RHl>T>tW!?q`6aR0+ʻ9 尩[%}`aBGeRcabS$ ō%` pJ j.;U7dCه0`n ,f6>||eaY2m:nd~laЗٮr\?F9=A`gwaNn|c$gzW&Cl~4;P|r:FrT٨@rLPjhcTB>5"h3]aZwʐtl*D;TsrgK$4jRЕLd T Jςc3Ǵ~3VҊQL1{(}}Xr"[G}EO}j `EZh=,lWb؁sH w߬FD!eNhXpZ(d|Ǔ^#-j"BZ"Wgi|)3m^@n> |, Z6sFn  Pn6RX1#5٪=bߜs 폛/\}ƥߧIç[RO$(:Dz1U+7ٜҧX >'3X*!̧er"'OMkB#h?4"S៑yIN'xGLdW%ޒ,cO`S$$<K 7vM7Sz Q?D"IQ ÙケYIlѦl<1zAcwHx/\ߵlc,9g?}Z`; !%- U ``.)'C8XujR W9qɦi+r:'J`h."Z4rD)jc㟒 7st 4mK[+ +3]SsP&AYiT5J&X}اĮ 6yR` 7ƀHr*1s] Y ω)Oʋ0*7QU@(#eiiOch%HϹkNY-N}*\@0Q677MB' !_rnHx#IX|Fq`_/-8atI  ZƩy"Od>;FQ:h.*Oe̽x=qQIÈ^vzO+5䞨b2F,u[ΟK.*6=o~C jo>|q[*ů>7gMF9L7ix(IgnԠT?'*eLY!Ҿgv흱LYd/GiXdB>S%}FkpKt3-]N~g<ܴM/e9pjفL]{4}6}j6I6ߣvڐx·< Ѥ[\_ XWa._u@ fz %/rph9HReJ=i|A<  5sCܨzf~˷6; +G`$FZGvJ1Ʀ&v,85 U%9%J 9~ągp2В h/*yԞ`2es| ]s(WƜ8o-ܿ{&-iE&WΞ7s}T,?r`;Sąne&+,Z4aɨnv [&_1xBD`(1q L&h.\˪kNTy!Zdzrl-g~z\6JxbͿ+L\3U_A阹g>Pc)5gJF:a[RQ(NWLj>1kt%~tIQs~E|4B]|]я^ԯ:>b6TObYQ*m+dW)=3 R=?k?4FYA=ۻ?|}%@`{ub9"YWOܬ@$# ]F2K#Oе΢(H08ShB~G 尞,yAvm@@1Ѻ_@9*y!Fe$0QL ȳc\eJ58]OPiZhX5 +}\^B QٍcW!jPvcDʎ҆!y~&/](M@'<-78ҿwxoֈ{9l3XPlZM݉ 'TC{:Pz ryoߜ^2>?àL:7q(%Zh p8o:e On;끏#>(uð'A^RORJFT?^ WLDžgl'w0c",ğ̒Af -P3|Wyܓg˜u0j\8HUO;8؍ˉ@6iVTJ ,_@$:|J'7b/tth Eo[SDׇD["QP=,5CSIV%$$Ep$&D yWOR)u,Hը)r>2b9Q[ܢ1'g2Uj U[F?3N;N:ڣ)pZ0]*u\l眢Cos/m\3L/vr+H1eT^xtˠ &ZQq K][{maL;1ha1ل\[c9{x*"K?5SDZrT?J }gZ&0:mĬ qnnj=OMV|b;VEo1ЗFwç3=4ܾh1,C/RռpT<4 :PH1/ƭjљj2nzm*j quBcE)Дl>) Q^%|Α;;%S& Z~0=YԁR8ax4\8 U( @/?7 3ˡ}d nB:`]/Q+R?;NEGI4>gPS&FAw,:ŚV^ZNۢ38S:p(eU!H݄3Ca R:I/ o6{N;$Cp YewnT U,܂Q_ZB`ءyFfw(_R{0U[#; >g]4A] ݡprX{z$C{aꐮeۇD+]ru蒵!:S  k 4]XBf\UIҥHf X1:,lgX§z8U̡L.hw\Os)䎄QNHF=IAdBwf"TLe]#K>F$&C pd{љy7KأbJ T(4Dwv"_/EC#\A9-"7\6AD`8ŀwgڠJI"ѧy~Ί:G`=M;Ҍz<gj4Xî6I N C?g'uٳ3/qa=L|*%Qx#=bH[TfY"A9DoYpt^Ӣsko;3Hr.Hh3"Ajzڝ4+WTDO4tQ@Z(LEnR2彦\^cAs$$98ѳ`;z Dfxf1 D5&#MKd}]}זb+']2I'p<*VAkpuZamLs@]ImJiв"U٨ 99'Gc/=ţ+8嫽#M s}xFfO R?RT8f 0Cv;Tdi.*W#M}G0^I c~KD]»ȋ~jo“gG\e fg_sQ%BG:Øa Asc3``VUXs̉`O&)6W?Zhv/'[T^%([3~&S}rPR o;ǚ+.M@6i~TCMud.^³=C;>ŊcCzYdzY09U&:u;vzv\+߬D^iN*Ӟ[+vGnh|]*#i'>WDծ0ylsIÖ>ASߙfV{BE)ЭĐs{?Z{9*֨'s+jB:GAd[hQهg'#XYZa!Ij^~:>Q&_bO~^3A5Ҿ]nTy_cڌCH #]o 4ۍ&=(R:}W'E'Y 5z0v҉hL^Dg!,%$JS򪡩X-U~z~V6j ɯmplc n=ղhDl*hmDЋҩ˩R53l%FYݱ  㗴W p,v25Sѩ@cE\ۤ( e[ Р8ZZy^ӵH=K=(Ne)(wxXfa`g: vׄidsڑcjIoJQ#uyکӈtGf%Kа 888"9q)Ȭ ڽec7X`qt_t%x̒ƽ{И\+f<w[ÚV1.*,7qeI@G).ÊmF~n:Xw#=4OZɘ9Q;S9?8cW8*'cZU8fUy#%H?Rm+ädXKQ[G\B-hQQ|L㚞Jj\^,E`~- aqGal\%aU<[xZG_WΨѫh;bac5g{]q!]tyAʡ9U! Fj_1#[~jᩉm?d T\R% b%3qEgGt)a# \)*;Y[,_yZQb&nԏn-W sNd9t6r[^/#AxE:ژ(wV#7st-:ѻ#E.#묥V YO. !71z}7l/ 7,S#^}6;@I쓒y_Ė+ 鮄A3r8tUIYtB1Iz>,I")sj#]hiXeUEֿu9t6ƃ9,S$kLxDӞkyvɉ=\(4fXVXS|}P:6`K6A^rgyUMR[yL Z%Ͼ{9n%N`X*U! g'TΓ_ pȗjg=""/L{iܫCEO& Ϳ J*S)E{c_cn$.u%~K a66us=,C@gL]]!"([*y٫ZU"ړ6<qW8Sd`sLMFY=zC:R@gn5.=ĨQP?5o^}yi]lN>#YK kήFbs+1aq7(#@dgfE*8/ߡ:TJ`&PyxЛ<2~[Lny\#u`xSJ7e']47":P 8`];kL^L.45 '!p^܁ʲ@H/ņn #ܕBgx$#ws, ~*[UiceI'X 1f^ Xn'H<5_⻸ z /[A@T%<^yq|Mk@fɅu{ݣRi( (oar9ɷQ&S8JnA=X13c7RyZ|$rFxB6t" !є=t3+A=#68(ȁ-I5zj>G/M}Wiq ~潙}>>~#]=dgjrX[Wbj] ʺb/kc֟JIbT7 }ָK wtsMQIMLלumbBdr4Yҗ.H:v0-~}N`ņ11 &ZZL$kl.cx8JW'[yܼa, }u03$n#W'ajCD3eчJ&yݑx40(UYتK T3w-!BiIg%b= *-0 HLTT~aзۍ&.M9_DTԡ˖:: tt*i]Y ~cڦpIOH_pjna?i.hXQ:]F =)!1k%v//ʅh4q)Cк-u/,EtyCKʍȵnqLx:AP){L{m(B 8*(V1"wUzx`gThDKZv˳VTq)w}JJg57;򆦑l|U/- LgfjRH4UZ殘heWɽc*7Q{BZآO2 kD@l7} ţځ媒=PJ-aM NGi-$g7ٕdU'R,'&|N6²FLuv}yweԔV$ʂ:JK1w3x;66CJӭi|wYkcPmU]*8JخV1tqϞ;e%:^LLA|+إg]LTE V. :ǵNHm{)2 WSge>K`FMF|rM =?AaLcsnQR"~os K%LcbZˀp(`Xc_ge~(0nI8|u'@sm/*U=Zr،glBSڈcK2p7BîS8$:]kuI[aN #{:([:AU@&M i7V>c#)M|z'Ep g'rr Lv%o =kQ`r)2dϻH( ܯ9Y|#R,3SZ%G8<ݙ2~M#׬; paJXkRij-\.ЏuR?z"Q\ ;Y*ݥ7SO)T8!g܁g17ڀߗ\dk ux8ɊwɎmM=ٲsjփ ;h(ٽ4&ogy/s|"6!Q3Дp|Q,'NQ*0DRKTz)b5DhNRvcAO3(iy=\V ^DKH"* i^|LF/!ڮ)jhi<@S q`s\9> HL^_+ ONF/fNt<@4G$c]vWrS\zI+%.:e!*SjA_LjC*xtcbW⭻BQ:Q7AfR? I-a|"-Ob (jx0fthӺhDNHT DcQy1#˻ #Mʼnݿ#nuhͼCIR3t0R,Ax(Rz78`q3I Al c] KrbB_N2$,Ce֗:ipr-DY<E ,}9 WP]ix|)L57+S=,z|15w ih?XߋY ſ#%-E΋D;H;{lX)>]}rTubB@DQj}X3Y/3VnO./*' TȢD(4+ˇqˮD.2Ӯ'~))`ω;җ1a @i8X *(= I4%T'cZxs_D=a󣾡Y"WzM"=#0 lo-N|$@Rx:jOD9Rv< + c'w5VUWE=@zIr p`d=AZ^851کl2AJ_;P" 7QkY)(!Q]ҲХ3$tVa/%Ll2)2:m,Ez%"uQFkVŪJ dDiu;c Ou ("pOeppiFƅ%)"1H\fUilq½IDZ-#D.~To|pvW5 ,08)iZJCT4Bd`R:,qc-d\4)2@q5#{2P.:Kf@ˤlǺŎ#}LGڪӪ0N@wkjsOyOl2Hocj^3Qޮu8 36n\[\N6wF.AcIFB dfQ`}]a)0Bi1A`koYzc"ڹ7nهaHz= Y A& wv'\?wu÷ۭF39^. ׺2zer릮l5XX>u_rJrڹYQ2,4gD՞,(?NVC*^Qc5Zj;5:B"8YaOPƩ7j_/i}ijNMg#v*VvBNq8 (j.6dQnխv8rg]",C,N'IL"E৺2>QJ0vWZsukm4^//.%eL{#*:Qī=_= Dߊǽ}^\YaR<8X$.{z'ݛ=<)t/gA٠-ځ')IPޯ 6E*7JZ/Ӱ¡D7.>Ew&¯ei.ɦj~(DN(҄4~vymZFDjGOMy!n&mkQ'J+-S0O2 *X^+NK'Jft}w~)L(S AD0&N2_9Q6X4g˓R*̂@ٞssX<[!Si3סHA<23귓5FA+G nx]0'cyL6*,Z:{V+ewHGDKre32'IxsZ54Py75O`xh!߹۬#|bp cx| QIggz_DBc4!%)Odw&JFJ748YشN0PV6O^^g]F_iD r[pؼGd{ $#('OuOd%M-AHak[gYG$$e]@5Xj~u1+Qv᝛L9e;?`N$eјVZN}Lͭ-f?$͂A%ޠzxy/ܽ3x0@)D[ۤuS(e5$wHnI$.|2՘!|kg{H^_TKq‚6&T UKB ,}YWB-!w[HL"HH;튶5Z2#s<l@uǎg| m|kh2KPۏUezգnP?,{i-.ĸ7Y]{f'J)1='obWZKCTuh'i2=,#g *ᵞ}ђL; ~ݷE6hP9]t|IL=%_X/i3T>uW:~v fLfG-r ۗLwnZ!*gk|CSV%NQee6\5#ot|n$ Fft߷VVE7>p"l QMˏ(wqu?F.uxY`n'3]ĻZt:P4硻 cXرQ;ylj-p4\5t^[9eһ=j :)^xwC<%55ly|=C!;py4ý=4EBaּDz%|Tg@lw_ɏ< D;bb\1x^P[䎊-ӝݪ~vz0$\1nts|Mgt͂ ml܂,|:ԧI=ewZ(ˊB޺2 oAVNZw heK?ʼcJWGpƦ;( TLd}NꜶSvޱ z~k+-9zXY"an\^ڒ#QjI^(8SKIFYRP"@P<1|m!>]$q1Ce F9Bb5scT3G3QHLpmta&pn++ҙlKMXi±f8i#3b@Gan)0 Mͮ"Cuk)~e]@%`9:fwWQmk.!N`\;iM F  lHNi7O<`$ں5twg(%O\Z8?Nyz"Uj;`21(+9Um'zI:S8m0OOk -M'SߊwpϚLR!ɔ.'Se+ X^hR^ZDjK!2Lp't*LZD̊5<"$hCpaKlaB};af#N>OR߹e3)I5n "0ܖy=  ΀0RB qcCQpCPvl pt(N%'W_NtSnZ7zsZAԍ or>w_#;c ?87$ZGS 1fjw+ :M ;x=ۇa@Ls%iMGlO Ui?;A̔f'8!,2Vi,o,")Q",e!d;_ #9}Wi1<VPz5 X՜Jkjd,~6?V^5= XXN1.f|R5!HFe\nOD a}aNC5B%ږ :%93iT @Ӆpn]c6,:MlΦ1b [L\%9jMzqokQgg:CxdO^0ڍP,TQvzb>%BL|_52 OHP-f [t2V: B?9 8x427)DUoDWaeîJ $YnaZN!DQئ7 tDY?m`6!D{Q3 m/W716='d_:aED< ݓқ4x`9#PSnVyߒT}vM1kͩH_m$RdF,'P)(3^GmVaq$оx!!&g45gYfr @ޯ1Û۫c;H'lfpIljpac/6Kh`Z )AZ["= B:',2cV;(ц%ng`& 7N̂],@ 6af<cqo0̾TKiEV΍ 5IN)d1@˰bH+"YiM¶|BX]Nuh~i@3|8Hhʟ &Y;7DVjadnȠ، D621rE蔙 Uت6 e|B$^+ڤ=4V"R$AZU_5Ni vOFpDr{lXrClk5Rl6@M+NY ٢TUv b|6۶To?}'^q67 ZwQ fq:~][ Zj58)+lx,z_a K%s>0J0 t] P1Ag3}Zq_ν~} .G3뱊 su̇w~"=Qp̶d(3Z^ g.GS~@3Da/ n#攟Ԍif&W\4\@K:ÚaH#c=ҙ)Hhd)M]MPLY;S=H3?gO>&l$RYc^`琑UTsMwu<C2SE{INQYoXj!U]d X`<˨> Y 1Z 67zVxv:egbd(&=Fz~=N--%i-!n6ʚ:|RY+| n k*kS<|(K;)3 fs. q̦4=%./CGv,&2bXStkL0{2=Z2ԜyCp} *nhԘ!=Ca9 1U:=5rBc=B0J9t}}qER=nrLأ& 5!%rJ83cd-,Cg#R|pa.'\ұ)dP:gs^9JZ !17w .þ1v0ŇMT"OYH؝юVHe5oi1NBJS_˓9/n47Ϣ1DG+00Dl ~4DꙂN1hY(Ġ58l`KXƋELY»yH@_a_VrOM'(11tw3SaVn{6t+ĐF+(9a5Vz\p^W"H/Ck?B;9[ gjVq]zP'7CB4O%9 zh;/bÌ]f_!&Zhh{йk!Lbq!ztjZZ)֓Sbc ,#DC(j rmza;hw1=#Yz݀ja&#k):2Iu\:4(]\z[*G08h?qPV..ACJ *< ԗ,)ᇇFΪ9\L:r\ =c]B`%C"|[:M/Oh 5:乹>`_,-=`-{P[EnWTͯt<;*0@̈.1̕ _og.B&JaA^zy{YO.&s{@%'4ݟGqe VZeJZgrRk,(o1lFgHa#~ g4-iW'f UAAϜK5@5aB,VM)a(Nu/@IC04ӕG ;R),(ev g+WU ȿZ>r Jy.qU__{fqIh"`S td]BvDƑ`S]@h X>iXQ䕈O 2u$]^i\r :Ye]6zA;1\*qQM?M*;^^%֖=n X;OuGQ&qv' JaWRK">-P磩=@_8Yήx/4@қ@ z![̸i Tu1^:%nEHAfa~dA6k@k1j,؛ç3 3.k eC,Furs3:Vg#!Cp2_pc}YrpS)v1"yV h3Jb Z0Ƭmx 13۵QFS =TvL:DKWsN'Gx@V:xzd%HӀZ\lI;g!p61>(l)RkP7،3?a\t,ኜJPd*dz̠n<3pBZa$[ijp3N2F5κdo[T':! i+sp#B*VceUd]2Dp[W5q8W-Wq/rE*?Ud7]y,  ݇Ҿ^ LQ{.mD #Mp,a)hWaH\NEit׼r5[`'2;" 5Y^W/nW#Wwr6lX5U1EY.;ɥj-ڿ9l0 y87Y`}gyS4P"ō M>ZvmzY :8t1r?ʬ>nbó{PWա; ~qJ? Ll DKxyWWbLk D/G E#6p_-\ǰKJe{م 0ȥZu[*yZ5,|ẹ3y3;3UzAJՙ 㒄u=FnƬ;R7Ƿc-/d" ݹ/`M.@SڙC?xZL.h4v U^WbCW.FN~&F3 2S:Nނg0ScKQi q P681xs8~tkd!=IxsA+FxZf7v}@,б*DP 05MI`-) Λ5b ' VݘT )-hf g3U0!F 'V)hbQ Am30ܐI|3z*2!?[m<"RZ"S^0Yp*Kx O$ 1$*ؕԮdO0Zl=WqMC_@:XHcAˑW.9 x눹1 _*Wj ^c6AȸaO˘cJ rU24R>1 M# H*uP`MTBZСqqZ&uJ5?:iseיohVah,Y ԝFvmW͏!o3 [9pcW")#^Ѻ^Oq{}Ysl@ku#0 bsyC,\NdGPeu Vnzb3jWN5PJ^~\Zei Ʊ )]uVt՞ 5ВŠy[e`ͣ0"8j0v}6fD`V53N'l41?H4h<6BllC+ 'VBDYVPs ? !;vZpX. ,FSu >U_9aۊ kUO3!H@aǭ/ۜد:襶0 Ǘg7hHقGCJYWw ,DrF [YDj54iV!BDc)l-?rF?E$~c!ƿ0OOIXMWP0"cǼ @ϩrRfAnj47w)S 6)4@Qsf |iܑb!{G5:7fO(ER=K0{T k3V- m)f64Mu<;XG>a84ȻCXL_Nϟ)bB٤ׇk`]+aB%۔V+TO | .0e/B݄㔳,.3@<,^~W 3MiKcUa 1Qbje +! 19O ٜ~ѕv7}4("4Go + NFA3S6a#Hkm8OwyR@__(SR`y0ĝ;AV+2Nh_:sAhEĎLjx˾;['u<-! `_ F;@_˜ %CE3]pg Z@#[3x"\ ɔ098htg~1dx2^)wj-&\!/۬em\5O\漧!jnuǮiD)n@K ~C$ +.B64;%"L +2pSagn4#fk!zͳ.r|L^8~ǜq~ѷT?B1<@ DO K@Յ%/N!+k*cuwe1/Yl&x|agvh.ܹu}.WgP!+M}s&{/-|[XgWT#fa İ 2vԊg= cum1^OO9  ZBQUWXdWϭz-#z4Sr]*š*Y̩(H$x,$0O"ŔōiX!lѪe5#1Wꅶa;^ritܡ]kLe=9^sb׹\uM`' Y`n@e.4_ q@RSl&j!8Ls0P "3SdM ٛIhQDODYb )d.a- P1>c8/xS֧ )at1a~®皸j?h] fX2\VﯡKE`;%FqɌ``ZA! >5gXa+;'~u6xPlzY4x(|g4# /NAC˵Olqu߬ntY8ھ M "I#e+t>2r3q`| H;:_Ռkla,neǀ5B:j!_XqG;;h=yoGWbR#3:1d=3)@9OLy5PҒ` ,gNG&!{mPrBb+'2@;Zo-P[Zջhcccc8lye0l7f]u慯fܿU7kBd*,4r0~ֈ;n8x/I?dGk+̖#H|gE\ph=tμuGr-}m-!ֳU~CAi[DKцZ ʴLx4S00#Wf*$q+Iigl-ɰUOJt^yMc 3+O0.VKr p~/}pr_9h/yİRtQp\'ؓ{y6ϿM% @e l`“h6FZYcSeE#!<*eYC'Wc$Bz켑:q) SDi4* ji$F RI!vgvN̈MS뵒]4] Kіu^i׆环TGdrNHsQ=cY!}c8dB|B^CZ&(ӯoۙNb0V \SIŭ&60,Q6V4Py^RIX`jv 8]]`"bmY'/=ܨb WsN!z 5Ψ?V))> o5͒a:DNp"Uv ӑ<+@}Esi׷<,Pz O: #My[,/" jxD$\?i tm Ѳ~M4a%\LWՍu/Lo |24TyfRmmF9C&poM*B6@V9׬6h'uV8dɠ6Yy0`l_ ?WFcYRO=_wCK;:/2_(*, H"3*&%\ Z e5&etM8w5;`]EGqaY(Fl=_V^xsqmvRO 6/P]ƛyr݈!n3D-o1ZA P3@p?L !kfnfJb R/jőK,Q+0P/o`ژm: E g;XRU—nB"䵲>H*%5ɟSW3H ^Q2A^w3~qSFUXȀ @92[PK~+VTV?6k45,k%eF*Lwr"k8<3]B`ciPsqFD4J?0l8*9mJ8Cbu㹓xIT^̢+̠в'.}sGK q>U=׸R!dh@DڋNB,u,eAP*M/b҇`6fާ80P1[e8]p@[4Db0c7{V-ԑ 5Ԥ$g9d2 +{d>wr|:.|{U4 UCp(1zr#G. ^X`~9^.)>v=YMc3gS:b1 L8\z"DsIM&; pȨG.;ֹM !q  F }V4/V dΥ sUR~RwjdUUy,t-eU9!p.aPp/Z,6Q-k*XMƃ6y5=WkτØmrWڳ1[8R\W󱩜w"bC D1cͼuɖ`ńwrd4S]8]A)CNbNjrU9bBeuE:SODuDH,.lUs kHZS* fX K :׬@C1=W3ϼN` oDQuB&gBԶ"[G:Otˑ][1Q/#0Ng[i&&R;eSFv nROXctw0UxcA B'Ft9bfZx i4F 'p:bfvF|>?зӍi@Y@'n- 0p>𥂠z`r|2 m` O+E<8>B鏕"Al18/t6zl{$fkd-ݞ5',+],cDI&׺ȸ6b h/EovJ,qk:W/wU^^JeH KvbfѶ5 & Z\q`OLk󉼇ΪՎL-\mr}h8 Q`w ?7;9 Di}~77zZoD}*pOB?Ҟ><&B.|v[&v62_˺AiSeqhpGUK3t `}Q#b܋wL5YЮN.4"1[8,(/YءD2-v4V8P4-G6\&' L!jGl~\-jy6[FL͊+o)rz.K>T.`YB9l% nI@O \--azZܗ<\Ss>aw.c7ǘ_9R5cӜCchI*c"f -1 U#*yf},e b2yE(K67S}$@Z2%w~a\4&='BzYVPD؛kmezBđ۱a]"4zgU0~΢B"TF^[+^U x=N:⛫ďn/b4'V daذ.#V7TbT 48QfKN핚܎Bq9K`u|@3t+MŋS _y]Q]!z%ِ 8Y/%E̿쒺ᗟ;sTſ^V\ L'!R՘;`R勒0G`Fc@8A/,`aַLے'+X%r_>cIq/W d ^b? q7OoB!6[l Qӯ-"!Dӗx2}~E-BnW" TZ_!gw2SMD=&G(==VӫoL[{TIE{woYH^M}KBO7Ljۣ=B!߻B7 6|H(OBx#uULB>E!EUxgޓUo>_.71tz FKQ{6x!sDo xzfzqXݦDn3s)j]WYk~bޥ6GcGo1)r`[|s` [!!D-tB L\ ({"ߢx ˶>ߠVOP|0?o1Ms4dP*$|{lPC[y}y uiB5 > PVl}9" [mWrZe YHB g쫜Nl֙ 2-kSJxZe^-9yꫀ"ox [{[4 *z"}2t(,w`G~iUQLd4v7@ٱ?:Fks7YDM3DέTkDg^+|nwogb8ކ2^#`Di-O'h0?Tx;k03Dr,dH|2>boG`ÊygLj+;I]ll|W},/(Gn8kYVdFk~G*JSnx[( @Ģh|sPSuǛ~ශȗo}ϡ#EjyH"6|o+`Z]Z;0#2{6Y4K%/٪(:>r+toL hĭB })_E*_n{$|5K 4. 武_Gm:[-ѻ^>tԯ[_VBdZfr|a?Vl zʸ+oȍ+VGU:ɄW{&ح6@+%wm-VRDZwJ1{S=OkǠ9ClOު#Ό m<1 e+ґ}}aoEBIJ&DEKt>Kof|7}fÌ:@llʅ7T=EZ2[~YMXޒxHkn ?oY]StUTy1l[#9BPaJ"ʇaR$'¿ڑUZ?m߰ƈh0:@Zf<65k1{"|x|ܘ QkL죬&88[2P}aɷeVt'7K;-B3[ Yn惯#A6Pso5mLPx-{)EvNl*n7LmYXztmͿ~ Zrg*WDeGyN[PHJM_So+LB^%?+PoM{(ywNEX~.OF+,BkYј ]qKF4,tD ?V1!V)j5JGg!* |DP8:CC;ۦB?YK7{3ݕwC߹F=;N6+c%(dG W;(xpxࢌ}HmJ^ >s"{ 50ɉwbD!; EpB|1Z$/Uy0Hmz9$#h%ǩ۞QK+;11迧g^_Ybf.$d~]g\w~=+md_LVF8^6bcBr~>9JZ"wQL,w&ƸѲ(%h>R7H.ʜc>zNwr:I_XӣwH'IqeDL7~1"<>,~]Q67;_&:#*Kwxxt j(?ϰV򗪝RH`xg7Av>ɏuٝ("ODc ɘOlů$k 2Q4Hu z?$D(^(+(:GD|9?R0zQnkQ{BK0 ߺ &*a5Fּd^rn^cXMOMC7ԷkdW&f~ɀeM6^{$~H֬6 @AR  ;V,nROwJ1@VwN)ff_KwJ6%F0DzQ m/ď \*_2֪ӧMf 889#uo|AhpiQDbPoNn4bmMOэ;E;i^mi\HRT~н֦?<?G2?3>}!'H&v,۔@tss1R?jW)a :2,˙Lg6Vtzэq_{mAJaM v76l/}w,fc'lD1[zJ!0Kwjuv?BXZY~$?PCtA?dZ|q#bG>vfD'ҢyLQKl ]'[c?Za]@ͼ:S?=]B̅o5TH2D=4+?!JO:XQh $d#\D{ǿ?Dpl?K7v"_Ϛ3;`P$I2mn8V?sY"=yO'v-ϡSz4j+Ph? '7?QNS FCҿ@?(@ӝimQ̉1ۣhՃh.Uq6B;mԽbQop]Dxd}n'M>'|km]f?ڱ:z[j Qjupq-et'>:GkWX0Ϊ?~O0c:KQ;b=ڵ/Noi4xkI ;piǠ{vTcsޏ?ܨ> kE7M`Zluމk1*yzm']H>Gg q jpNm?ؒ5Ŏ"* /F7_:2^dp@/\!o H-!l"(_|JAY(/v †u :s [7ơm=`ZcU츰h}pzQz29\,l $oL;*G"=Dy57"/UtW׻eya}(sŶ%?4rJQʹ>jb[Eps@G䚻$_)GJ>j') vC6OJo+sіh'h}ԍ@) B Ϝj]FNv|Agήr^;Z'q4D)'i[o7Yھ8!M5MwIR"BGN?Mcs;%t=3:V96G'jv9oB4-#+AVA,Il| s!`׹Fd`Ŏv3꿸}@*}4῞"rz@$G,Զ `C37bǔ?~??i+WS~7Hgȍ#t?l(y#]ZK(jw Qh™!8[X1*1LBZ5b7IJǖg;ލ7L={>7EsRt>xEycW4eɦ^>mFsZ5."1V'NNyԿRI̅Y'DBբ?lzeTR5m3R.G0Z*Dx?NaJ{z_ O-y٦z+Z$H0@f=ʷEeho%+,xc6졿vv/f(~m!Cml⧗΅v`^Ϗv{H`Sbj{ |:6`>(kd!6!ߙH8\/m,1_Y,LW.̑ R>I.o?= $/K4OT4'eѶ"={'aY5GHVcɊO9aO͓89+9NC4?囒t7:Vg ̿M 7E8!ywQEjoK`sw=T\iJBB x?@ط eje#Of v X=Jj'!Uh *VPYoDJ=u%*7ah&umCaĞ/R+!:6os_Wƶ|VPZnABn캏7On,%Ezd͝M; |G|a>NΞֵcO(ߙW )Cdi_(̭z&JB lGsN۴\ز Llƣ&)9ҏ0[np/]5@ "$j ϵ[&TmG(|YȔmR>)oR/o qH&5/22“}"aXF)ߧ" ,H< Y,׈ywYfQf4$BRhoHю>ktCP!Cpeґ$u@B}ڦ:#bi`/EY!M,/ F&%"EBQ(ϊdt8QGO%6O !YT84%y=$%7OΑ3Oq^-梄.x=/"}gHꢊ#z >VtհŴX/6C%9BZ'?3ؐ#djV:q"ϰ> c])l5GY?M>6]yײ2ۼlhbY.[m 5tYxaʫNw1Я݈p>@3Rb#39Kq0lnOHtD*d{`Ȣug\󚕽 0߱i(cɶ3$c?h#I@rA-uEo^rwx˯+f0yl:zmܖh]g{w| OHMYHJ'mI+4bԯKYoi*m pF([jPE5zj|/#!⠃8)ŰG!ڵQ;_Z;սO cǸ-CuI. d _vW66?Ive9oӺ+=OjpJ-HԀԵN3|9,&xA1bԓ_P֌Q4 >GU=eu'dc4EefW,MhK)xLi>'\#z#=![Gs9zGݲz3qbY0 Y ߍ!CJ[y 6+t9eד^緼 #o2{ in녔-mA.Hw>uV+YQmǤB"dz;yaOƙdGɪw:$Q{R0O3*xw <}~>-qs a[T-_Lxj@quRC%#Ը1VkŔ2TelvD>ED|ˎQP$%s(OdT>O.DvT4 _i,hLM纄yi9S[WS^ Jr$=Fc7/s4ȔoREw5] .f8'xWytgz:'_'Ʉ%yŎvJ6pfǑ]k܍mS$ $p"擊⹬̩'1ׯ:?j^T;٦)&Ib?A!-${Bރ U/J_zT8F|]bO*5ΏZދ߭EDȸɍح QL3qR+{(*i<(|C Ҩ).{mD洯L1=3x{Ёw)e [zhJtOK'UL'Of&3f-r)!b#8"ҷJdWkG]=>8$@c Hk`BWr27W!R̓D}!ʟfyܤ>P3Dr{?hUjRR xKv UPZ?({(Ծ17zS"^K8nK(u;LOk*oD!ꨗQn#lOylټ;#h3#Ѐ%IWw67~G"Qf@1BW[ld٩mfU跏oysLk.En}c.+wpsj}W`129aOphr͗Y3T*XH=r71юo\7Lo`ˣ>.,骸uף o m$>0$?O罻G7ޗ 5.u >O'|p^iU ϻ\ JgI\&qC'waVyC{ē9a!$?{˗8u o:Fx[,!my A&? xC-#dby{m~^*ӈQbYSzB>D2Q&+xFIl{Lx [?Ȳ|2D<ho6 WE'CfwJSps˖lb'ٚO̲JIfo\;G|3̓%!%G ߘȱ^rbRl& bt:tt*s)WEy&ۓL~"Q;ifcȁ}:~%ǏQ=Nar)mL}188?Do"AyȲO[=DJ[ ٠qI{ d$ '"ZWIr.^=4HMen}4cP>g9rtn QCGdaBjG(<|uDҞ}TqSl>GkǝE^/EKi?KgBBmӕjx`HHunaPc&ϐLQxY7%b14#DˮN@T*[ ڈ 3}.dRv'ɩ5 v aOSr1*;PJ)_@< q>ևGtOz{BmYq Oܥu ' *~"|\D=}5^0~(d>ڠds',#?<> Չׇб уL+{l| Q4Qtq?ӶϢ${w#lS 3)ψFtbZW"N|c]5>vm,vN򀸎)##8z^]uo}M{a̷-.R>DC͉#>\we땮$:Nmu&Yj B{9$-˘l{P惗F׻ $T1ȻyN +eMC'CDOqt:;BNUs# o$̍E=1TG,CS!242ϖRTJ8ؔVx$`6& Q.EsRzQ_灣0֒[ʈvPz@啱`iv?<.}Or Q#*=@x8J(e ;^ <}VcA<`H(o..?d#h0!M/H|LhȄ}uݐU,u?=MVKUY\Gn0C)Hn!z@+,Ym!BqՔ}6/bAMCI^KE{$_.W1:B:XV?rLȬ̢gL3eE9M2̂O3XCf4!y} ! z{*#&ghKF*{ן"f}̥ [N*˒6,Z%Cf[C·{ȬOcnH?t"OlݫcMy1:Hx ]ZE59@B"+ԇU+sk@'g +F>q{M cA0(]G;$1No6פf (nc isT#qp ;ٻ=B&ŭ,%ǭ{ B*p=Oth] 9/AT^rրTd$LJ<# !u#C{oLʖ([s!1he[Z m7OnjR=3I3I\爃" 1~r${ ]`8n+җ8ˢSeWP%g5+0s9F%Y|t-R.s8AXzNvz1rڈ_eJ#1 -v$}vp5 }挜#o d~Ț!skkBg={w;2> Y"ƿY7E$tX\Y'g/qRQ|E$7}!9^VIlg*q1]ȨwC(#d="#=ŒqX2P300E;HzI2e8}!1ã|I@cIlA.c&HA$(7)_yH>8uD&<-QaBnhj=[֕fO Cd^eBp\l^~ZՒ'X+J#V|P`kb9ZQ_xr|X@'>y@Q(=7.O+WAG#' F`||kF7JUZ\֭뒞\aאn=J Jwd.26Z 7Ag|i7!0TG>ϧ* sFuԫ;b |'B Ҟj0j(TvkhWLLrChŃ ML{'hWLj]^y_0/^r) uRIlΉBcћȨ)oʹމ6L!Ol- t֝bx;PvisLYݚc3ݩGGE%Yʏ! $x*P`+J3$`SU/h;fԌpTMA`9‚ۃ_3Ȋ=*?!z?OIZy=. .LC$Md29d[r#S%BcT#l ? iǬϯZ!0k[JaƬ4y>SwUG"e]O+j~RX_5;G.Qǚ ~0+1t`z?*ц?Xv .K][!CE].SR6ҍ;T$fZe3DyCռIxpUe\fT^ADHYۧ$퐻QP4`[O#l5V8c9=#C}^=ꨱ|BrB{Rc "^Fo%JGuu:裮lvs aP [P ઑL["hp̍ :*@V$8@ )41cW{ypr2Pv܄ڑg$ٶ{ dk7Jg%JQbtD1PBHk& p:Dv#STKUbi#cyJ\PV;S t<7x-DZ¿0[ОRَ;dҦ7b$8$Gl;79J\. Ptie[#tomSn JĜCrd9q( cG^d@*mIgl<~e ]&]YN\!h9?}v#&bx4;cB;b7^NF-F>G />%T/KySHJa_L=>/ʟm{!~1"ku9݌/Ԑ 5.VcKkIjc6a=oW+qEG]F#?sgH'> |2 `Lǁ\rQdt*EJB3xEX_;})ራvG TMO=]Y@T 6ӻ˜TE6//)Op?fGzy UmO@PwN|PF;Q텺Di}!țl5l;_K9¤M īK$@+[;s v[DP*|Ü1y=hBqTV0aS/Wm)5Q=K̙- ˼ S06Tg[ŝI9+F|۔nAՑm{jS2] ms gEkXC.#(*y5:OvyTZ  q&MNޥIo7IXLFСNT*HyPŶ8N_-􊐿WA?WYҾ91e;M)@Zu?GyQ`SX'n[t\>lq43zdbzV:x8HGXC; }%C J%U㋺}`d[Yu(b#'t3n>Ҿ /2]h;i}+^ b>K-](;0@E/Kͽ1NinoET?ּMjrj46]Ss#Jm%jLEVSWwa) ۛ dwMN(B*8>Z k#ĸɯS;J~tѷUoB&-Nli2}/3+pad3'W,A_Jj|b1x3/K)o 5yf,KTbxƠy_̡9 ?ŰG#oӰAcBSQgl4?9E~}%b1Wȏg?rѧFhR_ &-1B1b][hrL& @: A!&d#il ]ct*2J*?THl 䥫pB|'0-%QZ0Fa9/oanħ`AgTg`Kma @|^И"G0&VCЀ̋="CDe X @S-OH?)Xp= vn Gdz0,A]hJ%Oԏ;BܒE0}+dߋ,;)@y=EÁ.D 6鑑MϦг-R3؍2Qm r"PB!!4OEWxK8Qwvў$9b]o&rƾxCs5l >w`KYvwiS\#:Y~uGGsK3?I-09S'1RņsJשm̎Ac>I3~q`\spn;R.$csKT f4L6YR>Bz/T~5w;IMM(vh#5טI{C*O (/VdlT[Qy' om&lbԗ%}T^ W|2oP涰s ^`xiBT]-~1rМyLZ hr_fB4ת_E`4.}0絀n{nХ#D/GɖU8k4H nf"yY72js!LB'~{YdG"KWu5=:Ax|&azīiNGϷ5zDGQݐ9HſpbYlzߺENn!hYrG^%cJ~_1.oP2c6y.4 2cN=v:JdL80&}Xd=~*|dˍ+Q'5a/ȥr^ sCcӬL"QHr خڶwV^fلhUm/'  sT~9 䘈Dؗ*kJ_†(}ҷB<\O;p Jnu\r7! r`紼 oˈasQի°~:HH%Ѽ%}\K\"k~|L[ezM mB*))*#Glg; |$&n)>Qcq>{-`v2<͉$կco\SB5ۑ>ܺVnYI2'uro]Iv#ֈY(eqc'R0T;Yq9|CjfSA |eXJ7ThWx b"jAv $^?JQvRp}fkXËXLPdpMA^y#XXHHz@$%^4԰c'wy=\RyHCP-@i8|zԌ)\b1U6avqhVteu݅S6)'w_Bv"BO(Gֶ%]b$%숀߱/vJ@ߨW}bMhn.BU$ &%Ilnq086 f?}7Q㯨V0WN*gh2GFǞg%siz4;2wqx`]:3܅"k㯘LXƏ}nWgx;!'K8T]vĖoPܻ9y>q u-_)`9V򛄩>;M n&|D/CދpG^\p C% gX5uYq4:tд* p#C0敪42.8̱XEAlʂ}TϹ0 rzŊ4Ki܈Щ52 ]5b}VJ wmVu$ ^2E\l:,ౄ lQˢ )Vfu(6ڶ>SI@7DT_T;rS)/s~MrT'v4QDPzxTh٨DϝKL+ߺDnm0ԝ:{cٿ0,Zֻ܌6sG/Jvzh]1kKg Z3"OO &E>Bw%nJ=Se!Lɏ0sqk ;2z!/t2&fyNO$v'Jݓ$eA3q7 LYEt $0o9wi*_CcӽP ϋpAz6RO?Bt/R۝ѧBi(w6Vb'O7XWj^} *_Y*zW,U"{h:_67Vp̽9N{V/a8;m<1(mDrczBU8|%@{+[cZȦ:5`|/O%{uTkW$+M |!Qr[f'w?J[WeaZbl!|ïtUۭ1eVny .8/ >Y!ߣ - g,f : Œ|`˦&| Πsqb H7_POdH;xVg͝8Q0$MInhe(vfGo y4Gq@ RԘeԉ_gN-fJaȼ]ذxLPčA>ac>M1X;:i ~:ͫ`Z9dU#BZg%x-g:ԗ+rFȑ'(m67DoԻtiƶ-OSD-TDri΢<.V&m*M#ɑ(D [ر'Qs螮mepM | .+ǧy, +r[zd].oھA'bF@7\xEp !4'LjSY:DU@%~6:",yU3|gZn-7<(g"&еZF7]jw(7tp3F [+_I;p$*f:XPefUmi[W}u*Yko՗^7r}. 0,?{#gn=5 C8h`,d6+!;_X_H]zxhCgS@]FT=K?3gN II@X]U&95EQ-*E0'4Z@#éVB\lbG!/.JDUp[G2e XM]v]Bz!n NcEM{`ɹ]K;i?K#}='ť-]GúFvGyʙm hmMCŁI?0W$|)^"?k)D"LeVs3] cJ,5qd,4.Zf:uG4 emSgxuп#ܤj,XO+e]*|Hzuw԰^^Ѓ.L} SjD^l}-鄞7YTģI7܄TZ@Ə5(wA>C3|j൷K8[#%Ѵ~"2݄v3 AY{wX'[%Kr^/=BURvLPQ+R~pU[Ls0=jW(6[ħdT8Iu 2[ÈAj`P_8L]yC[#Oc_Sk<fQw4葵D0;n? =+^p=g 8({}bh'7C0|$OW9Հ +q9Zã 5K[pSKqz oMޡVQϻJnUHOPRfV7?n~r-K4}aal(I|S Ƿ}SL^w3^2 +xon^AtėBZӍWNmB8zV vЕ^ffF& JD! x)b'jxH|Qw2Ѱ>;sJS[IPWK̿BmA܋m͊/_r:5a6. 9&6'}P* :%6"d8{Qlf^G CCm! 2 ATlm> !EFf0ϑ H9gZRWĘzڣ\"\d*\Ļ_~ݤ,oC~IW0aGO9kvYz_!PnuBAGfϋmbo^GҨ9Z=פfQz3)PN,f,isu+ʜ%EKGOBpmcb^9(zDN.\mZ>LoCJRW[)P$hv#pY+ H2fHmٚkrи/Hh/3Hx9F;~n_{pgB%|}p"gM|kӠLx+lG,VKk{}V⁔GPxtoQ:KZ𽮇0t>T"~^ȝzkx[.ַFjOW2ʌSJ$ŝ#ͧ8ۡn.EM$4ȂlO;T~W+h_UQR/¥__v!?l2<зy 7fVmafd?bGd|/Oz#씙(6p6Qn-]r_#,tJÈ4}n0y@pRBeShPKj`xlb™߅,83VCdrj7HZ.oJhg20d&:Pi6I[u?%i;PfC LZtu7Qv/Wï̛% X# svt]{pM Z&gb:#f9W>`] IK m?"͔F7)iKMjoaB03&=+ZhmOP *h`4?'a{?RA@Vu6惷`*+@_e{΃KfJOA=xO9Pmm=U*~lcRб|76Etb6wwn7<fEȊmi@SY,{?ѐ_3kJrg4)顖z8v^rbG ԹI\ɖ3pne3 *p- jLnG-I<@"ϦvJkȘ'xDV!ph`|\</5 _?~2o iB~Ǩ,=/ޯƑBsQf0 ${5 憎8kzqd҂ҪL f6t R,q,7Ɵ4uP,Pؘ³,Ǽb8fqU進 l~>YM:,l{:o~E)X-蛤%xVĄF[ Ua[`1lPXDyR.I-0!ʅQFn5BeN> >dm}`eշ̠s4~ֺRU}[l(z9]ʽ4ց^j; lzFZ7(_hԵG;;hWDTIR3' T^,R؁ؠG$2Rh/lTM1|aF4d[ L4D1؍tUb.,:]25@+(DN99pC ; Yk'Sh`\!ߨ8UPlX듔 I]Ń :Qҵ'7=kV'0f:R[UTPL#}rm&z2Qzn<VxBom1WI[R5*Pf&A)BPjn7Uw!6w,BPa%":0WifÉN|4Gpb5Qi^s!ss؟~Ү!(-9Dp %3%d)WM8Y Ke rra R'7 eWޠ|L 5y򆑰vySk$ X.'޹I sŏC Oiޫuj3lN+h7'^7eS_0\ڂs}yvAqNd$h!Hc&\Zb"U*TH$p`ID_O1E"1p[:iމ6}dMDAz3!0 gwb=Դ9=}۟zȳ$fv?Yp-rlWB );k(dV(<-yy} QYlT?P!+|obn $l-*K=ۭt_PXf0>] B\}>cl$]+:P#|_6MN[Gi"$-<$5)O'9]'g9e V3PCΰ8*J`>&]8)~YZA-uڧTgWwؿCTj~ڬ n?{KT1G ){k lA ? V 6 z[N\QjazyY)+~ &_; !55dhNO,Z)ܮzb0+Q"y.`9s2N`tOJ-heoAW34jIDZT6PKe1ihAU4{bLsw a%)aУ s wƟ2Wm|+cN(y[x}Q"Gz sx&klEH'M:0y&w/A2;7'|.ZIT{ 8u딧}Ć2l? peA$(6%Dv(r7Z6BEYH5qjm LfВ7C> a MpˍS6ZpZQ>crVO' Qo@Q> kͩe34syLT9TG5SnF(2 D\R9UP!d+/W!^dzZ(LPN /Ttx9Få \ 6?{&5 T(NZVW^3!fWfN(FBB_p#98M)<2@sU`USg1Oͦ6szdMP8'Rp`"8OrnwXr6I'=& Z\Њyf(v?zs ThE=86u$MئVO<-"^fx p%B4Wmq0{iŝ]_}j' 8!Am7 KKL?ȈUk{ ۋFͅ_h֣O S.A%R٪=[w3)(N1a$vW6h*>sӹ> [P<9`>CP6OSb0܉X-OżDJJ|MD"ZWoP;4̂ n@=C4y3),i'> ʣBHңW}L8@e;d Z%pkDmG?(pDBF!G; pW.#_`F6ApV&P;fBxwaMU֕~"h\n>Ӭ@Sgu V9FN-HQ}p?j{:DrJҗOϐP U. * JD֐]@qC!Ԡʃ:j`#mM^r3 T61=cy5λn ڇyik9Cu,}.*N2&L?3=qrڏ cREHբ3RR` YƉ!/`K ]=[(f ;yK=M Nl큈'Ã`ƒJFt+`LC />XFO iϝ,mEG9}`nA'&1 FR|7o 23aM# <]l\,'}ikH| (CVURFh"{^k%<v˼sG*q&fB-=GqW~C t#Ƥ~}v~٭BST(Z[G R5@54/\jdZ0Ƅ/Y5>giqfLN H-u Cjȹ?@ &3eG%3vbo7PQ2"͡|Marsmr/i <ׯ9j,NQV-14oo}ʎ) cv#F7eUih0KٻQ,EyrȧBY6Ǐ L%xm^ `WjӺ"):d!#?38t@gY9RNP.;.bQ\ $d] OE ĮuW(a ֫u\/`R:[+wX?H\a4j5 # H1kGV8A57 P-W#f"L(q/aUq>%벝T=i; zh`ލ1̉,=1HW./ʾ Ir3${Fy&#E< ]j!LcO(b\Ej:@w;Jfs7xJw3( FU #<9"͎]G5A̞[j% Ӣ& E#OĜc<1 N5ݴ^үAj"+cC:Uh0MZ1o}pSٺŬx0Oɳ 5Ū3ي0~] @foj&b9Jɧe*\| qbM@yAtpNۘ ~'JvspI  o9%/*+D4|.%3'H8Seq?C >ā Fs7}yqwg2i P5)%Վ[%uʼT-V3/&5u:" 6N=3^/^E>{ęԽS*ͻS 8lE J0/Ĝiv ߽$@H*$<| NK?ٗhWt|3\2QVN7<ס}pؼv1 zYyubnKxp&&p.q/7y;nաnꀏGlqĘyqվ ÿmxO/vܬɮ3Z(x?|o3mO7-ۡ}OT~TZ/RSnK$  ?xP ,g:6W(M5]EUx9+P߁1:ov`O>=5\7փ!xyt ́W޽r xTS6 L޿rC0I@Nت s"SC NκިWt&Foxi;p^3zp^2xu%t6ڛ&1Z\^/(pɀMr+™m Ӄbإ[:-z٣&T|jԵCEkTDM3h0Q&aen[j ݖ{lI*`Aީos*0uq)`С6d gxCy{{[s`Cl8tHP^LwҡrS ȹt}*ȴž%h-8h0RA^upEk(bFYmAB5g, m л'o6Tz WsSPn⓻.,_ÍRI WQ(JIDK=bO*^(ktg! t⇿xQU5G>A0( k㕥@PpTbE$YW<)Tw M̧,==TM~>Z5EU8ieiaRM fú?C$G_A9CT kx M#1^)fUOGv%X BGhu MbwXܟ9KtDeU`sAwB׬0J-5f oOUV 8 XSMv}ZCّSDc]1 Ӣƙ vZsot #7;q4+Y_287{l߼sqc(RGxͼAW? zX@XAdbzKHȟ?)WNń|<J '`h\Up(P wCCa R3`[WJۛL~FAQ(Ke-8j+F }9!gV^u> 42n&t;f 9 u@%@Ͼj_s[h+0x)`KJva:cqJ'b2i.Yza:V  *9$p?]UA{[nȆ0Ew&9?U[2,ISN{vH0cHdʂ (*U\Ox^{ym 9P x]døGDyTAgo 6:)t2CΚE`-rQ }2&&[QK@5& 56Ía kJTe=>r8ĥe#0C³fwU.ٹfaJtA+R2HDOޥSjaİaЀ)]z nZsز8AIΥvF;LNXMvzQpHBYKgsj/k7{7'v5귘xMh4{ \5;󇘁=J^ 7$aW=b L=䳃@4lïhiPO_ 4]yا_1-aF<zŪ-@B4 Bq,uA!G%;Dra, J^g F %VAxlSŚ#9Yŗw~J{y| OuȮ(z!BQ;y/?1bgA{W$893)״)csN(b- ,Y.A gD;ȍ1vNP zUAusJa;U`ּQxq5;I' QE{ Zv_'wizQl"=Gj~Hvm#,ūM&=e'&1!y$CV?gԂcf(WJpw.(#EH!f0a꽉_8gCXf5CBL86hц㵅GFR2ywzp $ )WM<]P`#_S~f’#,Q^ L ZR% {vH=e3;~W>y܂D }AߑSZ>uR\5PVu.|_JwƺWڀ*S?C9i3Vڂc)KJśzO͵Dؒ/~I=ggW*!dM9fyW$a`fc*1dM(; mHs1~ܧ{p J lYB Ld#mj:uɯQ  \]pqY݃{vռk%)/eVJtuZŠ'1#`=}ŻG((Mo$n)PЍ$q6xK-֡s sE9#aqFCy,W5_Uh6 #D{}Rzdd6<ۢ֠ıHKhX1wX\A'M&iE- MZ{vhC$ o%l{5~'@[I r]$*4LJJY!oVum;"b lIOK$8j[P]gSA:d6Y{g /F1p L90Ҝh¨Ԕ֏8Fz{Ls$7] _bveZI cPzk{$¯j:(k:7{Le] kf+?B2V  (4AP_*} g+JG9&h 9TH-oq=^ O; gS+0=8@Phǡ*3z fn' 0[)) #Hz.i*` FaxرtW+L˔ml̎9ܭW%󱍌7Ya~>9${]pd@[CkJ6pLN s%QP_'tnīi-0F%ᱍMy24xo;f.pCP߄:>,=9y5 T岱C=18bڏ }GLz%?*!z,!6vh_:uM*6˛R%=#YaBAml=Wog16YcIP1X"7Xkjʫpܔ8Tb xۢ0G8„>dvJV*ЁL٠Fvi>g)v+z.(Bxiʼn}'B?`J)X-(<Bg$ZIGLKoNȟz'2f*eJNS"G[YߤuKinA]ta?W}mxF⇘*kGi4^ -{* G~oc'/DW4uW߲uA,7vD(5(Tv{4WR(|K.e 8'L/uI6ѐm|BwܱPu;9bG;T6Lhfu46fq:`Vpo#]U!_2ޫ#!qp`V*#v|ܐuRP8#5 ]8ap.t%Nra9 #~nT`bx bslk $ dTZ\u*Ǥ7̪i=;&̷0?=LwJ1[I,c_ FpF{䏛H]~UOBF$oL%¥ jLs݉'؋.0W?k'CYAy R/pM 4jySepΙ؊Ō@aM@u89 T9.ᝡP9+ C<49Ax7`LAH 5GJ/o{{$JII-s@/ݏԣ̆G04ܴSkƮ nj_S]S#R ݐA)1>沇&)4%{ԥNXJAj(ul"59JXWN .= $#&bdUx,+ErY/:#A>]'JNiVluҗ߱! lj0dZQc׆(X# 9ɷ=0]:ki*ݰL"=_W*! p8eSe[I$r Aof bQgBG L\ >Ji#* _[naۧ 7xx11㏐M&&X%50baAu|m"'(]Fgqpf0`ODZiܚuL٠ڴI` >C%{Ձk!i'0EʲwY> NX!fTD3q`pbyd,s<8 k֮pd5 !'!I=I0)icR`#~kn䙵[%A _thu|9tә+ ǡ`N̕"k6B/]0.e;0I KqވGN|>C*HpA!$('&%..f};YbB $ueejOu70/0^mk6KuCfs& wĊZyl؀^e80ONzo k")ZNàPc7CRA_Ѕ>J \$Z@wQ1$0vrrV>̎2 1! ,0 <ɋmHTaW$x^g2ǩGES_G'k5&<)Z%20ms# 8 V%GZX$|MfpfKY"GQ8ވ@ ڏ)q%kdPw*Eɸ ğh("fC . jYWh'uDdᅧVSr+%plť+sA4W0۝aЃǬ>)Ŭ uS| ||.CS2mT![{7(p7K(Tr3g]tٯG$X$/\s_g*4۳p"X`Ev5{,@a #-BS/|&PK\3?0W;> `29{ CiQם/-X|o*'֧?1O*d67!ˡB bv˙>Ez);5VdrFZLh78+g't;쁸3ZN~,;XaTaddJۥ7)EY2 Бp\agNc>¹ŵ[T L'"N%[qK/]ߙ uH9), F4%GۚY5&wnyjNY,o d-KoHW׌~E%BX VqruvfJ!LEisz9ՓLIn:yfztJ7Sus_O!6р;qEXaZ+z<hPƬˆ]ACmC%Ӕꘆ=VK^k 2e"Db7c9{؋1CR! )xK/Iԍ#B'?*p%T^PEņb;x۷ДzN8sg b/J |eI"fO$Pߪv9˦wTζ>& <[.㖊`Wd)㶤xľd.b'{bv4G-'#kfpfY|`FM?S=;X}CN@}! eVMmؼ5=F{ M@1RHZX[\K+u[ ®P,ႆnL\%oK1B<3vc(>ZW%_`鸌 ` RE@wrvRgx% ,f1;8 Fgs!)z?wN44&jzi.FpMXhtau2y(K=C{J`LX92cQ,HL1?\' |J|&x`P}<nhS$8xL0n5qO6c; P1p9TY 3{6>t U8ZLAp0cF8s[mC A駼X̑Bp\o 2xKORئhAV vCļD`g  OC w5:a(R7~mp 4/Qg؏\fc+alpvF &BSLݲ1=l!HIQbQGgLQt)Fre^LJǻZ tMhXao*D# !liA(-(bHWs1$cٵS=hL*l؁΢k hKsv4 V`m9|ɀЊ<`Iµq 7ѯso`7$'s^Fq-{H1 }wtY iK -ɬϾܝm`cXZaD™_x:`DcJ`vଂ*=ؖ4_C`dV k ?:h FBԂc y]BC4\"Cv~C6VcP @<18}P9KD&5,ǝ'#-†E_ӭBi3IuMwc<8fl 䉹뒜y$Sӝʇ? jpHf.NuZD47'{N5:!gg;eB d!\ Fv&@J_ j)^?/rr܏ gO^8>0ѪhY턑>w6㸍D)k]e)2tTآќN4N~1EUv&{1lJR+Pn^^ɁiېE3CkBǷ9% x(ǔiڳ%)1D@uSRr JRveD?- LtP{>~hOaG+nGkӐM0k_*.~QGɃ%JBpK5.\`02re t1S%HO/M:yFhH;@YIYfJ9gp_L0 PiR24bGցkJAek#Y <%.a(jVڅ[g&HU("pw +U!O6]y#@6|Λ áCi.&!;/WY;֘Dh"d oxp%/Z y@"#h4:7ww^D\Gll1ƌw2{<&`+TԒd0i͉Is}Oh +TsxKYCS'1gnS:.Q_ :$ /X.k~\PA?Xs$4BB`i)4e%E ^$b9]8Pv -|-b}Ҹj =&P2滍{d#ʈ[W uPx ,'P𒣂 :ηGX:.=Ĕ%nNa6ɭa, #7= ^5`B3ɎD`M%)m|sacaYCLD6d۞I,&s/;G +df &fU_ݴ'>W (C˂>Ҷ>Fr}7rޯVx}&\jz+8yHgDspE6)p:eS(H4T!u2iAK"oG1r>Bxo{3b4w%TE˓"-N;Tt͇#XT!oIH/e5#S?J 4C!4}:ӹ̾b&+Qx.m)r-MudOmmZ]&G;}zd6ӇGJٿ wQӭpJ3i2Y=շSB5P͓.YFVDG,CƬA@1Y)NS&_g2{IЅc\!qWzTKaԤLnIg"!}!H۟qAGOSLCKz0(F@YAX_@Ŷb,Ii,8dÃTtJL1+~w'C3,W>{]'( z4!L-T(0jB túAPV~5@gAbI,W=_]s$Eج1{̋"0*9n،Ůz>zP~?\c#d9Q;`i>*,Kw7!OGj2<'3cإs/"tqA\l9f)!j#g -ʱVz)=СfR%|m@.0[kiM:m~S73Ђ虹PD8rqQ{?y#( +?#J}ڱ5>%ŽJ.Nm"9s5mRKMkDg\\P ߫.N*TS'g0)6I'*-+jwi9ֹOG_ 묥Vw'rٖd^Ȏ:F]/Zk_.$={FXC,yIso awfLl! B",0\#OI?=kuVP0 V< S1_h:ړs4g$R{X Pqʊp9NtWI&&N)g=ޮ 0<0 d ZM ֘2ٴ §]\dkR0wOI9W y/Jɺ#vBsV#΁,αқ!> <֥"Ёsj06@ E3%A^- j+Ц}6X6g|#kiQA<)^Zs>C F+1 31T=ƯdVB1)0+TJ Zq.)s0dԀ%_;@ɡ$PUWJ$j9>]  e73?IoXڭg*rYŦx'(tep*ktyY3k^!2 K)s#$ \,݁ɹ'SG xЅ.\EkC q&F|tpbК ㄝz2uc12RH£lws7.J" \p9:*5 ;O9P3L3ȴA \ZDd@? -pj02&+w%}ʷ !hcZ(JZnpR+dLQ8mTcV[8ky 6tL`4}IM#YjIZ;Q7B8ÈM!iSQ%ʜQ(mhrdq@̀=D4npA.5~Jk ڭrm zamiSkTJKl-S`8tU<j~m>..j{ hSҀ\~>,UwMz|{G8'QBQIqk4!+| .Ś]Isc,J_kj#9Abӕ s k«EXMZ9)|.nP_y_:KJ*q1jV3ϧ^!Pl-n^ #O\ 밖ˬ!wwkV̘896ӠWEp0W.>S7cL0HZ~:_܃CEU yB2װi.<,MqQ@t~!IpMc/VrKԁ,:|4ap̞ }TAKd@Gh7 ž?!?r'c0ALO`0zxqn.S e^XG8阈QDBlnnN T: ()tDD͎ĽϙXX3x!]<F?6jڻ(tty=5Rc Rcc +Xxcy/4'/j?ǴNL~˿ǹ:р0Tsy(g&KA&ߎM" ġdYa& \fGQv\_#ȖYBK, E\LziX.ڮO-*RZUxA'63~1 콙/V9?tCTpL&8G!ey"-H`<+[ IxN"A$^YsG&M+Sԩhs p+ B.bd"dysĵE1w:gh!|YZn]Cp% f;z>bJOxrDqñf1;n(6ӗMbz"ϗ ϗ ]ʈ?nUI~"ނ ~" Q"f* g^"[9j gPBe<=_`6S=0FSTa?3@֦)QJPm)1 0vhaRphiZ MOȍq]'ۀSk]9w{QMD2Dw7ٙ]a4QD*f]PeB`s;i緛ԺSc{Wȍ#ӃGNPCmF6!oS&#hJ噇~*AkfngpJ*34E+)2Um~"$_ keJwtp_)QbQ2S%B}GeW+PPT&Y'QZWrMJRJ|#t(>ҕ#frd,8Mv2}|i$ˍx'bnP<8xnF+ρDT깜Rykh8j$\udV4t0(,{) վ9Y23*Md3{BgO=S?iWo՚_m&È/§RgQx{l>x;8r,wt;aO(%+d$)+SU1ɢD^!xYs!aB!"S_n_̯N9&b"+gba5?1aZnK{GhD ?4_:ܙIXL0Qj=H6'z⸫z#{֏F㒁0Wuj!8氡>p[d G ݇1犯z6$K%(yB,3a]Yb` ^OwҚ\S6 ; P<<]xR nHRiP82rA{]^_Dk5) N3Q}|K5k)Nkÿ\䶩-r'_P<7 mbh0ڇîF&I[ZhSGϛ{.]gl_[iyO :dJHM{s t")\#.n?P:P"ww.*, Z< *ab3@3D| D{BYс`gSIƈLV*h`쿇w#AWxiW(t_~dcaFpVNw4/͑@v0\Lu`' vۡD i?U9ZWHgȻ +=n(7 OvwS$B*邢  R+1eᵁѠ6ޟeՀdV(X_c M$`ȿ=#Dž悲1L)u;w) _[JbBKHD.* V6' xbVqwٿCzƹ`JlwгWp A:f_#P mx˛LtHX$w&FTHQ ;Z=P>#6A! tJ?\zR9oX6km*ˍhb%ǰOqoaP^Z'G&98yPJ>KF19KI F߃0X%Hheslng:Z60$A2#6DJzj\#o|gj>e3lE&|yBSd90;8Z|{(%Zmߑ̘3<&5?'=MԈQ0[)!0Zb0ZB|&)L3y5σØbUa(rb|;[-%GU e uڂk͋L&~v*`eAHwtqqEa$!h-, &Kc_!ygu3v椻Pxjy:{ gh/MRttٝĢ96FR7Sz!ď棍YHca>GXB[" =H|9w*1_LԂ%F+Ie ٥r›lK RI*>Zj6!%rI -}\zmm̷Zz Ⓕ=Eg.\G<T1<"*8sLԡ4lC*BP37'd:n~wthwWsPyxiZ($+gP]/"_Tͺ5}JPwåxšNIB/~=)| G x 5r3ќ{Qݞu͏TjF^TӃ~|H@TTww> ]\\\ݻE֤9E'c30l7wfa\)=E9 WK|kD H^٘$<._@%.Ç+/@ϪAewRBH-2 q"=@Q]I)Af6~1= eU⦅ =y_Hfs',,%?$HZ!EOf;.bʕf^_ݮ`N@^f9#~u FfT3`{FɳВ@Z[4l1f939k]B0j&ZjTӗtoEJm(&|r(k~d}L:5DxK Hd&V.g_g'ɦw[" Wo^(^);߳*TcF9бc~8Ш4j쌷2%;ۉ_*0pOղCr[pƔxY<;9a ';o>( Qa otoDE̻hKrml7C_ :v3V,LuzHQmfJ.k Pq i&|4&*)QX` 07?.ʔJNTl34|H;e S&)[g hPd#ޤ~*Q$ؤr}3 `C|7e)vFjȗ:Lr[% [yqr7.~?["Ggk}͖Xp%#%G-q7sTѕ^JR`T}5/;[ܥ/"| G#D9OӀoÒl$r0|f;"ZU,_ |u_'Tq)BTT9`Wu|J|8A"9 Ў gI YҸ @2x@*D>2o@ "9efSO˶HAȚ2=P~#)_](<K=7pԓʲSqt;Rd:KG#@^F0_4AW.d3TwQJPTe80ZLqr7^h 0Lp ۝4$X>:n!cWYO>eH@9!dacV tԀ]uҀTbm8Z}o04Piı]mځ`ATՏ[DsF}Өkܿ̚,>SF7r {\?7L"`kY(۝:38`O5 0j{_I%zǿ3H.'5toq H{ Ia OCYQ $Bσ, 3&3\(_-/NHRJCamySZ <׽DDs"F!8oY!Dɀ .Fě)Tk3!`z:UDY$. C_)Ӌ)CK\St.pJJԒ}UpcKG%ɛS5Ǚ"0`Jcq1 Tna0K#SOHP9?9Op=mр3C`!x!!܎<`Se?Y"otg/o=Da'DN9+*ƋX A:8-~د}G4?L# 6,: PLNFO✔LND-+M딸{ -D`g5tdvjB@eA&ʼn(Zu$P-o%`B{Idw٧0$nS +ǤC)-߿1F# 6JYMP .M(xC, ݊a5G"c')L״pRP}13["倖HFY ~Eća==2ISxC'K݇d"1.]qC ,UdUő$aƬh= Dˏn_D9S8(nȘE@9(\Ws Uןs 82`,z/fP;&ʫ~3x:ptH@AI ub`aGA=kBƩtv)P*Td2)h'Q?zߪJuYV (hEGH `<̬? f)V IY"92ټTj)Gf vdQ7~/QeX8O{ѓ;?:Jp@sw~%K}=aogէu &*22[HaJ7i .GMyfx)pdz㲠0> p΋*_?0 U$92.ML3p n@ *AI=6u+4LF4"E3, 'o>  5 Y A@18`PЙ-x`hRcb(cC tV\ת.z2i_"4`nNC|v3{&\ PyXp*{;ހ@r8f[f&6|O X7x4;$"tT36MA6v/|C,}n`LOX#hoDc*Z: Wd*t|y_5@sD}b 6 НQ0oW,ߝ7o PHp}kkp_w{(? NXty=`ї.KKe𙂼\ l?re5KnqDUzW]A9M?$Tl-[w03U_SBLp.t \c/M|LС3ryMJ[,UR!`22)̀ZR6vV!* 2;fs2 u'yS7@<${X ݆̕mPQVWa`I4h/E"% )Z|Q>/n M@ԭ<:$65C~ *ĕS^G1c ܈"hjs4XE&M+DJ3SLRt6>5, }yGy*/ACԬ4~;^"!Kswr3t>s 'dQ N™0j`p|C-H@{aA!'˹i8\7)XQNQ$&%n e#o=)uUBv T0̏CTD\JD^:'s$K/\(LlRx"An !5"z~bйB&2mSc9J ;EO&J)R % ΃oqǕ? W{ '{& hB, tX, 8spKϔIp0W5a*Td[LnLNH%T8-~>,lh4h5,+`.eHMmä::3TxA`H`@-hSN jT!a+, =u;ϥE$")i'S#7hJͮT|-`t01wDC܁35,.I%cj&67O]b~i|*{Ka~Hl/:j 5D sHR :4TbĿhBftD,ZfA .Dńlk-QɊCA^65A5v }ڢ @u%v=l@<XgFLǂi ޲B)P4cL0b$g9؉%푃_)hӳ9,#ukFiW \(g+\He YrВacT1yjvҮW8jс&N 4϶2Hup+IxR? W51t< L {H &qy>k' A"8jY@^v aL>s\iڝPyx6lJ5m2yWLy6PJЏz;u! ljD-x*g̔C1 /NuT0ow}2SP$z?E/wП8oe1 Y#IP4)ByϺRgW١1Sz엩 L=Dߞt}2Q8Mu6z~ptD,87^ ?!yI#Ð{ܛf UT g<']\S̮pr;Hg0K_Hj/8f"AġnS#BqG9akS,J (lO NX L@"jAXNzKAw2[M>3wA0_YtHΑZ&Ԇ߂C\8ᕛ/bgmZ P(f"ɹW["~NIAHJ&1 Mp׏_P25)Лs ~̖aZB ?A\ʧE>9o G QCuP8R ^X %HgEpHO`K0v\`cu@[8 :쉜~/W- N7 }BqI? ~foЌEj7hq17n=q'pXNOSYrjp+m/lWj{1 :":p!:LY.rO0jA]#+ ⬥;bL)W"^"@\!aDlB`,zuu=v3u!8C<9&;"Ԑ(w|ʉb4LNԩq|Sqw  F8\2&wy(]KĦ\7(VHtd&80[|m$"qzSdb?4Mݵg4emvP5T_ ˉ8|CStqinoh4;&'PW( "T@偅]|%QDGQ[6J̉#FyчG R;H<:ʐLcgQ3M~m'hi(j?!cpg\ 4GmWw&c r;Zt0QK%GzEE}\DžGq߱VKAw@`# {(w"%L$'ݕZqUr@8PP\D7ֆVEʌ8t9;?Uo;>hF,pP?}-)c?vQfKհ\-tN9LuN ߅oAy"w}(cbX΅Ot" ^/ͼ}-MVC vA˅Djq1i?xu8z7ZA/0X' 'tƴOr7Ħ$n؁s BpIR[xu8d7Z.R-i0 ѪG]eA.X3C7иyz5n$4d߯*5877a;QLlLJM=PL(K>$uEA3D1vB"͡Lu0L'8YBs,]sic xprFQkV蝚Ԇ,IQ |ZUﮚfz8smiA;jd5`52P"@`w:Ւ1+h<+a ̇}E{`VyU(X@My nLv[NaTҹ ̈́fZle۝_e=-Owhɐw5Q?ݿJngbN?]kX;Q2~)d~*@5LL4%:I`| F1 @[!UfZhƇa/%n7 L$] nr8 kL?3Lmpo8/+d oTs!`6)kԃJtJG }w=]l|8c wn$PLMw󸯙 V3t; ;~5 [8:a܀d(@Fm4mF҂HĪxLz6H-SJLFm.S ϓ QĖD?*BTC)V?x:; xy5؂xm5h@u #Xsp[:hSՂͮ@CZ|8z]o( pF7z]# |bJ,CpgU?U$x_hj|n 7>UUaV_: 0i*{y"ǜ}xZQKG"^}SL0V\9ʍG7Y.nxU{dL`j-j1DՕ$m4jn\q B0aAp`<ހ4.9`vI)o srkm$^)m;d7xrOyOɄ琾Pn59 h9I9i Y~ove)b&m'.O{KL)Cjf~ [h} -N.ѶWphu'f9`/j3YG8x!_@(@OQ)]1`bm>g~Ҳ9\sEA@mWqҊk }& '*2ԏ.w 7zp }̘ރcVb1b{6 $h)pf+3a ;Y>˼@IzeE] Z'ۃ1Ϝ:pI #G#ÜK9 L- {!ΣڻxRFV.tR>hC@<6 `?G^֩W^ڦ.wMXq~n7&d$O:YHB[Kp?R=qfr?zWڲl#|zdeq{'e/Ŀwv* .:'NY)sɫ8Dm,fƫH8)&OUAs6tQp9<hPZJm{:HH3nRt4B=347ث[2$7N0[&)њ>.GghfCWgq Vmbo=C ҋ5D,]kE% KQ\tDntMم|@1ևMBV8t＀,c s@ZRG{|ҏu#Y@hWjdއ 2nG3)#eVVitAz5ntfFGwS-}΢"^fwnj gdPr %%u%!ŖoK CސycK > Q zFYO?g`M jJn]dm}@٣nj H*?a@ J=}?jٞɆH lM?]؋ 3xc | uq%ڜr.e|ʀ<ewe֡M3"i\y[=FixZ6Ƚg.5vi݁.8.C4#E%j#h~e\`z.S)muwVǿa0!ҕnsVZ-m'Qyȵsl3ꎾzj5EX nt[ڜ8mO4ǬM!?%Fw/ fF~…_.عu9TWWThDJ`ߝ T~X]-@&'s$:"߇O 4ӏ&j| K_8 !gD\! &#dS\_KKo]Y;pc?uu[4bŐ`*Gnmj` Sb_b$zWБUt$E&<'S8#:I| (/+(p lc!54MPqks L]>O4Ju?pNT̅.L P; *!3{t\&s[/SaYy-Ia#})`gkMH6_A⾧nK=4 7c@]2OOE?(\P_ x(8/5Pį) ӽ5Zؖ_X#`U9]}/ 0KZUrdumΧQMńI5_o}sp~1U6r;}V[s ߘh}?\CO9I>|j$Տ M(f."KE!W:XwvH.O򚔠}\pX9ͧYB@H+.*CI UfRc]\g&&NLm &S=\#LjH6Q"3CGuEI\U5Gh`N]D<@-H&^V\jjZ E_u{7w7xRUșK\vZ:zy\h)K$hZ&xy ]tp:(0)KF4~b}9~lu)븷31W$a/&Lƽsg-̓Q嗬{cWx ۮif{%ws;HRS|iApA<~9LQr}PϕFɓhdg\+ʐmE辖w r-F#L]w5Z_` (^ ˧lXE (ELL]0̪g51k lU\}`3e@+4THD1azO]Ip>;yRe߫70 Fl/Xh^&Nԙ&!n ̞\%^*dED_Ǝ͜&;٥'W>DŽ3%J{?:g=G"-4jPDx8BJ24C{3\F%ҭFJ:(#s[r2Y XߺeC3o̚*>jk_ҼgU3 Q;~A$=] SzWH!S'rkf@tT G.+<| ⇑dn⯙T@uiHlI:" L,HΌlY zoRh؎Ū_ڥ9]@XvK Y)*-2dy}O,M.™8B& -7~/d_ s[ Ebβ`PZ\ Cł?a \ uɏ3 S͂[/ئ׳.~s$q&`MƄ:7v_I)o !0Z9#)d)ߑ,*[ζ i<7vx!/9a3Xv%34Kȯt D,*//]5zVUviaSJ S|%{>$(&LWo˚k}}EMO> ^@j㤈6WAY~nF/r W!w@-,UHDeyݖ]|]yKI' xϪx`W|˜Y>~N9GD& qF >ܫ߱d-;]PBz DH>_\B'p=ZO @tpG,́ خ0O z`6};jRAv>RCjgsu–+ EJK"@M&|"Y9cD?8t}*bGAz?d>Є?T9]A*gZ"u dTJ}D& n*IèD9$|s'9c PAƸKROC J//hx] jb{bhB :Sp=kS@`nͩտ Pt(N? c/vepҭg(B#XgB}n= MWy y%g_a2b6i4JB_0 gdKxm֔!{~d:ȫBsj whTjmؾ\dCd*Q,G' ۫d͍^y:d?~ <QlT@AF)@rgr?j$.ҮSWr}@8BF0'c>%cW&Y%HPm%sKgF[5ȼF<\+fgjBSDByϸ\k2Ux"#GL'K-8Ş wq(% x)53 {C!̔A]-ޛ2 n59 סOe' C2-+5~ ɻ>' ? zTA yW zwm*MXm(Ęz0GmPca9 duKu)&*:eCD5!>.[CvEĉhHa[ ] vby$8Q,_qC.{gzUZGΉ^Ic:688f%NS>~U Y^bG,3M IO \[O[4`N]+1Sl &6m`HFP*x*x6(R ?8p6o9>(6}Ԑ+1P?٣lT7 G>zr@wql&Fv0*ÞL=yʭ' FIFiSE0L9D}:,k:а=_ ﷙kz L /cكZSXe- ):i[1fqVʵYjN&+}D,lH7e:tCbܕB6Н7)r}¹߃V;kr-.Ug>do:v6Tz` 82YaYIAyAYb'澆'B]eJ鼋bMVtj7xzLMɓFIf+>Z ǍrSmEG @;ۃR% @Rk_נ#9ONqx6YY9oс?Dtr֗'|tkrR(;fC>&A9-lG%tgP (2w؇y?,T .@' a$ B7<*4ƒK¾8>AvԁP?}!Da*LLh&hU&ib΄O ;ܯ}"AE"G|9q-!gBHלF# }8q{Sj^pԙ)Z) Ջfz_uUÔ|{}Qa{36'ß:';ova uƤ[]e߯)PI"\hEO"ys%ODy$aރ#'4Ckw}@5\*%PtݨJoR_l gLN@ȭ̸\ݛ\TR}tU=gV(n9 Ad #9M) F~w?r<Ţs#6?TF0}>"V'@2v јJ>R7"FLcG2Uآ>zP( )$If,:20Bb!U YK[4BupH&:9ûDYm Iľ]")ŝ5IRe$8X.WwKԤx>_U/?y )A-9) B]蔠8cL1QkZ9gpǴo!Aj1U+NحJBlâ/Zz=7{ێrߩf]O!R.pNj RMu]MPC_hO"F'r=˚7 xL^Qg9 kǠ o8f Q.vJ!,] T҂>sUD6A4P#7jf&{>s^153Bֿyְ|q<z`U6Mbk݃v{p,c'"W4:DiX@ {wM^F^~X_l ֙x7",JX%}Q=.Ý' $e @=q;ðE :M"P:9.;D?;ѼCDKY L_H2x`[Q!?SS.]p 3bJJ;tB#~ Mz*B'P Jm/bJS{{mǠ5I,K,t|[uea{Vi  .e kpc"-(Q@^@=ZOiAߡ 7^U'e4~͸a5L+| B+Y DQEx!!4Nk X]?IDD(K&Dv‰3ŋ KzVzŸ(+ujAxŜqUjdҺė.-F-h17~[lDBac( ;j@ps6FXCXaBuɼaU0fm/کUϕ]5&x(>U5sHqEI#e( m*q+7*m0:fāx3z̀& .iԀZ8S`-=8jLu{zLApѩO]YeIZk^ON A:%,2V ul0=Xȟ*?MGw1iΔoīK5>q@i~+|TfY8`$22-fR#Ys@0`/䭯`̻쾉E-Ep_>)C/\Q6*&n՝CzŭUp%@w#qeB[dܶ}JTq& G8GČYI37ĩ@M,Ө}MfԊ T ZdƉt}ʀ4rF=!i&u-yPN`p,98tyM{X ZnHϋO2˜3 BGR WKA`WA(e?5p^Rвn^gy{/U^\8I$(G|Wt 3[Dh~LW`(R)>Ammcڊ9uN MeqvfGit9f,,kd%ҧ)O s)asoѦI:G7:iH]gwD# >i$gߐO60<_o_bK(@~;1(A#>0D*1ˑBp0'ɪZ2=K)~/Q%J#$k*_"k#iao`ߢj &'Ն,/$|ފ@]zs;㦇]c u 7d+qr sQ<6sişNX(Oʹ9|ZgZ;Ձ ~6?)= -e;S:y<y.~na%0N"GlѰ!qhu1gTW̠#co逈l JR3E2:Uj&9X[D"0H z;{ #x5@|A7Ob$>.tR!"303G.X`8$E{{r3QxSoܱIz&rh@ݶWgnB}Bz镚h"E*쩟(odDۍ:x F燃XU% kT#]4\A>+^JYd@M-dரUͅ6zpݸF1)f8Uvذ 4YǴ3Sxڼwu'IkK[癆oԉ;Sؖn<֌E@u}V*6# jFp//ᝏL?Z~{Jf/> 0姚,}3F0=d#HKK;>#rmhW'.4~ހnF|:{εHyȣ=XHmPݎ\~pZw@!;>0I;ضMn \1;wj|v&q - 3Cھh{KU[]WzוHQU:AgOX(w+<*K|cW'v4Q4bέMs,ǽm#qBVУY%+)j܄׏i%C[jApo]oǺ=E ( %])v-(~u]g!uX<$v o MX{*[܊p>a*'Zk+߾72R7} ׀•Dl"nel G= fp\=ztϾ0ARw*'9ZPGnS58@ۨekpe;~Zۤ:{gЮ*)mV"p PjA3?W5Kx6[6_xT0[Yfxj]a.A /!a8&0y:bhk gvKa}NWo锪 @L@hX^utf?P ۽vh1x"N~Tx e!Ư)HSZ_>'7+VHWySݾ_[,IO_JmM fzQ5$Q-ߩSoMU̿ PkU-fg'=ơc1No"MPQ7~e}i4, K\7@U6HkntɾVM{m7)#X[a(uɕ:‚Evb}rAcQۓ8yu&mJ׆ &H 0EXcKyUOCf[Qhx^a0:.h>_}(+7FH؏HGчhچ$i)2+U`AE z{@QbM?hnDI7E -ۦVeOu#D2H&$vaV@kf\Ng[YlY,!*|wӛɈ:eGs{[* Y|ӆꁶ?o%Hbwd3=b8d'նd>DmCaֵ-hKVL;B' k"|.D~W*\ .X:NR"\~c_P/4wU&6"B0 O ST^FpRE_9̮h )7Ki&,FAW6q/(֟>^ ERASF h/NڼvW h Ҏ{y:kY`P3kvE^)ZQ~vws2Tv֣w~g̚\}Api'ThR<邕 P+uJ<ΩH_D64[e7+jN\#ŠZmr'%ڛ^Gz4EA[%s$`N2P;}޾] uL~^χW{] bfzF0'Q"g֧S}D?hgQyoC6ɔ }Kw2dw Zs=8h7)17} ̈́`ʃW(aF‹&~z*~\e %rSҋ[ "֐lZFރU!!X GN-ppHv-(l&T "XȰ?{e]g #wnAvp!t±IHiBDb`ﳘ̟+R G} C ,BE%y]zaMyrGHri,9Vj6&k;4?u .+~^C}3z%5i8ZĮUphDt[;Uo;px= W1g'q=7;PVp+~ (4otTpR[*SB(ȬЧH:v+tm3/> 쎔9dDk sOOb3P tZm C G[>3pv"o Ǥ]{}}5#A4R$8eMdy=?SuT}Π3Z D%8hKr0vʌ*N0NR ~ITnKYl1y޴P稿If5+`~5lm m昭QuQgm'T#pB$F 'l}llI5 _\ѽt5 >g'м,n_}mzbS )gK5HpЪ4r\&-593sHX~Yz0z@0ͭ(]ݎb'(WjJ4?SU܎B>[XJƫ)eLPYqh]ֵ֫ gi+RǾhN Vێ$$ֵ,8|cB$ 2ʙV)ႯAcO˄ LF 9H᱖BgK^ dL-M@R>`}`%2]a-זK90(JҪ{@<]^2}X㈓7q$֛{-@VOQNL!GB\6H 7w S]w -Pźxi~ua^RE"cE@j6di$4(7K V{A0zG::A/}op7M4;+E,I>ߦ֎*W*7SX'Ŋ[jrħ76 L/a|/H!4I18G 0VGX 5@-$,.=ӁbIo#C=2ݞ2RnH(xoݼqS2Y y$5_v:,`wjމW=.GHPw]MdA-Hw]BLp %[heڻ 7(_`V(VX8d\\0" Rw@k+ Q# (ZX܎v_"n;p%;ۉSki,H){%W"?zQIT?(nzpSٕ^I/W i5(`Js7)L"VimE. N;.@65m*6T (Lq5Xr)K=kGg&bPƔrBw>`ys\z "Ui}7\"ϯr\Lck*׎H^hʔIT'8+P}i&"T&y /ZANXW[cڇVy\N~lVf/Q@q[# pGi} !Ayۯk~Mb [&\6 )<RKT aS'~lmClc.0z mt &m41u $L$ ,\nBb/AxSwCBz:0`ȌVhv!@ؿ;{`r3Y73ß" -87qx*CJO)fNl   LXAW=~ Կ+x9*1 s?޾'КՂ@Dn iy$ 6i(~廢Cq__ZdoFFsVSx?үJ)u嚎t@`ix|~AO=Z>H{ Plݷ`?.,9|ryD/N,3_"Chh&h]XGhgppE@ iQY,HM 3=rITR!I/mUו<8tբn)qz1[jmx+h+z@Qx?m9 ( BIgl8pZey!W)+Y/`⻯w`C{pN^ 5wu XwcΘxXO9;K]~WIp iH@ּ AmǙALbibu3xq;ǟyxI=qM#p頊D5R =\;@xC)!CK߆J6&,:U -@֜vⳉj]qcj/ Ҵn;l`b8!"HDm-bj(@#?LT4TTҀbss"PLa|@`<<؋[/!&^M1>j aFwٕl7ݼeV_ G|F)/Q(72M$L?s+5A) TSfHM `+ӫ+S^CAݷ)Y60pE!(ocid73w$[RĞCk)٩::l֏vŸ5eJ &Mw#=po3<#LzL6,1v":9.Jo:a h0&<Zo] jWX|\ىfD:ŸCҧ*%`q}`ӄ"`oBnQc(lUn]34kUVkhцb; f:r#Rv?K9#M "~xoj_5?HA,ǀpjs0=;NI۠,\(bLXk bm:lc@V( \ acS6%Q:'qJa*r>&!Cוֹ҄OI σBi}͌h~5!)~{Jdcpf`į>&a0TɻOz;VDƋDž# lWzcKu`uJPhMϚk0]QU[;=f *EF#PA@f|?=6VWX)=F-[ ڷiX5a)AQ]ʃ2 ĄpNgkK7qnG3EH.{̪S?'cND`G4*"Ԃlk~tA<paK-O0zpo}hP "AfNd{ XWnl4n(o>0%c^"p^m)ګ鋿_*V^xa3$ )hmP^xFۍ!ZS Rk#2q6  8i xk)|ԫn?be 2ˌ`:pɁl׈@ ҿmD0EFܥa vJ\,o90=GbmمL2 FW@pA9oI,28:BmyJv#%V!jQ' P~X`sLgU'mHTpA) q6z<}#f?l`+Q^¶w`@}>a=Z3 |v#vaܮ^hgMZG:| `Uj/xfUV0+hƨđ5y׊b=v(8g.h4\$g,>3gIsbaQ@p ؍>iɓSecHCs`3q `xw)#qKIpL Djj !U|c* ;c*O}%3xuNT wm+nqu3#HrptApx0Q+xL0DZ,B'Hz4Ho݀MSZ 5W*~ktg Pf;<1@k' zAMP؞5 ֕aGG#<dc\QCF*.wvji"3`6Y3Q.So9!)=R2$i}CWa(sK_nv7_}-S#*NLp3)D Tx'•uO}󌟿/_/(R`0280{(R!8fx?uCb~9/V֎O[y_$U#w>ŞSXH T8F҇oL7ki֪A,s=%8%㫓hXF̩D*QjF*XG=ČL Po~`1Smƪli74\P}>c;ŋ9؁r `LӨxÏR ^Ce*<_u*|Hb3YeLJ 1w؆kwCwm)ju- cy1QpOX 2 vsɝ GSknG;U#3~? BKAK뫊fyq n+lp&)YmZN%gA>;a7EPWk h[[+۟A5؄6C6YzYg=S,z\I`b.@wo!"&߸6Wlk@L5' Rf>C.:8xS߯^q{|\QTh X5-//ogyɩLv?P0Jc0=cQ. 7XCm|0 j3Lʙ x;DR ^@6X3%<=l:mz]Hv;oW }0l9s:DtG|mac kאw+@ji`T1K.|)`pcusjKLY8R.{' . ~b\P<(%j>m$ubkPF H@r[1rLa;5U jH (70"Mtr-Ats7ޏ{trasǾXڞrT*Jh2L)EjtU_&+<@q 䱳&^'܁ULԂCx |Ҫ{oރKOCTaoaXXpaqq!讍6F+7FӐ'l& akƃ\{ILs~IoK `h7ԭb*QZ,9N’h0v[ ƚj(Ɉ'ux;>c ϟ]( 70|/ ozwLN^P$h)1%E eMASgLľ|8"f n z;-xt'3B7+.!>UN7"t2͸n9QcE=ooI< 2䶑H;[5U (>ٱ"RT6[jީ ֖S@\H.[3iR%(:gh.o݇ aW=#՞{/U3foG_B[~³ۭ~\B\(020cy< {$3)P,>Eቂ(BM!b h>OAJ"!Qjީ_h۬[`Ċ5c6eCcpHXS8;0@16#v~9¨}/%Jg@ >6=4V/Ѽ4oľ iS?ؓx~m#XzZ+͵|-ԃ/νtH;o$[Z`[>Uig_a/' Q˓xo,:;dWoZEb >yּꘀu&NK>Wp+*(yuX 貨 ^^={vs)J$+1t-ڽT_ۊ!22.ٴ&(@Ro9xR%ZR_WTJ'M(GPGzNpDS;mO! ++xđG&ΐj:by_ ^hP7 9З@ I١65x@|A yGCU_gHEB:0mrOH ]jď3n{w(bﶓob]7&Ֆ\=^_r?=ÔT+G[K 6 >*^|.#?$C?6p`}#&%RhO2Gn!v=)+gƯ?Ga#c-iBp:ճYmB!(,AKOc\N.OkwJh`!79_ fu"3 NrxПQo&crMiDt@y+)CU3']&ѡ%=?+y. cibxJA0Ppk@1HtBnr[C/h]@4ڨ(4Z|?'^zy+'^puDSy!Q0p,3Hmğ@PmʒCmT 3Xx?y, כ7L: UPmchE<;z`z? 2^ Ъ;=]C'*+Mjm3 auh ؘl,\[h%Ⱦхן8 !"5ؗ CA8:M_ ND, _{J{nBpA"pT0}>cpeUU1]^敫*ߚ!p/W:n@x& >5R$ 期S3F | :;E^Pp@4[w|vl#ԅR (1p;JD`Lש#`Ahrڡi} " B;}]vlr, lZJ > }qWzFRol?{֭Zс\.iI]BG՟tvKng4bgn yWX`NWyUn#$=%=R;Izy?71(3Ǧ; eli71xU@o(L+1'MD:#JoWPt 45 $`g:x!r7~.`RUMv7]1*|ƀ9iU%Gj43juM{S~\{ i~*C,WSeßw{}[ipB[3JySjs~6J }4}yIoA~mfAl OWyR!Mov13iŹ! U4?6U#/`ӌڸThmŎcxPxQ+A_d _T0b?pj߽ rk k\ ^/uXp?cZ@!';&?p<ڬ4f Zgs7oݮQmՓ^ܴ!?Q`>DY|OC_V^sgՊ}bo1IEɾm]vi{6#m¾?vތ"jRR a 蹖͏m_?{CtV׾3~qvܦ8 4K1}I&5"dChAx/@筰>e6y j^o{C훠*ޮpN;P4Y* 5DUem5֖|\vIޡJp^rY_ &^=ym {,'4<5@Vuh/.7v\4Nr_WիW9%{9围z@d4?b>kr;Oٟ^K[x4(n TߐUoJZX C$44ԗlBhϘL9V 5aL#7Hkfx &Q-L^C\GyχoT\i@ *Fm1Qɰemê-{(V DT{=Af Үֳ_k4;[ϛ {7Nu0͉߄:8p?'-3tf?y6e8(8wp3귪P{rRYGP̌Lljj#_qe`wۖ*閗骠eT>(o&.**HE64HBO.υWvӃ~t ~بK:uH_3߫^&M ӦZ |p0p[B@:qi6d@ae .?܂-jޛNw1K9Oq\Pazi~Ң] c΅jp?mOuk~wIw9n辉 VWOp_GT@M 5g,NN PEssnv;4 b=tC@i bIA@ۓ~`r{η6fWlf&[yVl~0(2Fg%Odۼ{xTqćoˈ"Xt`I$VUک>Eߠ4 eyZڦ߫x. 0Hdsv(9 |9- RP6O1 t&?8"F[?@>ZnPk?k{X9~ og$䒌kڎąWyNR4)wZ\2"9U,A7\ -Uo2'/@S^u\2pIJE>;ve6բPeyl jŇl#PwWqX#LPsI3tKŧ~)#: bb?Uco߀gN[j*@@,zp Oŋbt~=g[TY ֻI> t1LI"hLP훻?Qs4ڃ%*A1Zx3nVYK(9?R_#`ZLya%UG4o{P)"A"IJD:Ti*5Ү73*>7yUh3 C%"݃:Ud hclyF I[lBuQ5&t{7kת{]3VQ-碵#d .ćUbR2=i S![t+ t`¿ю46l,~$v#XxM T!ct5/W9ltdU3j:o>ߏEcPYwcwq@_<`_y}3B%Q3,ki,8'Dɡ!BoKsܖrQ\*vaW6c剪` @ˤ a@kc O_5}{iV ۰wjׁ1"g22@ULq.{W 5=?SjӝV-{Pᴆs2PQkO;S1e;AHP*pDPȵY+SQc(tvƦ@s5ߊ@²i3K oԦ?dq3Puww]OX\C9%k>găr׬:pKnqpAHS)T\KwEՠ?pf>W0eKTLom)c*~" D'1wrJYF1;1T`PB$ʤ'='i_y9AXⱐκE TnIXW5zN+`nUl@v(@G0QCH9x4&_?pyH@rLQޑ AN!# NDE##YQgNO1~V_wG5G ]H`'%Bq~Tņ@:s {TYB`(j@0 lz#J< $[߮͡3.fkUR$FLImH֟Ye9|=:LN%M1u, Eꦄ\Qy$ɉDʂ_kHhEc+L ǿSb/G%RRb~p H4 >Q<6WseQjJZ^l'pu $U!.H#Q 5Rlp` yIЍ, hpOAS``͸pðfu`vC5] +y~UɑGl*"A0Q tQIpFn/?RU wc2e/SeU p!`ἴV_J9>R $8Bb^zqf\Ǯr1=C7g9O~]MV[sZJql$ KJd# ȩ@NqT\ z |`{k1'u02Cel]\wH6}tj 3UvDsbkNK%$8F?Q |V y)$ك1͞c͢;4T4!8Bnj}%Vr:qMܭ8uI"/mw<+8&QEwBmT8AexO |orsDJZ_54yP:18 - ZqfD8 s*hgj]Wa?q+p{rSЋeo{yjtz_@?nSz@vԐ JzJe\{YvߍP]avEhmT{eevǣYÉ$UswUbK 6L9, y:# dp8( g%FpN9z6kY"v2kܵLwqh?ke:$p71ѕ{R gM[}@bz@gņNQp;,V@FUspEɱ]6@Ŗ;2\%B:Ua*irXooDzU\Gr)ae AJ-Omwjozv*ec @{qk/NneG@tG cv$t< 4y-;8>QWJCcƷovhw!z4+K]pȾiKMx\g0=y.  XCL/& U|Dscڵ[4lU-j$ &-ǜI;;R60*3ߺ ;6M{&o͞⚖ "0f8+9?q9.aW }_X&Q~ }ޚ:٩ر4A=5Z`oz=C߯>~c-ZF`e\ssh7In7e4 NP55ZQľ7`Ҿ&lgm+Ő6az䟦G7&6zl Փ<߻Q8m)LWlk赍 y:"א,YHv-ƐR+'Vwڽ,į20S0H8#ˠ0BқRD}{?}`0V5->d"-SnnA"|<*]ʆgAxA 6(ޝP,.J;E X_c$XR"1rF`G>؏(qjJ`Tȝ-A;7#_CZ V7+1!G8E`fz/oSpr:S$Kڳ: Sci"xUur-Ob'w foUvNk"Ҷ|xziE6훉9ad}'[VL @f;U @@ewN0{`T U S9s*K_($OH*F%ʏ!E!m++2J0I3ibs*"c%#% N,x_?<7䈝'Q $C*.groG8h4D dnO_g\0jDX 힗fQiB$ /hgX.4~8,- L!nL v"9CAɯ)\ޢG./gǻ?!7Fa[9"p<7+3A#6°8[W^I #^H_*AQqݳh1%fܐ+c6B3w! )@G4ubj*8k%LR2}hL%{ ': UɭA,ӄˈ\)j [I^\YAήP]my Zzэ y& gۛ)攊uN{V-;h$O)h]*'P֋0^#FqdH'-%7ImDLP>d 8\'_ XaZ1>A *}X #Y _E{V8 ?v;{%@~&\0q1SN`ege EZ=}X(:er{:L5<|%jk0#V HDT125|R2]/tt$?%aݥ{da>uFM %S;<崧"^J2K% "tǴفgϔo[(L<}]c=Q#^ҩʉ^Cugp(VsMu<#Vb !Q6ڞ9}h2s"jd8\S& O<Ҕy&yL'3ksY*wC q=d%qU6Sϙ=0KkM2H=2pQs1+Њ_N{3o,&94n[aG2Rn=gw3j*xi9+cz"̊SS=†gikjIXuh``ČI'fL⊉u%"c%!1`k;5q{ߠK&X ]HJ/np\>&ϖ`CR).mKׄc"[|1r>0C#nQBelwN /(S@ { izN>wϻxM.&5ZܖX ، (ğw7qkw|vmQ? ĶF`;*XP04,'iLUd5NWEc7Q_h2TE~Cv,'9/c&}{~xc8 a:-Y(+]!@L6}0mxO)ju A _/6jX!!m>߶`{Ndhiiho_&֒Dp&AH~Rk>OU~5(B4GE $`7vGIq@OX}!46"rvl0e3|>aOa2H1 Cq&l"e3_ tn6ITzC Vc7oԡ<+rAs |%Y@3Dc .WDµW$UPo_.Շo$sE/ 43U}%L uq+NJ=?x/3D_ۯ?usN  fv0 }{,Lք_{yKVReyh O:փ00#H C#X:@ n)fnOM?vɈKuwijĖ13!ѯZa]bvgV)]دGb/O!t: RAG9XXNz`nCGExӄ hjHY`̵݁un\Y@Fj=[KjMD[ul1h ̔5SJ_DIn]!9dP̽ߌ!@nD^u̕s$Q9o(V56>imCJodUX0ާ}Go]҈`({  q0A61MzSJ5B8.;6nNb.AE z @*/T KO Nhw)(hɢyjW??#],? |G e$`0^;N)񘗎Ng6ЩA+)3/12;I⮎0;B\1WUx¥U&B/_ߘQjJ}3Ⱦ]DA0 5+7ҤQiC([V|9SշRc1` Baۚ;+@06od:L`L78».h ×ym'w=;xApC9MEKn'؋d8wE)TT^ëݔQv|}`WP;x:)m%K_E&cH$FLr>x^ (jGoS/n` tA\i1jǫ}W2T%3zC_BE. $C`{e@!t㊎QMx5җ -WI +\:p^IZX1If#x>T"9SlTG3>7>E4UJ;; '5[Ąv=fL(#?쬢||}b"ԣ_p|H?w&`xϗN [pB2Wf1,z]0H IuD :wH湬45`;+F`Og\㙬zl<9߂_S9l89 8OOP`1Չv7m`i5m%t5Q-*hpP~'u?c~Q:TA6 IIA||QJ*QǪuU0!Rz9C< eVKs1|NUL,~]H*u> Iik/RG ljܛ~elq:T\1M9_2Çlӏphd8u$~URSN, .q>AGlpZgpN v! CY0`0GQ ۾2ԱC."rxf`,N:U:)HQ;@p)AL MrL %^iQ<'^8p M_^ S$Q W A(R8,JY" 0H!feGO[Mk4&3G?TX:(*Wu( wr^q3NLNHjIL$} Fw2, A #gDwsa׈ߚ&\0%@kGk1g%}#>rZP\y!0ʈ6F7(``xw ƷsKL'IbQH0taOtG\S5yr Бȉ 㺢$h8nI"u#t!!vI P(b%}pH40q9Y/V Bm酣{˛6)c pYNJ+]BT a:| pP*gtujJ iY:cJm :E 4 b(,9r?νoF@L2mP*gVyڛj), æob nO(@[RBw+(wGUS)1Fl n0.vR<]tB$ ?{?64K4B vOs*0ȵ8qRcwxb )S'HHm %ɝ8r[(x`<`p3!*NPWnB֪Vё{ g%/ ͷRQwu/RX)sVWDOxl𜉢 HϡDp-nP5 &ea56)DWH50v PrIpBP{TqD9 psaaºCas6:x|@q,\p`SA026r(@J֏! C87eŷ SɃ@,BS1vemH궕g(n_FcV'1!(29RXNb51dXtg"2ռzlK>ޣnZ2Dr("KPm=bfH"WSoz5^ZTX. VVM't:mbr>1|-܌LJ*&}j$yhwN(4R9&`|UNc@fTfy"$N)M`'Aa^!) "Nrw$v68N7M$^_8iaSEɜ81 @w#ECw6p6fO j;0`^)*Ff^bv@"/q (sT"OX/C#E~La' + hNk8?s7x` [9TpY]ؼ[A3߯dI>9dxJ h !x̟ 1YI̟h U8aυjX!3[]UyX\f.Kɓ9+SU?T9טBXa03(rfnraÁnЏH(#`R$q뿓%+\3UP&;{e, ~T Y\ x]K*T HEGiA? r\6PR|M.Oԍtbp)ݬuj%V㔟]&vфU^^:P)+ o8q~ci.'*HdPl:^襉3'SS_|-%G*h& x>ȟcљ `5+A4aP,y/~P\r+֥Y08o'jĢ |F `G%R%puy|tk ~4 QSi&ם>eosR} 7e5oDw 1oL $Qxvgc&QH`}?Lݻc3VPnDM0 d2*h (&BPP(W*ѻ(Q1Ah:b-M ջ|؟\ SUg ¬ ?= g|;玂 fԭ9(zP_~\%06irh} Fl/ `ETuHL uxLPq;,2heS3c;QٝO-qvrxL.[onN 8|Md^>Js"c&!4eu@W7St6C9mcϖSpdrz qk2F\3pĽ(j#vv$;R";E s`0 p32zWATH`|k9 +3x˼iOX=Lp'VA`z3~ྀL`c0ͶfVA ]=)-uM}T[D6QՏ2+THՆ(1ImC0#CcV6T<|^T;fsJ}Kj&t?"L@BOT"a0W5S=xTQss۸ݤr|=&6V lvWq=QF^4iN}}j{fghbDn>KOApa3#f>dA bDS F*0Y< ̈Xk~H\sk8A3 DE"_)]"pr{uXʪ|bQ+<jj-QkD+!ӏgYz1r2L(% vg=v|.GJ˳u4?l&Yb3OI_IsI:eF4jPTDFhꨉDheq'%켽(!9fv`znb$w;tMl̽I_G k{#Щ,՚ꃷ NP϶;;yB7f]Y mWWoU676Ě{cT+5%ҏALIT;z8sJeGH&p:[-Դð_΄p8-: j?f zDu8 Ol\.}g' !)'.Lu>Q_`1;0(@ro{C A6OpDA8 b-F@Sڳ+Z6Q1=Z9Aua" nګsChQkP ]*SZD,s.P}eR&IuE&0*IM3Y 'oN63 fV KsEdAE|Q:+z=K TGbM0L(Dbũ%S#Ev V5`NsEKɻVD.vAC03d=j> ͩZ>tTU$"q`)4Uj^BTbOU{Ōp;XuHa[]:` ],Sak@tY,K GT$U5 #xa d&5ƫpC1%(|UMNEw4NL1+p{0kc2z*FyqSyLc$':>jl D/J|* t2+OSu4^u{d"i%^>[5 V!ŚEKTs/gKmI)K u2-*[U7 odTT]uS49gS4$aqޮ {J_{wrmܶ1wB|A$ ^ I[p" \sdeNZ*Ѓ+5 HAcT+lC#*Fޜ& BCL|NQz{"\ @OӉb U &mAOvwgь0hG0+Qt"-m7,- ̓iasFZl#S%f mS-[nSTynO҅z"GᘺQcҩx >60l9uawOK eK“ mO BaW^\,cniYJsC>&}\0}eAsK 34$ 2ƿ/e|dxdbq|)fۢz|:k96T";<:΀AL `q6&ူL{a$Al _uc?R. [KKU,aD0H/i㉩IJw7fAwϣd9905&a _a}c2X6xVeZ}d1Im福25%FuLT-cvI g(&8*`o*门TߒajsD6DoXת황'ǝmliIKJO1k <XeNpKyXo8,̑Ε Gu{[ז5IzGtkƴ8V}JO@JއРf/v W'"b(S|($C"xn.ĘU z)Pޝ.AZYa]P}DM|Ş;]Tl۝51M0U6O)ocsХEQH='BΡ<;)VOtXۦO 0U |fNᨃ̤wG,+Qq8$M@@g*IJ[Iݐl&bvZ 굡bcY z(,q]2d9o4SEy*c5sf6&\"(525!=mkNf{K\[?E+cK p\(W^`7z@C4gڈjԲ o,0<@dȁfD0JM#"S܊ͫCz; *Bj-_і-Z_kšo@8#LLz#xm*@ܿ5@z̐ xeETQ|=<]V8K=pGsSBT%aƞO#EX+$.R^^S !& vD%TKt 4}ogQ8{p{%7]2HBaj)Ȅ[PiԅYC"Hy]QnOjfyY v "Z3_ H?ϥsAdMVX4T ^z3X!{h,aH ` g]"߼mm7r4x;p ]L<5V%k?t.K{?;>wQl b[y)5;  s.gD?АeNLO0vDɦ {*e5J@S AC'P:K55){9[=?%Rg[&V0tW1cr6jŵ>:7UA/Xl6L),3d_+ xQjU-Z$wJ~/tsܟ3Ō۪7){m~sF+@<~]gIgN4v[ւXt4@L1@0T:S2t>0H_{WxǦW;0P)/\$xSSYg=0FUwdZBV7^ fjfED;I +'٫ATanK+Y쿵P`k^UV,[QWhj\kR[QXyw6R*TrIћ%F+w8U5.| ϵ Oz T,ˁM\*&.,f%4'cXk;q'Cx 27 HEYЋ&b-vpOԮR3|RjSW@54 {ά$ =IfDJT7p)uGZPkww[#z]^SYo&O]б^?"J[^tjLwN IVsU!;2X2 PN b~B Z^B)%bgnt_.f`$SزGS7ͪlĤkӡvkI=ssį*^Sn<_ꈙ{@b}?wUE7}0WzR{:AEQ U?)5A%߮SswEUVN:J4w Ġ[p?-7ȵC U_ʰ>ks膔J:i; j,,@+`F ,MR*ePG%꒑D59r\)-%<]T,ϺV'QJvD3~0͆N{ hW)tЧHGJ+W ^KÆkb& ֣OC=[S+`\UV^{xZyUJF{} /z캷HD̀U]TLP;s{?n,ayEw- iv`rUo*&-53 \~]v *fDI&T-@%Jp; ?fhNtJ E❶ Z>XᤪX8E[5$gk< tg˔N&sJw0`_苬um-ņZ7S%Rmwj~9ýHG%Ez7ɝHBH?Ӎdw !m$F+kq8&ƻdtng=lOj1kFQZ?}&1n}_vd}ذ y@/TS` UO:G;Q}F- E:&(z lŎz1`v@Ac^ 0K+'UR؁Oumӫʟo"D[d?4ʆQq Z UY1;=8D֑{ݜ0{B=z1um c'<ڋC$5x8!;u :$ u!ZA+hjt8\ H#}P*G AS 38Ơp0n9(SK+VUG>pS훞 A"ԻH;[VSwh'H%DhxfF&^z_=J΍^apl_;4ybEDYC33a=EȝC &/>[)hL^K}!vi\>fSS4zaqM|!e58 αLq63%0iû.}xLQfpm>j[qvɦ ,`]c:=d@xJ fPo@?z9]x@U$xUyl#K&A'WkW"-ψ/PM~/27^<1pf?+cfskNk -N (i|@EgG̾|mr @ANM ܇/Kҁr{%9H([ېAϐ4KsdT{B9%*78hӃ൅`zs N^ c 2`sa &8 _١阂oAV^2OhI6whHXz3#N\o~oTƷ90:ML@sze&fUt/F݃rfװ\KirT^in(2 87߫Pb*yze.6P[~os~ HzrH]yLa1_"+͢YĜ S]s@*Cبgm7 %"InpBӬrX,ƮSCdW6zS'#YKW`X=* 9L0-&$(w޼ٛ_Z[<ŷn~1~2:CmLext`HgkHN] [ qXgp:}G2d7>H R MC'JJFI8˽\ċA|coKXd3$3NqIMާyXN {hEN}6#uʹw\P6D/vyK%C-\R0F4`J Uػ.&rïʌx;zU>|أqvAV[/uHoH6]f ni9I]CBz&Cn^@\c*-/W1[r0u(6|1kUKiöIAPmL2RmgkdVț߃OqOvL -U;' MM[`=Y F"f!zkP#4T_fP8&] ^k4L4nɾBI'Qϛ"6?R?b3۔?7VB1ۧvp +{~ 늙ʼn] HRnQYL+D`x< 0ڟ~ x5 _BԱ3qQɪ.mnTj.E_jsW"hi[0;.n0V+Vq#iUXm͆5Gre߲"!\BgV$ߴTn#K~!u=)em􁙴TNK)B(%l~Zw0u9UDsi! T:]q5F57_B[!!%q 2FԜ&ۧ1E &.}:ڸ c7σ*<ٟͯ8|%}S 1}3&S6Tc*U਌bqy|GO8qj$Ugu6& K@)ۮKJ7cCZ}ұPAnT]rcjuCFFٽ1أSU025=V]D>NwF)l^1DWh?/u8xډp'֋`-G1 J>O8&"'#Cп厛2.ۏj|O/|]m~{QS?tiŰT}sQ(Z/W j@j KKujz,ӽ@AP__dwȆ1=r@ٲ+qm Ԑo(tMDa`<|ŢV)K׺:DblCC-t5J?@԰>#l=hD{NG %@g, RRݣ];bQefs#F#"Q#Ao[B+4@(0XQHiJQdiH67Η.az)b0W;Rw|@Y[ăn/)S,EKP9 9,E-MB :ёƠsу &[ ҁ^DݾV-& ZQ)`}P9#JB,UVGpݧCV=wqdSD/cMzyƯ7a1SТvab Kf9$ߗjz(#%-W b4T:m( PV!r_VEl1cRW=bMT^n^ JiXt#@)vCIpXZ(;h/gx7yq(T fpGcE]F )w@//꫕C ;Zk22ne !nbdu92Q Ƞ$R V:ejGADF d-BS/͊lLxTݩOO"3W1¤2R/ԑu$}'EBQc܋jFn4T- N2 >(5ɦh [ ؉ƬBCO s;\Jt(>kNn$& mLuhp7hݠxEd_1uUDRP"vd"4x.: `L'XiUiV`%VjǑփqvnNa6Q7p*JcޘL1 sK5jknD~^3Sϡ6ebHD.X"tΕљ02mKnC$"W_=pt`U#5E]n9!/ w-w\d9= #)Nɯ4mȉ`UOB44VzMOҕl|Q2?Aܩ^$__g&VUOУA_ϓ,7 QrHyZnhZQ aepBoȯ S 8(y>=پCٯV1i=prBhPzQ w8/ȲYW%5t Aӌ))jM5c$э R /"eu{pܯOʱAlokn??vM,kn0NX"iBqƉA3'qA7o5Yf.V|4:a% lđGQ}DkWy;ُ(=IdTMKR=3MDŶ77* cB㣥v8sJӐ*(9%` kW a$F ;QvwtjVe=RV}֏44uDx꼇'_T>Z?rZmEnE3 %Q iiOCP3K2WACpkw֡ImrXsvZ.rDc0  %vdž}%ҋ",(!'q sg"*Tn$hGȝWЮ@=kR>`^;F͎<ťKGhIջqCW0[>WA៑z}N3GJ74O4.H&.tpFh(Jc.GAP?+uig OűfUNy'JsuCP! GZڃ7ު~Pk''Mip j3p\Mcljbu "ҷɟ YS>EOXZgP3ʉ/;[j6k!1/''!sJgP81m$,>X}'g&A59$`C:%m4dh@. yeۤisk ы*iYa_(,iB$Ű.KUNlq>^L@7\LWhoQ_O=8$l8 jK_U $;LLL-DڤRa&0Db|(A@r@9.ڍƢXJQ&"#5ldYH= ZY^#K/Qgi7}ht&Q5v)pUkLA=JX+︤ PĒnƫ4a^@T@_()ݯ6 _XJ) Ԧ"P7AR}U.IĈx$ַX3{ܰhwLE[$I&A>V-gB*=R1k˨Ч<Gc{ )౟|B *W Ut\|ހ %#{a=9v۳.RǶ r(!nK`ǻl0em豫526nYe ~7B'Zhf(^Iw<ɒ ( 5}.M.KG`gmvmcq!:9TG:`u]^j,kF6U"1wZ ̢ ./q"| .2:0Z;; ^S))@~M $ 9@F P]B @f6!&nSS p ږ?͇ Q}g@4^R0w%q)ؿ:--/ P!㎷LPMUuO0s^=@0=tzyؚF}ʆJGXQBrJր4--@7Sl'+ɉmF;_Zv~h ǀVz%a8VZb@|~;0pu) IL.>Ci 43JST ֚3&2o '$ q ktژKFH@M)X%2Lj6i"D+L X2$hH"pDj>7bݤXo ;Dc .%a AAP)`N~"QL5ۡF ERf>U/lH  ?z}8=D4m>xWᯰF('eiaaw - Urնc\S+arğ.ZG S0e cN( pCf h0ϖd5Su~ՠ15gHQjԒ]!GI"K;[zNO`&@MC@kT S> 4{?nzTh=K8\:D)JN@ԚM#(̀!:`"àݒ0l`k? vDު;$;. 2a%- N%F\LNPz3-j %I^adZ7R % * ]92 }6U4FK̻v09//ZF%Î;`T&Ie%SPBAT_pm+t pʁ(1h O94w R0N"0ө2mo2j 63\x (mRsnqB >~X(`*HDr}W 6 DQ^$ju#r;5'& 4P+i"!pc!9gjk %D̄rH;@\+l6$/ `rn+&v(4t0"-5|hVd2{1>mcpڪ;1QrvA3){lsxJ@ 3>xw2b]Ւhh#ſ]_M ''ٜAV3y$Q9#~9@rf_?ˈL2\"t ߶m-ck;hJ0ZȠg 餂j}J'm\!joiG::+U ~8ezgR܃6%>O >b A X&H\=倐iHp-h=Q i| lï@.%!*|JNNtv] *jTv燫Þ/t0_ఫ|||VJl$C]g:OD {KE)P$;V Hj uڐJ`z-6aR1bn$o WU;IC~}OLʯ5x5A$xv UrjۈGp D*MtxB_{p|j?xnX7x!n5~ yt zV1MhLoz] g jȕ; ޫ|.%XO2z4/⍛-` ܩOrhI.h}B S-޿*c:jPC vewIO7U[)~}AY%(ʧW`ԔAzJ'!uP.9C7@~JwSӁyЋ9 k70~zy6y-պ +ϐ+N>Y c }jl'ւ1/!ʼ"`4ZDSR/4FN aWw$ }l/֭ޫRbB*!3`^`Q{8Peo C55c(!04Q?G"8GW,#wa;S. Xb '_1ƽ]ߒ"80$0 >"0C<_ wrQyĴs-8>q Cb^tn[m1|^ǜ+M6\O[ 0|{܃n je}_ {l(dmKVU딛0ESY7Ceѓ\m ):2th*ba_H~vxCxWLpJDrɵ-N#S7lqK$:ta9aMN3*'{dTK`~O8L"#P(ggEaAsCy FV\L\NVuG.,n-2طPq"T̠W {c|K% @~?ۨjIwx%;?kN܎HXd`Xhy/za #wA Wb"2#䵲jTؾ5zB@u腕Ls2"};E[JtnSNCo Doki]6iӶW#i.Ҵ*&**^'\0E0wk(]#kP͆ARPE*)QzՉĆ([e0ڙ:qjglҦ|8)nhOU (3<XKGE\.'6Qq!/pQ=\)ph!G%[wЏRJ 떲 *D;B¼Ο]ațH@Ь'-OKg}ݥvK{mg FmPzư .ثT*J Wg7MO%:B `!+$2G;DfH\9nAnOnrS@rw^@$6M}vlr1ր;&^,da)(uFy`ѲJml@Dۧ-b| o=D;E ph!087 [BXwрzr2uAm8**bHkQM<׆Ԑ3^fTHyDWsA5 9ؖZ`>S/KW_ e!ZU D-< S 0<* *alƒ p:gW6ukT#XMyŨ"dxnXeKyo)= ۴BGHb1yNp5>9uC&rꬲ[t_i) A ANyL02H|DԨR+W֜l<dtv9$|V 3:пl> I۔bDْPV9 8Dtudy<͠j }rÅ|+ݠG0 P [*J`I ^[ܤ{]rڲV-_<\K7Dވ'yYf>j CEcq{4:dVN~ܓN+' ?r.zZ%Y$Aߩmۋ#ywFDʝ(gZ])n gMu^t1 Q^ܮk)3FG'+(|P$s^kw5r :^K?[e?##_?菵E_z W՛jU$ݐA܊QLӒPHR&U=\ap!7j'̧ߘՁ6EZ>rW ~֩4|PW*`&CIg.Ԩ(ݏ@G>jOwuSYG0Fn^$G 'Ԃ)9Qzۡ aυ/u>l J@$tyհby?՜ SN g~W *Bs ϥs(Qߏ\_?Yc7lӥeaX>npwis)_0}&[=.]ͯ4; CnȰ>[kO\#dy6f$:P>G#d#aZFFq78ȇ y(un4IvA:Ritcن˲Kʡ1p"h{ 1f^8Ip^b(mԀ Shli R0[/惲wQhRcf8bbئ"𒍲.(B/lQs8&@ _/!2e{ 6c"l\o [3o:I/+m7;`Bk9lWyq΢IF;H sfi3W ymW{ZY Xj둠EoO}_6[`וhEР(` INmeV;2!>t%-+~7l\v FwuA@IF6\%bi k|`۲MX;yq@ :-a <Sdd3_ 6PQHI[\\`k*K y vݯ@7 a @補5!iGT>'*@X B?U׍6=QBmVr yIR+N7f W7daR 0(7@6k)$DE%ȽY3I5A2wEuDlS\jFmY|Ti܃PP6i$*L=%=Rg+y:^y^X6^}%nk~}lEեX4仃 M*߼[KݝP"0?1JĔB;9bB7pE=H)>SsԹo R Jfw-F>lԿ9›y I?,A3BxU~A*aG`LAM ծXN1g(\hhlr]dW%o%z >>93%׾L D~@2پ4ROz-F9t 跦rh$[tѧ<{!Fܪ{<3$?/CdlOVZ}/odjDnm'L%*Ru5 zH>6Ť:&/uםR en(1K<[a.$a]VbNM 6OU$`'^d>#w=B838Y: YFaGa0 /7=!KUפԋOVa/?pg(~6~k6y\cpv+wυȡPV߿{`WToJ8j` ~xKH |da56}IiG_y>E}CJ`,[ߏ4rJ)H hn:skTo,]*$ArgͩQkoG9?rgj=s*g;GM#㨎r#Oy (C; y?mvy"u|܉6v>H&oȃ?hǑLD'"!`>C'gQblk?zMk4ʧL}!tz?d>j ^c(+ h+iowmX /+`p e?k@ٜ6ɬ V Q>?nW 3 ^K tЬ r둵1矪c8ՆO<!n7ΑU/#Q)X *]Zkf`Ƽ^[=@s^®v 4q`i M}鏂a%%V[TAŇ?owۀLԽ?zi.Kcsް /pQ%[Y70])>yFQ@(n3] ]ʱ%G{ˁiEz` ۵ УϴGM 8 ^eEGS8Rd:g$͜CP>g`ܑ^ZG4cz:"@*q*cU8cK%U#YpVݡjUFd>]OnT23-J{`O˪MG` U| O.RdJ]}K0#&*VY i  D .dk= fQR31Ƨ F,X( 0l^u(g/`t?v1X"0sWe"^R.k3^ / >>k, <7; BSG,~ *McH+%Ltj;yȟVX /_¾JLE 㒘Am7"XG쐶L$^ wݥ~䬂V ,kA\ 6*`5g7oo >}+%[xܾ0yEMe=@7\{K12@4WO ~v~Vzߦ^3 Z-3-AK`X%^P`J." [>+Y5pJ2wnR.ԓ]5QO7gؗM\콣v(+S0P"ϑ{ J$(}`yP<qs3_kt"v"$0Ea |WuKCTĊWTZnկS=2dIʟP*65B/%xZݏ˝×C*Ai[+zg|# r֜ Mb [kEcEi3S|5i4sO3pF nXNȗDg .^bYw[S#|%8CO4#*h6 o0tU]~J]-NX_bqjg ) S劽7k &͌5w,BnQ+P(W`W! 04 ӝy8[+Gݺ?]}[0OрJUä4oM A@)5w5h Jlu9ewC 0rzG@T<-uta|UA986`S<4" BW<}M,wB&\~ͱ8%@, 1#T:ŷ H~ 3_/ax <ڟ1i[DndP:m4UWJh6 ťGR|)(0* ځcpe(DT+"[; 31͕;=zضG  lO >q1Ӥ?^Li :%e[mizv;G9 ROnAߺ-mQsx0VZSe,3 ^ğ(~B]qV ⧖ {WpV5 }.E$ QS].i=C7I,vpqsZz̙oh˱RƻZiN@cW7+%|tbVwKkH9c&fN O]'E ̝J,_`@9FBW~%!$7RNz+twͧ 5%%ΡOsJ5?PX cᰔT: 魗驡qk>iu~ 6mM` J^&Lx"|͡6A/=#P˸Ӱ*!G)߳C(n5%G,~%pN+)Jw=zΑ.~zQA]r#G,{cCaҷEPch,^{sϒtmC@<N_]KQ6ge◩p}-h;_ h wbYD Si(]Q0R+8S:\Ըld/џLj0T6.iiʌY\%2niTl?r5w0d$K1I Bpye, DM@|ZQ?x@ v e\zBhYmZscPw̐DآT6at&j+'g0{J _빅ț?SNZw83I8:ibdG n^;* }8CQ4Zw>zG?JnOs^pԯZlOs|gf$_TaptPϰ%6[ l@Œsު9shl7 }mFۣtՉ )ܢWCBCWnY韑黑x7z4w:{r c.u l3y"^4^ITVuX$eeܞ;bۗ@6D#YJ@jA&w?~هWGINq \F-}k$ N2.>I* ͤ (e؈+9YdWV8,D @t- d!@uc-ܡjWV}י6=:GP!rK\Q!|LCS$D&@GFHSFV, >4p(svۡ}Wb֏4,Jj3-WFr.4t!ܝ ;I2h$n}"pBiɱCf3s0yp!9DW7{o-dG"_qojUl8Rs_S}{05"X$S\x7pWrҋd W(LS7%sפ C#%B("6FjZȺTqWAG dMzt]>iCF@rSK\XIv`CEht1@ 5 yA$1:#>Wm>z%>yCp͍ ګ8( A!@ Q$sYq /rE{ufKYp8FL m"S_FYQ#WX8cKt*&!4B !f(W4D1"eGZ+]7rGGQh:y_5i ~g4 ! ̂ Y ݣy&KVIzE)qSff6Cqu̝uiLڟ(#g+G.('^EOfTDlmzkF^KEl#EyfDq'&w r(J=Iӽ*wЎ y;L-J>9 4 tdK'B+6$ &OVxW(9#4b)ǚL3d}1w B wwXЧv'K\D@q:< 6 Ţ= $XkV7._ЇJڊK/얜o,5o*}r[n@~7ԡ~#Gr1TjH>{$uq5w݉NgGT>I(䴣_$AA![GAx}GM>zGNG .ɨ؉|T:WӃTЏHBѷ>< _.<xjڝ|D~Tc9/O~/4?}U^C:(m?^Dݖ\صGɓA.d|-iԅuM <'. ]*x+p~*.u7-dS倂!ȟFZ#Hf@>DCHXtSWC%$x݀pW $c^Mm_ r3w )x# t+B]HrRoYjJ-f=GfJ.uzl@ dt(bp࠯=_ֱd?{_ҨQ<|X #?D~5f `j$n苺_gh3㢯̬G\hwsٞ¦Ȍ}4s*_v8XsJ* ?E vi[̣Jeb FT;NiwMW'OvP'}2 {^?1#I}{ڈ8@Ofu*+`u`j1S$p@xS\Hx'eHB<WJ6#BSgg?HNP;tn:W R(4$V2`LQ3\AbspꝽ96T0_>>+Z䚝f =X&HibD^&EyfٛTtuk[z/-w65T0DROLc/~Ly* G)U|w}B$9C#PsuZJS9j-JM2y0×~pCߞŷg!ʝc4 C0W3ű9]m|_|1CjX0[x#a*wԙuv@/E{$Ke Y_Z6v{ء^bU 6`F㰥dbձjrءƒ+D:YLPuZRKvKD*['EaAQ=;mS=$ Wg6݁}A:AWF _:e|x E髐KG~%YG=20; RA .hÚ'=iQ\)ek=X__"js9y Efꗵh f;pl#L?UCw/unQ)T?t<7!A.t@O\:U,x,\Rg?S8D44JdF_A||!T-@B,[~'3'R l2ewkӡ`)6ĕK@p),8 Cw'_ `Iӎ(Q9q]6Cd@6P14xcQ."61YI,?{ZB<״Dw` Wϋ?ەHBd2'LK Mx¯.mkYl T6RžTpwwPrswM25-3G A:,APF<ҙԴs[Ľvđx\;"e2!mXsM6c[m2ʭ[ZE.fil,oG y=GS?,/ &o Yaz)7EڦGIgn(nA/z|_?NtBFRxIDE}GE/;m*{=|15؟^JC>#V(UMsZMĠxch49ȱPÐ5 g+K^胝e`{4W8jgUO``{S({P;47)m k.dv7JEn^k\p$+٥#q}y$zu0p# <rƜ+LnȤᾷ\"ЛZ?#FN /k jvERdGa沲'iզDӂ"_5l;s&T s4 f8A d өK&7 WqqxE|dY*tL_틯,G>s8>n(%KbN%g)k]zԅ){<އ@ei4`掔{KР;G0- aS|}5 @`zYTM}q1bJmE=w??2cwfqKA*!39=q&pz9ak9-$w Cu?E} q41QEoc"k1Z+[.E ǘT "͕le<C(+$XUz9@N{2#0H(~<_ u0揞 D-(TW_ >Vp~|ޑtg-`Zua31* A\2wf4qnƟIBc!ϔQ_K};WSU<@!Al@W0b (tVQ畀NY0 AgB%F~`NhzN"b3eZm ݣp'Ŕ ZPb pzt^7o%m3j~Q.;׵u=/k/|i)~i&RsQ7Q-&w;G}2eG1 $\Q!mlqT%37<`I6?ڮUQ9.9vWl`@=Q%)/`rQ]ᣘРx!&|R*5 dx}c@dă$Ѐ?`%]kFgdp~ "R笰IwRcwi|pT?WG 5P 8""d_TDgo䇴岤5[Sp(NGN/e[ȈcCjC j7'`ob nSq^/X ڔ $J"V= A2=mϟ2 rZpYk:ct.<=̠xtSu; QW0#9Dqht ͳyu6X/R!{V:2aJW%q}+i!)`pNU⇛ nwr'$,VipVl;=NBTS1߉t]7n`tE`2b{~bv ֋%6ټ-?swU@'W}ڽ%>)+ߒg CWP2?ԛʪb9u|W҅saLy@/R|ˎo1@OPH 5̔1$  6(,,.yaWpAH<],ۘ/_TIc =NY}K T8AWd. \ TwYc)CXf^Nb>֞`!M vwx6σ}VǦ4]۸Ēu:x{L)AHY*\]99@Y'vc&!eY*dĦs,1;~4q =}i,<$N{F&A@`(f6,P{r&9^85t3fC䖜1$(Qz 6y<D*j*25OuPc>/?vd@v{>u`{C19jpߣi{$c qDn(S8tee8~ܞrtɒ?"Vr8ZBD/gwAyTlc(.b%ܖ'1ؙfW:XACG^~ިvS,Q>R1)|b y3H-d @JNh5XɱOҹ 9t -]%#:nc*%9Gs xu`e%EК*_Iзxl2 *ϘuwmDB1ISj,+/ T?ԈGƱgurmΣ*)! n%v2q#˨Ysό*sHPWFCk ֛N{?`vt298(֡ F*N9Rw?@h?rcgf;ϩVm@HXf+ZaV+1NʟT5X9`W1d CVW}80L.IO(  v[+=xW<0gC4`z}+.:RK#>iz]yF F* yWbuelys %3`G Jz\f吥#{ו`D*8a]C&|O^ bq6bbBB-&8A` i~׍hH,ɴ0W(eW`3/0KIfbI'!5+qOe1-\ʐEB3k!vq]l? G Fߛy| / my+INhAh"K<Ú = '5Byh[#+5 .v9 Sv O}#z&pZ㶠S?}DzO^W}<]r )Pϲyfӂߜyp%ba½*ixx v/d)b}b2a3O!TXc@ ֡hCZc*^m>`OݹXú;-Gk#|V"0l[Vz;'Q"[zxyBS*2GY;U{CĂm @⡥ʭ@n-OX# ҄+A$t;0hUh*Pfځ(*+p_aw4'@ॅ6wԊSq3&h%RjsĊ@؛k{&BQVg nb{\b쐸p[`'FhSZijmk_ Uj@d+I `1㽘PlM` rLbO{C}E$ B1pK.<-jɃ7 r.ogpr/bqHg9*{]ЙVoS]OAx @ID"@ KPQϖ̛(a~ @l7fkpY SA" |5"aP]a\D̽}xS'`EF+XQSK-zBa}(* \IϒR3ϊݔ^5~m#y .Geیu_G9U,ۃqcõz!_ޤ4 YCmUEk׈B FJј)yy(") loQ`QJo X 4Zp+[`V`AOSWgƹc4 e33N(1+>! MbHY<@6};w|. &t  Ë/EiNu@-sK澣R8]3s෗\ r,>4@V}tLCRArȾSY/E 39r+4B-r|=TsHH`^`@,PnczwW"vMߋ"ek5^Cyh3Tm}WG[Rg XE . ntwKvm cCN.|m"5ƙO\g_~Fu/P|x[ !#}w>SUf%!NAu׆3eS(ZieC)=GBox n yG~ׄ48Gj-K(<#PdJX8ta (WLkCFbywLp)]mXvC۴^T  !m*SG̍uN#BB~wTXS,ckGCF^ͩlB.9lc1 w!`gyEeuO3<^fkӮ{췦 @xV@-8p06O_+; ^ 0h){vz LS⸉wݶQ\<8ҿs.߫//Y;G.ʊ,&{sox3G@[rDLUV ;ϰ s:"A7&Q|)uI7B1' Cƕ 1n^ 2}0 6Fc^tpK|]wB->rrA܁8oLv.>M&4- M3AdQU n@t 9~SMbY<8{^xj7xf7C=!{7nq9Of`*eӚn-A)-zTWaˁZ|i,N??~%C3.]\o=:e3C@%Sӗ b53 T,|V!!Mֺ| U?RI݀8RrKIZcBA PO91~᭨z]X> m 43uK|Ɔp%Y%6&|yQGPGV>vZ--}ptk:ƽI^=h|FSZ_-%ѬZ\lTf>x`~5$ pQ0@,B :]r0U_ Rm*`>r ӕڄ{i[!:@XGЌ䀎a|HO^V rmԂ֐\!H iU;n\.QQSy+cHb<6׶/`MV!KŁ^v= m PQ9E4e ]_l`+jcGjrH7l?9!mH:FBg*e|=q KP"5Tn)fKOHn:AVA-+ MA%1{$x欮0\?5U豜g*kŃb ƣ̍r4,+CȈ`1@҂:z෬&TkXPaص \ Z/Aa2[et|sZ{|ʠF{gPG萀 f4&$7*7u|_vB N\{²{V%^/pTڕ״;;s!O Ԥj?8џp.P"1qɵuLo_g[+9!`Y? s@gaD y"#Rsdt7HJ6C) .C<|E6^YKM!$>u9rђ/i.sx!@2W+ a;dNy15r/d0E^~(2g 1.˅Y鮬2ɸM$ݭFÇ]:ޘ"IB2H I紐XnsH]C5Hc vfb/1jӠ) A^@ >37>lt_0s4Y^wߞW5,hAd#yWC/e yedbT2u<׼ R TYC㷬gEӅ_cG^')9NҒm"e,4]8TZY}!s|]iLoI7)i A\\X\=N43UrV 22k>{F ,]B)kjExPnתiiP:?#;t {Dfa , PIAE!urJ!"!zkzZH<X;9xm=nf15g~s;?=F<@t0 D+!~M_4lm.gJgO@1x 0)BT-T_@mbvw`8Y%f8["?E󜻒jP[~1#L!?D2v aLfn3 8AU!$w y GWo90 RW{ԔU|^`(~Dlb9+(6s/SO#1^ř| _Hj"C+<|_ƐWof($Q)Iő&#JHݠ9}#S ͬu9W٤y7Zߖ]yn~9w>9%Kw'PwykNNp0g#z:$qV[d^S<@;s͋׆ж{}Mr”grxΟF:vX:f&)s JG9Ɠq(:X꒐ }|6}` k$/QyǴ+d]nx/M+ɒ7 }6dvA9w3٩cWF =+)/:xQd$gw3O:Ԟmw.LR2t)JpDx&b' dI(ds©T}jj^V55A#e AnUNA&J:BN/?5^#؀5 yBт86BU=W~(A4.)08 aLfnk(v 6hǙY`St-f Э<'kDkS_K3њEh7ZE\1Ee( O7z$ ;#Dpi[?"J_ Qt=eQԏ;}__:TP2?sSE _sN} B15t6 -fYvC5]s6#!g7PpU19{pn.)._R%fg \kW1ǐ_4h͋m R z-uɀb 41=g΋ v?su3n8 EumVEQx@a =‥Ty0Y Ġ [nvÐq˵)5c'`IΡS.[b+-YqPT$Q:s*vPC#g)Tg]O"FHޏ?/RY[f嶆W=H{ݢF!\i`aD4\#-`NAbĔ&%髩yHLrʭ^=61BbKxޏ^7_{lo>,d=i" '{&%2gBOr%#} b³JTH[yjAh#4~H9t&4yAm=/Sv'@T^>%Rqp%s@z=9cf:iypKnyT4(J\Mv`wa%nTtӅRza9Sa05w0'kf{F@s^Q6w֑Vh9ެ4/8(HkoN~>ծn?AFwEWTz [.rHk1YM+0`i+ 6dxsWi ,c ]#5&]iM[RJ0Cܥ%iw~J3|O-UQmqLbA7sNdz?us[ szD%AIEβ;*TRd-YK~GtG4G1]Q IS7t2+w ٧[S,]Nf'BM' vjJ@ r v/=.OهQNAy8XASLg J*-ЎN0tV u{לчT8{%%H--K[#Vһdw%io&S?*V`ݞofE`#o.]k?]Mi"5"8E"Ov007~{5h4@ j!K#u$xdTrᤃfm&tCo8o;EAd*{?T"䦯7%D.[O Mu$xwX #{9nԛ|o4\J$:t;oi-?`-D=ǎi< NRN&75 -ծEe#P!;QHDD'\Gq~rc N$W-M^xn[(ʒO$WܾsW2]M(ֺn.'{yCA=a&agjin.Uns֣B6nDLJ X+uׄ~Da%iG8La^s XԔM:V ;L&e Ҽ܇TW% wɊ=ÿi?:܁|0C6Ä}U?&I)E[:!gI5%7WxRk5f铂!|aPKBߛ<8%R nu@ߤ&BRmfn Bs*@= M#`@|eOy'}X" ,/iG`2=f]9 u4~=8C:gd˾,| ٦ZVo~{[Z/i7+@l_*!!ie%gxʂ8ݺ/ZU"V$N}v켢)´Mفs TAlm|^C 6}ځY{xHd!#$oКcu; ~Z_ezqY]8'c𺲾Сbm[rDuAX-=}Be.dm2KX.up?ȟ In=Ũ-ޱ/ѡRu&Km?(gKtA{ +O ][)2է*go-x:8sm.f 2pIKdZwT8*E?*E@?8*졦^S{)d;??]бfcϧu[{-˴7trQ=;(uf{=%Tѻ:T) E%Q,!u$y*{@^N!0bw'`9T'$ųٵH}t_4`Exٷ̃d8oY rzRb /-vKgc OKaؖK; O^#T-SG^τjmUtwzW)%(b9WgdvK=ԪSxֱL@:|R$H)VhȒ瞡%Ŝ5 jiֻh^@A:3l@6^[lOܢMMO Z$| x Zf=ҳvsWQ9Kk}H,W ~zP^܊P_Wx[cm@=|]Uao)}J|R)0`-&lf[y3h>ݛTtgˌ39j+ԈEw{C,i?3=Y}Rmp-<1x;aQ#2 &^^SY |)GI+ݫn=e[Z otǽYI ,mr hʩ ?F~BNݭT/B zt߿>@L[WvPmh] g1׵9O^>ۗ$ Phspg iJ;➧!bA'ݷˊ3 @S*^Ͼ=f/hƷ(R,WܛC7}6Y~]̸~&͖| 18BlZDP{eԮ]ݸY-r! ,{ z+g6ܫ|l7FvhK5cmv.[3dmWQٵڃ!E W(O4gÄ[vH;M~!o94 4Rǎ 1]tcTWAWf=覵Ԡ6k7{)}!w7]ES3E~ZD` 3ouh -o蒋O#mw\ >7|Yyʪ[BB:fDExR^YgaJ᳈\zj,JaL%n[\qn:68#t_Bu(YX Jc,|ѓs,?Lg(x=#3n(Ɏl\ZhIHKV:T~@].`l/F}"ꏍL,'cyYawE_ǃ'ܘ D۪7%W.Rc,xY7Fm<+r4P=_AqN?v:$Zgo*a#h2QU~|\ݩ]=PRglDx٣<4T&[B4;rP$W7~1׃yNv3;zZƘqZi2;k5$e+n9MT&Nb3}W;7+VVg3u+:F&{7z<(y4B9IP]*x*AⷸJ'I`[3:< -/x,s*5Ĕu%/#x:1sIHJ35:Ђ)z=zP|#<7b69 \B>淎)]G,FciNi7TtihZk:DXsOqzD ]yGA$suԗ\O{n^M̼~zbSq`a ñ TQu..tfw 0fx ,"rcyLn3aDO*;|qջ0Uq-JcA!㾲M" ufIp;:%GNubZ#3hAd@czY ~] Axjʛ2/kjGh0 V$(~D-~1P]Z aOe^]@7(G 9kU RgȤ^<11s̺ڿzفaZ(jsVX:6 `!HU+o=@ | ֋mII3g|Gu@N#ե."9zj.JO$&uZ?- mܐ r MȮ my!}=,=HP58^P,ZŠ)?R3c&Jȣ^c8^h֑k)@Zť]@dk~BfBFJw̡:0`!8.DؚǑ< Щ4΍|2R-+U+gz{N zl=ұ\%rF=6çdkA`5.mӕ5Qf9Ք"Ijk_?nh(+j iD'l;mⱁM86ZS$ jfψv R^Z*7"TGp_;dJҎ Bstp@siM 0!AےTeĂ J ~a: 1b}ϭZ]4-3L#bÿ2yeޡtwBϼnFG$X5h%t;pTy B[6XJl+ 4>@~|-;1 _ԁ2c[h).ffX3:P7DPyn9JdZr=QEo$sSg٨u܉׉>[t`1!`5 Jo8枬zkC~ b-U[:^\]9TC9r(W2ѡþ3KFk_W+z9۔$r؏kl9/&̤Ep90{8y`%J  CQpNU)[PCwN<<7O) =սViֻP Q4*v_ v|J> O#YmLI@#jBnq~2㨻 =lU@ ļgkY^.$Cy/dW2?p㻍C.,tǓeBÀ2vWEmyz kr+VXڸSDXB.F-2@d m`{mEyc"BNY@zVCFZ uDTF 5?rF s2^A}ٗFp!j#\3a=˄\~Yn-Y4A|UζOm=Xa .v9,< {go[/ęO7mp8!QVzX|lr'u`Bx[Kp.jj"-&~D8s-Za}l˿"LqLP9Kwe#"S!vYp?y3\: 'hIW/2<eE/Z|Ǘ7(9!DWZk2ItmE>wӠ([(򫋵#zW-UF挓a铹;DʝW$5BS \Xokܫ-U{ۖmtJB(?F> sXPTEdR-}}P_ԁkCjnVwZzcS%`۪(ͅvG4M'A (".CoVTژg۪/t?dQ[o8]UkD{#iNT\,؋8=Ѧ, vg`nE0,rXo%fRXnQw];U} gaAMp8'M&\owz.'z }nP(\a5)vJUsp40UWU\${?6uԉ"qkm3J=ÇLq S'ȐMrM@NZ+r"ĄY Wf;?9:;.cI([}\^`>x9'6Dn΍Hdm **s=z\s*wA5PJ2*8vdB/AZQv4I[=xK=WlBabH@!jk:B`KZeqeG YrksYP5[8ս#7#vn8ggI`1U\%MBo{lL ?3L*)k^}HxqRlpedr4l4q <4,>\Ph`4=>|AX-рV*jQ_{U|%^ә۵/K8 tzkـ|d,3W3A}_oGǭemSہ,״^+/տQ4vv6U&~[ U+[BTJ6jLlO^IfiHPw'Bց^=ug6kъڔ8rlw+:K%n¨tS R'a,H:JjCrj;´ʉ3'p7E4j6v8RtL`iɥfMs9=|B F13nɈٽI>ȶ[SU*7%0ʣ&o;k[^$z{ ]IL]?&v.o?IQozd/r:$kMdZˏմC _Y.\3>M>%䧿ll_4C՚݀J:gʂDъH>]=TY0H,ٴRk}mXZp.9LoeZOV];HTg? orOf;HL-+~N笾<&)\,glJlmYp| &9?wm\{D@eo"UCB}bT'0BP;UB? @Y#0`Ă=AW.I^# !#`JM210tM(FK;SsB`eOY6!4S0hEK=DY@7GaW4IWtblCnG6#K&QϭDA3#)aJ>}FSc.83{E7|\p+ΥO?:@31>"@0Gޟu=seT ~ϱO_]887nho!)!c,X'[XbሴkJDՉykPNĨ[[ !0౦T֎v!]!TL 9RhCJ-a^RKWs_rD+"9V;ݸGH9Ӭ~l/{6=b~x2eBc%,4+Y6muҋFO"YG$ۊn/ivԫq5 >u! Ǧ`^Q>j ,}Ya覱9xC NU3@, rsi_QzʹACw6R!A Q)LD~Xʹ-&\0]/&$(&& Yxh=(?{+ud([=کtc"%49/jXk=XʨM]u*,a m,ow;Nљ^k!wv S\0Fpm{w;wC<@@ΓƄt:ɹLc(賣(Jd.;e,"oBݱ Tݝ86Y; ڊ 8A}oY7m)̿sV%3'X?pI@B#J!h[ Ze.!j q,d ~`L.lKL<p쁥Dfqn=o6wItK Ncj9ͱnr.OsXJ cɘIE5.j' & %y '`S(K_QpHR,VKS#Dq7!Lt2ɿ,"gQd\5Pc*2Cܨux+~wBo_Xް ]Ti^Ս_g'J'{.B1k(Q)=(:7wN3w7LyO u7ޅRnS}o ?FA]ux?7TYRiP~pгiHZ $7 v7Derj=VQʽCf[8+I_ A6ǫGQķR\/'şX O4PSƎY3wԞW&JfE`CkR~2ecOd-`}p{feSpT?-Egpv ]M#ɯ.ybԂ]-Tf4f6G}0eu}] =;x`&J2UO~omBdT# c*A|'/FYl_Y`tٙAQ\uREOJ!Dylq ކG"c͒mL|(UamO ` CR(wrp`~C+4Qo?Y0Dwq^q|CH Ùc9DͿErHÀ3222 NSxL N;'t1Mi+԰3V{3`D'ī+^pvȜ RuA7nopf7]{LPt۸HM5#լn*w3c?JF˙>2oPmTrZx)\Rx6#l#@ LMј^=\5{n6}QL-wE/}a]oџE/L _BL$E_0DpѴcf]57yQBȾЗ`,%#J$<(7p9y wKE>iik(q=ٸqڇea} FX`x=!/˽yAx9YM>)fwo(s%+kLnNŒ6oF 3AXñnI[+9]i8p`^9Iӎ ~8&y =Չ5t @#s0S f=NO'6S:" mGG&&`ɄG&Wm_#U#? ^8M"KنLH?MtZȾ .M[W.ld$ 7+05f- '.Y,w"o2ґ੓;=+OQE=sp@A- @!  ~b,y`bkfCQ. 9^jC(c kBhyί:~ 8t+~T x~hFy{On |BQ)ƽIve1=N'ҝo>=MeRXTw2m ))['k ¿ $WdpBZ[/ ME@sI*Πԉ`{hT^RHyNe MV>YMӗUWe\!B@|agQn O.ÓDxDSQj$[RFT ` ̯+xiSQPB*t5 [F.Xw8 0}Iϕpp56pYG`*Fk ӸPz JÁ2st |dL{QI_0{)W'~as_XTj:c7_j耽"T"<&`<.jזz 8@ᬭ-Foj(~%7#u#f7woz''grmu3 U><hHaXV\K,vA>7w_҃@Ԇ7N$ DLd 1@d[WbN&a3D@3,}֏Bg} 1ຌD/sW*$`Ш"%pqғV%0וeϺ@薫a1GDn|])yUc3_=pLA5F>J }BtmL`lIk3 q䀱Ra%jԓW|j JF1օjЗ S9zJ i}Ϡt@'y5M4}u$*6Hy|_1@-pnxQp5~8wei| Na< Ē99'yBs \`,@ʅ 4JݰI9ͪ`2EQ%R, zL 'ySac<}QA5葛!H +n]b>50W]ilR%^ #L.ғK󔰒]&"x FY \ MbhϥY(yĒ@$786>П٠RN-I&PcmYƪ9u`zAS?G$sgDzGZƵ (5>GS#Jt3/QfcF$﵂?@@jqV A6x -@̗z W㜎_Lx8% E|*cY 71j{u> +3l1OfMaH$۞Qs9h fݞ(~4e0A3W~.5WǤ( yh9 j<]9#wvl_ncE0۰/w\Sk2Mu1޸Cjja%F>c:oTp̧ b Y:Z5<q"c_ P7;r&+dn|DCk q5k5ʪHU5. nt('Dʜ5d#˨K 0әxX]y+I+iE\X%rHKH \H٣\{]hDa=b8(XsB,9]FaySŹ'E#2=ڤHp0*R RjMc>CA`/&&n E3eIQrrOgcLp(I]2L[ːAČ "0xe\>FF)R 5i"x8Bo弄AoY|k{?ϬT~E` rSZAp}+wӬ'p,pȱ"՘qQUF<^K\ oopȆ D ۜ;`љTϷZf& O>x๼@%{e%TD_5*'DFL}Rkx ? ęj[+Ph2TC`s;0*& - PwQ;aqS`8 ITȅ,_niunLL@=O!Q^h`~mI oU(vһ#PK $Pa-)3<*NOy4OFЃo%.c3 |G?n'?5=t|2/m%J'v+W(a]hq 0((3t@3H<R\^pFp".z\ioz~Ž/- 4]{!IJ.G5H%mR%b"44v^>'DV'3 ׸2T Db8u ٴ26pV2g'om } k;SI++eO5N!<,drLcF\uJ+ьJp8-eKj!$X & CK9vg^(*b6 'ad]../,u ar&P-y4!"< >y h3T$>dc7(9"%:BdՓGקy-R}P8\%uXg0[$H%!?DV\=zVeWÛ EĀB; `hoLs*<qu_QlߎVDA)|:+F;`@AY+B5bv$ІwY>38;64|@*sw;0ԅiL rI@ fՌ8⥷'uW`g(.gBe K|6%x9R O]5k8H`[oSQt SuߡdrL&4k:!p,6 xD2L ԦUTЄ~|{[Wa7Wy*.﹇9~3R<ʤ3'|1 w@ZSya^;2w`ؾPX GF!*fC)6:VԦIrbF9SPӺȖo:/|+ PQ!S172]鞪<`T ۭO &!QX0]D@Z+ɲRhr#A<ҧy4BF B}~#eѴOq7z0 PR\c->6" y#W@>Hl!1hu>|IYڀʏIQ?J֠?Cz4a=Vu>{EQ=+~UonMn 9)3,nc{36u4$ Ĝ'pJ%|evY*8'GyՀ4 0|*ikoҖwIO)5{p;=,d$mH[v}Z@(FB̯tEu’>s 6^m_)=bgD`B'/Z8 9[a!YZ9@NbVy hJQWHqLJ ǟWO+7̾Goc7 qnѾ&u+w 7'ZR͈JZ*TYIoooZ֔kwy_ 6^ts0ŕk,C[Р=OQ!S77'8 p~ yo o6Ju{ w ?\qĄ#Q攦{GTН_k3"I &وa$XF?:ӣW 4fb폹d>@XPr{ ;]QRL xPi:4O-"1-v11DžO#E!)/%4O*|yy;oИαx}bUYHb[w;eD3hS|T~bZM O!9ahl"PGpNޏBЋx# JU)S5'E$JӸߙB(˱wہeIiDI 'k%d8ΜHAYCW4u:`Bw[paP?h)jZdЩ@8D̑(&gp}#k$qw# b񱍜8 e{/#&Fa0׻Ĭ)H8a%O-Qɉ HFQ.U* T_lZ Os/gyX뛹ο&;foOѣ.1dzuSQxgd?]K w8hxT)CҮ>b*"w1% ^>$CH:MT f|t,]ﵞHDs=M@_-(B4\ǻ4u)c{.GQKGM0@dB/ޟwS]<@l Byng7D'8sY!g)+VNi:lײ[n%ޯv59)7i* LF﯀h@S0ZoZrkYӴKPII+jAABW(xb/!r/@\|u9ہ)FZ`G ^ }9`*.;1QB_LhGZ k4iNs(=q! V;;}+63Ul, T$4xGy1:K> x@=o `m4 HQ&; xUd8|s-L_9^ͨ X|>Kr-S3J£jh rG NdͷKP"{D<+9>c?)@q~`N_~c7~T% m|"'.vbUeKwm::s@㨾'كVeJ\0~RV@?r3MqHBsQy-wVKσfɌݙSK<>4ȡ½oھ'+azS Y nS?8QM O*:M&Ĺy? {}p=5)BEA3]ryF 91wJ&rOPv]=ld6\oŕXrӯj`nm H,Ll,^|Ԁ15p8X.;3ӭK !So_FbW6~ @0ha?J>0U^㚔gO\E?pj@r8L.]p#:Sv`iÜOaKtZ!G6/Ml5l` ^=},po'8jWv}{H:LA!l$Z0rMR1&s@z_ƬA7RL#~Q`j='9/׶"cL=D=ϩ zoK 213DP?I)0Es3? CN >ן&5CmW![} ~1s.y)=b/{0 S'Ӑ?ǯ1>IEքk,`}fvVڡ[zcEzb8qF wXR&a(-EƝUԈS~ܵchX8͡3F̺J;?!sNSeN1p PƧ ,?M1c{Wb뙩^ui;>}eN&!NvC#uGwl'wc'6(i@݁ ZTU36Uy B_{2*u4(pq!-@aE\ mb\YSCսM͔?@yS |li,=8eF2ԙ)Vۜ-Xc/1'@'ke@PԴUsu G=gCE1Hr-e q?)Cs!xb"#Tb|I_lW)!m(MHR3S  \2|:یsM(.- Jcsz>t%m= s}9@v&oW%LBDӽN '`8?F5=֦1G}ARd3 Mza,k&-Lp g?epj*`OLeAzeR Y@p`}AP 283p;')v&V&gI ǖ] {kDHсtT`~<ÐA ]ϗ镐*{3z>?H nT29XrDf@g+ yP /\xzU$ 5C3uj>%\ 2`+w5K:ܖ4d OJÜW cBi6 BA f)n 0U]FOGt&ٸQݯgjJ6Q6w^'KtwNl(={@Lx{͠4Z }܍ځG͐I'2pE眅$;<ЎӐTD pj[Ź[wݒӠ+`̬5:_뉙B鈀 i)P rS 'qեٽʕSNn@ceb&t{@z'`@6`0 p&l` cZ{D'ʯk}rf01D>]q3U /CqMN0"H !K>zÒg` <N4)FZCtGݥ1kǦ&qۄ)@ N,ȏv}u)/h"BWF8ps c~s @Jq"| 525&mb*T_A ?pǖ:L̖N P|lWx`LGQ <HE 84 G( 8L Vl-cgNEWE؆wWc~OAVOm6hv&ULcJ!1?V CZZkno;z^Kp0 g.,3" @,䆁0B59?1M=SAT-\\z@.sNGr@4KPlB=woxZ=MrKĬ30՝5B81 ɭ b8__Lv9T{_-ާXuzbc/ˉ]ol[ڧ_y> $V5> @Syb$xL=dbT V,q,8I7cF|B9evФ6Tdq49JH)O4n-+k\6`K OZfI zͰς!A5՝z@$M=`HN'jA\wAiNv|x}׸vrwqzA&>TA ,ẅᐠp)k$@2(?Oꡛr"cqhuh:b l@z{{5_I# s>?9l%0fHV![aAVL2<іxDŗA8|Ԫ4@FL ǘµFC+}DĄha,t% &1:(̟GIǨSdv =ǪN]g'7jq "@If8,əZft-I!cb |-L%ScВScW'{yi>+-E3ᒝL?{dp=k)r(e вI(I7ktɔazmm) l'?p _j5(!r$!Y:u߸^*Ycō{m> L+~ D~dc`\=I042t)W1! )sʪ(S37Qr, !zj~ A z~j;h(~ A͸wH0D *ZLSvnrQA5COz܏D}BVQŕp:/\u`&%353(rL*`Զ Z+C kw{${TT@^; Nr@.ptrI3 Hkڧ(@JSY5C!x_o oTzl]/zx5D@UD6Yz  Fktj˩j$|Hqp+22eCsCIٜun+ JC靠K W(7b _Da܀3 ?C`)JRVǵ cNn`颉 D" FA5?Q|- ⦂I_p0|W}b(dA!fmLBp e`A>T"-ضE qtx!` lrPn,v'ʓtJ}0Y/ *}'X3À7Ϫ Xo4ػн:zLm6ޤ$z ~DXm3 Y)U:U\'?tXa$H!+H.M zO/=!6Ŀ%U&@. vUۯx}or>Ą%#V-= zoWV\IFvzPXA re r7ʉ̤Y:8U57$#Wr%8o;5^bUk\8Lzpĕa"lK2 *o_G;ˤ]G^ɚT X=,l^$yf0d%rLvD4ʰڽH_0625e02QJb$+uLGخ q涁B(WFhDٹ 4`ϰb? du 5ye/&Iw_W0OA(ާg^!f6[?W +vHVd'0>b)a!MϨ'|ӫv$#Uq4]H{ji~gK?5sj#IJ极 AH9{9`*Qno6$#JܟwNv^bnS$yvBy):hҘs}ҧ1zC͝XϺCC("3o)$wQ^@Z^^#D@:Q9\'QdH P^SoS k|nN*VfGՎ*He%r'[]/8qGˢa}%^.(mzJXmzT%P{cDZόZTT=E]C]|޾jv)Y`اEgmI/٩TeGLcIxuU]u$*kAI)$Xnz_bpj߅afD0X^`kTTlKsi 2<yu * t}>0U PB[ |]ڐb}s aq%> ryHvMڨXyq-໮>9es cLϛGj xnowl`0w ;yh 4R@Pb f)ۆ=B,и V`/)mцN4a_˾[kYY &]ȨPbkDfvJL9+p#p?zkE;aDHMKp+t 4q?c#}ޣ_“ٛ 1s*$R p`Y$~X ܀bxfbܛj 3]hy=oE; qvqZa/ه z ]gt$DI:c=䬢\&jNb2t"Ucg.HJ Z3@c|m!/=SH20K)HeV==4uRpgk,D4I#J=]4eds: OQ:L}M'T]v5$)bA.';BDڕ( t\ 팿](6H je s/ hgt Reqsbѡ[0 NX_ҀADWJf1+Fc@XP&V :S|7 Mz)7WKG gZ6  34m٭2yxs &8!N΍`* '1qU~1+%zq))ݏcQF5ir3cdsim4(1&o(= 7n/EWX…;W A7&'_2VDW 2 @bE7%,WTjj|~KrWWĭ(|rըh˿^Hxf+/e߲5T|\L1J Q3=VQ~sCR=K1Yե+RUtZLP,|`V_q%ÇhDZf<N>Iw~>jǁs$cK_RZ88!dO8qbˢK ~(6 PAL;Տs mʍ [L3@?4Sd 8 JʛtFT3bQ#qNnZ:jg}ocf$=)cK ('TpWR*K:%Is`pp{(3XSa/_U<"i .-h8+e(Au1*ZXÉ UpH8IG0Ygwf0 IP83?U }w ?x Tx(\UYjhp,l`煒[u|j`jS&buts `_qhW()7$e@K ppޅ|E"8Ifkn&|3]I@.:0$ A%Is'33ECUYPsXaX2]HN~Ot(/HTW`3IEKbS =XYװLoI!k824!|5+(_$YW~j!#zэoW;bn{Z?"H zdU P_(kɐ&H 05_#Rp(|S*+E"g%c _'Ӻ.W>_] /c؆C82?r蠺 Ԑb__gmtŅ cMWg5/Lm\¦{bBz7 YƪjxA+o)1l ,DߌH beʅ yQՃƔ$ 䠑1Lj\KSC.9jbX->r˖$o$86E9H *goZgTb8:vX+lA}fy>oǕ< [SB"1{HM1զH6K.Y'u6}Wo,z}3zOO_Ӕ/ ?ao}h fhM[O/` pL8a?ﱦ(i,'tVcZ#g=0Tq%j@f8{a]̋! ED AH\# ,H#2ե3q LA{#"9}Jk1z/&0ePPs2JW]P_ er\Pd= hd']:*=VmvHY]=^@vto%L#gmX4t"bZDaЛ||P 4Ի:gƂ˪gxU)3׿%L\T`P{2lISc@ͪ.f^);XI(UI![qL``(({(ivLjT2ۂ=C7vWBwMF{0x.ꠡi.Ws19VQb?yaU#{y!&h:_͕i *nuoAsy?4%{G#i!&"[zd`B;~ ~,jֵcl|ȊhFFm, )A1]%.} r%`xNa)jl%0$yEMC{0NGtB}jx 7[҇oeJ!&IȐK, f;0q^tqn׌NJ>p1Џ@ܱ3}\G^cQ'LT}e)D"d~ *//!9 ^$8mH ZbPCt䲣Rxv H!AW%,پC*(T1Eexɵ df r_frb]k^t"YQzWp}D St^> @Z̊67-2󡇌~"]ZqO|$QDc&ޔ Н\%՜7}Zs`uU,W?PTQj $őx,W֟Cܲ6šbY(Tx/E9!zF!_z'fo3/2Q} '1( 7ĎSɥ#*^[?Dn+*o}D`D oIA\/cWB]!hK_$bAiqk xUe9OdqR ַr/ UL٫ }ѿ%&]wX%p\1;3!Qz]:w>V !rgux~eRx 5b:o8f¬'iiZI#I{MVNĐn1hKdDNP (ʝ^DCK@]`,sl&0NW z?qD7( u?AqYAH͒dX|#lBZP0-~>͈,.N`;s,fC[dw ~fOQc ͒J FD# 9@QGRI=- 01=7;tY. rUTSBM|hbU$V eRr4SXj7ߞ\'\ A Ԝm\u {7{[<6:CK8wlyB$,Ѱj"N@pfEExe o RltoAݚczPM?j;Q1̿yR،ɵWjN,z?05["zBNNzdZv@v3Īw$27FK ܺS9Q_op\ Q#}\}RO_,  b7F#UZ}uX 5PMf;:NHy/dm$g f[{zCMuA+k *ԇ rP/%1!C]o Kt_L~圩+<#kJ=_.M>B 5\_26}At줥 =iq%=ٴ?3Ҹ2ZSs&nTMBk8T0p塓euݽr`K$%$%d%I)#bCW;#@<TRa]{Y0Pܬ;p?T4ŸY{A"fabY$[h7%0*!'x7vdy}?|oC_zH*r,FuMqpѷ+_;qHtE/\7<Lhy |< f'ldqGہ෯b9W5gIlh-?Bt ͏?dҧ qBD } }-Qz Ě{ŧ5[]}& C]'R(zWq!"+Q%6sVp/ nKQc_w. j Y*d jwa\CrL7Y?=e׎_t'wT<+ cM?'AQj>De+P  3`5cYda8FbAć68z8KDm1VܸY Y^`7?+<`[VD&:Kn א8*! 8϶?N ?og\s~B^*GcPN=`} -4긮2o}(Y^Sq [ ZқLquk ;2ʲX + / އd 5xMj]b~\9&yìltBzo!@.cwF% ~,o@3x@{ 3a+ݺՄX9xѓ|߉H1:ܪ6"`PڝSq>Es3dF 9u"R04mh,d)o-NCH30Ѥnr*s;&mQ[)0(dr';B ;[_ lXTG_RYdD,*cf;CܑXD꫐c^(%F*ŊKCQ!>¯kS4&YY#z RikkO"Q6Q5^H=K3G6X!{QO*w ?G}؍?D, wCV>b9w V6B6텮|] OgP GA~}_O>=Mӻ:쐅\%Sc-=dԔ/>>()8ԤpS Kx@:C(b=[oWT)fNmFf ؾ@ncnfE vH Nٝ *~;)@&Mm%_ٜcl; '33?zkDK ~Xip~X(}yH{2$Z(/J5}\NlB 5/'O!79䆔[1Jۮ(ǠKT-$<A^_NI7e݊#_"5nZpromzܓԃ4 /ws&%pH\ 4ߑMuOaOHb KfXOV-y[ݔ`ctEpSo,hf W9Nsc^bn׮#WI! P($S:[?:a1B+5s]@`~;eY.4խE[G>@#G_ɎdҤldS%"w6J͢&j֝ bCKN\F"jA.jw{rTNM>"SY}gFTLӫVAAbA^zC9|\W0$%BCF%dZ]\<}EI;8[':}(Qw lR;b;kMĒ ݁A{ڹ,1M%CfPawD #4Ȥ:6tTz [p^)ƈ %Qrio1XIіDtLaZ+9˾>?8ݷ]V)JAyDxzN|󖺔O%j5zjS&zSMH<]̕$gG5r%cO~Jwgvg(ń JUJ/1͝ l0I1Jkh\->˳Sr)g6 >ly]*Hw1a5=yMjmeg~eCLaQ@tAse?rW+rU /}e{UOqY1/ҷ]2.#qC gnZ#vfqԌq!EBMPqm Quf @9n5'&-5vKkҺpON-~;L:"^aSΊ)yk]3⒃cG.RմpGACRo4W/&5ɚbPðu.x!odJ3U]A+:!A96|6EFL]:-.2f(ʌXKZ v -=O㞈xk}_~̐l{TlIXg3؊r*̠ :f<|KV˘ V@ C*jwLI)h&# O+E٭Mj|!~ a/b/q_MqyxIGbuh}72'=%LJBiUv;Z/=uᲕQNYlEeH-(b{ȃ ^БăV:!fB$J Vg5"fD%ÄΛ4 n̜]GmR00eYڦ8Us)\E&Z5<뮗Whj`+X#c<潱ݬJoW^pe(3=b44?lY0P;KÅ-QEY Ӿu$E0vYw++nݛ`c0&Vzut$i7UDi?|r^͜d?ћ-A(UT/0mzha&QS$bc_>E4UW39Oϖ>YYtC`n/c%`*R>*(U*uį:)]W _;ӦbǴ4]=bC&'{"Rȣ_wL"̔N[K-WLz3@=}lQUYx;kf+z Y>J W86B3ոu|maUl+h:o1e؋ ߾e,a}fו⫁ n2~43Ǩx1Gx.2EyiLu$eo,9 ?TaВl02K…8$ @_Lw@Eݨਈ2X|HӇp0dp`1 R!> (kz#}ysb_|*Qz.auk=o)sT> <,翯;ʋś{G =~*'Dӣs$O?}2}O-!U&&&~/QtKɨ@(a2&#t/_n+ Q]GrJy~88]k^bDlb}#-/f"p3[y>Xv>(g}@G 4p9.Ӿ$,67g*= >y8$tJ, n;8reBt@D ہ''!z+=wcso4Y*hQ+kaLުV.}8v1̆*a-  )&~X] GNL9+sUc?FIA2g"7AR1x9<~?r{q 5f%qGN\# .[ޣڕKc}ۊO],,F%\q%SWOAEN ,LۜG%.J$0d TB@B8F"4kDطZ7`% @%ӿ( oݛt\)9EW 2,7:y-۱L҉Yͨ93YI)1jphzAk?H@$JSJd3WsZđ!7G9w~@ (dto$*K@b/ -s' Ƀa-屑ҭcrZE|z=W@#v|SWUzQ|0px#M7%=V/z:m,rGT&Y/ɖ$[7\f➋(>p|pl ԰w+H  U%i+@݆SM8!`73J&^t7H4 &24 ptI2|;"T+սpg ~mQTN3Q&im[qYfEl~ arIߞ:*4b*!üMdʕ1$1L-\BƼ`"&gZj&@KnTkS&prJP?:T !K 0YYt+JorM %m>|LPin&nvcu5Q&mL&K&߰((ۍNO(೛:I/O sF$˧P>pjDd=F{[lq@0\2Md,!& sSy0Xt~nIzBlv;R<.蕼M!.I`>t@tޔf: ܈V15MnBa&Z1 EP$g\i|Џ`xT NIY!8ȫyh uvp>^m/;%de>P^ó>Wp/ a0H .L~SxB9?LV[hwXpiR~;uq ĩhҧOdIKT 1FbP0 ,\+"\^!b-y+stqe7yXۿGHW?*R ӠQ"lVY\{Ho>4Jz [v/84;Jޛǩܰԁ4Q'"jD@X~F?BSkpyC?d5=Vuz/1H!snXx5wk8'J787m&s,`wqWaxЛ[o0 dm(O09 h  c!X!U)tᚔ nmdj0) ܽ$,&J"x&jbMf/Q܌@Өs¸bq)7N`ݾU[gnuno.{l*N A2'Y(ټi`8x̥!qῤf?zt4Q>qM xJ+T+uG$D1pRN,4+H@vGv=\Ժ0^`Z :#l~EA'@cM'&"擨0tzJUv&N0H\7C- ҇EˋyJky8Jb,gPS>n&pG7"| G8,ͧ@}g]u-pC% B9ң#O_T$X<$}X@D9A6 ΥƒH-ַ%x$xn~?iQ%V2"GһTSR5e8! K"%#&~̯];Da @<Gbr)qm@#?&=S.FI ֋3> 1/pZ2p̟C(P b"`\*`}+!pwnce +۴q* ,*XAYr\0!n)5%OF_WrvZeqF~*~p1|]aK6N!`~~TS,%B qHxK!X+q:"( Fчq(C2ƕ (/F\IGww䈦T nY4u*hO !If"$T]STGO=nBNԯ>CwpT~M Tgyb-=#rtC ַX\sdȡ칕Y*3Qf@uJgDb-Drɓ!bS.$K?ޮiTs4# `QRgNMN!TD.$M@D2 ' &ʈ9 R4E,k6ހ9%5bxQrFĕp{M~wOw~G1nG)+Uk-|}CcbؿKM[("9q`!iJ2pŒkhK0_ $ :/!3 .m֚y >v6f6b! [m"9B(b!I^՗ Na[͈ڢsDқJE%# 3X"~SzMi⥨A&Aw46=U?}=5Ius1IPx^!?xHs(~c}<["ƅ戠PW:!W,$H,=J`I)["wXT8zd.LR< %F9e㔗W5f0KXt 1@ #N5p=Bht ̚i^)^M(J˯L^_Ӻ#ŚG/B'AP}p5Fec2H.fFpyA4BuFEJ3Dq}KήӹW eb[O4ImGgj'Kq3e]շjβ09ݫ:&a~ğrڠ My ܼGagxfAmFu%}5VqSk3G"1N0Bc'*֊^[,w`s&B@ *Zc@$R⺏IT;U5y 0C2AbrN0ff5TG_r  ؋cœv΂E`C1r"ƭ 5[8=al ~Rz'~JB@}(`P_$aOGT}}1֪k`Ӿ4<"w]v9;k}DpN:g Rw}Ls'Ip$?{`8 Xgl>IHIH`Dfâj$9t"Զ̂P@dȟdKy*(絓Pi'çZbT o@Z-z)[3'+G1c0.Djlh_ 0yn$sp; dT5I;0! ,uicOD"B̔v4K9xJ%4=Қ4Jp 9~U흮~5‚qn V]hLn~MR!g}#&n|6]yi-ySٍ9ƮK"&D{\ےdΖk.^wEe`qӚ\w0ݐ3Ni`#Nvc*3y\ V8.ˣLZn@7919k"i_-;D&X YَCݚU4۠nQ8:v r]V eC&SC5iwJ5 c:z^Ap\`8z~g6LM=75D *$)}]hV s20%>.K b}sl*߰#(̎,v#5}#V9Sc@EItD7iDP {blw;C5޴ya0eTwI(y%b>^4@w}˦U~`1Cd͖Fdaؾ}O>m)C\ 8W`q1cg`9$K#|cg3ň.->!.N iA9e06<܂$PAu LUC !?lxrpԝ/ZRлrS  S3L2~D}-Ն&* 5 Cv)~?\tW,{k4U!#w_<{,\$CnVC#Հ8W'/10Wf RF(P.Zs `T (@T2t`f' O! EE6ȐA]&޿-8aROU||.Mn{&-ϧjTӛ vmnbOsh] K$@b׍dʉ?'7OK.֣ qPh5q )_tg;Hc@ reBڪKi&7P+7bVyjYjCx{5VuR.w)p$zʚ|!dU 4Jj@'9#5} ג8wf" k8`s6S WPך AMSsCH8U1C?O巘aA /z _)6'{A٪fڼW 3+$v &DCV9_EmQm1SK/Ț8 dkj !A%s~D_!ߗ:X?O#a) C@fDIq-$ySyzAqavc*JzR6׫y!u$`t,4Q A d vජJ^{0>0vE")#oxuZ&?D\SkFh&_pU:b8JYcB<>e&Zo#Zs\FOn!F4f-h>j@&sjltkDW`!1;nH01oy%. Ya?Zʁ47"T`#A{jPL 4e3%|ʅ_1. i`$;4Mٴ`hj#zt7c6!u&u [3'F kN`'"6OO#_Oz{8ł0KF'loG Ω9'Ni[ *8=X7B S ^J4B^T-ǛTĘ%=P,/n$OI-^Ȋ3[M :$+LOri8Z>+~_[&qg&Ɗw f\HPz&= 6دh" A mH̜Vqy-UV `W9zm2ge"XN 2ȏ{Nz9MB [ZtEHȗ ! NtGּO 䦭xwT _I ǃ}i1flOirC,ίlP1h0bV`n, ?)QT$/K 26l$p>X,@?yWt3񋚟Qvq ۚB= N.7Oѡ!A)ny5Ã* kw?VpW3ߢ.u0exffjq/>5e;ȖlӆZϣ|da&jkY F`o]4f; yWƍ F/y꽆" ae늫 9S4qj`& KT6`zK`~59a$1aM֓a: sTֶѠ/;@r̹@Rpp!{sډ8 \ :ҝ2 ˠuk6Gi_`)#GbuE3mC dԚ TJ͉)$7~dLJ\VN w f~xmUA-|kW{wh'r~m0$ޚ p7S:׻\х3%I57S'5~.LQt+d1=GiXtr&TdF9XоGh[S8Q#ua棸J~Eh/r]TLeӏ7H+/ ~dypi7v3c}_*_(nHcȔU=t_ A"|7\c, Td*2n )J3)2i`MWbrEAk[bɾzlR_6Ԑc) ;+p*$@7x\RN@$[_X+{3E~6*$uv}'&wާ & e@8V`9 l:]s{&*$H| -H0Z}S?|HPM"u4Lzf Նf?=+(zv]:oA(ܷk6;Sׁ h72<,@C+;#1bڼk&nٽfĔ ߹Е 7.vS}ɇހЪBP%fw-(zs$HD`n֤RqBȄ-I „D!%;Z(8C$5&U.@syBSl%jx4tެ.r[RBy_$!C[ݩ+V죀A:ұZc$RC^NPI0!rRuJu. f15VN{LAܾ 07x5f׽z t@jbY*.h՗A(uyb?%',ow=c29̑{ۂ; #j ](R\~vxخ1 K kơsV]O RP2z+ 5?Uz|)\z?F㷦́`h%8 nw)d(Z7 7ꥂlc_P6݆si|S?'8!,tS~%LjK+ N^R9~0@$ rui?L H(m'.49f!UV6}Ҍ9w:_#[6TkDaf{CpC>zxfB1”Ŝ V>toեu/b[<e9Pv#\(SMR,XLlߴ~FŠ22I-wk߻=Vcw;ثjD3:%}1rخފ5/ Uӆ Orz)vbem ÛoCTeGX@x*@u1inCmtzfrNdPޫK{ 8ǿXwFQRB;o@}so~sa|RթDrl]rdpL-GE עVa^9̮LoY4 Lt{УfsI:Acs)sW)N~}FVoa4RˋG vfq85+KRXX`9APg%iW"k"aqUG.30g%9O'Ѽ6o2_}تNht HS%Bɗ>QCptb/[Uܢ3 _kY5,`ӑUۯ}C׃ {\=6)C]X-VNHj~%^aV# "~6e"\D<%iR\*#CqKhb˜l UF`yiI> Y֎t87YYDY ژID Wj,;BĀ UPD&A߰h{R'J]+}(+NA|"'0k&h]Ux]mKD`^)tlajklBPZ#_ƥk`%p[Eu1RoJkLQMWX)}h!HfW=fAv46`ȳWh)B7cxdC^0L"ޝ[03?{ M:*`nX`c~ +k& Q] !UBDD&N^"lgE_q‘+҆~Ck·͊=$$15`Tk(l_\eUA/}+0MM+th E*9E:}OpDjK :IΟ)G"T|~Z{ nXY->!!Ci"\U 23E.t ~ޛ'$Gb,Z֒8p)LE9 lSP(nfi4wj} eJ  b!hIϱgڟjh`/c.^ c!!U\GH̚:Z:NMk:UòFF89v$f(7de[г(* EJ1%3KB/v:oNd\O]6S,YwkF[#|]v=/>j{_8bغ4RLW= !Z vz|-V_. ГC'CG]?Z57M OAv+]f}z-B3.\{%L-$i? UNM:j%0x!2<a µo$JGuoaԘrz[18Һᴆo3 XCXXw: ^- ;<0ȥ!u1t a sz7<.Aoh&:Abo`Πuu#/QE~SлnS{k/IxGGW%hImlpl*FK kLL5a.b`{`*;r"5VᩆIP*&X1Y~6ŏP;xC^=*]}VG'F)z }%[Zbn; `Usяؤ ZiVpđ|ЛgqdAmRUR-"l+JFA} 啨BcvSs{u5E 'M1\ۙayCqM^"[TNۆy9C{aK/ M^VߡqH2PswJ0U_#?&nDe@D@g"\$C!|k.6?BBՀ+#z\~חtj2mIDΖmҏ;h# _"]-HTҤ6yBHtw+،#O:0:(ﻝR`NԂOuJX, ډ4ynfO cXNդ05:S.1a\n kjP @ ^M$ˊ(P۹*&O1OA'0m:!ZZT66=8\)И uxRœ~]kV^ZHB c8cҡ &$̷8,?Qz"RBk] ozFN'Ʋb0va%BO%x^(nlXbc])#yqwץ/bD_9 N56st{pPo4?j  YBfA2!`UEtW.ž8'mtZOz9v Rmޭ N-ج*XHgnYUȑ/id9֭HtSn. &vUI DD>nQ']gl+MMl6QK&}oٷ,?J{Jdo[푉.OHϮIje  /gɖsK;(R.wL*K2P|.<)UU!YlﰎUP^k_nqNkӃ` 4VD+T',WguiL$ \Rl\l )EU(HrYJWK7}zM3=嗮5.Eڬx7k. ҊIף(Q㦌5V]ۥ^r;[`|n 8)GJJ;opʠ.eKe6﷛V[lJh|AFk–:2ƃf>OVc*:Ep8vAQb&=]xikЪ?]2~|k!_dV"aK+FofkwBOܭ89*L3S}S[[{Ym^$2W"[hZC{(~w!K~'wvS6 ˦,V) @‡l% l>-ѯp,uP ZhW++td_(d0zq0F`r:SI+g~ӏQ+!td)u;t߫n O#h1W$eTLUe5pK[kTuq~x-!YIzզ VSuCwoaؐn &b*t]on*\DHqア3-LOH%Z~]o Kl T\0LZ;\qpag5*Aҫ^('mdA Eƒx0n;"5SE: ̠inLقL: {A@F69ܺ;_bgWx+QP;)4hHL LP[NCٚ %`_ 7?+8(td$0 WI:)9ɖ׸=dz3uI4´ hC2Itfa<#T*p)@N'&Kb AaXQ0`b$Dp\Ԇ ܮ3CY]ІUc̗0jo5"A<5$`PN+8` :5G1v53fg~_@qjʍ/'ZFpbz̙|lEDlÒ>*.+}n^_mFɺ% Dʊ{"H Rc ]lUFx1Y|^ ؙT!#M䧿Dk!,eGFJv._iOsFP]?7R"H\}1GQ xCq?3$7^J/s UYl'劫̉w6e[&1[HX;J. J]${2#o ^Saܾ<%"H~9 C'w?êt!#^:uQE!s0 = 1sbGkW9A`=|W(] ISDL^c{%Hٙ9[rs$@"P^"\ DyVlvC]<Չ`߯OS(4m=5bUmx(uJ=c[|#{a+Lz35<1{8)ٶx֋2{5@%h7TJ_鄲N$/ Li3 CqImʙ/e.T;4S3f8=XELYQ|tJh` KVO6b"6\԰c.dg=.!i+* e{P&")FԚwEt*)UP#}iFN\HA6} 2,R@oD*Yzb#ge=wp'T>u7;݉ɐLOKIƎ0Kx|raۉkiSY#[E/*U)l#~,߰ tbα刋8dha_`o 2tVU蚙4I(u&1?DE3]e@{"3Wp z9APa׭=xŔ@dD#Gѕx)cgغt3Wr@nFVl%2Xa"wr"vRt攩P IYx^_rm6uZK*Ҹrd*+薤O2ʯcbPsȪ!cs 2dZ< l9e:9oj˼ݕ#YK qaK$`#<'G'`k S6ǣl[uTQ8ߩe0`|Xc;ޏQ[-()>}^A[峁xƼS„'ơy XZ/h~lC6j.lR]Z@\$Atf@-ZΩZ"oWZBm&֖g,H1"Kbkԟ}˗or.G&ҁKv{f4^pnAp%f*?0@A!3a.2~ `fU&"=!;'X 6 4^#D/cXTwEke;t KkO!'Rf|լϔT{@b>#=c_m}nL]6:ϵ#Wc$(L^~룞R}k4V&q!&]"q @ R=z-Z2W?={?V ћ*WM'*&*&kwpcBY-A o]z"۵`?Ul~5edoA, `O/"_xu(`;SoRbvp\K8݅SFE (ѩeNM~B傀&~*i*w@z [ Z^Q$"e dybre0&|lS [AR^E䦻o=5 vr VWWF1х0QGJ t(G9y^FK(c .`8DǻySW"wb :եA-,` UU/zaյ?n?Ik7k oHv{P'-%Q3 h3Gsc&/Y@-fI Ye7y[P,sL!V8 L3yBx0G.hyri߭;RVV0dm9ߞ; <7zArjݘ)h} z },q}DA" ŝdXD`"Ľ}?'@uc؉Py$)? XBUE"#a}QrHazOabIx ?SɑME"Ԡ"`[MM$:0:.lg/2SK/:/覎T.<H)jΐo(⊂6`Vp@| \ X5,nknX8Z{ 4 UaEGjTgh)-86M/BU 3 RhXzT f~*0">ᵌD#Ŵr73ʉ{ N1"VH%J9#?S/qSQ%€Ƒ\yȀ f-EJahW(A+Ig 'pX.@2gGu~a7CsλiO \jk56>iLK'JT>^ p꟣ 8FoW&#ϯd켺֠ AP|'Ii`%~. AgP=] 1Ňt=[暑TnYZ$L0[`fʪ=Q^4\}ki@d*<2Lf4ljA 6#denMf pݍ~tD,@ BcK~ֵTj5%|RgkmB @}G +u KY@ QMtJ ;4qD$@TK(g_7&0WP$W\X`ԧ3*Dե>:S[aCQ-噉:[F:PKgJlG$,'_Fu$1I#$XzF=;Ǧs8r!48L )sy(mVL}B{5T0#N(P)5} (UԘɲ! w>cXpG~o'?iփ`iп|;Y8$ "ʓRKTuen*=064(}HD@ՕPEW4a[ VR89%^+)B'Q8튬2XRTWbkXԼNO m/@t n Ř3Fsr.!ʭCLXv4m}5*f"仠L1B5)G♞{g2yt0 p=n>v嵄Xh|K|Lƒн6+`XRS,e(_// E@`` FY ׍, .OD^ÏmYKo0K6(apŠO\+bnW}s ZHDaѨ-(~(S[E0$[?7Df$Q0`E=^w0!?;[`$6m kmS7XJ=dW/ 0D  e3:(.iC]'>L:{z= _"څ姉^"s7$\A*db"aL&P#/K dt:9vLC|84iԳFso+]m{\(w"c$믁_hrNv{L pr[x%1=$TԨ$meЈPzQO((s iOYF!^mJ%antrnZa葚LD`d5XskGt>]K5AI~'+^8nr?=ogNIU*n)c2͹* n&+ ;縊Z2" TƏHOR_(#j0jAg }X1@rPou?"P;>%l>h~=gE5d'zf$ o6=LlE,ƭ&B&ɐ更?Q_W ZdkCk@Z:<} ׉Oج.ؙ Aw%]'G0ؼdwΐ@㠫Ѫ @8v~w=Zch>Cu;uGN].Uz&G"TY9eS]6,H"=N#IkmoSt 8. /!oó.QjV7V Ȫ&}J +(CwCxKɕNR3] _OCL/ww(1 l I03uLV#4(ۢBN7vbp,H^V飱eq؟*. ΠcHFjAs"-Nf7~7]_m9 C (LL#V@SD`y4͑ۖQx-606EL2Q6Fْw?M{v80 Ea(gxIF>8'PBMJP%0H'; :0^S"/)W }spRcA]Jѩ42Egg~0*jKH&EW}t+~ata8ZN CQ!mGg^m~ 4*zzbE^;[=#p`"h^ $f)xk;ЏN\쵎`X0yE !T6dE8l$E+8Ds+y fz*c95:%o-Mz2 D';S8iO 5hF7 I%* ;ka͎H4d\ \~X9-"@۾10(wdC#ڻbp* K7;'4Xip 6{p\?Ot*lnR Y~OiK @m}+´.#N͋5[ֆ,le:$Jԫ'mܤkS&k1H%JWˀo@[4 w{{nU`xvk9U`фsJ9Ǻ Z픪zҭJpnۼ\1SX-tf `|F[up{E&`.m` dK tJ}MLnoaM) +YV+=xH"E%؇i˦WAA:dk FnƌY8:|h=@7;cPz5DZ=HiLջ+ۅ..t(Y%HC} C`38}3[!^2{xJHϋTJ&SgͲSAڌvo{Ȇ;:>WI d _%BUCD-u(2C2uVp>dlªirǥ ,Hžm/qgt0(B'杮Gx [& I}Q'xf`(4ޫL ifH~"},ej.7'0]Xs>)Օq8d~@QSj{#TyqƏ$؛̿9L*{ 8jggr8>2vNy@9i:4f3)as_V"2e.OԞkaNC~zkP慄å^OT,Fv*] ;(6ЌŚcmXl>C7`n(`*-xZozaip[FHHҗFrLo S;03 hVS=6=a6HݜaϥPiMg\{qLJ}z*E+`8};}ׁSp:Q)l?2+d" `2 =EeCkҧ~.rE̋?m-['V&~mH) /2H0JqqέM/zeƋc uUV6ʫ $qtI N.O}bD b3ͫ nݹ-Nz|?@c{i @( Z"z=q'.9H_IynA/JuhlOXfYn/CCrVz+ Bܺ:o1m#[#ԕ@|P M:"vl{D–cE,Tm!zWLUXNTMy=- 72ŃQ].+X$c$J ƪ`_\zs wp⤳?hr \bR S / ݵz4Z,,*qL kqӗO16݊FF׬'5nmlk4KҜ#)G$#dlbuYM.䁾O}z:R70M$ nڏ'5R .rbNV&m)?bu )qt, 1Bj&I27 =~E0lo~`tT?*p87 '}h+o [DFdyWb$ 6K$wh'a/`,{ܡXUvh}YN:̙!\o۞";an_!~.d/t.? #&$#^ ½FrV\)]ux qywXYRj^>!0m"n_. i?vbr(Sf$b4@૽GCSjIppc]/ftSfGfjURvth+68gj=,jN<2ZɒfxAGVⷨ~'\I Y@? A37e$9+TױjrHLL^C4S9S$ș HO:w/KuXY^! JsW `M ˕/1~_qWcw$®@a7&^\ Vy"ls5Y]`;ۢXoL="ytB24A i p#ړE?,&ADb{ζ$и/im@>c0er ߅#,(@8 `BjUޫ㱯lṛGZE\=2V!.үK ԡ+ݠlBrД"ε/2p'Ϊ&BVӂָm50R6ف5:L?پ6,EC8]9Yh6 ULC `U&wQ%N0=l\{h8ƒ*>\j qu`EHwtp[*AΠ}B49En3(N_ɳl˦C4;Jpv6S-^%}x=&biNAŎ$zn5h߲A 7-#l~39T_ޚQ~P3TeEAHb.%Ь_L:Hnܧ(5P˧$MARe?C0v1iVbLazƾ0q F+/>&/!=b3CJGĔ >TF+KM` 6d'Hb`" W|Q 8lhEm$ݦY))@`;5W ,C؎ "0]u&lP=Dg=V$; $ꋓKG_ڠJnLЩ&oxaшM-B(j٨*Bh!lG:Qhb _l(9<QK)rTK Dp¡`Bǔ08uifՈM8%md5eM\ (̓/`]hDP[!,PG210?p2wxUGBi񙦖*ha4z!l[PmDXT4BW X}a4KIP#綂f9 Yp&k {Pd;!(͊o| JjH(bEk;@j?wޒ_!0(<ࠢ 5ELl RKgV"tH .2DS IL݂-_ʲ7ֹK0&G>S(kIs%ȷ/b%>&09FAV&e;(ѐl$ ~g)jHUP߯YPVk3+Ɗ"Je(i󋵠< Igk1}otxūĪ~A-~/RՀfLaat0>AA2:?Jyfe0\;ЈjMM3<lub5%iwwE9U2;)"A(ۓ@AKtfb2Ӝىۅ4x @PkP6UEC!lV1_nd%G+Hy-2|=Gl;?a \ S;2s k;@@ NtV^'f>qĶD ̯/I_^sh<]S඗+TVbĒ "\ LJ [)Y phA-,3T"]}׀J FHޞ@YmiE;ؠUU\uJԈzBa~2!.uZ"bL[ހ(.SKsˆ*-WS~SsD`A >X W'2g6l*(s: )A?5a˜=T]$Im0XhT]ClQwGT4xͼ%^c-ݱއXuxz[i[a&.FG$~8Q-uFEшBmrtsQM(|hV!|M0ׁ2MmgLkT ޢ;Kp11odl3M.`wt(=F 5uT;>;`tDj-JmS "$-U+<47u37͂ D4 H@'%|! j36H}ˡhRe$\Q**s<M])#e)e*sW%>y0p-F>X\@ -CF6}IH}a@^Bw.=^{Nk*g$#!rq E"X@F9QEєT*$3ȝb<߁u!ïcN w2i5X#Xq3[M`waLh]^0Q+wJA5)x29r‰@>&u " K;[ F!*b7z-L,(㫻.$Я@P1s|DQ si!« Xu | R4_ r L Qj3~׽G)%Hp{.J,Gq*?qp,D$b{ٵ%<$§$1?[ݭΓy6=qM1uݖm.(2T{ f~G8?ЏMG |nl-N$;Lff-kG]0a6,L[J 'Wӭb8ZoFh60G%= Fu8OYCP@0T1rH1)"l=8 F?6L}ؖ%0sHxJ,'QU,ruZ9}|P\mDQ8[>-5'Qʪ(t$H_'50?Ml&_X2*(NqO}ٳLxB/$@vzۄI[o[(vMz`£?v"3h}λ6}wpf{k0UpƌJ 6cp؍,$÷<;A{<*)3 W ӭgӴZ0{Ĕ oyiTBA%ueС_sMu!qR+*I1l!i;%Q=7%ϫUpO/r^0 ߎ YL TK W[VbS5-`:HlF85{Fʢ cf夫$ဈxdN MHDpݺ81h`{g $}?*{CfHslڈL`+ .8d\27|.6lD0kL忣w'>߃w2'wxN@~ۊX1k{Vb#PvDDhAV5֠sz-VC~ІyC;KMݮO3GiP?C^1-6N*aUAj@fu6ME";~ xjA18fJ iMD U+Cی ~ӺP[AE#0DzOG%b@ $Mɽ^NJ-Xf3;#UA_}l9gz!Pp^J?$˥DZ64Gm?!j3b c} spLOTĩwmwZ 8'@CmLj)8S{ߞג#r3}RiV]v(em`͏@0Hε@0 }6!!yɔ*ߊ^CTi+a$#U q#K"@rLUY?ۃhx:d)?v߫#HfwwDj+zv(hu~h>=ß*6N$u*7@xIqEC .3V'@ӧ.6V@14aG73 +Nﰥ:6p_Q!0gnG e˶bec҃m'69zG~juK{R]PH&6e0+uyqA8fuHR1ҥC:H." ]/N3ͳ-Pv +<|w/q. f%8~ lÂ81hm,Y+V0[CђĨeE"K . AP4AMR 7&U8$y11 vo~ fs30S[Z\Eɯ`pSj~ DicB2L{ns?i /*vsVᕥ09BFDkN =h_;$ơv\ lrlAL'!՝ƙC v9rJ`tՑ w8\HCo4ߩٗM=w=0,En'ZZ̘*pA&ȈJ<ԑm< ^] Y+D; բuxA[J ’eW[FYNW ʺ84$Y#_#s6rd? Mg f}$?3 4/pT3k%L BgV8Z;d=Y#Wlb@0l`2>'Sυ5 eA-2^djز Z^ '!N[$.^ݙipF fBQk z-ZNó֭A`%rgLt!؆#>@[j]2pJAR]e t0/͂d:͒ vqNF/n7 Zd0aF c)ϧ؟mC)#eGm[ז >߮Lu&{7afK3C˿P2A37ZIcHC`mk )@Z_#>zIGqwj !P] ,Lͪ _]CК'|r)~O ~-nd}V\!`Jpۥ|o:LJXm_\Hy2h?=cjv> DmHiF͏WU3 aUSV jJs2k%9 PֻL>,Ůř#췯 [+ XA`p[`S;ہkw-{tf&>jNl|NԂm0xEG)=[lu2O\mh\]b/?1}li1~|tPٻ}WVáD۟")ViI{6z7t$l(c]Ć#b H}C7/x648q>t }YR9RX:F˪e :W<|Y<hRA+x:֫{(ֳvc] S<G={9xuDP[\~׭? >%M %;OiG)=IPRh X)T4ij lR֛(䦕(`/ߤڳ('7h[vC/BۻP͠0P8m3f"lGA&-h7\D4M&]o}V/ʍtS?|K2ĒVIk{%ikF5p_Dhtw`~n *9]"w9j{&DNn%>xmORQflL_z8Q>~76=T!*z Fv&Ğ\TĹ&-q5"ylJv`y<. u.6 Om9n(25!VN6G?ִ1$Mj_ VQ_LH+&hvDNWж(,%!̋tV ~2y6)?Iqge p3BioK7ޙGl*_^a; $[CB+> \=;.mTu3?fWvPiy?({ 0 .C"R+{A?X)̣j%;0Nv yҰ]@~tɝP8 ZۍGYH%7Xlo.yV C\0FJp~,y!|jϦDCE?m+ SPL=C  =[p {MFO|HW` ㌅񶓹 Qk/t'R.W;];$=+ 5ThHqԝ7(rC20jx ٬B0s% ɲ67J~&ӥ%S jڼ pRHmKK>+#ԈI0r FF_.orCDJSZIJD,K@[}>Cu}O& <0rzj]^\C0MOa@,=ݯV}xl,tCZyxߕ^>!U9޽8pe*&4w0M*>Ot` ~u7]E;|B/E2`c@*9C+Cgd*aOdVbXl;&AV?2=@?{ |,^UˡreJ=?*ax@o,cԂtۦnr Ӵe=] ;@*pЂuT S Q7u過kAY[6 غy%+HPMOpBZ=!eF*!tCISI驯DB$bPIQ4KIW1LP>v_Q\wSyi bWj=K-tA ^ܣ(Ro7izm|Ht1 t^EUb/5>*^w0.H֏L~`?^cȄRIX.Tu-daD`f&MO.D;p+ Hw˶Jr*uOYe;,`\5s oKnն7@O7s_j O/ \Y|+,KZ Bdކ0Zg=mSn ꔴ)|C(H3u1mK^lILĠ{G1Xdεkvب< 7fl ^zޭ-qұwHe!%)WBŕz2줟T>YGO("'v F) C @RU FPvK=wDq{N۟wRrwh)߀a`ϊJKͤ}GfXܽh2-LϺfң͎t™=B+@Ht@mC\n= o|obnBL6u}܃T(YOI`IVHVk{(Lsgxi#P mfhD|.l4+ P8s7>׿8f^ RuuQ(zEu r.[oO3Q%u}I8V./9h4(3?׀vztC8|lg^ttc t#4{QWow|Z#vjxV0#A#Ood|0_ݫpq"e0نd?6<(,i)m~4kAsy2P_=+^7i Ţ,De48l{CflB [[m4Jy!no88UCJ -<w?}Dd;?kKhlZE^c oj8Gl* "̲LN1WTMM,rO ]zi7]U@sU=ս#S}מ@`nK~ڬ4YgrNy*}?bTOGO߿Zl  Q܃4<c.J%u”q⧲ }cUlK߯4}G܂r߈v偌XsB;#޵?Re=[ͽE+YrmCgtu腡@@F c8U}0%q艪& (a9MDTKF!TqNWQkÑk Ʀr0TJ\Sа.(^9UT =Xg&> {@E' 寙$p H$$ͅ}ŝ p`X Vu[2rE2쑾&26`D$-DM(,R|+ QX&|[zp!~=q`8R!*L%O]RԚ*pT = udʗ3M`6Lhr+N$a5=Zi\]9IoiEϚ3DnKN݁LAfd@+ tYqN2`BjN3ʈ,_BJ(#XҶK<"ؓ\;pSM^-tL8=l[T fGƞO\:߈"4τ|E%g@QLJUoB;q}a}65<=?g)/~%:6%'ik*?JN:ݨ{r?xFŤ Uw/@*HC@k"^J,N,BٞE@DAr&Ihq63$ Ο+\\i)u5:-m @V%o/s7LWU@evE;{j7<ͩ䜼*7H Pܮ:k$i@"UheuhNe5d$:46Tb4#5D:ɧjDrUpwIgT2U(!mڸSR|&5;dhL#5JN\*(/Sd@0v/+B/͐\rsrֱD"33DnʣCnk([J^. #N(|{zȇ9#LzZpCAr D 𔈑cF^ .|80z_FfrHH]@{H=ݦH~Ȝ? Eʅ" 8SĄ!~hv>rKz *~X_L>O*bXMuV'Mm^-G'19/׊k\QY+ްhJHZ,ME$HRg][."aB ːn P:w6emYr2m')K*W"234xq`htj !3R!Q@/CBT3Q WGkQ$[H0X)q @1!# <$H7Q^*MtDs#K+J?ehLkÎQR\5O kS ;h͇ pcBRHhcw2=LL Pr| uI:F 'X~D]fSˌJF^KD5&3j4UUƠc^' 2_lN/Jq8uwH`w~TI:N6z%CjYjeaTҊr&dRH(z@ 2!1dmFɍ+T;ᓶ-N N (g&)U5SxU6NV"$Q+}H6i̭fuM 1H,)=h?A$4pʿK-_v ~+:z IX~ާܲv&7!BiH+*] 'ʨl*gfzQO  ?*4HOEw`EIáB^nԺ_.-913E Qz$_R5X"a8o ^p=NE|(h.rW,ϩԚR2J2$I+5/FF)m_5,@plDhȾsb#V X2} OSzV_z'ज़7˟1s2$CM\J:#1g";tmm^^L˽Mf*P 5ztl!7fRY {)7KMzɑd*i;ZFe!G!N Z$dBɗF mbrܗzqg^tBrd!t! &z_X &*- 5-{*˨z[9KHvȶ kƁnɨ$썐uNTGtj&[2Dw"~%DVNݝU9lpnhDR.4epY.$>۲#  \K 4g@ x@spBj.8FvΡ#"'] PRf#Y lFïW|YhZnTlw H҉lVy^UU7k!qOF`rM%!Wfc4) vŲ&F6qbe*N]IbH0+FZ!t.jifI!•ySâ-he И Y"w2M"Ih-? #2kg;-RjctJrS2#8th("kB4ИZsB줝:^eY&ŀI}F^/Ӛm3;$U;Z-!+EH( <6.D5C;1g{.x?5 -/sXINjRiRm0F6u 26rK>Xf'F~7׉'֎Zlh.N"b`f\. Z銟Zg9pڦ} .'v* 8j  QNXKuy>p)օHI-:ܡ[6"!A4WmE$8R md*Mɰk\H.k%cJALnWHnwIg'2"#R*o7)i-(<."@n+F`!9p4Uj4 ZVۏA$Ęù^Q:zH$-N쑌 daԓK;(d(4p^#Xӹhο%?238Ğ9&w`eRvN>Rܚڊ/Ӊ-mp .J W*J EC>R"qDⴏD@#$v+6 ihޯ\sHJcKGT:(gJ{t&C]YUDNY$i~wW"4沼 D(ƪD^ёdR0y нчBrgL]>@P`$&Idb`bwB;0HwPv# 9gw9)- _ڼ쟍5o cY%fI0$S&:z#N&Vr2L9**z$q qeѦ/KQNv* @tFоYA40B`Ļ.6pƟJ6I V+wjϣm!%T?sLJM6 T4@Y|.xv0= MopBJj!')37g ,MWD*ď7 ndZ9Ytj)ӕEÜWW2t]d<*"7/ <.R=F Ql]"1|w֌.o%}$1b='Ɲr:*(R0ښgViD+dBk2>$M,i4t cIf.X{1c~g-QLDd-G9I2^U ^|{S(mY7W2TTH+hm-48(z#O$הL˝ SǁH&%Aj|;kYD"9IlD4~O3 ]bCtmK8SNH_v4",Uۇ#&J߈y'kNzN0" 5xJH!ӭQiؗޠ[C"̚ %=o+elr!; \0"dTz/CL2U7Ylf \]uzMu(w>ŋMgs1=N= HdMpNb̍H|j@%ʜLxkA #Q!\A YU[&Tܮ"BZμUGB5~_)g3rd X T:)_G)=fG*W#wPz R͇TI@yjxk`2=us٧oQ"Co5bF, / |%q" ٮ_k"'PӻnbmQݮ.]5;bS o kVT%+GA*%a# n4-LrF׮j[.EDW!MNB!4:dݑhM,bf3`>[DBA~^8W$*!"U*5O*p ]$7ҿzg)]ztAt/Qxh(sGMoqZM9x̉F˿v)YZ#Ag,DA@5P蹉PTQ,\"J ZdmH(&٩D*˟kWŢILĎWR%)A"m=$ZR9V<d]tp4B&(W5<N(&H(2E:9^L6WGSmi/)=a 5!|~<fLga^/:q:[N=H)o&Oi OP5Qalg{e~3ION. < m6p$¢::!jݧQ([~sDOc(΃ʥ'E-M;m& ObCdlt85 ٺ*U9 W UaЗ4Xx5RK gCq+TWY#ɐa3 &=ya/-0_xkŤds!~#Y gVN@TIRꝌo$ GI:2ӵ(l8DjA{9ᥣx'cŠԐ^t^9Ep w܋d\tf=~/G,QY:OIH."oJ֒%@,cix)] B=q2ٽYJLhsbm|CWz̛[wIZ]@R{gY({Q9v%,JMs}A/ ۃewgH_%e<˿6h B@.C*s!*I]0, :xxom$4ep=n,DH`*H$EujB:pB~L0pT>>ZJDpOyfYPَ-_K;A9H D8﹐Q.JiҔ)˃lw$д[IfB۽Rv";Z3.~V_OR 3\HYy@SVJDrvWpxpE"-~$τb˗;*FX832Ex ҧ8dBkis!4$Ĉ8 c7 #ϟde\akUF$cIվA)8$NtNʼٴ;?a BHѩ v3c:5 hD&:g9vTOD['BGIT6Bl.C|ꎼ%K:1 ڭ߉+5G$T<GQ imCRDujd2Fg~GZH$H!:rL'UÈ@o}<M`|]T\i圭qmkNJq"NmԻSme~xzB$->RT v"T,,A*./Bd"aʸ5c'"WYV9@6؄:grn$MmM>S\i1\ރt]adJWucy!|!}m='(NTPDsYtX5<۾|HD0i;gNp3pEFf` QODmHBPH}bOg6jT& mĤP.?%b" $eBߺxD?E%%;>.ՌNS2*'BoD_4:νGh@M+=j0#xy!_0*+t7vqɒDt\*D:*H8)m'Zbm_$=yLhr $`8' )N$_)e|YUG8>.u߃DJT VR6_ +ItS[.`p?2fF HMW6bZ%E MN1U$ѽ"iD$½-N dIiqb'4c^=5ieWq[]}_~@6u KdvoH9(;!#mqaVFH^|Ldhl(w):ԷQ{[߉wY$IbYփfhb"l;(R%r 7Iˎl;e_igS|uZ%D|7pL3^XOx16ES<*)-$'I=ȓLw-gƻOXMZH;$D.".^X8V%K!z_fB|)MQeh4Pˬt1o0NHJ<GH꧍.{>U|ҩ\irpZV1\]/,&͟]r,y|YOc4GGRR6fY$(:vy/א9w1q섯xPͤ)"Uy+;A V6²(n1PWz- ya*To3!LX F@\۪zX%\BY]=Xb48)}4?YbX(`r OļwumFU%/d7Sv}iHLr`;xŴu 0>'m@ #M >/2b(#c3Is6u8H7>8N蹰ힻy!Y&'5CV.7ʜ3So"1Al{+-ŶoO髈:IEhd1{vkbjHSgk#4Af"rO iG%zWP+-g]_COKɅOm%ZKhMmcк* og9sE=;E]1mYb8Q6 Zf.G!_mo( ܺ6yΑ:z 0af8Yd{~HɉM7G99.ez]Q;npb1JIBz $]==]r'SBqtuDtB ^t鳂mBaY*g9P8)O;/%=mP?Ԡ85(ӛ.Ԙꢢ=@O Ts5 Rxknݠ}ÅcձLQ#!Tt )X|ͿN:;ǍiV<[i' 70k|U"ig C᯻b~c+AwЎW]1sO]o"N6W,,W D`0|H^IR/uXf)ߡp<BTvk?7 $~ws$|5fHoޅ*~OSsCs1?b]p'mӹVEs5h 1`+N}FK_\JO=*@_gFB'+Aotܖ5|L[RgK>$1$w(@"P{z"ry˳o&b]ɯ#Yl]V LGA&OGL*~uŖ@ e4S wS@W9X>}ܪ~L&H_d\_|!'0Fn:X>THAUXgl-xR 0̟:l-gOb_}с`w5` O=6b` 5C2rn2@<-h?\SA"Xc?yy{K5~j:~7 9d_X}7/wTOlyD5`O[tVh~_ck籬puhi_1+yۺciZu1uB^{Y%[>큮-3*,<)1gJT%fr"==fLqB8I=&6SFb'.[\rv. 庞>oֲNa#g 1p~7O@֣7v(trЂm1V,lW$@x[p8,.]u`4'ۜQup=[%]NbɁ>c2;fs/)?\t]YOqšO޺,OY nme]աP{?pNU378h[}@z6GDa27zؠZO(,Ak2A-WV'6܇4|gmd7[*c~XؚWgKXܸs : hc)q?ʌХJU8c~gx0YOD^ S /wV]fNZ~GiJW}46@ a;G_5,=1ebZi9aHN0x1˴u{wۛ3}jYrnE#пW>NG7`/Mbx"jD.u~+߯7f@bydGxS 7B i)zX7:yRԆD.zH~#>u\#|Ha'TR=!ݺ~8gV'v(a~䪔t/|@6W yF}߲m АjToXEVO-u[iE Ÿ}J{ is2U]? YIK}4kMx; @FnߘJ?a϶GAwe/<W'=Yf>6Qǟ>WFJkW * Nˮ5̀9HN Pk j=**#*'>#9\^ "+SX0&k?Cھq_Bw#A@hb`ƤLxn@QT ۩ebDHNhWV'K $'F hzW|h3f{|jǧq͌_枭V[///'((:P4kݗ &  ZEurl!PGh[+E% yuTΧ#׋luk'⑺>p }r<VpWj]L۟e .bW 4Zv7_V#S_M۞aW zM= Fd@<:{)H/ v]&z;WAZrI^l?L' {s'`pA ^ ){n;IbzGQS%byVB5O-}(3_nЭ@/L4dl@}c2ф)F;=g%aw}Y% |ߜ/ ʩ\=g] ÔƜ}6]h *sY$?ߗV7}K*քҳ7RK D`|j`~K=O4߮Ժ^ⓖqn[g"X҆`"W\\%=:2K~$L0ZLSi{~*4rg~I*5c#Jޠy%,_0+)(tǏp^0k1.m(w$EBwʥp&m<-o;Xy*KoNQѵES qefέ{?py:O/0p̥(D9nqu>eǵ۱KPڝ`Vxg+ۥ&ƕuBo ?=V-)-bP#C؊%:E'pBxnj}X9PB6P3uR[c$O ~ e<,\|'d1v4@ h}C7 U,PP&ȦP2YC6堧@;-_It~4'&G  3S%C5bCf8 يDOBk@__JAoI䀾2r%8D cp+)לt^仿=LIj侩rI )AAy=a9;3D ko, Cpuۙ7t,c Zcg}f]Β(3}?ʾbwuON$􈦝G#gDf|YZJ@k}ᄚ0"P:+ ;~ ^5p)_='CdS7ΖJ5J}e :YTPcBn{R Hsvuݬ)ZNMRJx83VT؆V`QU# (NJX"6,N^ֻp%'`27VKR e+U=H),d%kƗXlUs93Mg@}9Wxn5LpQe0y V~W.lKޭbIБRUͼ, R7 : cֈ:adʒ@޲I-!,:1THZlɖL(H0Go;S-qa_b)ˉ} ڡPUUX8E DMN)AS7L{ 5s*q*sĚ.6LGj;ԔԹ:[٪}Q,CK@tBXܹ}$A:eRދf1Γfy)nä"fp^ t1)ؐ)]><5&?x ~^5B &h-+XZ};1@ Kp Sۃ 8ӝğ U9"dlQgLRAB7 7 ׸87P_x+ T^g0@Q(R)WGoWwQ'8D =LoG9蓗 ui{A?L0 HCIesok)-J $ݖ@0 (I?$ϺYaNN9v+x9A^d}-D* AMKۓ#OjXTw+iDs3j$?3Q׀b;nF {#-qbjJ_+xbG+(%+ I6i *ѽN ~}sF}%5aA6j#Gb:)|F;X`O֣| y3v.pcFbF e o!~4 =(Dvћ B 9tOvF94x?2ωYǍ3H(zsv܏8?"=(?~Z@9ݚadygح?xJ`v~4~wD0XQ@y5pGlh 53H,wXEمOm d^:ءŇGwVDe3Lӄu|+8Y`AUՉ%& gM`ZK HTJ7ig)sei*%LKc6[(o{r_[t0᪖-2%Ct'0g06lg*Z» u։BؙѴ,`::@zsXDMZHKRp#X%0 jCJI'% ;*D[G=XZ(:-GyM" @@+}ZRqV.o4_*ASAi(2,zg|ekQ .]Dh:));PKCڌa4ӐDxio&`ԀC{\:#Ў>uTAGS_FbwOr=\I Kbn UStj`~ڃ#:i|zF^qkWT chyVZ{8N({m>J +$z{ Pth J=/ ?n|򞠹&BI6>d>Z(:!`X֙:]:?֌ډZO2d( wbXz;:Ll'(%:'6xX•zh@) 6:ɭ?ϝ5r{R5+ءBBjXI on 9)q$6=b)iH#+[(J%p0歔η5DM `%fyIŜfANudElQOo~+BjL+o,G2 k|)d=g 6ˏܵNnBM0<=(BxO#5xXz G+r@p0/KÄIn]oM4KL1)wuc%#H#G|j$:a@ 3@^o2@ Պe@5ܛ5Q7 g~HЩD۰ՆQXN~ˆ5$ǖ*)kGZGJWfyc]{\G Ґ@zЕC]#T8 z3C@sRjwx3]J F ~"ȍ}P}Ќ]K:$j7C ڮNzBN%g402}IEVk(cz#e'[W[YSaL&4j f]6V% v=+131Ў;;Q*dr[ݜwӗp%04#!~S& 4j)PmTK;t+_% ^a /}k" z:YcN/tˑIhU=>0/N_(i>ۜsRzКwU+T0<䠉@QP'X埘Hqq= ޲#o ma3?H\W̘hUa>'-n:0x¤y& :(yH)wMNf"5ZHiȿQ ͙PU^~XB/$O:T.ȽUG͓U49{M Б'H(m,R8} >6ŏ" Я*? ]f+% /3%8҃sXtz#Ti8=F[TaQZ?WH%'1F 鄪Ro D'E )yP -"BqE Wz]씛 E{P/ N3U&Sߪ9B MۺlV5!P6ȅd ͜a;/c#=eP4+Xf<9uZA_W=T IHG3AB! uSu[,;}&BU81U#HGFW<0E[`7RţW25 C(LFNJ yI5Co[<̌~2: ۪uS,xDa$NZ ֒UU9 o$[1C>`7`Rl}G£"@7,l:x˅G.s8A|7JKK sB?6\.fA, #,;BD!2#hHDdDͯڀ5≌J0"LpH6XAnuVu=xBR}k33wBIyLRBBi)#P4#Jh/h781:r$ ~x딕Tdw9Znd*4*NၻcA!%ga35b;O.6>%/7Lh7^7x9 HPQΊM&MHı _% >'(RP iG%vJ CxPچ:J9Ʉ-g tDـJ(o@&. -w\UGC})P&ڭWM/;+N%A4bMybF,}Lj WPg4SJvs/,vcJ((>vO h iI~8_p[r캥e7iޝ1y&LdU[:Bou9Qi-8HnTÜӀ l3? IP-ڍ(턠C\jzriQvr)nj=(E:I$N̄_?^ȝz>S=Ҡ67qp82s WAS ߉BD'gy"yo EErPXH! d |dSS*dQkhˏ<]UݱFI<'Sb6r Is$moHpe_ SyAޏ˘(N6h{~D)oɅS7Ei(PЂgBiW!"YyԈ_lP 9Cj[ N 0 L4*6O/s,0Ε I~u[6.2$$|%CJ RAf4Jw~%-0EpvT ivi5m;n䢑-H,I Ӈ$9:#FSK 47FN.`~yo~KH24tXuGp[M˩:@lOo?!E o I M#BS%r .|,yK@1xYt C!!QHvTl7 犘TPz>㜨,E)ikxCpFpNiX+X5z"9xI])9H"UwmŦ>W[/^ʺX z76,|?'/0p,M'ic[F̜W(1quO89}_v8Déɴ"3Y̧vC0}*S>h< v6H>BF5HHu3Ә: )6wŒ]̭ï$]Rc[``1(ds!k]~̔w3e5V\':bDHT7:00c?<ی'^C؃ZƮ(5 j o,)6@"B ϔ!T67N5H.\ _OJ4Rb#,_J`֠I$5<@Wfc=QheUb)F)l#L,3}[F͹У*yCvGӇ.a V6=#󷙚@X>rS8QjMJi|QYp&ةNdTu bxQ~ Zz[t%B.zeKe빟C  TJ5LQw8ڛFXt85pKԃc3>Ny7u|4BV7}c`[JHS:i;{ʼn?$4)bLE}7߳mT=LJիSԬ6}34?~ѭZF[3$P(L˲/J!t:t,[D&A3COO[ 8uZug_Ât|n8(L9y |d]i 7yV>J27X&nQfx03[:Lɺ'`۝>F зN ܄KXr}|ˡ8I i,wX6?,*U0}~IrMTB2$ 3Ll vRh0!$h1`#G ;ص5ռȩN |*M ڼ>s +"ufH S G?HDug %l8Tnl'%W,`zͨ6ƝiOk(0%3_2O7~yQ/?C1`x.gH5da'ԪjliLJc2)uUBl%v'<,4tHLF!"TR@t5)pS.$ `?4T+lR0WJo?S:7+&=EU|AlxGV6fׅN1'PaxhPD4Whͅh Ҫ_Q _r'֌烝xU8R~Krɗ]FiA *܏ MZ*̋ iV `4)T_@ ݫe299>RFvR?`R@RAۢX^+EuP@4OH5A@0Ei1~e ZmtBo UK]|RՅ"`` !J}p܍ + jk h`3~`p챂\^ۛ ikJc- ;;Y& dy$NT?L/Uޔ ȟm\_ZrrdQ)*u4GEnm!җe@駘`6+ Fj+E_83%Q)Jr;WS jJ/EDbu2ʖС@qA0괭xioDX9l[Q[H #g断 S YfzF9 +Ŕ(Yښls_*FPk ӨNqRA/t U@Y#j!Z2em4d9Lq$N@-G(MC>QKԂ/"֏kiMUbʺLf -{BҁP[yZ"쩓jQ0t;bSck@H_FVͧP:߅$CKc!P:M-q4t*{MCmQt*sU;_ee(`3S2RtAlc6&Y;& ͛*x @ӲEM\MA!LpwIZ%҅@'ҹ+&ZҾ&75 ^Wq&wheHF]4Jr8$yܖ}0X2)vuXjv02{,;3A'0}9DHJ N;*3ŜT` cHI+EDQ)js9GZ$; S3T픽})_lPdI^* 'ڷк͋Ğpc0"BH r2*.u-P!f/xvoJ7 =?Ê$w6EBº~TaMt1#3.FKJ[ICzkq"Edg ~Ⱦk2gr"YwGзhtcm"/Y̒aH4A/ @mM6?JlvлD ,4(lI4)h k" m`:;g=H(6`ιHlZRO/#!Vnp9IW.ħ 5H[l߼l/}iI4$EhF&#"H|m$g*&I9 D'}։L S4$'ug!Od$B'mDC L ,?tSKA(v)6ȿKK:@%vW9޳'P:S>)dncewpD :F ^}7%' }ﺇ! N mI0{ Ԟi /;5= %HA]SfT%4k=" RбvvH`z.eXN>ul(05 t:3ZcDF 'D?ڍ& ǡdH7W*!2w\yXLs0їV.)R|@$i]"E2ON]Ije-zoL>$M@(ymL,'ۣW.Х Yް9i˦N Xt v&͂mؼ(`NP5 ¹6L\ 쪢3Qtf>/`ЮL Āl`WHi¯Ȭ4Ȇ 4\(LΗ?QA}^ࢂE711gX>;^%!fO'1hݴϫ1JW;SRk{΋o'ߺzO7h޲s@`}1l!dFق dqlj(XTrnϻ.bj@vÛm#*^-m:?3\QT-g EJ):y%,)1{ ʼ.DX8 *#kKrG-*clE^tm<b9ʄ WjuyY#\U*Ж   k1\z:j>ᄇq~ӂr[.؇YeN) N*`Ti9!'`hXߡܹy} 4@k6 I*):ˮҝLovFҒv]tf+d~6Iq̙Dj*}_eQ?b6:]LT6C&`H-0ꇴHUpdJ"y`Pv: @7U>24öj[M%8&<=no^Ef~9j>6`Zw YDisJj[N ^3n̏p(ҤAgJ: ]0 o[Ժ`ԣPK,֨K5?ނ,ӏ8NYl?L:CQiz%~)tv7<59D󷐢֌^3j`O#h;E{PȘjN` T.\LJdz_._A"ceԳOpn,ԉ5"Cg p̺vl9x0NfD OpG( LqIvU߆饸ooޣ=^F Zn~0>*-;_ՕF|Qʛl EAP7FBЄgZWrK=4mo= Ǒn"M{PCY`V>xr6R:k; /&$ݫCj Lz{<^B 33؂k\BX񳅸'7[F12v*Qlk9y,+~r1/'%@Ryó8 6bB1cU/5<_X,;/~߮~Ƅ542+V 78*w<_.Yv{&n5 Vr#ܧF_4)dްL/ OHdXV'AVmK.TQ(Ĝ.($4)29VTj DƝ˼m>úN W#]3v= SV.ݩ>]e*pIܒr7S{n\qnͫo?Hv6 Va~%4ˁ3/n gvO_ON2Q<"FR[bS:}, ٘ ,l&Z9<(G*U۾k(} TB!WɴkFvBcIY\A5ڥ']ݯ!01-3ڇJ$a|l'?Yo-yW{%_&"@J7(B`Od)8t Da=ȡMMN(~w…F ( ъ/H'uPq] Ǝ~ɤjM+]:V0P& Ĉy' nv\Ngq ; `g aOQ;j[@.+mfj[9~:+VyFD(nQ+۟͊8g68O }tҀΗg>nr_@p/pGpE l 8B ],AW舴 TVHp[#xN]8f PkMQݸOfP9`F: s""MfKO759el E DIx=pύI݂h=5HfŤ7j"-i*7z8ĀMO^!HJ WQeG8#gxx18#h6`*j¹Vn#WTb'M&s}eaCZ=l{q@c= UdL/2yhW3! a tF &zB֨j)yn$:Ma2(6ըUkk s6?3Ȉ nRj߃n;/LLP/uv4S鬹49*̊ROstD\!a*#d5=bN fs+6(z`;I9y J Xǵx6;DI{kn&CX%J'dmf}nFs;v ޹(]~OV P -Vݖ [gKڕS]/uksQcukOE8A Hu3\7ߐ5+P`qTv; M??>kk,A1_%e`l&'Xkki=WvT|sWmO'"'}Tm$Y/Q1JHFbblXN&Lok 8`p+ąZ[{Az2(N=Tl0&f"Tr6fki)u.86B5<\4RE/fڲx.F ]Ks4X QO vk_I@ʅ,m{*_dRW;EU zU#H1GGnIK]&H4pPR;s ncΜ F!$8\]B<џ[x]kFqYO; rܾ#nCKׂgl4џT`t svfy$z0R I% yO97<W#'$qբNx1E~29m8_]d"!s)ku҄JqkXCmEo?⿬IX x-w~Mbvh4 ߂#)r|vEEtZ >Dy,^_vN 2UިoA;?o.|>pZ2cN i`ey9y_dMe  j~"ҽ ?0TI.UpYd,mXg+4^sI~=kks'͐ gwRgߘ˜n6_]J\P~ůL/1WY6+?HoĊ/R^ >و$7 n#Y.>ٕ4֑Rd kwfcCw=ho2Dy썛u,,w,C$ݩg=[࡟ߵ?o uxtYa DeI7<̥6I漟xaGM&Y\Kߜ4ej72Sª;n#.X 2 bE)Pr?uxfl*mW)fY[ɔA)tSí W8_?qa_nSYAjBN8>ސ*ؽ!jǡE`RqVC?"WLݡ\7;$(%Gat׺]~4dbv߅㎠Awa#u69$&ƔC=[`Do-d"~Ϧ^К-ѹn4KqH'>}w_]d$F>0A+N0tZ ފ4PŞ,M:2 >#tO>6Ѣ`ɠe)o1K/E}C$R9:??|;CԌTsmόHu49[-gKp#<Pph'k1K)*U??;.ݫrz+XSC/$d(;rK7EF XK5vpK>SS.5A'/(ڜkV;a)ؙɠdLJԬa+ԗ:RRH4T,`*?gB`׬ .E$>~eۢz %(U Dϭ@]Y,.n!&\7/]2'\h8ªCgx@¯ c{\Ӹ tĀdgj13HVz;S1Dj=**J^ͩ DNs̋|r/^t,{IȫS<Ԗi 1>}*`ba)1d8xofRglpK`KytW"~yqש~ZARe#+}#,51s/(/vmPbEQ7Eu D-;^[ѩ>(l)é z `^Ɏ{r> D‰:9'aO$pM'89 Miqi& &K9OZW'/mɧ  $4pګdbP46։:Fߴ*v$Fwc },)6M(6}vr9BY5Z{5!O N\Yυ@Oxŗl>rb`ٳ-\vOzW>zBa_GKPSt~r{l 8\XW L GIw)xb[D`hW?";{vIT$*4KAՊepA |(o⺚\jRjv}ݠ'$ǜ: TH9RP'%FĝvɂoDF+cHX`|m!,`[dB)w[!O K#ԭ! KzZ Rc0ꠔ)+!Nox&0q؁cVҌx$4/ʛ$Ҝl&[ Z 2s-ZBC߭h1vx UP\a簇ϥR0^[8dCn` h9-wGZ7J0avsҤ.Aw>|M;t[;`0-\cwKiF7ɎF^)S/LzGĘش8e up6fZoqb)E6octE6K+5Xs]!Ze_#&8Z5+xjKzY/0k89Iw%,v.Pi`p!q*IfG0s)-NjKR'k$S 38Ork4d"gM Gk}!9D~vg&*<S^1O"m$i1S4^+SQ pɚ'yr%k|KQb ?En0QA/˗HJ t<beq-}*km:X{jеeCE;X|T GŦ^,lmjyLx9$F"1%P|hPcz[*zwD Ѝbu^sE"xmW zQ Ġ_ 9CD).f'8yW]>C"T9o13BI@>w9%*%*޾ ^JҘof^cS,FDO9'ƄRw, ZUb`܅aь|!5"8=) ]:V}ݥ`q-$Ou$C͉CPқg6D5J¥Y=7zDԽ1Q8&F$Xw$ &)K|FS_VN9Dw;5p60 I`yu]FR;HQe}hMEd=2xIMPAZ@ip*AUpe/)^R7{-$HRm|켼ȺYP9hL~ݶV$M}'{F^-57>Ұ ة,_eiT9EBĉǛŸ̓̿CT[\u:v0积6 _Fn0^Lw@(#a'XXA(O;aڵ#EK=Ь))"Im5|`3-) CCw6 Cc;фM @kzܨȏTwv9J72ʇ>&m:aXPx.C^eH=>4+G.Jn0_p ]|hʢr3ue9KU#TR5xfЅ~'0O-Ql@caQ%ĔaFbVn+l{F] Xpl M$9/eYPoTR-JY~uv4uƎ S@r[y6K lIZLS1`}jҪLPHF,2Nk];R)E+ Rsu1rT* )-IaSO<J+ø,+X?a7bө! ![^Pvy?Ɔ[}sL)4Z6#knflMԽiou'Xݿ0)m#zc;gxէJe=È4v2Bp{W+]ylg0** fQl A~,'x?=s57{G309fDqhkP,Vm{9T`[̒lMnnMAl`u5n \'(74L'1ʟ o"|LL@. / 8i0OjbR 13V$d7l8Er@5r'oX|D3cXBx$}ȇ4Ddv C9+Nm9Ru* YR>٤Hݥ2Ө3xBsMu[M5? GDm$#nx`Hq)PP7^x-6XhԠkfKc *LPCfnFK?hR7z PBhR#0hZ";7-eע;3 ہJN ElɡmТ D%8.VRyU5oK 5(L~0DŞ pçL'!`Q_]I)_ ͥ:,qҋI~pvmhʧLWГa>|H;ge=?ȟXN. uhU{rA 7 -fA.IAzˏ"Bm6kL^b ؇pi!]?qO _nG?`vAgBX,WFxBJRojq?o 8D c3Ky":BBXڗ84(!T{1?Ebq\aGNZ) =K~pp%?L̮-~˓ҭ㋒jhM5{Da"'?.£a~,s41=0rʑa5LHxJQvB9AƗH9 𔕎<@C7;Bp!)6B}K=x >ft-AWp?fA&!$RZs8ASAxzU=Z+GCfPvM0y5C1U]3YMß) Qx#E-`*ïi-G~%yyA|uL6dz#qx=M,:S'ãY9 j͉Һs] /m;CK;m¯T~Y1Ҧ/4Ƴ XswX$x.-H(j%^wkoāЀ6XS]^|kUJ.@BQo=@3?#=zrvVtR'RjON`h ym7D!89iyLn*azW RzaNcX[ss vbS:-/2TY%d+4|4cZmiV Me&|L 2򔓤ϼݗw@o>,_w 2E-0ooޥ `ՀETϳfS/mv&[GD?p%fczvv/WR4Wy*^@͈$ގD,ojG?A+y>#~ѲDpfF pݫN\P^姷R#mY;V յ>`4Rc R]ui }ؠ>Xϑe`G>W+A^˫Wn^!Dg]-\٥J`?_P23^wE@*&p3 о٧Bx*F$M_@:PHle\ڱ ު . XJԞ نJIѡān觖l&fchF(JQ ƺD)jxz YH^E_iD[<_5 כEՇN%)6a+R d=- ١0vMbQ9rc.k{g r@}+f'LzIA{z0<]J/؛v3kJqS8۫-}lw:m,|>)s ]?yeÉ>s5rq~{['1f\9DjZJD5 |ơ)B䣰It4[} pʼn:n<-j&ܗ=9 3(J\9א孅$U .fxgD> L`}SJʚ~ 8+i@5^ Y`I|C ճWAuy̋Z N~˟a%A˃8sm7܈P} S`O>VmHp{.:L$|2P@&Rf<OAWnh9g|kM'&턒hDUᙜ:/K  VK+ң"iQˁSf{]ф 8N (h'XXW Ё54rhk('p| ᦁ4`ama>OV b^h0Ou򅠟3^q(cWe'Vy7bRJɄTɔ1@5Hg+5M; _|>jꦂ_1!chG6lN{.! (FLP:LOҮZ}%AeKd[(E =|m(!^ݎ xu; .ۮbnX8)Mzg0f j\ OŞbB &v  xIi)bYMPa`?mfdY=#k3^X[=-z!Oȅ۬>d[X=bLct,ܻOiDJS_a u+,ZV?\l fWz7Q2%ZEaK D3TߛGv(uKt)/+׈)q#8ҳ@Ƀ!2rB#<; pji"w yJ`{&xw7DM#$tb$y%4/LrRE48Я 4\$ELo߃V:z&eDlnj|ȨVzGJpsRVNv5I{{zH=HbfWMg4i(@$̑4qT4M,5QU҈P=DfSqt/݂+dS~?DDSUK/8z]HP*]DOzaIgvB~DSE5&&(.]gȯ0 +:UVG#%[/yjޥsew;y_ĆGVN^I.yCd͊sLcD|C^ `F'^q낓5X_tb 4xQ7ԐfAeWL!Ik!8poa9YH,ֈ%iv?,Tw2- #WwsN 2R;Ke`2=&4N7.V^(0F;nmءd+@E 5Rj,r;]㚣Q ^3V@Sm Q#54DC⼇Ĉ#<4}_dQX> T:"y\AY P,!@Ug(V ;o>=GFA`{L(cLak p<I(FP>S@ IIyLgGd-^dId/d-dD}ܜls7҃7'&##}SVoY  pWO3WMLe?w5~NA vd*Taш{%J8Rhd ;dbxnox"qW౞!Es`{ |!xs}BQAqjND&DJ_ZbV@`b9zx=Jޗ*les$`q8@e#1$D+"\:t;5H*2b'A'\^ \x}ǀNS.kD8p6v@T,lR ͐=耪?p"Q\?prԒ@\mZ9exț |zĉfpGu8D WmJܕ;o=r3wHj e'b`h9#JEJ'}bT468Eh p2iΉ;j7wGLcC33}B /t™~\"(ENBJL <'6'dVD9jogw2&E9& XBo}cE8Wj(5ҢF ȵu}y*uP>ZgUNvy(h [GW͌f 0<"(Ff?ym@Vf{ -j @JOχ1n+[{4Z>ZN}:z))"PG C4}K=@qǂ[*Z|a진sn_|p+SfĐ}1A;tz "ӝ gGDS:.vu$2 9%P Y Rft&(Umѝyo XgChwОjcuR+Ub0`a"إ4Wx/eo: TzRt`#`2%: I[uR`C nL3`,^Sd2) ]>;OPo@HܦENBz$z;*Xk$OمG"'.:@fMw%&M+?Y|}Jm󕰑ay1éTA*'Bgx3%9/gIsoο&@n2׾j2)c٬@}>b'4V0Yt,h쬤¨$zuŬ=_Jz(/lTC"ˊn' M;ndH7}}J`5vˠvF㲜%~@8{fgN([fA?{vޜK,/=K_xS? h`tKWg|C ؠ/m@U>˨K9DcP M0#j뷧=Qd큻(1퍚1hy.8A^o0j u69 `R\Z$itLBfU:%yw%Wސ|Ys a̋{C` Wh1㰴+ccC80GS؈.@Qr_DjpԀ_?F:ezPJ.iޱ?.em s/[]/i o%5HrI0/Iˀ`cΡ>S6̥6kr;ntѐ( B~´жIc0onW }nGҲ /qtz)^T2Bq1EHĩdO(nnuA0 472Mv[ʰOm>$_]7зD-oEKZ:IvS;5#.~+l=_tX7 LPn ^|KSA[3Z$P)DѺ;l1<=w~Q@edXBfyqNcOZqn؂O #H7&&$UNDÆRtvL(٠8(^h:ke#qD '%fxKI!})81Vi# dgRfM2I.K}QfƘ$r2 @G,AHߏe$[ 5Xc2wА7G#JulQi#JerP_pH́_'5AOtEBGybT;S(-pS:!xH;&A-PgPȵ>`kIq(Dt7B^G   " eOO=)ۆϼ`dT峛.sh'%C2qw]8!P=wl 2g-ofX$iLX8[Ѳ> ;}At8q|7ߪB1$$w (} jn1Bz}g=> O @5S~rkԚc4͵VX9A)ϑV;D7"W$|[=d ~MDtVu$݊0ǁӔɝTb4|k/4 Y=F#6잇㐭C1#d%@b`R) iT{8(e៷w9Eav<8cͩhz4srkنQq9hC]>憨`o_ ݡy2VJ-B*| FIZs&svYo1TQ\Fm'D͘O+D{NB^up@B6DsᕪT>1 BDUB7 =+\A9)!؜h.4 {RJFk#**4TVx})S\z64 R=)n&KLFʧHAFEj6+ұonwCi䦰4%G\}N_ȲU!xOEySM2KdݰUU+ӍJŪU#tfs[TVso@qaFƵci4MY%E͕p@ZX1%M4x{.=`t@Wk-'^̉rBQ4,F8*]CƮy7AAf7Q['V(&Xœ+I$XmvEWV`Z YZBrt,w˱094folOY"n *J%RLt=x+<7 8 F /Wq;ʦX!t3Ssx^Dsd}J$ܥ(]Y=u TD~#t 2b +ZCU7!D'zXF e )yAc%`4$ӝ] D.搈D@;G;'ה촧X"Ug\2Kݨ'ӿ)9)yҞ:xUPZܜƠZU0 2:'>UHitbJA %賦\"}N$'X$_0Pvb@"D !]@2Ҡ4ZP:Ӱ`!jdsgv{RJ N7%KuX ;6FsAt^Qtݩ78rxXUX6à939)J `VqAl:0RW֞UOixg74+LkE{hZFNtdQEX^>EGz<7BԌ!S,MjỸ*c]to%*xb[/egb无_ǯ'Ӯh/fуEQf&%2Yv 4@*39l"%sN d>7 Y6$yQ4R*ASVS/5M!%74@$ܙ_M0njCqKu'>q3zukxBM.dr< >pcԿ8JrURV0"˄w:k%PXjz%ܑue}FMgOOiV$^v?DM©@ ſy9-mt'nk\*k^WVv .( kx65X0ճ>@,vDP)AOsAo3sta]Ct9tGkoBxgN_n ϴT(H~7dS' TtCq?w*[>8zAhH;(^|Rw_`29=" 4P_hg`zSú 3 lyp#\#Ω^" u\s'#@N&erA[&57ZT9J2 0ށF"J]'Iᑥf'w9LfvMmczU*ܟ@2N++LQ fmА#a`*'-thkEVl6x*PL겔{ ox"r ,NL<֚!MSmTx3](Pv J' 'S0Aᴥ1P$pAبh*YyZ]N>]P/6p/ra/-b"RHհ7~ 8 w- ɕC9:qBR "=}݉ ?XdIJݭ$ R85%p&XO O\'ˆp |N,6iL-O|FP؂aa赭 KRoN ?td䔓<# %0d;E;3P9!|vb̲9װF ,acT9b_aP0\텆 o؈Cy@i;5C M *wq Zȷ/졑v420"C&',]'{O,9L=ZKCr 4:@ݶ(ÝuAD]r9(=JH,-wK׀tr`%\*ЮCj PށzW?Ic#.OO/?DČJJOhL\fOlmtȺcQ襏>V= D[挐`b/FE:HHB-I0Аq-etDҖ\%& " AHV爌*{ӄ>jzGY@mdX?;ױW١O,ڣ^2Ls&. )6@z,8DR=jU{(AYDgJ%P'',Ƶv/s{hp&6IIb;ɨ$rc/gh%;I/>yZ$#`!vr|S"X9peT#@):H d?Q'QkTad9ePCDwJ#Vp mlsiem$4hUy磺Txs%ҝ4\&>3X`vяvBkĜ!?BiҺ J"%#eӄyv@;NH&G褴/U.: dB/;T>z)y\ %ZuII~w 7ի=UFⅎ_p2̾F%mCR!18?^> vSzpNtX;gCIäxrWy&vF|J qBװv0)5ٞT}yNHNHH|TzM|$0tL}Toyc  Ͳ 2e2,rR7 CNʯR K,ju[5QIQ xVXDTixrc0縧]E"7KamEێ _< L>Jܭ*/|xJ1!|*]#a!ֲ9%KӴ!áY@0zi䋊ݶΛ@,ES,9mAhP~{b-J( ڏRN'/k)qnDv p>2-g+Ɣ~aҨ6m^B684zБ!%, Bh6m91F%|&6y=/=_f (>6)0yT0LXFL )5pH7XdZ*M.j s `*n;ȳHsg)p\o١zFJ( ђ4"QQ02=t OJPO蝓rKd Q Ej?@,Ti90lztp*SkI. (̐0PԿ#6,!]~Pݳ1ڶ^1 Z⾯U1$I]p$G)BJ) 2//;VjS]H.SS ݢ!Bia=nF֕JDG2Μŕq6Fc{ߤ)$IT3qHQ,T\]*b E>lO!'?q@luZu?YAlnגMw:ٿz\w,i1G"'+VVAr_his֘),C>K n3yDhB=MŎMehqYY a>a0͒"ZZ͜@&.1^~m8]]ҧ'~5P.d8$GYJvݞXxt}Y<%/?2`?&e+֨o1HD CbHjX3pU ɓ磔l![g̅fXR~AQ00Wn,Al@N,V3AR+lrKkDݴ{*u-8xu(RLMh>@GL8C^W&Nd&JHjgdhu şE@~?RB2dYcs(?G= Z Q.ߜ#xVε LDP_)uf`X$MSZf9qQ > Ss=:D)X0̖h&٢irQAHnz-|R>%",[-ev<ݣy%G 2J,8 ͽ֏VE߹~L|cqyCTgί80(VK+R>u0e ##K%"r#%)LRاO^ݛOշ!2UG0Li e?(p:3 GjNb@($m3{}ކ_EhHR!S(zL |]A/)ОN&d\WBJ(1ru LNJI0.JBN!q 2F> &p'-ꁾ/w];룹;P`% 642ޘv]?Γ+HjEfGܹQ DiIԝ,O QOe@lpĐc]?;0M#Db<{|m7:%row$PՒd.݃$c =qKԅ%6eLpz|cӔ[ ZA+-R[Њ}?%@>ȃ4(od]/ɋ⯃4|MkGc~ة+u8VJFr0Rh{TUBHP )<6Bf jEk~vdDZ5 $w=BN P'!Ewb:V>H{|;̝7'pwPe@O *Q0*iµ ؎)I6aRDzlb1IF)V4h~hG_P04}F&StBkV)1 +]R7 چG6 Nzd?m>eI1a+ jJ:*8 O@!8A_h/ơWܖl'_X}H>i}Zb15XLW}"3EVjo3qO Ǐ_$옯H*.$aBINl=xCNN r/ReҜ vLAnw+$SJfN?K8SN^t+NW¡#wt`J8v2$ޡI?$ի$+p?Cn I7`-H^(1[7:qMUea`B0#28n7~T@I']cU/3X$S 2F.U jVܤBj!:"cd¥6̦8w0ˉN>M#np.QuSvFI)˲l@ ͽB(u5 %#i^Niq(>q3SRN6b𗁆=2MEN9*{/Wwh@$Mw .t{?*0ⲉ,L}{mT+3(^gw=Io]BG)5SK6Eos\I5 ?HX/tKU:z&2qo^+kVn0Fᕔ2|g IT*N lNN+WXK1@TEE )G-?xYN/e8t@Y $Eh Q| `-@8HE$ShTv*vn U3P@\ b)6fJ<+N/-SZБ_L#[b9`^9BK!ڲ3X.Q1oa | fdoį41FS(T_3Q</Kk KHaO*-X͇Iwy-a$H6n"_Oϙw(SPSWt6PRx!D(5R)n J*qVu)Eh?5t|П}wKARǙXX 1`gx(N#z<ri3?DHLF M @_~\| Hd@ayp9Hvd ?G!"CN#:1k(3_3*ӄՈu)zcfSF5\Aɭ@mF8,ۚ*цHN2~HtZ =Ė@RB2bK@h;.s[wkl$J|H*)z20<"a!7$dLe ͌25wi[/[SS^YgNN L}E$GMLӰ_'ip !}[\U%C- utb\1E0*!+e:淃Ԥ8bbrZO=蚺OuO,THb zlŔ3'=ayMRXmĬN1R.urJ ^84v]M(*\ 11{X @wq`N}a+ CGas=%HCj( NCSnAV2“H۷L/=9HLC[kآk,lxU 6/O΀\iPҨn臢`*,X4e2aYU/dpBzЈ DA<>(ҬHh =e@$dA vHd%&NߩI"-.N=P'N ;.wY %SR@+v4aXm-0 "}Va]F_^-F8Bou3Txa^At` WXA)e}t]$ΈjJ25 .eCT8RI֎!y K F%kOG$[MG ƀh,!:IG0NdRwc!J% ݴH@ zy+өS-:k c(39as&"+]c1;S] вaET !'mf|Q5xK`vCø}J10vv#B Ʃ*S5wotXt0r)6"4&-Bobl~.C!x-aG9<@VHB'ftljS*ϒv ]Eӈ)dufƏBiJktKrOۏTwYv>|v#a@*pkٚw/QNiuS (iALܼݳp8~V$}A׳}͕>uϓOmN#>/agq!"E2Nə аfuCX mB=wpLq5j'L]@idhp.YYZR0}M2N1uR89s8q>ҚcyF*=ekg]{%ۺ:'!*?BUH ůfvLRP+Y/c*U,&5_eaVGZO:+)XdAP„z yE~(}s)7Nx"-!p$|0}wMuD%oKQ]6 c*}fI SlEefڜ)!ӿ(F4e8 ןR2qrEֱUsJqnb Y.XԲ112GBFaiT@I ߸K[PRƟ|吅O}Z6xRDc{!3_CSQil/ʞTCФnrvF!L_(׎c>6i>G:Y5Q!%4Pj)T>a.˨*%EMc7yOi.Ts# P Y85\p֖Bn  +uXj5Q^P- A,h@ؚA `ishxffWߪa`S TbJa, E:GRe Ka )_xns~y M\z*-pZEೆczFυ94v\`pv;87"3 C!Td_%S| P(y{.kp(tŹ䉺j[FL E!1E[ \zit`C]FOzj9W>"P.v![T* v^0s٥ #S]@+ft%F/*pFLcޜ_/ 2m06+J uY4Ug$.aYq 36!d#AmLqDq RƹlFqùcRFDn$[8![Қ9cں[{Vz8WRu#Ш: S'h "}㴞ex*+D; ݙPp6'X,u5Mr TH0]!Ǔ u<)mD]{r_<% NYf_[fɝbP 8jâdV1uRٛԯ&sprU<T }7ZBT ܍%qɳICB&N?8$8nt8ژ:i[{ #eQ]i/v@DרA/sct|bBaIJfۣY|50_.; Ӕ@]}ۖSyG$Vi8 t.pg ƀZ$RrMM>27-iHPG$nN9|i8 :X r vX(尩zxXT$otB Nl ^'@%d! `,e97" znjxPޮaD 6K!62(=ƩBAH !:֞BK =@hexpWkv&јPrS눚s*xȔ|3f _]bЂ).T7'zz-%uSZ}yVf%3 Ӭ|}H鐷HS%TZn#Egl )-_~e>tZ4e#H͕eejBV(J-#%u*]re -+fM^N, B(s۷hg$f.)L <=8ꎷHξAKW i!XK,In*m MXk_s䙟&K06Xl$ݯ_=5uܑT`iT_;Uc#OAt,#3)ZdN}YdJ^LoGx)m+mocik' AMXov3QF⁩*~n e_SJ DdZ*ZG9iU\r?n#H X m OHĜ|4AO7~!QvĤqLH)1\)y A TyhwY/z4՘ P1Er%"tRI~C@qw#93CAHT "ݐĺA)S' ^xSOͫ4k,3։sBHL!nj$H  6/$<2wǨ & b,$ G*4, ťgB|{ÄN,ˈX%~ܓS6yS3] ]ɰhtu'<{Z$üEF0yMts4122IڔTgѼo|c?Ng%~Mj92[fi N4֐QKfRH=jNp̄^>@}jR2-H{]kԢO{}|[ևUYUtg=GLpk7s3[o:-^? PrC">ǁ7Ma4 :iA3w')*Qa% o. NZ ֒'f6gFKK-GndݪlN-W<*t`ȞUQ OcrSgtam@ub`nA @#Hw/V [5to ^ᧂlsv/䆽 No,dZEŮw21'A@.UERzjmiP;EZ6O I_Bqw袍z3-|򼕾:yU\49*Xjtdt4.b-7{˟3gy!py_s@Pm_nl6k.u-Do-5%U~$п"(zYN"iU3-ѥ BhZv:@-8P咊(,g6]W_2"a t*eՄF+"IH8 +7#I45&NH>Ftlߕ.,GJCct"OP#cotRWT )zE%dܖ2c.i2gȴ`#uޫeUPؠ[&vN[ٚ1w5brvTܣ^B~U$4?.ۥ0ͺzWJƠV2+FE-x-%nj7 ]{=b.J]/ B6r1'/:$[/"JQ~:4C Rm0#/mq،x7ax;escb[MV[TKFb ڜbZQl X+ ͕$bufMMNV%(-Bz8sMCQ %tYg/sP C'&5QTXþ.͋̄m Nm;q)16[,Gx̓vd( 3 'FbK@ttzS i_;!A}(Yŭ2c|ӳ AĀ-ʠ/UR9~4Exf]|M@- @̫kLWq0%r +T(B\[E%z XrIIw4v=bGd]Q+A c[&͊0v(dVox:#(gNaV@|MrO-&<|re,/ɨLRs2Gbj̈Pdw_ !>ӡk Pdr*&65[ {;'k Ym7s0q.d?Amᘁ_`RAй1Y^x:F'/OÝ$ J1]xÙ@_HH LDr&"4e@&DϏ xY_$`o<YiHBC8{zA P3Ig35rY'YO:wP؁!0VO_!=2R>z+؇4=S*I#b,{.0@3$m m=^Y lC/3AɞEsIPtZ  7Sn&W1jz.B,gd$6c~3QԏC3!{UY#vyXes?2=#˝'ȁz#J]ѹT'$n}8Ew4n!Esu۠8Yi|q&qbP ꩬ@-r.zh?󟟭RG-S7;$`IMF(ٷ;l{13unt6jΩJ#\>WV@ڨ|ӽț̾}%+ Rệ}}elVqJjpO-ф#:$+>tOZVxv G@zڏxlQCk?\Wȅ\(z *̀\n+TOҊ­<S3QZOӳ{TcAIk Nd٣6Ej(v )} Z͛=l/G?{ZuYԞP]X`!yIˌlYr=dl=@DL]i^Bզrsd[W;=*鹑"Y~XĬ[ *jJmk]\d݆E(loq*R6qMHG،Қ{ZMi@Q[ʌp.$~4^vHXSje^}jp{uSyh9н*T5[15, <ˣ^5 G8iN+fͻ"o/lT UZ&;仺tDƟKm*J_:^~~SNBB ?V\#7vհ|̢oXIKd/FE`8U{:ʠ֛ˏGAfx2b6mz܀Wi˵%??w|W/&iڔa~Vt?hkϦWYGS4 ڬe?T]BT+tj&R,Rtƥ !!fT]xB0APa7uN^T#(xBhoӐBޭMJ|>C9媮<DB,X@[ *"Maog<ޯ!ScCbqVx+wq۲~ѭ;DW4:U\`CJ T͆YlS7 d;U{Y@uЙӋdmCs+4ٴNO1?ZY+JE`'^g*p5 B̡ EZMlW*0{d"@=)[LF_9UjlA6+PhƎq(?p^P 1+h ?d% 0ɚP^tEu[+KgW$>^kT\`_G1҈(OtD+Ϝ pyt[4(lʳ҆Kmww#|Qġ2z4[e382zmrsRu*XhTn<gc5CJ}>ճ ɄPTC-:}N*ۭ,?,Njq=y.mj7>u8E*O=k z*4(>OCKX]b[ZB`|iz[gn4mw<=iUtEY&mZ^WdaF^5XBy f|Щ@:EcwQ > 6wx^zZjm_ð$/6E`)NpɼTϽJ'"-?a[NϨ@hV'w`PwN(/Ŏ`CsZ`i֗l\Y[3'Ί_jxZ~@SDt!ԥxr5nS~ iD>mhܿU)it\7UDpKki4掸y/`c۲uHrAzOP|P;9'myJe)x{ ;q^(NUBFIڡMnQ`A5Orx6M>' f wpʻUa)*럥lg [ = ~]'‡&B' t@lvc=>=VM qE?F?aU?N|γAE 3@ &Ù ~0g^VQ]H7.KH;wVP?X,~#[Ӆˏep`bT%viJȿ!O9:az]N7Jۆ9eT;{1${d)Q6pvyЩ@?ߒOzrDS= _uouL+-/ErvC!I? `MLg ?0A.ұN;{k]l/9ӏ^[!/ BtT-{)<іZ1:]}:ul_aJfmmTt`+ ?BvD AHؾvN ; I{"jo;'^'(^]xvmj6^|w;_/fk<'e,M#26F}E7,oFR?]E'-\Փ6ŀO?uq׀wD9efETx.899h: rD\Z/!(R.qހf<ڨ )Bf}5:G,@? {BrͳkK(7HIɷ:&V"XP G}.h%EgHD$JDBkajQ+w*n@¹Mgj)*U_s/ ylW,/c󯶬UE)zCne(jBSkoZdhPQI͋ƍFLXü\*rjDTu$ ( (-nY%zL^'@^+0&鹏;X<_~^P[%A zۖ6*G ݖg[gI=:$E]P(ĢB&2;ŻQT lSg7e'­67%gOehJi468E-_Y RaBGHo0Q?@Zl $ȻT((&h}NAT ŭ(g y 7 1- D #@\>nzNns-?ؐМW5IЭ+-i(P6 {mo"eC`JJX`B%=aǦvu˻SpF]@8 [3/tHsx@qZMu9:#q>LضHxĜ+Mɧ ###i~RE-K*URYKzzqˉB(Ăx樬 #\O+KJt@p< `E A-~HӽUl'%` Kкb"ψ"bK9dlϓI2h CMЂR{ -eοצOC;ok# ZۺCR U3+K rǡM}^V"\(jK6LQ@DUށKrcO]m[\%4*Q)|C4Cc~ZH &X7lqbSz7Q[4V S;u@+ EJ>.Fv(* >eVG#xLΰ|L^KZN.crˉ06Ҏrˍx+Ӥ3]+8,Q_V}c|"#8eVp#FTHB=!!iӾ'٠ @0|BJrJ_2wZC#zhhH-H=Jm)ۅ~ Rf͟8F3u%'q^'eЈÑE HwRO6>@ZxfЙA/:D|zA.$c7A'a8|PJ008-`e νsg/t3j`pw\NhSkb_jTV yMawO6:2NjrswS J0,| JQ-򜘸!4J-xB 2Y@|ȁD+~+5MU`<_NWxD2WX| 6J7U "RR)dL3̓ڙ-'Dq)`>o?(WX BQsuDk_ ؈1+0XחSooz#Un!Eo>So?[|)p,tfDfFtwuU%@GjRmuV0|AW|:x%82XKh~K`Le O*ĒwaӘ:ɩPw]p0-Rm9 s'3Z@_h0¡x8ezoXE>OF5ۀp7p#dys +t[ #歕FԣJPdp~x R$%!!~:C3òJ$h# f(U`Z]8RQLF,޷{81eL& abMۅoR@5𫾶%9אu@{ B}$0(H_ %)֢€X086(zI{e #_`Aŀsj'n^gM~5e`S09 D<`U+Q%|7R c(U&,^@;(ɩxU368W}sBD5Qhkk`5JZ9alC&34jk.xաibxE dW.!@JQajD$0JG*$ei!}^BA ;>Ԗ:c<<"&i$C}{Caxַ"О0 *~#Zx<Ѕ4<{ !W[f58[;M>t]^`Gֳ 1mN!Pvy,5NZ) t`S CkUʰbaZ;Lm7;\Je bNE[dJzI4VuXJ- 52f328$=O{> u)ZgPkLٸӐrX&$?KnZq:@ulʙ 1}>ڝyt7FrS'DDK\K=mGЭG2ٵ)A=_Mr;{H2F\xo T-@PS&IC^^p4]_aIn|o{TDv1).rcBQeߠȈ.gJ~?}z }8YJJFmAÀ|}/~9g4Hml7׽Q 6Pt#Cs)Dz0eFR>_ͺ8/w^yWFGB07=x.fb;PG5#D F/nUj@ӧ@mvƒvqNiWY~*okJiP˲Q(̿ ?̾ `+߂j+Yy)GgZ|Xv˵^bdtsA= :m$mHMSR'l1 Fp 觢9\@(DjI&$fiIdWL%ؽSBx+WWg8Lf * hnd)(A cvȲ1bN4 oHe؏,D(vVe#6Hw8dUuIB̌[ŷɣ p6TH}AW^Rڛӈ/xG NؽE*`9u5gNi*{(5'91KTZ-ëc)De$bXnͽUS"%หalOy !JcrJ6}<^L|'f \Hh  :̳lA-6Zj9_C|gذbayґ^`dhn&0Sr]9K>p,-Tބ"}A mkҭ0mjßx]6~ 곺M_/-8yi驩JWp eI -;H V Û/D2^"`754Nni O|@+ Fa_-Ԝ4NU'Qqw1 \(@Hv87@ʑ %]HiXHD^ulg tZֶs_IWf_P 5!%ƈyF U`@'y]Dz[ yHw@R 0ō$bR6n{v+.zӐb<@$7Z~P`!IS+8%F4q:wJsF R3m.?[k=q]NjU_z*RqO-.÷ˑ͢O&rbN^aD{Nr#/>ClNB*ýz7yﱕ -o]32M~`ON`G,ծ6~DxbDN8H^_abH߰äϫQxT| ^ >o;զ-b!!AH Ϟr6z+G=.3">i˵G ԟ=s1I0ĕ)OF*U*@>mU~A|-V~ac3OSY8U '$@|ҤdTNfn<)s9Gh4$C(\QTS*ߔjVfKbM)-t F' <3|XbXq> ]^Lin0ff;\HzW\tr $-ݎ-MWԊDj- }SXz_^tr >&&!O0,  [; =„/GYF-xMUUy%fM:\Ev(A{Hb @/\LJ?h9%X $ƠoHK`M)03;Ēvst1u(0EFI5x@t2Dg~bDmU?Cp_ANLl!|3+Ϩx\geuz8JuY~JvOBx`= @,F%/.BDtTϑք* pXU:Evg+C"&@hM+ ĭ`Cp,NA'2.K_]sjo-P n ?\Dks>I= N08fyZm@-X\Ddع H{H+,kO:`SE"dD2Y %/١U |.qjWOn wZF4 1\YE:L"lA\ph"rP27*[AerЩ9D"=+OA6V YL7rxUy2N.{Gٝ"8Í"]ߨqvZp KW)|'ڡ, tO H9SbPREjΨ  =9!Ȝ[Օ륏t33:44{N:z z ~ yo4xAPcpU I 5iP:^^:!-(Heꗊr;\A>o 0g/oB ML(q Vh!/zNA[;uN ޑGLĜ/@Me u@g#({ tcdң)DHG6"]Fssn`QmZ-VAVI0]>.v3%7ᩞ;ZI3ZfG0FFrS\"9DI A,##ލIx3>3v`DfL3bd00OABȞ#db3 g8Iv@A04f07%ƬRW[k&=3dֈ۸@C'+ȮT~đ#oV7S#0㐘R*0Je`J'֑ %= 4:CAd 64=I 1&Fg-'nPts4 vUXP;TX?n2":,d$GDh[\HHT! &X}C~CViDhv1VJW'F}J0'8bTV7y-BH(+7jrb^4[?E{tN№onY[KU9VLE:]9ND3M+36XD2jJ&Y j7qLg E]nwU =%zٓf` a@`:"[3BI I<(#Rg%h:$ӑ8LiQ eUv,y;\L7iWR\<~I DZM'i""dvҽ |;F4x굪(p+o5(89K,1{8'J1/3"6@Q$$rJs@B1M\Tw Uv# f vsbtb8˅d B:C]d5POMfּdr߈P 6MM&BV^酺)19XrWZf]@gsPfu4rU;47P謽у3SFo:i~`љͥL t?S=Er#U'T~*iw{+8Rty tGDbHRxJ};KHy#f` *9$KOãg;$`zvUPx:qy|BtKuQ+uL?B ! =V]K걫DRj2h@k8n@h`',6[RM Ҫ"@eLFR[\a$v Q&l?I@~$^C4 ^}#JAȇ`d艒cPoXa9UnYӌe*|KZXblG^>/1rJRG c( xuNS)quJ& 9΀`9ŤJ, 0/U:nL("\7Bv^ nuO0*BL sS>dW km# d.*dca5kPIJq0jΝl4/?c{00Xsq wG1EsQ6ttBR^&>/^h?'ERBNFǢ5KE$2\|q-L)5+)ZƩA|mVIiڟ6>gO#Jr 9#_~c=+tKS< LI<$HJ^Ʌ{WɾWG[YUK6n˩@Yм]OeuhP'A܅ ԓ Uw]A"6*MaI##GX"Y5]O#Z?͵!Gʺ&ɰ !(T{Y(%JB7KTA'DdHMHTE&AӐhuߨV%0m[5\@-2qD6eJF6 2ԓTmJkVlj 5uDR3IV)yEid\`VwOc*vg;,,)m2f!+!IEjT4Bu0NX n"0[!DP@C^HqZ21l KBM q!,rft!;b"IC\1Wwf-25<t<dQO}5W/rW&qɨt|A'bę`} )#'fu9)GPԮ.{5Cx"d<^=~C| ̺ n:C<YQKJ},A'~Q!'wRce"SHS)n} =:1LKg;S02AAFz?aoFg)2!ZH's5XB=sPnGNz?@{NFAN&JeW0y<*D%LR\'Q"+ D1y Ԯ۝d,u?Kk2$5maSCzÒpRZ0<0-RlAR0I+*B[DHT(V+oxJDت9%2@B׈ǪL@ԹȔSIu/7!b)@d / g2֋fl|TX_fU}Sq瀋0,6bo<ZQ3'&$"`MCFBTR*Rz/9Z*:hoGqw:(H=%M:dӈX8SiP"5=KLs I4 ̖ ΂HڿY>pUr65ȽAZ(e{-C ~ 8 "Yj2]wp^WLdD`؀ ;ɷB;P`a:,* ^,IqmD̡Y`@a92'hS99Wƹb[\^EbJD2} ] yB*N.b TkUfĦ…;5\H{Y-%B49KomǧXULϠN5B ~,Cɲk`YBIOqMw@Kh*T gJaʩe`)ߊ&8Uoahs k7*"2,3:P78ou8*@E0d_ߏ3.(#M[I2Tc5^>mXB_{bf*/%]?"ĨsaZ@*'|aK&JQ4u)Q2f߿*͓ iש4T0M %ԌO&:!9~RZ0稠f=gnm0w乸>K:#97Dp NFW4.|pʒ=(l'IHe5Y?:?:4Swg4O:45@eOv<]QNAuPK"zJ B=ϧh7 T9HL /pT]5?ָ)i};gK*$5qƑO5FsտX1?/uw45sZ  /mSݩp$6OQ4&i *O_ܻ: e5mڮ~Nd% ~v.0_{rjH2=SpRF8{5NfqU֍;}ᵕh-S%U^T YQd4:"ӧKe[N5=uT?V[v ,MX*Oq`LMBMKQ)=a%PRJ~^mtE5rB%Z7شNVO? kQR?gVg/%&Is܁Rv=.ЅʦK9xNHwkϪį\\0:{~Er !r)nq^kt/0MʤٴOH nsn%7 [7m v16S)G#g[Er D~J[`yO֥˔rXvX^NUO#{P1J S5_to1dAhbS@+Qk \ڼmTm~Zָp ԓY rxӬ[(PPBbvaXdr/H?! Ec$Yt͢q` g[:P@^cp0s O f$idF{Nd]@^}#)3eLԈξCu_Mj>)N6=eF%CK}mna0zJ(\Ϩ {U7۠nVD^)?5 j{m;_/tedx,?X q3ptwkn~:$uFڝg !3=\X0#<;"MAx^YD`ZI$p6X c?[N\oKo?%r .x(X*O ؓƚjd"U?J'ص8 zwOJK`RCkܰ8.wZXBݲJޝV4ujK}TZ`.@G1@0>hZdဓphTi "UF4|C(gQ3aurn3Y _]S<,7|]:gѷ>=WGO9;XQ8US#zSCh%̼iz6З/g :|iOtZ \O|2Y6 =ij6P'x˔Qn-Vzk@XMDYco4 4iO8)۳˳;gtRj?,.q.oBHML^Uxt !ǷvUkBDO$ |so\ _KVd_)oAܢnFÕvgY$#P!܄LF@B{ oj[Y ts,?'\>qvKUbV6ޗOW&ifAFIiK#ݵޠ#h0W|EY%`o:Эn}F A=5?W_{ح+#S&[\5z$jⷰCn\rDw-OvRsِ[_e$+GWUrl 9[ô /&et) –Iۮ;e* 匆4?rm*뚻\\[b- ܙ2Sc2TUT3)"޾ ֵ*@ ]mmJln$f.``Pd 4+$_]hgv; FTPO$EGsW;TG07 UȾPKK -h0C;߶~IPK9wnWFQrwi**۴)@3]}^ˈHD"HLV#(.)vjN{I&?'/@)P͐Jy:u`@=cSI"%M^0^ělcS8P͟@ :`CV8+;ε|u@IeL) |Y< ^k>LD0䅆PC96xD| `~\$o2] fˇ@0˺TԖ&aM 3CEk)ں68!Z.1c66Rijі3A`k"$ja44#ݣDp$Tdb9&vo!G.|t{fYOIy$C\d'Ld o;f! 4^\z5Ff ;@%<`;YR8&d س?ei5WwOU)U9li5|rȮ|ϕRk•(1.)}[H{-S 4s%8YC YF)a2kb*z%vBBŠ$ǒ!5E?tьofuuIݤzXHJ.ŔR!{>V>97m V(.Ar>ަDhKIs~LיQd-˟َ5,3S5߶$,ig^ Sn9w\dM4#W%|S%6hK(ƹLo\S!<륳PL"Wn:fghT+XxڙU:CMTc_LD:#Y ??GTn!ēf9mZc:؀o;/˺ 'd^Ԇ/\ݴ6zQ{Vvv:\5-]d!1CԱ,.d8|\ a.]Y+Ėe-q5EBJ^pJ$<<^!r$7:C7]i6((g 5)?wvyP!J%/SZy;k-2>i`wI48`1^Fr,ƀp|vy"MB}[Yw2xxYr&1G)9qIDzW)s/;}綕RΡU LBnqKi/f,ٲRTx_m7t~5qᱴ?!&αB4 yG7˛GJfAib*Ah }$:0M ؔPhu?3B:KV ^L,}ӂ/r]mWyg(c:8C̗;5 k9M\kR풚hJD`D\u+=뒇  )3D͖K`&@VY%(VmCYfC4uT%FHPT ۜ]TyG2;}1Rm' P=w(lnX+t}= +^a:#ͷ~)wIY\pj#.e=mN#gΡ ^C LB+ВPYhϱKi=xlp&`7 u5f8pN e@,5{.;x_Z5c*Vq&H=jLnr I/~9ރֈXjŦMA?GuWJ=O3R`ka6?jH_29 F9p.)ږ:4i!h3)Tr{6DygjQvMf6VwtY`ӑ2qk[s&F #WLNz[ө7vR \ˠwꜞ $_~pӢ>ggiivoqNQ 0 Y56.^=Fo+(]7k~&*~31HN޽}Yr7_x'K% Qv|(ߜA>mgup7 *Q6_vZB 5OTjB2[0Ϙ@Lc4n5:ӐPr+ (#Þt³kA-9~]D -IVÆޤ_LQ x( ֕`pb y3j>=2Yo#F/-̝{jf%mˉFQ (CIi zD÷| \%Di^T}&xg" d3Ç֜W;UT+M^I$QkUxCTֆdEBt^ =[fj\rU-:C !8us\#?RWD?g>}'(7}"g|<3ܸ^JGG~R(՛#$*8ڍHp~!'w EK_e{ tj2,30|:=G,{usa0̞.Sc/.iO#Ѕ hwD1`-+mr .6LN\ivRr%/7l2Oiks(6:QМ%@6Ses ֺۢg^op@M*'3~aɲ>UFۥsLTj};/>c'8h+mG@Nwd̴Y.+^Pz;DCѧJ:^eJ'ۮoxp7>5|ϐF6n=Png{pɻ@1Zˌ]=5mϫx~|[}d10HˣJ`Q/(p[ IWtVA ݭF)H]@WQ^)Dm =,le xV%?>!(cҍ+b;U7Ž j iva͔x?r<|[,ZIrlv0-t ʶ;P_yqSdcɈq-f٭/;Btt;3rDO?PhxtaW5 =ַ\;k sJ#˝41wsZU~08a (A1t&Y(xZsa3"L47M[s[L^71\|\ ϣnM  Qa&(7ڀ&W.")?zˌv#7,`|` zOek~/7&V*TtckJ̾?r_/FX 6RVwoKH&0넦/G$a4Y]/ROk`2Ϩ; NY - ҩ9h+l͌pdk #F4 Wu{ާR 'zTԝD.a|Hg$J tZg7SfIb5ι?|p)Yq=nn왲E5j'G`Mh$>h'Y&sg ⾝uW"!7. pl2bPCN<{ 0+{G?ZF` deP4opǒ~)BT%5i@9S8SMVYPs?ԝ/+Dn>%H,h}O]9?r]K(|C:5k(f{>jSP޼MKlpҮOW Hش_IlG~1q /4ǖZxjq) ]Xv0HO|TSs^n'zUÿ*Ǹ-̥Mbj 0UN&LV,'pywdz4gNr7@I1Bk*ֽW̭Vism%W쨷t%II;i R}դ>hU9wu bA`SG;0g~D'z"%xG ̹i/m=Piz A 7G7XE ςP2=tM> q~ԵꥄJo.Oho3J7n[N?ՇM& _}]e1{Ka 3hu{nå#o4*á#2pAӘ\YD~>J2T^D^36Zr7cS".5wH~@]KdQPgHCٵEp"=b\k sҎ>甇FJ=;N*b`vaݲ?jYu."TU@R+s;`ǐ)px(~\C91ph_pp aj΢og]R#&,# E-<mN^ \io8b!'.9BuׂBq 2RHok3/su[L3`_d}n";/I,f;aٱ/,)WĶ/'a|nkUWu7nD"2L5[& ͯ(tJx!`ւC څk?2_' sǯ蹝T0rL%#߻Yj33t+'3_/4WؽNvW|JdJ67z+E LOڥ+ێg:AcdO'L7Ւ~`R'J(0> Xm XhϨiS.28jh!M9 R2AWU>JksNimn#,_A=_МP~hws&!*.G\i.r8 ȀY Eb A#MLD[w\٠#(% bK\xW]I J8H8ZY Q} m!0KNiDf|$JRb ۹ @'ciGԑ%ExO+pUh!zX@/~*OxS`Jcezz hq27BJB~YdQBM'B 1f詡mm)~ 4;'X>EN_I+ޤqac@<*L0.Ud1gIltcKQAai|77Vvp/õCa *Ķ ߶~*Ws -CCH=d5`Yb-h)C|}-4aS¹=D3л|7\Yr jy$y 8QNS3al kPe}?\z#1 .i$$SpoIM!Brn% osJrS[OP-:S(>APS598U_L6&2wB\#cL*IzAR-i4IncK2{C-,sKL^L[>`ǼӧPbDY䍛ßA ڌJ>Oe?_mk6Fx%Ы2<C\ZNAϏL!v@D,l=vON YC+n!p˿$ eL;zW!+&'z&a߇|V9 n ^:PX;N?ǯ_0~R^KG-W Gʯ蠞u3d ЫГ`O;9`D@5 .;kVfP!CBڐ Xx;\A8pĈ;mą#1̳SY[]ԞB˧F㛔9Y„ pzݾIk}PAWbᓀ梨g-X3uo rʀpE?s )|'ŧ,tt!¡aHD՚ƒ`0 a х'U' 8̛OMq3i"|KHf H0}()@y R >SdBq6A.%iP 8z6gM t Oxt BN ':GF,T؋aAk5شLΐĀ8e# 7"򖋿7M>]$Z#=?@־2-kXC]#Iݣ؃j0/oe9(Tn0[薁kT$RьFUzW*h9D-ۭ<R}(.ka0>C`i-yEL>jVm&^69r1͗׸2k5-3{S<(t*|[V:FK`SJ0Y<񢎡*=Ori8讞Azǖl*G,SK_ [F-,6'}2'Sgv c˘WF]@֪O_ol՗ĭBRxOL>ZL )/ف][}eocZT{^3\JaXcKWI))qL׿XBtgڻ>}-–ʹB*#St3F&D\rBA]{o+$\QY>E/aQ!9@N|ze5+Vj;w3H!&Rz>Mn2Z3{$߲Bzt$ӀI۾;efglps.&Nʪm:٘zO4#dXZ5SlJyˁ2Yִ5GCӱ9%k[3A7Kݛ\1SރL'4y?[lZn+js}Q*L/fz̎g=-ױ5\ ܘИ 2}FlNU;|ZlVzM ][ujF {dZhD3U>襃3?ۢOj2ңh (?w^OL"y#Ϯo(gR7"l+7xr~ (^EDD T'<YoF3/j`I~ͶuG !ʳv"6=pŊVܨ(2X`QE|T 3h6cz>T pXXl;fx-.?*&Cj3]ʂN[XbvK"*e%%Pw(xqPQ5Kg$JA)N@~;Րàh+Խps֘ȿK7~65 ^.~ưKT8Kņ;nCA wVˣe:ӻZDVׅ/"˕,V ޓVqQG Y};3MR ?;ATLYB@*H*(5z{bN4ĺ:2XjAB|z<!Pfs%XgO e^Ā+]6p<`6Z (X& jĶ w m09mAu2 #֯إ 9bc>c^9&@}f0,S߰ݶ{c[ Oޗx (9GE87->c$ *x>jJ{h"&8 _0كC5&=uXn6$v7FA|*@~}g ~{XCY^wxPqo3bHjY0/ٰ7lX$Y7h(>R;y>ji.4J9f]dgH.LZ @]Q5. ͎5r¿ KxOmׇHpQ[, 3?Q,ҟ-8gꄅ l ;Ggm8<{FȋiAO!? Kp&!( C:jdn:RE}Q.q\I_膥!{*&Dok oqMgΔK7!kH̑Xݞ:TĬuE-FDsKzWarJ? M!VL4C'eC|G,#`3Ƿ#uSS~xﹺcH$FS!4h+z5j>n.̷?.!IzcoC V nV{cD(Ӕ @ Cyth)~ %@)'#GgL6Uv~y,ׯ qw_,(ے/7sѹ8ZlލضaPݭwB A)̳=n.'(;* q {Nȹ*ugkC/?-vx3$'Ux-C:z%}1x̪E9os#:ã/Q7͌HC _fA+Dʋ x$. 2T E%BSBVDnU ʏBGH9gr؂?uwqP x]=:^_@/ )'E'z%J@$f+8ƦsU+jnB\ NI>= [*oWɁ ˊi7-ltkE8ʄٸ??!0XrYٖLJ _ =º} (>s[ 8qbbxgm9ijȬK6 :h F~@̣GbN]3P6M5wP#K cWqKPOh'=qj$v¡u1F豅_2<6wUEG X]{j|i耪xQcD%IDʗ1ey1,MC\l՛$'au8 T@hnwhB( lw j|a\&0?`4'$NV:ufX=r]$Aɑ%{{ԯa*dM)6 ]vW bet.GǛDx1an/? 6_X/*ChTȇQM1N+~SU,ĝzH - ϻ`1T$PUBz ZX;) '>x0 8@..L0{~f2P.K`~YفH2 k}ȶyqAT!ѭ&Vތ >q)^^K>;8 SU  5O0œ3>2A9nYgz`MP$c?eKvPfäI*0ٜ (*G4¶7/~ˇ'DzoB9& B]7Ƥ́@2Y8U>yr*<ץhqyQ[hNJ9Ug%< :~~N:tC t|ECf\STc!ƕnsGǡYd.>/C߆@ `Ѕ>J `3ˈ] l@~eeN]Ƚ $P:ҳ1=,`7jRBq/vpSvXN اY 2-7BO Nw^5ra@8镃3oQ[-Wfά_TrHϹc]| y3~ƙA:+~^K07S!1Wi{X&ԵRI P86˳@G$i]A2MUb=Zʿfd~TK`% gsN-Wy4 &(+yn4ln[g.JB0-bka;_uԅPL] ?ltBi&xgn$踛·^]8Dm [ŠحzEwsV8/ 0)Bm6-Agp6I:~[}ϭ3%@ܝW9 =ȠjP{KaViufYa>v'O}j@DvMzΧ/Nd+ܑh>4NxY@ بhl$yZ /:P, |g%_B -Y-~jWBLA.A.X<3}0a4; vQ{fS X{w]P~m@%W{&a˟6q\[8kh(a<8I /ђz;&*~.'h&Kޠ3d[G"V"dEph!U>h`hMr:f"e'Ǹc˝eю^z ӯf~xi U/Ju09N_eVvI|t=#V ^\iԪ'tRS&KZxT"F&vЦ cgѵHZ^_B:]gyҠ̹OkwѺd~ iQ/~5v's;T@ ߯ $H`𺜰^cV9p&_[C0~9ğ M7Û:˥3&튕U>9TJ}^yRPcy Kč  s޸ӿ L~PEW~ARsY?\UP؛4VAXf+8bx٦K ^jNCc* w@ KDM~-Cs褔c _csgWMܷ.K-0%^ȽGd#y2jg(=V G Ji΁9 XGOnBgibisBp *H ݩjG ;cX ’t {[n|0$/d?b4w~='Y6KSMMC5OsxHAuxWw2W>3*BA~gp;_ ~aD8{>޹0+ԥbBy,8L䔉N÷IԊ `k)sXD*,}?.G16` AtDjuo(wE|)dBq&K@L9zѧб'c #kKtf xD~b@hB( Dw4O A!TDՅAГ[?=G|V[<=.8{}Ѡc*BzrhdA18EQMOoeޠ8%4#~ Q sJOzIN]Cxˎ^:MZ0=2a>"d2AMFz l; :f*1,u'Cpy7$+F hl1+mV =w*PKm#efv3g}sV[6z N6) T+r%+U 3>T3c?fJBcd;`1qG(hx៩ m,S)azA,-5MXiޞxT'i4T֍फ़#tzGf ]edž_=MbH{1gXza|Ib١NK'j'ʃFL.XMTTnЧ\ ja|J|cnK V,\ X{˥X\` exЌ.WU lk4Mڍ]f}ZpfcX 9P\>zz}y/ÈW?}NWUvv47BvEP&LXye)Sh*k<R5E30gMNɦUYi2:G4l@KfW%d)à\s8 #﹞1 k z64d1N):>Kj 6搹% IvŘ Llׇa^ZQx) ZeK~R٬p̙؂x􈨔](h> ›sgjXXEkġkט$Ak3g)K1(]ɦ, GM@9f6K'{Uvݰ1@i]Wzyqn\΢I%!5n&l lWxX?rOՓh3NTdoJu3g56?ؙsW<я^r@n '%|S$ȏ܁K3-¸tB a`fw2Ot, N/|Y'A3CH`?ZvcI8zG@凾Asb'vSQHc5 qnb j*@|Wv n&x9* =@31<?Wj=zl/[gcf9-SROcc=ʦ=647C~Pvb=*2`ѽu[=dIm[H%rCC—76 m\U! 6đ"8=(ptNJ>k<ёn.N:H;7P<alsˑ\U+CԔ1w4( A\oX#zgXqOZa {ҔI%Pj3JC fݯ7 jf(43 <CK>D%Xb܅介*RanN/ fTB6*䄤x]᛾K{'Bz0`%ReVg@zHw JUGs -Ƭ .LFZ ?MEC;kgn.Y~rqP`;4ۃO<ޢkl 8qiB(">/(߻7#p0ȿBa=,\(cEGK˫㹡|GGK | z^+r'HW@K@u&q; -+ۇփ YbG3= [<0Wx"3LHUŵ^!z/n5kRDԎ1]6$2>51EIzb@_Z$'O85{\=n{X2d H{o [t@&xK@{5ڨ*,t16сD$rFKwU³f,?f_$ly}S-5Q#}j`d%V]#> bcC5 3"sqV[&b;,%tT3=XfUףCHqzvV5Rp!c$s(ٴS`h' nCgFm[e?$v8 K~u \_$be6R(RTO % P@ őN j{z1vRk,?5OR 04Vh.=,B1$McJ>% 66JWӼhwF^vZ| = 84`JyIB7O̸P腋 \# :۪u\ISqJGhvAd߼l2"&$ƬT}Iqčk9XFz\%*cHYo-^:ԗ`*xT'ܼ/(fq%:ϖwYDDXPgcarreCK9=Jpִ|޳.27a]@K([j7֓U[vt?K{ 4!U pۻ::VF9s ]`21Kq}T>#yyv| |Ԃ7\RygxZBM, Z#L85W XEX`0%}ЫKȱn2ugJx6_2B=f:Y{̧RHsjHls <ñ9~.|yR5?Err:ΚF\KbG-np7-434bl4lz{\S^~浾WocS |"bFy CZQr(F.Dh/Q)I-JI;7=P xŽЫ/3ެ+]N9x)nj o9oVBU~ùOm|xO 8s&9wR|!)ds~Aux)ůO>7m*0z{-hx7C#W V]._Tf&c=ӈe?ޓ$áGްU3WG^T8 @VbW .]NTI~fhg< jFN / :f@$FUݘ(IСda WͳRAݘ-eVXt#!!E2{3P-PZHoOKOn$DǪ=TUL‰n2}, #r_D"7xK%,"eC[J3e]b:azFp>U=!eď(v)7S`KA!$S R3@L3k> g]٪>vc]7T}J|)ZݧOȿjQ@D5:Ζ}M7pۇw iZz 3BcA"Y:rk+k;;ٶ,a cM9["t$Uzvi&%KkvP)`-iŢiIQ2;s=N;#:s up1!:unu_$[Kw̻બ=bjYVoրb1O0-%VK| L*PȻ`Ƽ[$љJOE]c>+Y v`3<7Ezat7M u(T#H=z>V@Ň` 3sa0!hdxcoNvZ….n\:~XP[JD\i齷Ow34H+ 1HǻAkAm5e$l+X{Jo$ I$ߌsm.A(x.D4/cycR4 2\xjӑ17RP|vi}Jc8urN]8_KDVA0ɼOH?]p3C\ wύq%Vtp5qIK^ 8=hB|( ތ8^.!`ٙ:#nK5]<➿,Yȿr:Ø-'\*^0u2U㔟2Aiĺ+ d_\qbP̟^4Kb.|fh~~FyDI_]Lgа> 5.0CЋHoXs }սF~vjnGCS_|NܖnbIdj7ty h*Nz/꿫MB9ƹu}W&0Z`}oM=VK.NN&H^dZ!(#>U]njeI{cѯ6e"hSO5\U"g$*=Oԑ{%uҥ&oB; 9U72E=\dFA8JRچY &{LυJJmd :ƈzy-`Esc:E? s \O'F+W Y!NԬ>s]QoIqGH0݋.ΆHtqB?Jh19K_P%C%Jk?TF?Ct.=o>:vX0* c'jҷ#j{$B⚷үLo$>: [\sv hĈ@I#I]43$QuX&lJW>s#>֛ӯfݣد9g 4#FG$҇="#SyW; v}aX(^ЃgNO OAD ΀PxX,vCL@rAp3 ~G=Jܲs*&vNMfn ^Tz!sOWxy5.?j#0O@1$ H@M N] %q-O)8ƽT`2>/+'8_Z@\ A tvYLZSxȇ'"kG65&W@ o"~{"׶Cgk 榉e!pJRdt$`o{j1-'A|e.!#tK]I12!\s{%\[K2W҉ ×E,9|1>=W}˼S*/ӅCOGPdüˁ`,& r5VWdh@L ^7}xQ.ix9sÊ:݀!ZcJCQ{@Qmi4Y]:gK[c59%@s תy͵rNJ7T3j@XMZJIr#8ג̪mwGfHh̡XyDLќz6-m$yc>͡ KѕX6'xQWs`D 4L$_|i<'DB#?kƱ]N{m?L$#&ߣکт`|䧺N lE7(qޡ|%U@T8x?qdfȏl9لp 9g\Уh&WЈ@}7} " l-=n0D dð\R iT0p|B E6,^zV{j L!5n"ETSzR&-=f\ҥ_mGt&2U)/&2Kjғ ,m-'ߗ>IdIF۔v(J\UOW'vT.bN-i/3`ż9Mw4!ʭiodԛQ ebK.[)uK|wY}%t("ί3={˚$]wɟg`qצ04G4ҷ%'6d&`i>E` Nɿv…^'Czn~) sì&}Ͽ-vvKu";Ο/w_B{\{a~6Iz 5nz(^P n^([,iѻle#!GG l1i"5𵀸Vʧ5SN Fta$^(cv4x-"3+K8:Q6Rv?Å^#-%h[GzM-:H*=1C(qHPu!/]hqhC-"Z;630 79i-0 4` tN(f-3P |VQ#C ,"q@0J&*2k% §l[LD )2"q Dԯ$ fwlgBB:r5v߃tƘԹF`Hq/pZ~Y9:+U$@_WqCbF;a| Pt0PyuN91H@!/H nP C:H@HdKIuR8#܆Gfg-YtZPɃdn^ qC߬*WS̩8RS%bmP%naK Jдgv=Da?pe|4w{9WFԈ\7;q< *@N"fKEANZi04v.lv@"fNrbo+"E}tq-5% tŲtR躚Ā_0S/0 ;;0Uķ[}܃G~6_.6Fe>Qߠ76 Z@fT$ը> .zl0֨Y#8հKCWU:7d>3h#@o l'KAвLQ:pgkg2;.o /A c(D~'MP.ҡm7䷆[" ][q8+T42 kiZPmMN!HZjڣ_U LhNێ{%YwK_%Px%a'-Vl{!eɂ_n_a@!H tv[gN-8rP2 a;`G Q,,zS(}LĂΈVYc?dr ڂ`\D3|ALk0DM3wkMq-ItbA(k$X5 8$ 4` CF. #t ӮN,H/Q$aEyRu'->Q?{ޞM (A-15y O20%sVB5i0R=y4 $=x9UF6xftAvS->#߁~1~EAs '\?$ .kXr,V؄=8%QyXC1Ap.6XtAi+k1̉ @?řP)QYoX]vlB,F G܆8`[ t͘YW {$uMz<d:Pv=C"?h5O [v" 7@iҢbia[PcҘ+|:]PܜLPcVVI~9BWODgyG ay?PꭿRǷ"> &^J 0kAZ0+wzY"1Q<Ц7@|ȻAIJ%ʃaOnhď]7Zd,H6 gkѧ)ۯ):ŗ,Ю>$ʳIKpLjLjs6=.u Z xL~TH#AVy$bģ=n=Z cz#5ڣ_ !4$?Hq цR=|wb|/0(WHCU bĄ-=dLWg51n X>Wå>g-kzN o" ;|ء{HcRPὩ< į 72BsW@F/R&ts6-$MB6vi'ӺKϔez\Ll'cs?Ĕ-0÷ \_O.24; ?s4dk>OޡLX@lY堅V)*nMp锵w=eRV ,8b⪞W4~-' D<׼FD,_`e7Yz6giVX%V\φ 쏚c=<̚qaeVfK`?|SxB 6fyyg9,*J0&\K](cϐcCNjX:Y9^QhR_N}:$E~wsKFQ{hg{c% $lA+FLN\י[M38cULIm~Qpp9LrAO' . /~Ul}\G[]|1f;@Є({# [Er#7֦ =Xӹr!8 AbmC~W%8`j;rg JmmeHRiD]h޵,g];: [HF}?ఴ(iɝ)!Υ*h[)|!-/ )Ї}%)Bw*Vd& 's_D).?rOT^f vmZ(LbZ߆+Voz'ktڷ0VAު7gDNJ`1I(5`X.Hv1ntdUgpe7*Rjgo gfs#/81Sbр4E2ۘB)&tʾw4g [-T&.kMc@♋DS'W&`;Ɂ{аY.6n% ;TUo[c+ S{.Z1;AGYDBgۼ!V7Xz6ٸVum߸z 9tҩN&KYW@P;NX]1bY@C (ܮA_t[!o. M +T8@!kg iU4i{C@gH~^eH,jӠ7S9/ 䡞S*3s_2Qvqe2 ];4'cYգBvG'_q*=@w`HFqyԊW\MR/%"um9gPe}~2ѰO;LE4$vk'g0 T݀$^x$ 4 Xa4 y,`,E1(M6 Tc,V ~ʰ@`i.NXg3fBjdYe;n@qYW)M XMCLj?Ʈ4mY^=yg]E͘]3[UtL*ĎFшnSSK",U"#ۦXtdDnhအ8%ZCQ?' ߭: w?BA Yg<7I%Bg"܃Q'M+%Vm];ꂠ'W[\=ZFQFu߱L>r¦"A:}.Zޢ:vJU)^ 30,(8cu_MyCI7Y"Y$-Sg'=ehg$%˕Lr߫TȒQT\O\xYMA, Z{H^ m\T&W X@:vs>|EN-Ҟ\bK12ѵgD2WQC*XOq)A8ßm^٠2Wf DXK6 l L)͖'ʇ`05hEҠ!1Z}#Љ?hrn >"x_c (v,!a$+`7UA3Iw93,F- P#2UQ4qXm_6\G{C&H mCŪAgӡhdU]MxSu2ɽܸzִb8qR%qHgR3YgULȂ[dPfoJ/"jע&n: z{ (5(%>Ccfʦ]~ՅDSr[oY *l{B^x”ԱjV«&>wS(9-q $Fl. ڨj->K3WqB)):n-?֟uPoj ,ճ(̛NI ,>{0~}?=kC^"{ < }Ae7=7)QIfPe drV`yˏ^0W}LNlp.t֛jGB2]eK&!ĥ'X[M:jEOTz}jqDx\-eYN3b Nz S: EI㈹ォ)$UP6{lg5῭].1 lYVt@*Fh`h 1e椹 b(7ꄫgy3y@ozm.njwͫoSֵ"ntsu GM3dz &^,xD$hi6GX|"p"lO=Y(*@ܿa{5g*vd}lyviHȽ$j#aUrOQ}{CDYXr{Cn ej6 +\HS`7p VS9e]tE(3ΐ;U7ī[\%OLjlh7,kRSkT'Muʊfb)-LJ6h1q8'z$܌?U˕QD_l!_H]MdFO+jovNՌbȖb+ȩsto &x{Ǫ]:N )ֈ`HsX-"gB[~0Btmdv.MvGZFѿ"kvt NVG4ek6*6sM뮢̟Tln6ovQA R#˵KuӺFDĬwlOZcR@U%,PsjWPC9-0i-\(L'fpqӑiD@H 祉̼Ad#L m5SW cC~ef.CSaF=a=:a ;>THj"30INzSbS/"rQq2P`*%4r$C+k[<~ '=v> HӜ#XJGqE, F7) qH a-^KƸ;N v !_n% ՟(kĐB ŪzcR!`=\ 肶.AyzB?EE#[%z?ѝw)O\e`Wɡǀ.u k$>3&5) RYZey#g5>C3DK3Fyjiד &y*"܍iF仝+uc]պ`59DI@U)=D5">P(Nb^!DGD5EhhMΞK\ ̨@݌5_G:hd&t11c*#D0'E1 W.D"'u{(4(zô)l ӣpJ&\Qz!+ȵRj6?g{'xȯ _GYjzJu2ق90X6 N,m c9Ulyg{ !"\a^p)9kG|"W\N<i"sG~$N_ peL(+Cg' drhuQ>ρAT钢al'&KS)V%jp9`_v~hV3`KXlc#Տs }`*/A:j}-Tb5}G^c R>\aSw3mjCmТZX6vO:/kuΐ)9pLlb/D@Fy0l 0 U|~1`y@'R1JdȻM_r5UEoO z>+[IĈ0x*b*XF~@fӠ^(,@GcIkŀt"6*qђ-Bn%dqmIeYpC\"{٤$=t5aah AN,.L0YdM!G\ءsbdEm9h#À?F6x$q)}UƃQd}.Lvy}n!a3&Q`*`  z$In!1>.BYhJ7b E6j/U7]JR澊Hu_V4 l ut7IpoQ2ǏW$<6|Aynt!4@]eUOE)g]@PE^vцZ1^'^޲i)\ۋz0c@j3+b8fh `B0JC"-yuc|UI|oq 'Ez 2Ⱥk)ͦWڙ%6{aUBT̍X2 x bQ.圔alɓMz ,K@ihIiVD)޿k>чLɈ@V,~ix ,PŦmg9A-*7@G<o*};gHujfK+ByhEz#syeؠž)tVRMMj.Eu!G,~똸V☰/h=9 "7^iEhh{LsרyBlv/dN˩.۷L ū@;0xj׮5ܵ@K ,"(,Mgۗ)A*SA4o|[: ^cj du4N c~$3.8iO@DPGf${& NKكSSS \xN`L>n#1RpsI\w(~6mw61ccF|~c"2/=s2 p7z՘wM) VE*}ЉQ)m꿾B`L:S"kdT V1gƱU/C۷N$9keh;Dt"?@k*\Sɤ/`[lgR *Cpm~H \cu"lfa .o"Ftܽ+57I#qK9"EZ0뙏(J&d;`"o&! ش.fpoD "&$x1hhQ GqO,`4 qϮ9?--Ȇ`Δ?)^ 9U*]N΂Pg `7z|H4>lJ_R֞7S30*GQt~muk9j*^!pPԎWq:+*>}W|uSF?"je f+h ;}貋Re?"D&%Os ޱdz6dY/3%͜Vb0*.`6L 6/ yBd•(.EN mnM`jZy[~5UܐtbJ6mU1i$Ȧ;@J%j}/"@}=ׯ48~EhRmtW Tϸ[eX0d FdG܋1tZ!oF#rOVuhwf&xj&+I2~8\*4ZClHuߊ0j kMKLF_qGYqK~Lmөq/F161n!E nQ첉#NBߤv◂P]D0Y(Y=uj- EsWZ9jhuvҭnGyg4qw꽻Vh.t5]#:l%&-R@ljX'n< Bf0\W+n//2'R:OxՋtcp8ܓ˅)f}UQѣTnDhZT恮,7҉CNP 8BZHZjh+Y.S!ec *W7UCTS<9c>WX.2RMv 8v ( Y"/ BܢqNIc,:ݳA\82wCIXʪ/D{$(C̯YI@FS'j+e1fh۽Y"au(*ޒ kNJ-@MN+yd-g*Ԫ-DD0U3WnZEjuA*^7t8PAg-Ұ%+|LMcr>R/1G4z+pcZFQU;M N m; z'+`ĄP(0`%+.@=fjRb/~Y}5=!=S$s6)ߩͶ6oM bE25M'Xf|$ܚjl5RkAZt ;gL2Fuz'3Si)o\ [ŴX3њծ$5rXE%GVoՍ!"SQ7lj}ԺH8DnRExjܚDL~EQUode،2}!0{1|=BTOPi}X;nv25uG}UpL3-h堠<З#9WƪGQD&ʩ_X2].)fN(ӾUf#DYCnaO˖N>C9R3!P:쀅 ҤoňdDzH3J569)&O\oD9@y/Vߟ5Q7n^O RBu K`?-t©A+)[^1 k0~I  3@_tnc23C9AKHϨ$ xћM6)۵+& 1`Ξ#-0-Jx^1/.Oh &qUJ=IX{; % ujzmaB>1t(ɿU p!V~~N_U(W[BUmG@[0Rj!"+'^93]ģ]~q[!(0kڷDT>(AxVGoN4w"E2 IWC}<dcaf]Řw <]TnWCCO}wo4Vqyo{"ppwZNxӜ˞,8biOnqa ukޭx'-špPŖEJU $Efna{ӡx 7۵*$\Z.<%{@#&l@p"XXi e-+kZS1暓F[%'ciMr4 "*}qx,ur]W[Ub9tkŠTbX<n0 -~~),-Q#d߃z #p`[ l[$q 1i>LJQc$[Oh[5V^Sݸ w`Πqgk^Q'oBE\2GެiόVqcLö00H?sкa0W~@p3tfFvۯkwhTbzX3 zz00cSz…e5`hJ~%f_9P^|ٛƩb&m=d~[Z ੝@',@Z6#Q⦛F,xTˋƃE5,FM&HNT|(++impER nY8 i;(}1mLfs0 zCZ "aNi]"YHR-cJJ-)%jxh=~`]Pdm[c|uAyu"oGWSqO84ssxeVVP} |.5XCJO[@ALf5Yݺ؇vѻ1 4Eps_yt y[CƲTqg AH \6#m'c 1Q FyW?C z۬,{;B,w5\b&@Q% E},zקQ{VǁQZ_ۄmp%L}R kdI_.eA,U bϷܐDb$$P'*Fi P N/Y͵ .ßTmN 1cPU<'x+Pb~. fq`lIyNQ ruI?~Y,{ƒ\"\S^L?-Idt!A-&JlX):{Ьn ({ ]{]T: FxN}L*`r i ҥ!иk`Wn5G7 '. RXUz"80XnTxĠ|X:M HrßdMf5fb5bxՀB *φ\J2' N!+!gK@_#b(j8>6-G5 jɭ=[ 8` ӀؕAj@6RA@C;dA7}MO9zo, 4>A!p V:m$:sH^ (Zu+z;E=;̆d9ZEPjCCOV_F A1^@#hw!yUG h`FHIw8iw  ~2cp 7:߉e(GY/BcSE5M k[4TxW=RjUݟ*Lbh_[g&+ ҦӔt="g% VBu/ֳ'(`J kO5Rz/ Dae{.X<"pZz!&@fΫ2GT*S> ʐsD~%DSB@`goHL_cs&nG~Vg7HtFL(}REhXG|) eݡa`o&=</+A0W$ Dav{[ss A Uxgj˗{bo[La4#`#Eߋ]IfI ia5(.`FqUBwU)$ZtyV} `|puZ?%~ Vmb''gi`sɳ(́_=}aLHN* /J)\l'S:P| BC@*HGs+ <>ad"̨/(Yg~L,S4rjjvm/XfKs-yYbyZE_E1]~*Lsl;M31#E]!c.>ZwxFbwn*Z<>N\%syGQ데&e{Ḟx!zL#1uQh<H+Υ]y*[h^  s)*%$VW^ ww $uy'Huz/L>irO;H=6]E{jq.nr#b]~"6!nnEτV ML^q)ho&Z_?TDaz@9{廋Raiÿ"D K}9"B8{\{.E17)Ԫhe{_ӉDD#_ѩjGf!(SD)3H30 i;uWhLw CL@X,֋b{Y4d- y)祖}u(A$g"(WQFLìUeF:^% MD'WkXtN L-[_7  w^*>MTx crNMC * @C`b}Lg1rH" +{-A/kgzIa 95&rْWkOY`̥DxN/Tr؀ )rDdzǸ+%(Zl\EQ7@蓸e+ijG%̏̽!kXxC>JKt~(.ʵX(umT Gn^ȟj1cnrGp,+w86P}f?Ý XQ4_hƀ:+J{.ū絛 #[t_^ e<;E&VuRcܝi{Oh!CsALQV6N`^*ȺM@$$G Ilco{!paP߷o#YFld;ŸQ9V-'[bֲh-MzDz)Ú}Ň@AtY!k \W `rYY& O%1FGn )ϫy c(v1D)UX3}RA~ _x>G$ԃXDe/ &鍄u'|΀t3sZ=.L@~_/?$T!;h)C߾AeJ&8RT@geFE8 E+x Bf4;sA{uB؊:d@T'pM^}RWp.1?;/PUFibpZlAs<5h{d3| vߠX̃›:NēZcH~ Xs\x_j6Y?2Mj ]U :forqv( 625a7ЭV ``?9j 24P|gàY$bP 7tĦNDN( *U#V0TS-r`A dPv?e8 ^ V6jcxrlSrY. Ю -;M8b51@adW@Gm%`Hm^vHޢ IVT, l yZ!/bE FL:(4ImA&$HO(v^ra46s&SCW +!]g!*G4L- 4ڷˤU{%e:[[E94"zDqpȶ;<6 #mR4(VT]'Q>JD<0qP =oOGV9܇f.7LJ-"rkF1| reI#spM wd}in5XV<| (hR/A0ź-@b(ӵ;^*j%AQ#\kEj'^D&0Fk$6nL> 6LҴYrgԂ?iOiQL=5Y/nDu1r\jd_Z:>/ fL۷[$HNh0Pk!>=| ?Ƈ)L X ز=QGY qk68e߀W|E GF{[.Kz?:H:o[,`=!D+AKp] {yۀxJ`7%{G3B)w-(wl{p (jKnVDDY`9|! ے Ӻ0*xj)4wEVxBtQiN.ٱ0h0~VJ2 Ἠ%xTn%0'B+O"=E3 POF䲢\lSk:$(mSTyfH ϳĔ)XLАA W dY*Skjt}OƆ |ތq?PB@:Sgp`xs kpIH|i @g5WګD#Ko P,4Z߫ԩnERՈex{P7ׅ/z"N*Y%G)-\(>OքY޷[oµ~(*͹?,?'`ڃ)>\,Iݿug;wRdld±-L + D Sn尓Ȳσ ̉>~7=O|mrfN+V''JDS/JeC.9Ć@8~+bGaaΎHg0Th݇IĆogWg"Q6+ov EϠFV*t؃,IB d.շȕA6|&r (_Aw"+c24goCE@,M#q{rwxfxԹA}(13♢bpqv%bQ` nkyƲ174[qc2(l66=ǃm=}(&6E1x4< j٪ O1׶mPZ5@y~X\&5D k/,xK;MEy4 ~c-B(%x Z=FM,?y/CjB3&HoKhEX|ɝO![%4Y=_=|ewe,@KňYmr%hƁ EA4_n<#j7­ۓr{@\t0bHD(7[ckL2}7O9V>WRޱ^* P %sw+(@Bhe Pt;hTZpUwjkm-OY 21VE$RDWh{>-.3c#ՊڍD8S2Y›kEz*:NuWOjM @Hh&}'M0\j&:ڣHTASgf=>fr[0`_ŋ1E9 =jr]4a(pl^jLi')8Cf^ʲ츧h[tAke /bEozqԹ{W0ۖ'v-E^,y(Y+B@%ZR.d L&y30lE"¤Q;)O5$r#13Xk_Ռvn5}h=ތ;Б:@ VTuZ. Ǽ\'֞{ 8&:&^\ZAsw"je8JA A</kyP42$H' D#b]Sʫ6wk GHpR0ܷ-B܁3m۪.4^:*h05鬘p*7SkFph W 8Vc-? 1,ʐm,UڷC0HT`)SjIMQ0O Gh'Qgg~suעMn5nq,lLkK-!Q6?ZKh{CNXXjŃi>l$~1)O.8f~.0mͬ{E_EA:a0~faPeN (V8|\yEqI Z#["LŚl <$@PM(~ 4pBf@3!kNSVx65ZE}։/<4"c.y*z>#2LoVC!(1jtܡTSd!E1@;K }!4pK@FI<`A!"EvHr 9 Z<T@q2 \k{S\lC($-S~ >EC6a9n7@d 2A,U{ 4.Wulh_,9\aml5ŕgszi7Y<凇2["&f|303Feĩa4I1c' l4$ 5W[{ bWͯ l{-x8]"i^oQbd0L+UPv82*.f&@#qMOybKX #PoE) NB("4IdTo#8p"Ҡ:q}#HYiۍ^.>( ~-)u",ffxXcEUeix>HЄqjD!aqir-G7jatU3G58esjV} > (*pU 4&/i #UН)W9 a Ӹ&

r(+* ZP T3]M5klXv7d 6.W s Q Kd؅DX׫XC҂u :d,;Ec( `+S ] [ЀнFb3ˋehBŨP ~z>  Xu?ybP)t7ڀgq:LS9yC9ھ'U-iMݜۤYSnG>\, @ZyZaS8]LhhhQиL`A;Y)okp a><h@MqQ-w`g^e1< @ L { *5Y+c8&}^?S9f\)A?U&L7$|grx> B(`A5ndw̬d,y j>ؗ(JܻV+ʣnBG ꟑcETr6yNY^ζu*"ko:6)мYu ILyմAPŠS"i tC6 Cxt P[+䁲Ej6ɃyoʒidaB* $!6Rc< 0p ӥ xvH[1q`v?Й itQI?w3 ]_#8$ M@Uejj}aЄ%ɠ@dЅ^3@*jgxoHDn}g:m0ga]s0VlR7ЈF A)U *PjBX (X5dȗ~wbFUI"As(5u*.kǿ4R $PSBlne@&=/:x7 Ҥ;J}iZh_!vn~U̓9|?ᐟ`}D8ZpLr_c!rweI8C#<~ϲO4u!؈&_ǎA[UULIlU^Jp{@y¼H2]5 S6.\-& T+~'`HmD~&3٪l%K.n~l$lR ?[F%td2idu]b3EKOPAPZ x32"ARt':\pMp (w xɠ14%'+-.:$8wֱybطDCGM0w1 ĆAfЂ}^OTIGuP8ӗn57M1g+A6gPk$j N`nYrJ00Zpdm&\.Z`܈ʄOE2*/F',v&[J(z*cY` Ч")/*HYrhq@`!B0ɂn/*0"FRqROUkۋ}!Iկ¼38``=I!Y}a6 to-hId@6*b-%yN '?ˍ[s QPɄ v "u@`vtm:A|"%Z` ]x+\u. P4~Zz8MF^%`*W5#,sGY/4ߚNJ.hy>e㛤>ڴM\= ͗%GNj*zCwfZѡ.%h"| ~|4`23`;v 8tp./pX$e*h>6{Y]<Ti6^_ 8QS>ZMe QsZcBsxꐗr4nQ{Pnzh3)[~V+(X+DRT=a!CCEmwU{ ԥ0'EkܢK7J* N1!1 tdԖmbE,;1VMV׀}zq?hX lC\V W9$/ G'43R}aMfʄ ;߷)R*=aЀF,= @& wr52 ˹Q]25sꢃJ@*QIDޚ @b 0kO u?N`|򱦀wȮ1- jc~L"[ޡw["h2hR^`T\y03# {\prk%ء4_E )C`@q_ex;c*t ^p#Asӊ)X0gRB?]ݒ\x|fWuu/OU fQ<3n8z 689eMj9M\+SQ-odGpu?c=0 ΐaa9A鐍|bԑ.q \u{3MRt&/Tk`SV0Y#нm'2i5Ꝃ/E?NrsQI&yT 7+jxU>4\e] טNE@+oʢVSs> L*y.<$3#+(iA`XӢCiwlDkn(Lyc0*@֎0S@#3 Ā܀X&e134hIb2oi%:xc֗{Oj3#O_Dٳe;sǷ#p4C *(G` \UFSBWҠաݵ/l__z@+@{0 V?b@ u>"\S 3N+bF*MgW2Dk4Apmr61q!N5`ʄ|bmsM$MhǢ$a種rt>hDW )=1989Qؘ'_N/f. QTpOlӘPэA;!7An4~v?;$<)`SݠѭN=˝E@GS{G_B,Jݾ%pw@8[fr;2n"2Q)+{{MD59eAX/ YG3P0j0o^2E窛;j$F`a6"EBO&b} <HC\*[efpb 6pN@t[JNnXQ<~$sJ~ad I5&y! wCف=+bSC0S ]kTB*D`?Sf <55pJ 3+: :ej8F,WE)qVx!Q(v痛}qF~zOGCʦL8ήDm^g?8=k0z'ً7>WK@' 0TzP;D`)M |b[Ss)5hHO$40~3QKuҪU|:IFVA|M)[Mp|]AjnK+v:$;|,M ?L3q@u&3G}dI!ȦCޮ (>"s^i,Mڛ#GJ#LH<׳ C@7NАX%>bcr9Z%o`\$ĈXB561!_b[0H?* NXK|._B$ .t::_XNԝYц X7 ٚ`eIs(ƤfX}7^TE^ի[{A.)Z4bϼ&ĂnEP r!YuW R v`9B_pO2_jx᪄*DA8wl2Edf};9EM d+v)LOjǪC s.gX7Q Q3|_2Udj2,zj*Mhqk` ^r<8] ".̥2N :8d%_)Ck7 HW4B gg>6A+ a0aXVWD/Kfgwg  k}i\ޠ5*vU~9C |2% #7l=S>U `}dZ ;^YJKdDLV1c&^H(+ V,M@1_sAB % `(m̟VWٸ)<ԖS&<ˀn"'LI  jU8# 9 L5!#Ij yt;"₰ߛӾ]Evt'ylTuq'MtEr6YП2u䓞zh+|4n0D~8@ ! =RmxsFߒ7?&pvcg1+:Oqoߘ2o&9P^LF$ʆO[XK0މ&zo} $.vE Ų+/8>lCէ2m8v!Y,vL=ߵ` 1a. `͹# HY+)zXvx^ ϞS<@9ab,"LHxnu-@tgh~I-HחVOIO .0)%Aܘm3GCat1.}?MmʓU$K*iRi() zѠUE-|jk۵nT2o52bSY}`+62(M!Nz\tȶqO1^ x |~w NJBSY?zHTM#^.[p-iË򜗮;9W{v](joQ|e5l4èʷذEI'KE6^ K ͪAfMC+[0 9e=/jz2d""_05O䌙MW̲FO spyC0.g> n,']f.]rNj@3 +/ LubB]{=^"0&V?ѻ* OetPV[)>RQZOkc} 3?]{5;!ޗ֟ If~LK svbjٞh!O۟ ߊ6hqA g4ɃzS: $O`oE|XM]eRjV<UYSؕfǍߪ=C^Tl+V Y.pR>}.^P zof;.]}0;+?B34"8jPos4?r?l=G/X3&w)JIv8(`$r[T&X-h`@JNVHl U~w]ҰC ~/v+`'綬%D ODn"Sr qpDòC¿lK[ K/vԪ`ь;Qb7\&l5EY: ?DIocVnIIt[eE<ӵKF-W}Ud&ҖhI@6RR|NƸKJ#v5;@wk|B66L1TSL yF+e"13'ר}Y&؟BDކTJd.8]'=bl ѸMxǧ!,ŽTnIPDg!̤KitF$P6h >FgmRc6H~mA*El?euBχOG""i we|t!"쨳@~O>qtʴ\j^. }AgV¿F,#Ol8+NN2=47C 's4nHlCy{D7@mgF݈Kn$QԞL #8F5ECWD ClUjZaiOFJn55`Ǫ1cfxAJ7T;g1UκzK čN3' j<5-N[M kZ5E~eh4i^FzZ`:sC{CaCD8P@ LxݡMѹLx|MB(TXe!j|4 AT: 6?Wt>RT}G?dI'l:xQfX@TsRx4T@VK-B*BYlYY;(rb0i郕nI,C,$IpިJg),MQ/t@6XSC~̩%P54 w\_D9%BjDZ^-"ɧG ᅬ8tJ돥Dpϧ E{ !&ҲZEˑȑazǫ݆dU~@i4ۡ5y<\@cu ^7;}rA.EV2I#@(Dn];^&P>X Dpis)[Dqnψ$zGH.~~G0ZQ _dc01M_G1 9e,lm#9o0$g#O4Հֵ$RqeM~=^$fe7o*,~COBrH\6&4E|^NC>g)$-׺HYj>:jYVfn/jMc1pNlįЦvvP_~[$Z5cWNnv>y`ҀG4s E"r f7y|ZH{5Lm/>AW:۽ZUb-l+ 8fGTg$`j&~Dv5/" ,r nLrH*UJ]fJpGMe6!d-9n;SQگP$wj,g N"?jA+QI!!RQtMNL{wj—][)-ҿ8#];Fm[TI QzLt{O4~z$[KC Ν0C0ĀN%&"7))߃@GL3Q9HgΎ5 %nso:x(SWH! * o] <~s 9*kQlQHU;N'>PNIM~a+CDrƓٟ$+ƙ:9`?x\`?D%|rfb3vޫ*&eť$A}T`@+$}L{xB1n2I܅KX| ;GqA6<ʞ~R 4$}e OJHeKC0o(Q}Ea&ڭOwoZ7q+KHL!6&1F(:]q35<ɓoc)X&FvRܴf0`8 SUe) a63`axǙ QSE7D))yC$Afg`uXfGʩ] =Z2!F'~~RCl8zDOXpRH`꽁G#W+tJí{1"\^T9}B$g%xblA"NC~/#q e#TW;Zph 4^Kv.ũ[}*z{ >)s<#^#=֒|,L@VGG7tʥݡl7P6zA:K\f'mg`7__\!c8R:Vm^^fJ42vYë^rl)$+="09hVDrXCByI C=vD.vYLF>6 " [j1䓮ǡ!%ڵV,f,m# ]x+(BE3w(* ڹ6:U9h π, -d7"82 ®NSB K[]Aቴ:eX^QQ$g5%cp1yE}̈+nNP/tOU2+xw1Or\4B0S?_q-=_tQfX{IjNفgu?bI:}Ƀz^ø'V/`-] 8 FyA HOjB: V¡m wȫ?Gُ`]bvcKQI5Ϳt [1u}a߫K9=,H'(oqcoFo.CUy$baLG<3$_rݢb&&P ȹPγJ5 '2bx-[_<}]!)sARu <#*:ra/Zݏ?Ьc|V'0fov|@xv43]?ZhĂZAj F3ri)>NP9*fR4 mdGcKMnȮʱvԼ~5]7(T|0L,b"CQ0&OК?Wޓ#,{@ rS!/("Qs< lu=`X7S =C715\pt 3'Vf^JBd[Î"p/V S{(R/3qPg}; ng(z`q죺 0)JlSNAXux聹RضfU8ۘJJy_rOħܬ`>0j y;+@*WLk\bΉUޥ2kxw8ykPf^^dn>yB,*A[6 /^G>y{s,䎪a|E!w@ m}Ki7zQgTAX)5O>DZCKi ;JfR'9HSgpU p^葼!']$(q©6<xusy"zgyTO= >9x茑x0p°C|}7tz/Iu4VꅕЇS`a>u $ہorpc%QnůM>@joؤܪk ߍ.uF}Ͽ0L>+1!?d e; b͋ll~/l9HV\l;qiBM? 8sؑ 9d]3o/[3@LA^8:v~`Ț I,Z)A 5R[6t6>쏩aՅyA u BDn# DR'mg`A^Rfߦ Q+>ZmɁf2_d)熳Uֺ7t !X,s2 eT8?C뭙a$67`́UZ :oBVӓK?řQ|6ɑ9*h[b$(tLX5D.X^A1z}AЁ# ƧVz83\tl?Rz%FM a-bS0F" j6 e~TF,=@['ar`GmT_cÑO8'.E<ɹuHW|%r'Dom%B@Jb[ F ƅ7rܶzDYm!x8~5_ca'vnP*s 6 `Z8 !(&YGV8>3kƒd>5(T{7>2y|>=>C@Q0uk@Tp6E"1m_<%MZ&/}"i,Ӆ7FmsP9~@pH Gk`YMZبus#`#  $:;LYS $  QxSt%l d['wIό3TBɐq>6=hĩ]lI J0ZޡOۘ֩HM!F}ЉJ@*seriZ@ϽN `\>_5{SK t 5͂G% NbZzqbu۵|Ze[a ׏LיK0m`AE)tF+g&ӺXP '-Qo+N^ht7O@{Qy-Aj*@#Ú̓BZ]j!j4foF@vHtBBtk!«R4bvHى_"H,P]Pbs姒1H#RaL '(]%^4{}=Rg:zL4[1\Aւ!;{.JeFŢ2"g [(q>U1y05yac fJ>Kjʠ z9DJ 㥒V d uss~2;k y\ - >=sX~NQU$ǹ$IJu}}4p`I+t &>|GwS\& l1Wǎ92(J71@\A8aL8(X͋,E iou5`gg8+kI&h!&S?g=> F^!$=ѧ:1z܊D46)CTܯ%v`ʩJE8i og[L(u{-0#&c6k{FJܘzH͡0va&);p?^h#+ȣ#g,u'0 `ov,/O>m(Gz{d6:a&PP~ =(!DG, ۽)-he9F*6d 6qJ@#bۇ>l$Ld5]\%v#@=ҵ- 6KEz*N=bbEw7FSU2ֵǿDKw"'C̬s<q%Ccbe5~J?SR+3.ܱ%G M+.L*1/f՜짗4Sg6Gnͤ`ZQ8҆,@N? 5xq@wt Rԙacطa?'so!MA`] &7q͹t٤@$}5w-kߨ5xU98t%3j4 8Іdr[~&kFQxDs$~0A7TO 98r֗Y^2 ΠlԄZF7jײjcZ/׉}QVo(j̈s1$үنzM7n3R3(?MJ|~;R់ݝ׶B6xa+ieF92o&+hۇ~8: ;Dq~gSG'K&;Q9μ#{s0[k3˝(m"3ME ?UQ-tA//~Gw20 fЀI;Xa :(HS\ " g01TA %djC rn`"tfҜàRV}Ƣ\@#oD(N'WLt1^H[6ǂ4I5 ,q8n>\2';J?"M%r:)@t iڑbˣkW`dHLW%iհtf s+X`n$0{Q#]N\L4Qg=P"Qh!S'nRWἧ%%cǾZ VJ1"@H<^!zØ>bĵhȠWl,է|@`el⇟Ko؇K'Jv%(mlVM=1"ayЗ]Ή*/=K D5<&Ӌp]BˈP}R2h)m:]yK,*'f#R*V^O9=/cژ#;f-2`N /_嫹 Cħt<6|S-zZ6emtYKde0$og-a 9MI{!<_%sFM<))⚤A.nD\O"DJkٝfҩIu, x;yO`X~d2ȾrB~}TOz$-7"xfn|u*p[%4D#U. *X) cw֢'ThP.<ȡjD )pZVP}Yu]MY%JIA%LƨHöG*(%{桳eC:T=vR/ZX _ t9';'+s*8m]vH’Vk9/{'&wlHݯJ V}٬V6^$m MLtֈ;Qb2GAC)5<@ʕ3_ќ]~vXd0_ɢȥq$5y01S81羹@>.ҾNXua>k_pGj+[А(8$<iQt,Gԅ"DT:z}*{OO`#״٦#5^U^\x멩_m5^Tj&m/B? j=\;T'dRt}4J 9~~CO cJn ?;j4ol|~g5D+Nm[;"'Cah=Q>], a4=E@Su:\g;䤘HagiI.2_!6ENIEݟLfHA!> `{ \goe0b5Kg/3VDx v@@O[y0vRϋy y7,3MX\)#0RP3 u[I(?庐pE@7mchu~c=-B^| f3r!)m.1F{Xi0,;/10wgkߒIj̈́1@F6l88םBemUxPja[b2tھ=Y0ʿ) µ9%4DPK L3dTH>  #dY  kc&f(]J?Ͱ򶂘^ԿF/~ cFuXp˜(ThLmFp􁭊#L>3L`GVc6iJbmL\Ljplח82sjf wBW׃7‡LI%La5:AtDZxΓڐ #4l4}Ǜ!C \`@aBMs6O@1![aX )x,$ё\XUTS %׻`cAf00R [FbM9-Ӽ6PpCr]]?   Y덙_wp7@m~&Y|*._(Kar*+de+ rVcwXdƠ<Hl?`~#Kpq>A(wvV맄=(_܋?9A\yn ?(؂6dM!5Ʀxr`7)48rs\h΀/=WK1)DC>@S!f{'pD2{$/Whl$*(nݐCt5@Rтt i@㮟^#(%a<͡ӳ"2@|m,^`i(o.KyQ1ʃ1mv) ίt %CC01 JCR(-C;BUlA9 jK? ) ٚ~)b ,գ^KC^Hb'Biw[ Л\2x-oL\m5;%鉡Zo_Eߠwv{6Uc'ZyEcM AF<%椲G|0] 69-xP.tZ#  sšڬqijKdS4)\ Rc9kuo$=7YeOV~r2yH4SaWFXYx Qh[kto 0yb&~Ьw*H5]că1wE?޳q m{gz|ɝAkރ7ÆˡsyC5~h38.%_4:0Yd_+ $1 '`?/0$ W'ng/XvKgܷ|0WXz-dՃ!BС&f9jA??W0,վɆZjh ŷE;Q6X0$4 "w30҇_pl3DL ܜl/ ;-a(peT}9~& jU9L>  ^Y MVf3 p" >7^/ӶSR6hϡpFG\8-O stTnWS'3J.e󹭱>YE}EvH>z$4 H<XH`68|].;.F\jѼqoNDo|ҐrCz$k){\!svQ_ yGIAֳ\C?USݞ:D{bz4ݵ:ʘ( 7JSHWR%9Kv-=m|kPG $7x vl/Dt `6ฒTw;l_O4r@X(ÍlH5k@6) hGңdS3U (:JpB~bmgpqyh@i@ AsJqf>GVaDÓa F=ۿ,#?k9 B[n5СŴZ([Nj`^a;^EU;:#_Qݚψdu)轶kGnQ蕡)MQq7F2Xmƕr'7g!_2Y}C9h4s '|ؾž rshA(3v44=`Ӛ шQ: %.{E/_<+u]eK>P{9X0#D8vS"ZX &FA-H8 x8 ,8ؖK/'պ<-v@qͶsy'4+ۤe1bAb_?!ѶT>2yИl F D˛A[_.b.fPhC險B т(\/&5T!lj/38>*mܖg2>F t$2)i%#/ 1nĪ dAk2s.ComyedLuF%d-ɗo 0D~V`:/5hbdiؚ:qpxx\b􈉵UdM HZlq `LC>Z)Nʸ7#DIj_n YZaEX8M}Xm yT)5}` ސN? M0 RA?qu%jefi~wAHej_|(/;h$7z ܻc DԽ6*%$c8p o$Ĭ3 mWKL>+l G/y|I\>cl<А^m30aX:JrEd̺g? LJJ|tЮҖxcSgnq%pe.-o ƮAQ͋ߤFt7ѽw 8L| l!;4ᐩKEanow&.GF K_>_4#C9~K!+y 9a P|tߓUvK#w!dQ=\ji`W3>i@e#=Ao%+ɷ~,!FTc^mgS_bjp;P"bU<$_NOD833D 8v@_N8ZU]MFmߍrmYc]y* V/}'M\MpKI/{O:@pCn`PZW ~ AOQ;*XolؒfKsN ʄ ]&=O{) ѭ 8yOS&̾}Lĵ*m<Xtd5K="|9?/Klj L h 觶E@6F@@!.RSNȶ)+&_'uoY _e¦%Ed<+ӹP bN̗(CfĔD" x.Z4 Q=-'Y_ѓa'DS$*cڹQyqZVH[?!mm\8UX^!*ڈ5,)D݉"8 B%YDE0C#V"5"8btM4Jw~=?x̷$xЧNKDB=75^#t1kCBlT$"Ddڮ8R*<]( K<v<7Ho<\:` 9.-_RRҏ4Wr lD8v" ,pZݘ+_ZbZxw#@ە(C/GףSrSN푞Y9c -U!sΤ6;#4fpx%-S͂ULzŨMk[8lUFEG= zQB@ I/կ%ј萇u#L!',3~w rUtk%ctH)'x^o\}$t|lKףtZmGo-ͷg N\ot &飐X B3~;<V gjrمV֖Qu>CHv#"wL;D(޴WoT@u)5\@mPqF SSr{$=LIT 9θ@9\cO\S MNZBvM2CCIuq"ȕ,޼%ߣv-ڹXD 9j'-{QjGw= l IoT2VJh̯Jm;MF{J!+IW7Gtd6ꏰ 䗶$%9䤗9۾QGz<'MgMMa؍MPI4@SiX5qyrj,=Ҥw=3:nf(Û%yW%w]q{-ԙ{u(46ڤJT̡-DU541߲˜@l& 9w [;uFmV_b9&ը?gEǺZWn%]4 cb3j S"Z5BSmơO'r˪Ë{-1>3[f"QE3yQȽFזˢggT"ދ 7eѽq{}(Z$FDs{##$tMVE4pw(O`mI{\&mbwamLn%b۸fI:qNs>oM^moZ"NH]%B?rGb'7ӻ(+м5J5J'APwWM `mEULt역&f6= -i*V~O.ݥN!nj(>&+cUy7CFcur7~5#PN0{03Tg۾ is28uTo̮"M6kADsm|vd(pb\|=NDDJRۨ?}& DV%ޔ~S=׍AR| ;\D&tx!#lOd7=M@gstGmIv~{fͶLrA]DZrCN^ͼ \k;x%`S*wH6d2U۫D^,G v} ̲DAS&}UFCݪMG˚q eM(֍6E4]?Ǩr^45y|IطvVߚ~Tu"sŴ+ba$&9Y|Dh9铨4A] ܍' JhWNJz 3R+ѡ%reGU,]69J#ޮep"ؼ khzmd!3(Ye183ʉK]DKi︵DR%8۲A(1;6/m~cVLk74+uWaS@_tXFD|jg3d~ZqiO7{K.S1W-bV[Z |Kjː ]X]EZƺ\3?jb^rv_ʠ2q)MAB!X-ñ 7AJY߿yΝ\Lx%I/qvGz{ hT5_ƍ<fjWi'Of "g]t:=?g{wd E-^xݳӀg>!,6 L1:/?%986r?FէH}#"1D?gU?v 9e8CyY4E?cd)tBj/{:9Jc3z|~O=B^9=5?'l }/!+ҁA|a(ϸA&#ds^E?=~c2+~`Rގ:Dwhh2aEZ[ϭ?As6pׂZB<2U.a],wBWPTP} +\d+) \. (?\d+) \. (?\d+) (?[a-z]{1,2}\d+) "@ function Download ($filename, $url) { $webclient = New-Object System.Net.WebClient $basedir = $pwd.Path + "\" $filepath = $basedir + $filename if (Test-Path $filename) { Write-Host "Reusing" $filepath return $filepath } # Download and retry up to 3 times in case of network transient errors. Write-Host "Downloading" $filename "from" $url $retry_attempts = 2 for ($i = 0; $i -lt $retry_attempts; $i++) { try { $webclient.DownloadFile($url, $filepath) break } Catch [Exception]{ Start-Sleep 1 } } if (Test-Path $filepath) { Write-Host "File saved at" $filepath } else { # Retry once to get the error message if any at the last try $webclient.DownloadFile($url, $filepath) } return $filepath } function ParsePythonVersion ($python_version) { if ($python_version -match $PYTHON_PRERELEASE_REGEX) { return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro, $matches.prerelease) } $version_obj = [version]$python_version return ($version_obj.major, $version_obj.minor, $version_obj.build, "") } function DownloadPython ($python_version, $platform_suffix) { $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version if (($major -le 2 -and $micro -eq 0) ` -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) ` ) { $dir = "$major.$minor" $python_version = "$major.$minor$prerelease" } else { $dir = "$major.$minor.$micro" } if ($prerelease) { if (($major -le 2) ` -or ($major -eq 3 -and $minor -eq 1) ` -or ($major -eq 3 -and $minor -eq 2) ` -or ($major -eq 3 -and $minor -eq 3) ` ) { $dir = "$dir/prev" } } if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) { $ext = "msi" if ($platform_suffix) { $platform_suffix = ".$platform_suffix" } } else { $ext = "exe" if ($platform_suffix) { $platform_suffix = "-$platform_suffix" } } $filename = "python-$python_version$platform_suffix.$ext" $url = "$BASE_URL$dir/$filename" $filepath = Download $filename $url return $filepath } function InstallPython ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "" } else { $platform_suffix = "amd64" } $installer_path = DownloadPython $python_version $platform_suffix $installer_ext = [System.IO.Path]::GetExtension($installer_path) Write-Host "Installing $installer_path to $python_home" $install_log = $python_home + ".log" if ($installer_ext -eq '.msi') { InstallPythonMSI $installer_path $python_home $install_log } else { InstallPythonEXE $installer_path $python_home $install_log } if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallPythonEXE ($exepath, $python_home, $install_log) { $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home" RunCommand $exepath $install_args } function InstallPythonMSI ($msipath, $python_home, $install_log) { $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" $uninstall_args = "/qn /x $msipath" RunCommand "msiexec.exe" $install_args if (-not(Test-Path $python_home)) { Write-Host "Python seems to be installed else-where, reinstalling." RunCommand "msiexec.exe" $uninstall_args RunCommand "msiexec.exe" $install_args } } function RunCommand ($command, $command_args) { Write-Host $command $command_args Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru } function InstallPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $python_path = $python_home + "\python.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $webclient = New-Object System.Net.WebClient $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) Write-Host "Executing:" $python_path $GET_PIP_PATH & $python_path $GET_PIP_PATH } else { Write-Host "pip already installed." } } function DownloadMiniconda ($python_version, $platform_suffix) { if ($python_version -eq "3.4") { $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe" } else { $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe" } $url = $MINICONDA_URL + $filename $filepath = Download $filename $url return $filepath } function InstallMiniconda ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "x86" } else { $platform_suffix = "x86_64" } $filepath = DownloadMiniconda $python_version $platform_suffix Write-Host "Installing" $filepath "to" $python_home $install_log = $python_home + ".log" $args = "/S /D=$python_home" Write-Host $filepath $args Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallMinicondaPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $conda_path = $python_home + "\Scripts\conda.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $args = "install --yes pip" Write-Host $conda_path $args Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru } else { Write-Host "pip already installed." } } function main () { InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON InstallPip $env:PYTHON } main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/continuous_integration/appveyor/requirements.txt0000644000175100017510000000003015054046003025202 0ustar00tcaswelltcaswellhypothesis pytest wheel ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/continuous_integration/appveyor/run_with_env.cmd0000644000175100017510000000654315054046003025131 0ustar00tcaswelltcaswell:: To build extensions for 64 bit Python 3, we need to configure environment :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) :: :: To build extensions for 64 bit Python 2, we need to configure environment :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) :: :: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific :: environment configurations. :: :: Note: this script needs to be run with the /E:ON and /V:ON flags for the :: cmd interpreter, at least for (SDK v7.0) :: :: More details at: :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows :: https://stackoverflow.com/questions/11267463/compiling-python-modules-on-windows-x64/13751649#13751649 :: :: Author: Olivier Grisel :: License: CC0 1.0 Universal: https://creativecommons.org/publicdomain/zero/1.0/ :: :: Notes about batch files for Python people: :: :: Quotes in values are literally part of the values: :: SET FOO="bar" :: FOO is now five characters long: " b a r " :: If you don't want quotes, don't include them on the right-hand side. :: :: The CALL lines at the end of this file look redundant, but if you move them :: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y :: case, I don't know why. @ECHO OFF SET COMMAND_TO_RUN=%* SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf :: Extract the major and minor versions, and allow for the minor version to be :: more than 9. This requires the version number to have two dots in it. SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1% IF "%PYTHON_VERSION:~3,1%" == "." ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1% ) ELSE ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2% ) :: Based on the Python version, determine what SDK version to use, and whether :: to set the SDK for 64-bit. IF %MAJOR_PYTHON_VERSION% == 2 ( SET WINDOWS_SDK_VERSION="v7.0" SET SET_SDK_64=Y ) ELSE ( IF %MAJOR_PYTHON_VERSION% == 3 ( SET WINDOWS_SDK_VERSION="v7.1" IF %MINOR_PYTHON_VERSION% LEQ 4 ( SET SET_SDK_64=Y ) ELSE ( SET SET_SDK_64=N IF EXIST "%WIN_WDK%" ( :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ REN "%WIN_WDK%" 0wdf ) ) ) ELSE ( ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" EXIT 1 ) ) IF %PYTHON_ARCH% == 64 ( IF %SET_SDK_64% == Y ( ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture SET DISTUTILS_USE_SDK=1 SET MSSdk=1 "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ELSE ( ECHO Using default MSVC build environment for 64 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ) ELSE ( ECHO Using default MSVC build environment for 32 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5675366 datrie-0.8.3/datrie.egg-info/0000755000175100017510000000000015054046505016220 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384580.0 datrie-0.8.3/datrie.egg-info/PKG-INFO0000644000175100017510000003600415054046504017317 0ustar00tcaswelltcaswellMetadata-Version: 2.4 Name: datrie Version: 0.8.3 Summary: Super-fast, efficiently stored Trie for Python. Home-page: https://github.com/kmike/datrie Author: Mikhail Korobov Author-email: kmike84@gmail.com License: LGPLv2+ Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) Classifier: Programming Language :: Cython Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Scientific/Engineering :: Information Analysis Classifier: Topic :: Text Processing :: Linguistic Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* License-File: COPYING Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: license Dynamic: license-file Dynamic: requires-python Dynamic: summary datrie |travis| |appveyor| ========================== .. |travis| image:: https://travis-ci.org/pytries/datrie.svg :target: https://travis-ci.org/pytries/datrie .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/6bpvhllpjhlau7x0?svg=true :target: https://ci.appveyor.com/project/superbobry/datrie Super-fast, efficiently stored Trie for Python (2.x and 3.x). Uses `libdatrie`_. .. _libdatrie: https://linux.thai.net/~thep/datrie/datrie.html Installation ============ :: pip install datrie Usage ===== Create a new trie capable of storing items with lower-case ascii keys:: >>> import string >>> import datrie >>> trie = datrie.Trie(string.ascii_lowercase) ``trie`` variable is a dict-like object that can have unicode keys of certain ranges and Python objects as values. In addition to implementing the mapping interface, tries facilitate finding the items for a given prefix, and vice versa, finding the items whose keys are prefixes of a given string. As a common special case, finding the longest-prefix item is also supported. .. warning:: For efficiency you must define allowed character range(s) while creating trie. ``datrie`` doesn't check if keys are in allowed ranges at runtime, so be careful! Invalid keys are OK at lookup time but values won't be stored correctly for such keys. Add some values to it (datrie keys must be unicode; the examples are for Python 2.x):: >>> trie[u'foo'] = 5 >>> trie[u'foobar'] = 10 >>> trie[u'bar'] = 'bar value' >>> trie.setdefault(u'foobar', 15) 10 Check if u'foo' is in trie:: >>> u'foo' in trie True Get a value:: >>> trie[u'foo'] 5 Find all prefixes of a word:: >>> trie.prefixes(u'foobarbaz') [u'foo', u'foobar'] >>> trie.prefix_items(u'foobarbaz') [(u'foo', 5), (u'foobar', 10)] >>> trie.iter_prefixes(u'foobarbaz') >>> trie.iter_prefix_items(u'foobarbaz') Find the longest prefix of a word:: >>> trie.longest_prefix(u'foo') u'foo' >>> trie.longest_prefix(u'foobarbaz') u'foobar' >>> trie.longest_prefix(u'gaz') KeyError: u'gaz' >>> trie.longest_prefix(u'gaz', default=u'vasia') u'vasia' >>> trie.longest_prefix_item(u'foobarbaz') (u'foobar', 10) Check if the trie has keys with a given prefix:: >>> trie.has_keys_with_prefix(u'fo') True >>> trie.has_keys_with_prefix(u'FO') False Get all items with a given prefix from a trie:: >>> trie.keys(u'fo') [u'foo', u'foobar'] >>> trie.items(u'ba') [(u'bar', 'bar value')] >>> trie.values(u'foob') [10] Get all suffixes of certain word starting with a given prefix from a trie:: >>> trie.suffixes() [u'pro', u'producer', u'producers', u'product', u'production', u'productivity', u'prof'] >>> trie.suffixes(u'prod') [u'ucer', u'ucers', u'uct', u'uction', u'uctivity'] Save & load a trie (values must be picklable):: >>> trie.save('my.trie') >>> trie2 = datrie.Trie.load('my.trie') Trie and BaseTrie ================= There are two Trie classes in datrie package: ``datrie.Trie`` and ``datrie.BaseTrie``. ``datrie.BaseTrie`` is slightly faster and uses less memory but it can store only integer numbers -2147483648 <= x <= 2147483647. ``datrie.Trie`` is a bit slower but can store any Python object as a value. If you don't need values or integer values are OK then use ``datrie.BaseTrie``:: import datrie import string trie = datrie.BaseTrie(string.ascii_lowercase) Custom iteration ================ If the built-in trie methods don't fit you can use ``datrie.State`` and ``datrie.Iterator`` to implement custom traversal. .. note:: If you use ``datrie.BaseTrie`` you need ``datrie.BaseState`` and ``datrie.BaseIterator`` for custom traversal. For example, let's find all suffixes of ``'fo'`` for our trie and get the values:: >>> state = datrie.State(trie) >>> state.walk(u'foo') >>> it = datrie.Iterator(state) >>> while it.next(): ... print(it.key()) ... print(it.data)) o 5 obar 10 Performance =========== Performance is measured for ``datrie.Trie`` against Python's dict with 100k unique unicode words (English and Russian) as keys and '1' numbers as values. ``datrie.Trie`` uses about 5M memory for 100k words; Python's dict uses about 22M for this according to my unscientific tests. This trie implementation is 2-6 times slower than python's dict on __getitem__. Benchmark results (macbook air i5 1.8GHz, "1.000M ops/sec" == "1 000 000 operations per second"):: Python 2.6: dict __getitem__: 7.107M ops/sec trie __getitem__: 2.478M ops/sec Python 2.7: dict __getitem__: 6.550M ops/sec trie __getitem__: 2.474M ops/sec Python 3.2: dict __getitem__: 8.185M ops/sec trie __getitem__: 2.684M ops/sec Python 3.3: dict __getitem__: 7.050M ops/sec trie __getitem__: 2.755M ops/sec Looking for prefixes of a given word is almost as fast as ``__getitem__`` (results are for Python 3.3):: trie.iter_prefix_items (hits): 0.461M ops/sec trie.prefix_items (hits): 0.743M ops/sec trie.prefix_items loop (hits): 0.629M ops/sec trie.iter_prefixes (hits): 0.759M ops/sec trie.iter_prefixes (misses): 1.538M ops/sec trie.iter_prefixes (mixed): 1.359M ops/sec trie.has_keys_with_prefix (hits): 1.896M ops/sec trie.has_keys_with_prefix (misses): 2.590M ops/sec trie.longest_prefix (hits): 1.710M ops/sec trie.longest_prefix (misses): 1.506M ops/sec trie.longest_prefix (mixed): 1.520M ops/sec trie.longest_prefix_item (hits): 1.276M ops/sec trie.longest_prefix_item (misses): 1.292M ops/sec trie.longest_prefix_item (mixed): 1.379M ops/sec Looking for all words starting with a given prefix is mostly limited by overall result count (this can be improved in future because a lot of time is spent decoding strings from utf_32_le to Python's unicode):: trie.items(prefix="xxx"), avg_len(res)==415: 0.609K ops/sec trie.keys(prefix="xxx"), avg_len(res)==415: 0.642K ops/sec trie.values(prefix="xxx"), avg_len(res)==415: 4.974K ops/sec trie.items(prefix="xxxxx"), avg_len(res)==17: 14.781K ops/sec trie.keys(prefix="xxxxx"), avg_len(res)==17: 15.766K ops/sec trie.values(prefix="xxxxx"), avg_len(res)==17: 96.456K ops/sec trie.items(prefix="xxxxxxxx"), avg_len(res)==3: 75.165K ops/sec trie.keys(prefix="xxxxxxxx"), avg_len(res)==3: 77.225K ops/sec trie.values(prefix="xxxxxxxx"), avg_len(res)==3: 320.755K ops/sec trie.items(prefix="xxxxx..xx"), avg_len(res)==1.4: 173.591K ops/sec trie.keys(prefix="xxxxx..xx"), avg_len(res)==1.4: 180.678K ops/sec trie.values(prefix="xxxxx..xx"), avg_len(res)==1.4: 503.392K ops/sec trie.items(prefix="xxx"), NON_EXISTING: 2023.647K ops/sec trie.keys(prefix="xxx"), NON_EXISTING: 1976.928K ops/sec trie.values(prefix="xxx"), NON_EXISTING: 2060.372K ops/sec Random insert time is very slow compared to dict, this is the limitation of double-array tries; updates are quite fast. If you want to build a trie, consider sorting keys before the insertion:: dict __setitem__ (updates): 6.497M ops/sec trie __setitem__ (updates): 2.633M ops/sec dict __setitem__ (inserts, random): 5.808M ops/sec trie __setitem__ (inserts, random): 0.053M ops/sec dict __setitem__ (inserts, sorted): 5.749M ops/sec trie __setitem__ (inserts, sorted): 0.624M ops/sec dict setdefault (updates): 3.455M ops/sec trie setdefault (updates): 1.910M ops/sec dict setdefault (inserts): 3.466M ops/sec trie setdefault (inserts): 0.053M ops/sec Other results (note that ``len(trie)`` is currently implemented using trie traversal):: dict __contains__ (hits): 6.801M ops/sec trie __contains__ (hits): 2.816M ops/sec dict __contains__ (misses): 5.470M ops/sec trie __contains__ (misses): 4.224M ops/sec dict __len__: 334336.269 ops/sec trie __len__: 22.900 ops/sec dict values(): 406.507 ops/sec trie values(): 20.864 ops/sec dict keys(): 189.298 ops/sec trie keys(): 2.773 ops/sec dict items(): 48.734 ops/sec trie items(): 2.611 ops/sec Please take this benchmark results with a grain of salt; this is a very simple benchmark and may not cover your use case. Current Limitations =================== * keys must be unicode (no implicit conversion for byte strings under Python 2.x, sorry); * there are no iterator versions of keys/values/items (this is not implemented yet); * it is painfully slow and maybe buggy under pypy; * library is not tested with narrow Python builds. Contributing ============ Development happens at github: https://github.com/pytries/datrie. Feel free to submit ideas, bugs, pull requests. Running tests and benchmarks ---------------------------- Make sure `tox`_ is installed and run :: $ tox from the source checkout. Tests should pass under Python 2.7 and 3.4+. :: $ tox -c tox-bench.ini runs benchmarks. If you've changed anything in the source code then make sure `cython`_ is installed and run :: $ update_c.sh before each ``tox`` command. Please note that benchmarks are not included in the release tar.gz's because benchmark data is large and this saves a lot of bandwidth; use source checkouts from github or bitbucket for the benchmarks. .. _cython: https://cython.org/ .. _tox: https://tox.readthedocs.io/ Authors & Contributors ---------------------- See https://github.com/pytries/datrie/graphs/contributors. This module is based on `libdatrie`_ C library by Theppitak Karoonboonyanan and is inspired by `fast_trie`_ Ruby bindings, `PyTrie`_ pure Python implementation and `Tree::Trie`_ Perl implementation; some docs and API ideas are borrowed from these projects. .. _fast_trie: https://github.com/tyler/trie .. _PyTrie: https://github.com/gsakkis/pytrie .. _Tree::Trie: https://metacpan.org/pod/release/AVIF/Tree-Trie-1.9/Trie.pm License ======= Licensed under LGPL v2.1. CHANGES ======= 0.8.2 (2020-03-25) ------------------ * Future-proof Python support by making cython a build time dependency and removing cython generated c files from the repo (and sdist). * Fix collections.abc.MutableMapping import * CI and test updates * Adjust library name to unbreak some linkers 0.8.1 (skipped) --------------- This version intentionally skipped 0.8 (2019-07-03) ---------------- * Python 3.7 compatibility; extension is rebuilt with Cython 0.29.11. * Trie.get function; * Python 2.6 and 3.3 support is dropped; * removed patch to libdatrie which is no longer required; * testing and CI fixes. 0.7.1 (2016-03-12) ------------------ * updated the bundled C library to version 0.2.9; * implemented ``Trie.__len__`` in terms of ``trie_enumerate``; * rebuilt Cython wrapper with Cython 0.23.4; * changed ``Trie`` to implement ``collections.abc.MutableMapping``; * fixed ``Trie`` pickling, which segfaulted on Python2.X. 0.7 (2014-02-18) ---------------- * bundled libdatrie C library is updated to version 0.2.8; * new `.suffixes()` method (thanks Ahmed T. Youssef); * wrapper is rebuilt with Cython 0.20.1. 0.6.1 (2013-09-21) ------------------ * fixed build for Visual Studio (thanks Gabi Davar). 0.6 (2013-07-09) ---------------- * datrie is rebuilt with Cython 0.19.1; * ``iter_prefix_values``, ``prefix_values`` and ``longest_prefix_value`` methods for ``datrie.BaseTrie`` and ``datrie.Trie`` (thanks Jared Suttles). 0.5.1 (2013-01-30) ------------------ * Recently introduced memory leak in ``longest_prefix`` and ``longest_prefix_item`` is fixed. 0.5 (2013-01-29) ---------------- * ``longest_prefix`` and ``longest_prefix_item`` methods are fixed; * datrie is rebuilt with Cython 0.18; * misleading benchmark results in README are fixed; * State._walk is renamed to State.walk_char. 0.4.2 (2012-09-02) ------------------ * Update to latest libdatrie; this makes ``.keys()`` method a bit slower but removes a keys length limitation. 0.4.1 (2012-07-29) ------------------ * cPickle is used for saving/loading ``datrie.Trie`` if it is available. 0.4 (2012-07-27) ---------------- * ``libdatrie`` improvements and bugfixes, including C iterator API support; * custom iteration support using ``datrie.State`` and ``datrie.Iterator``. * speed improvements: ``__length__``, ``keys``, ``values`` and ``items`` methods should be up to 2x faster. * keys longer than 32768 are not supported in this release. 0.3 (2012-07-21) ---------------- There are no new features or speed improvements in this release. * ``datrie.new`` is deprecated; use ``datrie.Trie`` with the same arguments; * small test & benchmark improvements. 0.2 (2012-07-16) ---------------- * ``datrie.Trie`` items can have any Python object as a value (``Trie`` from 0.1.x becomes ``datrie.BaseTrie``); * ``longest_prefix`` and ``longest_prefix_items`` are fixed; * ``save`` & ``load`` are rewritten; * ``setdefault`` method. 0.1.1 (2012-07-13) ------------------ * Windows support (upstream libdatrie changes are merged); * license is changed from LGPL v3 to LGPL v2.1 to match the libdatrie license. 0.1 (2012-07-12) ---------------- Initial release. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384580.0 datrie-0.8.3/datrie.egg-info/SOURCES.txt0000644000175100017510000000275015054046504020107 0ustar00tcaswelltcaswell.gitignore .gitmodules .travis.yml CHANGES.rst COPYING MANIFEST.in README.rst appveyor.yml dev-requirements.txt pyproject.toml setup.cfg setup.py tox-bench.ini tox.ini update_c.sh bench/__init__.py bench/speed.py bench/words100k.txt.zip continuous_integration/appveyor/install.ps1 continuous_integration/appveyor/requirements.txt continuous_integration/appveyor/run_with_env.cmd datrie.egg-info/PKG-INFO datrie.egg-info/SOURCES.txt datrie.egg-info/dependency_links.txt datrie.egg-info/top_level.txt libdatrie/datrie/alpha-map-private.h libdatrie/datrie/alpha-map.c libdatrie/datrie/alpha-map.h libdatrie/datrie/darray.c libdatrie/datrie/darray.h libdatrie/datrie/dstring-private.h libdatrie/datrie/dstring.c libdatrie/datrie/dstring.h libdatrie/datrie/fileutils.c libdatrie/datrie/fileutils.h libdatrie/datrie/tail.c libdatrie/datrie/tail.h libdatrie/datrie/trie-private.h libdatrie/datrie/trie-string.c libdatrie/datrie/trie-string.h libdatrie/datrie/trie.c libdatrie/datrie/trie.h libdatrie/datrie/triedefs.h libdatrie/datrie/typedefs.h libdatrie/tests/test_file.c libdatrie/tests/test_iterator.c libdatrie/tests/test_nonalpha.c libdatrie/tests/test_null_trie.c libdatrie/tests/test_store-retrieve.c libdatrie/tests/test_term_state.c libdatrie/tests/test_walk.c libdatrie/tests/utils.c libdatrie/tests/utils.h libdatrie/tools/trietool.c src/cdatrie.pxd src/datrie.pyx src/stdio_ext.pxd tests/__init__.py tests/test_iteration.py tests/test_random.py tests/test_state.py tests/test_trie.py travis/build-wheels.sh././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384580.0 datrie-0.8.3/datrie.egg-info/dependency_links.txt0000644000175100017510000000000115054046504022265 0ustar00tcaswelltcaswell ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384580.0 datrie-0.8.3/datrie.egg-info/top_level.txt0000644000175100017510000000000715054046504020746 0ustar00tcaswelltcaswelldatrie ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/dev-requirements.txt0000644000175100017510000000003115054046003017301 0ustar00tcaswelltcaswellcython hypothesis pytest ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5631542 datrie-0.8.3/libdatrie/0000755000175100017510000000000015054046505015215 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5667973 datrie-0.8.3/libdatrie/datrie/0000755000175100017510000000000015054046505016465 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/alpha-map-private.h0000644000175100017510000000352715054046004022147 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * alpha-map-private.h - private APIs for alpha-map * Created: 2008-12-04 * Author: Theppitak Karoonboonyanan */ #ifndef __ALPHA_MAP_PRIVATE_H #define __ALPHA_MAP_PRIVATE_H #include #include "alpha-map.h" AlphaMap * alpha_map_fread_bin (FILE *file); int alpha_map_fwrite_bin (const AlphaMap *alpha_map, FILE *file); TrieIndex alpha_map_char_to_trie (const AlphaMap *alpha_map, AlphaChar ac); AlphaChar alpha_map_trie_to_char (const AlphaMap *alpha_map, TrieChar tc); TrieChar * alpha_map_char_to_trie_str (const AlphaMap *alpha_map, const AlphaChar *str); AlphaChar * alpha_map_trie_to_char_str (const AlphaMap *alpha_map, const TrieChar *str); #endif /* __ALPHA_MAP_PRIVATE_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/alpha-map.c0000644000175100017510000003407715054046004020476 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * alpha-map.c - map between character codes and trie alphabet * Created: 2006-08-19 * Author: Theppitak Karoonboonyanan */ #include #include #include #include #include #include "alpha-map.h" #include "alpha-map-private.h" #include "trie-private.h" #include "fileutils.h" /** * @brief Alphabet string length * * @param str : the array of null-terminated AlphaChar string to measure * * @return the total characters in @a str. */ int alpha_char_strlen (const AlphaChar *str) { const AlphaChar *p; for (p = str; *p; p++) ; return p - str; } /** * @brief Compare alphabet strings * * @param str1, str2 : the arrays of null-terminated AlphaChar strings * to compare * * @return negative if @a str1 < @a str2; * 0 if @a str1 == @a str2; * positive if @a str1 > @a str2 * * Available since: 0.2.7 */ int alpha_char_strcmp (const AlphaChar *str1, const AlphaChar *str2) { while (*str1 && *str1 == *str2) { str1++; str2++; } if (*str1 < *str2) return -1; if (*str1 > *str2) return 1; return 0; } /*------------------------------* * PRIVATE DATA DEFINITONS * *------------------------------*/ typedef struct _AlphaRange { struct _AlphaRange *next; AlphaChar begin; AlphaChar end; } AlphaRange; struct _AlphaMap { AlphaRange *first_range; /* work area */ /* alpha-to-trie map */ AlphaChar alpha_begin; AlphaChar alpha_end; int alpha_map_sz; TrieIndex *alpha_to_trie_map; /* trie-to-alpha map */ int trie_map_sz; AlphaChar *trie_to_alpha_map; }; /*-----------------------------------* * PRIVATE METHODS DECLARATIONS * *-----------------------------------*/ static int alpha_map_get_total_ranges (const AlphaMap *alpha_map); static int alpha_map_add_range_only (AlphaMap *alpha_map, AlphaChar begin, AlphaChar end); static int alpha_map_recalc_work_area (AlphaMap *alpha_map); /*-----------------------------* * METHODS IMPLEMENTAIONS * *-----------------------------*/ #define ALPHAMAP_SIGNATURE 0xD9FCD9FC /* AlphaMap Header: * - INT32: signature * - INT32: total ranges * * Ranges: * - INT32: range begin * - INT32: range end */ /** * @brief Create new alphabet map * * @return a pointer to the newly created alphabet map, NULL on failure * * Create a new empty alphabet map. The map contents can then be added with * alpha_map_add_range(). * * The created object must be freed with alpha_map_free(). */ AlphaMap * alpha_map_new () { AlphaMap *alpha_map; alpha_map = (AlphaMap *) malloc (sizeof (AlphaMap)); if (UNLIKELY (!alpha_map)) return NULL; alpha_map->first_range = NULL; /* work area */ alpha_map->alpha_begin = 0; alpha_map->alpha_end = 0; alpha_map->alpha_map_sz = 0; alpha_map->alpha_to_trie_map = NULL; alpha_map->trie_map_sz = 0; alpha_map->trie_to_alpha_map = NULL; return alpha_map; } /** * @brief Create a clone of alphabet map * * @param a_map : the source alphabet map to clone * * @return a pointer to the alphabet map clone, NULL on failure * * The created object must be freed with alpha_map_free(). */ AlphaMap * alpha_map_clone (const AlphaMap *a_map) { AlphaMap *alpha_map; AlphaRange *range; alpha_map = alpha_map_new (); if (UNLIKELY (!alpha_map)) return NULL; for (range = a_map->first_range; range; range = range->next) { if (alpha_map_add_range_only (alpha_map, range->begin, range->end) != 0) goto exit_map_created; } if (alpha_map_recalc_work_area (alpha_map) != 0) goto exit_map_created; return alpha_map; exit_map_created: alpha_map_free (alpha_map); return NULL; } /** * @brief Free an alphabet map object * * @param alpha_map : the alphabet map object to free * * Destruct the @a alpha_map and free its allocated memory. */ void alpha_map_free (AlphaMap *alpha_map) { AlphaRange *p, *q; p = alpha_map->first_range; while (p) { q = p->next; free (p); p = q; } /* work area */ if (alpha_map->alpha_to_trie_map) free (alpha_map->alpha_to_trie_map); if (alpha_map->trie_to_alpha_map) free (alpha_map->trie_to_alpha_map); free (alpha_map); } AlphaMap * alpha_map_fread_bin (FILE *file) { long save_pos; uint32 sig; int32 total, i; AlphaMap *alpha_map; /* check signature */ save_pos = ftell (file); if (!file_read_int32 (file, (int32 *) &sig) || ALPHAMAP_SIGNATURE != sig) goto exit_file_read; alpha_map = alpha_map_new (); if (UNLIKELY (!alpha_map)) goto exit_file_read; /* read number of ranges */ if (!file_read_int32 (file, &total)) goto exit_map_created; /* read character ranges */ for (i = 0; i < total; i++) { int32 b, e; if (!file_read_int32 (file, &b) || !file_read_int32 (file, &e)) goto exit_map_created; alpha_map_add_range_only (alpha_map, b, e); } /* work area */ if (UNLIKELY (alpha_map_recalc_work_area (alpha_map) != 0)) goto exit_map_created; return alpha_map; exit_map_created: alpha_map_free (alpha_map); exit_file_read: fseek (file, save_pos, SEEK_SET); return NULL; } static int alpha_map_get_total_ranges (const AlphaMap *alpha_map) { int n; AlphaRange *range; for (n = 0, range = alpha_map->first_range; range; range = range->next) { ++n; } return n; } int alpha_map_fwrite_bin (const AlphaMap *alpha_map, FILE *file) { AlphaRange *range; if (!file_write_int32 (file, ALPHAMAP_SIGNATURE) || !file_write_int32 (file, alpha_map_get_total_ranges (alpha_map))) { return -1; } for (range = alpha_map->first_range; range; range = range->next) { if (!file_write_int32 (file, range->begin) || !file_write_int32 (file, range->end)) { return -1; } } return 0; } static int alpha_map_add_range_only (AlphaMap *alpha_map, AlphaChar begin, AlphaChar end) { AlphaRange *q, *r, *begin_node, *end_node; if (begin > end) return -1; begin_node = end_node = 0; /* Skip first ranges till 'begin' is covered */ for (q = 0, r = alpha_map->first_range; r && r->begin <= begin; q = r, r = r->next) { if (begin <= r->end) { /* 'r' covers 'begin' -> take 'r' as beginning point */ begin_node = r; break; } if (r->end + 1 == begin) { /* 'begin' is next to 'r'-end * -> extend 'r'-end to cover 'begin' */ r->end = begin; begin_node = r; break; } } if (!begin_node && r && r->begin <= end + 1) { /* ['begin', 'end'] overlaps into 'r'-begin * or 'r' is next to 'end' if r->begin == end + 1 * -> extend 'r'-begin to include the range */ r->begin = begin; begin_node = r; } /* Run upto the first range that exceeds 'end' */ while (r && r->begin <= end + 1) { if (end <= r->end) { /* 'r' covers 'end' -> take 'r' as ending point */ end_node = r; } else if (r != begin_node) { /* ['begin', 'end'] covers the whole 'r' -> remove 'r' */ if (q) { q->next = r->next; free (r); r = q->next; } else { alpha_map->first_range = r->next; free (r); r = alpha_map->first_range; } continue; } q = r; r = r->next; } if (!end_node && q && begin <= q->end) { /* ['begin', 'end'] overlaps 'q' at the end * -> extend 'q'-end to include the range */ q->end = end; end_node = q; } if (begin_node && end_node) { if (begin_node != end_node) { /* Merge begin_node and end_node ranges together */ assert (begin_node->next == end_node); begin_node->end = end_node->end; begin_node->next = end_node->next; free (end_node); } } else if (!begin_node && !end_node) { /* ['begin', 'end'] overlaps with none of the ranges * -> insert a new range */ AlphaRange *range = (AlphaRange *) malloc (sizeof (AlphaRange)); if (UNLIKELY (!range)) return -1; range->begin = begin; range->end = end; /* insert it between 'q' and 'r' */ if (q) { q->next = range; } else { alpha_map->first_range = range; } range->next = r; } return 0; } static int alpha_map_recalc_work_area (AlphaMap *alpha_map) { AlphaRange *range; /* free old existing map */ if (alpha_map->alpha_to_trie_map) { free (alpha_map->alpha_to_trie_map); alpha_map->alpha_to_trie_map = NULL; } if (alpha_map->trie_to_alpha_map) { free (alpha_map->trie_to_alpha_map); alpha_map->trie_to_alpha_map = NULL; } range = alpha_map->first_range; if (range) { const AlphaChar alpha_begin = range->begin; int n_cells, i; AlphaChar a; TrieChar trie_last = 0; TrieChar tc; /* reconstruct alpha-to-trie map */ alpha_map->alpha_begin = alpha_begin; while (range->next) { range = range->next; } alpha_map->alpha_end = range->end; alpha_map->alpha_map_sz = n_cells = range->end - alpha_begin + 1; alpha_map->alpha_to_trie_map = (TrieIndex *) malloc (n_cells * sizeof (TrieIndex)); if (UNLIKELY (!alpha_map->alpha_to_trie_map)) goto error_alpha_map_not_created; for (i = 0; i < n_cells; i++) { alpha_map->alpha_to_trie_map[i] = TRIE_INDEX_MAX; } for (range = alpha_map->first_range; range; range = range->next) { for (a = range->begin; a <= range->end; a++) { alpha_map->alpha_to_trie_map[a - alpha_begin] = ++trie_last; } } /* reconstruct trie-to-alpha map */ alpha_map->trie_map_sz = n_cells = trie_last + 1; alpha_map->trie_to_alpha_map = (AlphaChar *) malloc (n_cells * sizeof (AlphaChar)); if (UNLIKELY (!alpha_map->trie_to_alpha_map)) goto error_alpha_map_created; alpha_map->trie_to_alpha_map[0] = 0; tc = 1; for (range = alpha_map->first_range; range; range = range->next) { for (a = range->begin; a <= range->end; a++) { alpha_map->trie_to_alpha_map[tc++] = a; } } } return 0; error_alpha_map_created: free (alpha_map->alpha_to_trie_map); alpha_map->alpha_to_trie_map = NULL; error_alpha_map_not_created: return -1; } /** * @brief Add a range to alphabet map * * @param alpha_map : the alphabet map object * @param begin : the first character of the range * @param end : the last character of the range * * @return 0 on success, non-zero on failure * * Add a range of character codes from @a begin to @a end to the * alphabet set. */ int alpha_map_add_range (AlphaMap *alpha_map, AlphaChar begin, AlphaChar end) { int res = alpha_map_add_range_only (alpha_map, begin, end); if (res != 0) return res; return alpha_map_recalc_work_area (alpha_map); } TrieIndex alpha_map_char_to_trie (const AlphaMap *alpha_map, AlphaChar ac) { TrieIndex alpha_begin; if (UNLIKELY (0 == ac)) return 0; if (UNLIKELY (!alpha_map->alpha_to_trie_map)) return TRIE_INDEX_MAX; alpha_begin = alpha_map->alpha_begin; if (alpha_begin <= ac && ac <= alpha_map->alpha_end) { return alpha_map->alpha_to_trie_map[ac - alpha_begin]; } return TRIE_INDEX_MAX; } AlphaChar alpha_map_trie_to_char (const AlphaMap *alpha_map, TrieChar tc) { if (tc < alpha_map->trie_map_sz) return alpha_map->trie_to_alpha_map[tc]; return ALPHA_CHAR_ERROR; } TrieChar * alpha_map_char_to_trie_str (const AlphaMap *alpha_map, const AlphaChar *str) { TrieChar *trie_str, *p; trie_str = (TrieChar *) malloc (alpha_char_strlen (str) + 1); if (UNLIKELY (!trie_str)) return NULL; for (p = trie_str; *str; p++, str++) { TrieIndex tc = alpha_map_char_to_trie (alpha_map, *str); if (TRIE_INDEX_MAX == tc) goto error_str_allocated; *p = (TrieChar) tc; } *p = 0; return trie_str; error_str_allocated: free (trie_str); return NULL; } AlphaChar * alpha_map_trie_to_char_str (const AlphaMap *alpha_map, const TrieChar *str) { AlphaChar *alpha_str, *p; alpha_str = (AlphaChar *) malloc ((strlen ((const char *)str) + 1) * sizeof (AlphaChar)); if (UNLIKELY (!alpha_str)) return NULL; for (p = alpha_str; *str; p++, str++) { *p = (AlphaChar) alpha_map_trie_to_char (alpha_map, *str); } *p = 0; return alpha_str; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/alpha-map.h0000644000175100017510000000553015054046004020473 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * alpha-map.h - map between character codes and trie alphabet * Created: 2006-08-19 * Author: Theppitak Karoonboonyanan */ #ifndef __ALPHA_MAP_H #define __ALPHA_MAP_H #include #include "typedefs.h" #include "triedefs.h" #ifdef __cplusplus extern "C" { #endif /** * @file alpha-map.h * @brief AlphaMap data type and functions * * AlphaMap is a mapping between AlphaChar and TrieChar. AlphaChar is the * alphabet character used in words of a target language, while TrieChar * is a small integer with packed range of values and is actually used in * trie state transition calculations. * * Since double-array trie relies on sparse state transition table, * a small set of input characters can make the table small, i.e. with * small number of columns. But in real life, alphabet characters can be * of non-continuous range of values. The unused slots between them can * waste the space in the table, and can increase the chance of unused * array cells. * * AlphaMap is thus defined for mapping between non-continuous ranges of * values of AlphaChar and packed and continuous range of Triechar. * * In this implementation, TrieChar is defined as a single-byte integer, * which means the largest AlphaChar set that is supported is of 255 * values, as the special value of 0 is reserved for null-termination code. */ /** * @brief AlphaMap data type */ typedef struct _AlphaMap AlphaMap; AlphaMap * alpha_map_new (); AlphaMap * alpha_map_clone (const AlphaMap *a_map); void alpha_map_free (AlphaMap *alpha_map); int alpha_map_add_range (AlphaMap *alpha_map, AlphaChar begin, AlphaChar end); int alpha_char_strlen (const AlphaChar *str); int alpha_char_strcmp (const AlphaChar *str1, const AlphaChar *str2); #ifdef __cplusplus } #endif #endif /* __ALPHA_MAP_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/darray.c0000644000175100017510000005271515054046004020117 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * darray.c - Double-array trie structure * Created: 2006-08-13 * Author: Theppitak Karoonboonyanan */ #include #include #ifndef _MSC_VER /* for SIZE_MAX */ # include #endif #include #include "trie-private.h" #include "darray.h" #include "fileutils.h" /*----------------------------------* * INTERNAL TYPES DECLARATIONS * *----------------------------------*/ struct _Symbols { short num_symbols; TrieChar symbols[TRIE_CHAR_MAX + 1]; }; static Symbols * symbols_new (); static void symbols_add (Symbols *syms, TrieChar c); #define symbols_add_fast(s,c) ((s)->symbols[(s)->num_symbols++] = c) /*-----------------------------------* * PRIVATE METHODS DECLARATIONS * *-----------------------------------*/ #define da_get_free_list(d) (1) static Bool da_check_free_cell (DArray *d, TrieIndex s); static Bool da_has_children (const DArray *d, TrieIndex s); static TrieIndex da_find_free_base (DArray *d, const Symbols *symbols); static Bool da_fit_symbols (DArray *d, TrieIndex base, const Symbols *symbols); static void da_relocate_base (DArray *d, TrieIndex s, TrieIndex new_base); static Bool da_extend_pool (DArray *d, TrieIndex to_index); static void da_alloc_cell (DArray *d, TrieIndex cell); static void da_free_cell (DArray *d, TrieIndex cell); /* ==================== BEGIN IMPLEMENTATION PART ==================== */ /*------------------------------------* * INTERNAL TYPES IMPLEMENTATIONS * *------------------------------------*/ static Symbols * symbols_new () { Symbols *syms; syms = (Symbols *) malloc (sizeof (Symbols)); if (UNLIKELY (!syms)) return NULL; syms->num_symbols = 0; return syms; } void symbols_free (Symbols *syms) { free (syms); } static void symbols_add (Symbols *syms, TrieChar c) { short lower, upper; lower = 0; upper = syms->num_symbols; while (lower < upper) { short middle; middle = (lower + upper)/2; if (c > syms->symbols[middle]) lower = middle + 1; else if (c < syms->symbols[middle]) upper = middle; else return; } if (lower < syms->num_symbols) { memmove (syms->symbols + lower + 1, syms->symbols + lower, syms->num_symbols - lower); } syms->symbols[lower] = c; syms->num_symbols++; } int symbols_num (const Symbols *syms) { return syms->num_symbols; } TrieChar symbols_get (const Symbols *syms, int index) { return syms->symbols[index]; } /*------------------------------* * PRIVATE DATA DEFINITONS * *------------------------------*/ typedef struct { TrieIndex base; TrieIndex check; } DACell; struct _DArray { TrieIndex num_cells; DACell *cells; }; /*-----------------------------* * METHODS IMPLEMENTAIONS * *-----------------------------*/ #define DA_SIGNATURE 0xDAFCDAFC /* DA Header: * - Cell 0: SIGNATURE, number of cells * - Cell 1: free circular-list pointers * - Cell 2: root node * - Cell 3: DA pool begin */ #define DA_POOL_BEGIN 3 /** * @brief Create a new double-array object * * Create a new empty doubla-array object. */ DArray * da_new () { DArray *d; d = (DArray *) malloc (sizeof (DArray)); if (UNLIKELY (!d)) return NULL; d->num_cells = DA_POOL_BEGIN; d->cells = (DACell *) malloc (d->num_cells * sizeof (DACell)); if (UNLIKELY (!d->cells)) goto exit_da_created; d->cells[0].base = DA_SIGNATURE; d->cells[0].check = d->num_cells; d->cells[1].base = -1; d->cells[1].check = -1; d->cells[2].base = DA_POOL_BEGIN; d->cells[2].check = 0; return d; exit_da_created: free (d); return NULL; } /** * @brief Read double-array data from file * * @param file : the file to read * * @return a pointer to the openned double-array, NULL on failure * * Read double-array data from the opened file, starting from the current * file pointer until the end of double array data block. On return, the * file pointer is left at the position after the read block. */ DArray * da_fread (FILE *file) { long save_pos; DArray *d = NULL; TrieIndex n; /* check signature */ save_pos = ftell (file); if (!file_read_int32 (file, &n) || DA_SIGNATURE != (uint32) n) goto exit_file_read; d = (DArray *) malloc (sizeof (DArray)); if (UNLIKELY (!d)) goto exit_file_read; /* read number of cells */ if (!file_read_int32 (file, &d->num_cells)) goto exit_da_created; if (d->num_cells > SIZE_MAX / sizeof (DACell)) goto exit_da_created; d->cells = (DACell *) malloc (d->num_cells * sizeof (DACell)); if (UNLIKELY (!d->cells)) goto exit_da_created; d->cells[0].base = DA_SIGNATURE; d->cells[0].check= d->num_cells; for (n = 1; n < d->num_cells; n++) { if (!file_read_int32 (file, &d->cells[n].base) || !file_read_int32 (file, &d->cells[n].check)) { goto exit_da_cells_created; } } return d; exit_da_cells_created: free (d->cells); exit_da_created: free (d); exit_file_read: fseek (file, save_pos, SEEK_SET); return NULL; } /** * @brief Free double-array data * * @param d : the double-array data * * Free the given double-array data. */ void da_free (DArray *d) { free (d->cells); free (d); } /** * @brief Write double-array data * * @param d : the double-array data * @param file : the file to write to * * @return 0 on success, non-zero on failure * * Write double-array data to the given @a file, starting from the current * file pointer. On return, the file pointer is left after the double-array * data block. */ int da_fwrite (const DArray *d, FILE *file) { TrieIndex i; for (i = 0; i < d->num_cells; i++) { if (!file_write_int32 (file, d->cells[i].base) || !file_write_int32 (file, d->cells[i].check)) { return -1; } } return 0; } /** * @brief Get root state * * @param d : the double-array data * * @return root state of the @a index set, or TRIE_INDEX_ERROR on failure * * Get root state for stepwise walking. */ TrieIndex da_get_root (const DArray *d) { /* can be calculated value for multi-index trie */ return 2; } /** * @brief Get BASE cell * * @param d : the double-array data * @param s : the double-array state to get data * * @return the BASE cell value for the given state * * Get BASE cell value for the given state. */ TrieIndex da_get_base (const DArray *d, TrieIndex s) { return LIKELY (s < d->num_cells) ? d->cells[s].base : TRIE_INDEX_ERROR; } /** * @brief Get CHECK cell * * @param d : the double-array data * @param s : the double-array state to get data * * @return the CHECK cell value for the given state * * Get CHECK cell value for the given state. */ TrieIndex da_get_check (const DArray *d, TrieIndex s) { return LIKELY (s < d->num_cells) ? d->cells[s].check : TRIE_INDEX_ERROR; } /** * @brief Set BASE cell * * @param d : the double-array data * @param s : the double-array state to get data * @param val : the value to set * * Set BASE cell for the given state to the given value. */ void da_set_base (DArray *d, TrieIndex s, TrieIndex val) { if (LIKELY (s < d->num_cells)) { d->cells[s].base = val; } } /** * @brief Set CHECK cell * * @param d : the double-array data * @param s : the double-array state to get data * @param val : the value to set * * Set CHECK cell for the given state to the given value. */ void da_set_check (DArray *d, TrieIndex s, TrieIndex val) { if (LIKELY (s < d->num_cells)) { d->cells[s].check = val; } } /** * @brief Walk in double-array structure * * @param d : the double-array structure * @param s : current state * @param c : the input character * * @return boolean indicating success * * Walk the double-array trie from state @a *s, using input character @a c. * If there exists an edge from @a *s with arc labeled @a c, this function * returns TRUE and @a *s is updated to the new state. Otherwise, it returns * FALSE and @a *s is left unchanged. */ Bool da_walk (const DArray *d, TrieIndex *s, TrieChar c) { TrieIndex next; next = da_get_base (d, *s) + c; if (da_get_check (d, next) == *s) { *s = next; return TRUE; } return FALSE; } /** * @brief Insert a branch from trie node * * @param d : the double-array structure * @param s : the state to add branch to * @param c : the character for the branch label * * @return the index of the new node * * Insert a new arc labelled with character @a c from the trie node * represented by index @a s in double-array structure @a d. * Note that it assumes that no such arc exists before inserting. */ TrieIndex da_insert_branch (DArray *d, TrieIndex s, TrieChar c) { TrieIndex base, next; base = da_get_base (d, s); if (base > 0) { next = base + c; /* if already there, do not actually insert */ if (da_get_check (d, next) == s) return next; /* if (base + c) > TRIE_INDEX_MAX which means 'next' is overflow, * or cell [next] is not free, relocate to a free slot */ if (base > TRIE_INDEX_MAX - c || !da_check_free_cell (d, next)) { Symbols *symbols; TrieIndex new_base; /* relocate BASE[s] */ symbols = da_output_symbols (d, s); symbols_add (symbols, c); new_base = da_find_free_base (d, symbols); symbols_free (symbols); if (UNLIKELY (TRIE_INDEX_ERROR == new_base)) return TRIE_INDEX_ERROR; da_relocate_base (d, s, new_base); next = new_base + c; } } else { Symbols *symbols; TrieIndex new_base; symbols = symbols_new (); symbols_add (symbols, c); new_base = da_find_free_base (d, symbols); symbols_free (symbols); if (UNLIKELY (TRIE_INDEX_ERROR == new_base)) return TRIE_INDEX_ERROR; da_set_base (d, s, new_base); next = new_base + c; } da_alloc_cell (d, next); da_set_check (d, next, s); return next; } static Bool da_check_free_cell (DArray *d, TrieIndex s) { return da_extend_pool (d, s) && da_get_check (d, s) < 0; } static Bool da_has_children (const DArray *d, TrieIndex s) { TrieIndex base; TrieIndex c, max_c; base = da_get_base (d, s); if (TRIE_INDEX_ERROR == base || base < 0) return FALSE; max_c = MIN_VAL (TRIE_CHAR_MAX, d->num_cells - base); for (c = 0; c <= max_c; c++) { if (da_get_check (d, base + c) == s) return TRUE; } return FALSE; } Symbols * da_output_symbols (const DArray *d, TrieIndex s) { Symbols *syms; TrieIndex base; TrieIndex c, max_c; syms = symbols_new (); base = da_get_base (d, s); max_c = MIN_VAL (TRIE_CHAR_MAX, d->num_cells - base); for (c = 0; c <= max_c; c++) { if (da_get_check (d, base + c) == s) symbols_add_fast (syms, (TrieChar) c); } return syms; } static TrieIndex da_find_free_base (DArray *d, const Symbols *symbols) { TrieChar first_sym; TrieIndex s; /* find first free cell that is beyond the first symbol */ first_sym = symbols_get (symbols, 0); s = -da_get_check (d, da_get_free_list (d)); while (s != da_get_free_list (d) && s < (TrieIndex) first_sym + DA_POOL_BEGIN) { s = -da_get_check (d, s); } if (s == da_get_free_list (d)) { for (s = first_sym + DA_POOL_BEGIN; ; ++s) { if (!da_extend_pool (d, s)) return TRIE_INDEX_ERROR; if (da_get_check (d, s) < 0) break; } } /* search for next free cell that fits the symbols set */ while (!da_fit_symbols (d, s - first_sym, symbols)) { /* extend pool before getting exhausted */ if (-da_get_check (d, s) == da_get_free_list (d)) { if (UNLIKELY (!da_extend_pool (d, d->num_cells))) return TRIE_INDEX_ERROR; } s = -da_get_check (d, s); } return s - first_sym; } static Bool da_fit_symbols (DArray *d, TrieIndex base, const Symbols *symbols) { int i; for (i = 0; i < symbols_num (symbols); i++) { TrieChar sym = symbols_get (symbols, i); /* if (base + sym) > TRIE_INDEX_MAX which means it's overflow, * or cell [base + sym] is not free, the symbol is not fit. */ if (base > TRIE_INDEX_MAX - sym || !da_check_free_cell (d, base + sym)) return FALSE; } return TRUE; } static void da_relocate_base (DArray *d, TrieIndex s, TrieIndex new_base) { TrieIndex old_base; Symbols *symbols; int i; old_base = da_get_base (d, s); symbols = da_output_symbols (d, s); for (i = 0; i < symbols_num (symbols); i++) { TrieIndex old_next, new_next, old_next_base; old_next = old_base + symbols_get (symbols, i); new_next = new_base + symbols_get (symbols, i); old_next_base = da_get_base (d, old_next); /* allocate new next node and copy BASE value */ da_alloc_cell (d, new_next); da_set_check (d, new_next, s); da_set_base (d, new_next, old_next_base); /* old_next node is now moved to new_next * so, all cells belonging to old_next * must be given to new_next */ /* preventing the case of TAIL pointer */ if (old_next_base > 0) { TrieIndex c, max_c; max_c = MIN_VAL (TRIE_CHAR_MAX, d->num_cells - old_next_base); for (c = 0; c <= max_c; c++) { if (da_get_check (d, old_next_base + c) == old_next) da_set_check (d, old_next_base + c, new_next); } } /* free old_next node */ da_free_cell (d, old_next); } symbols_free (symbols); /* finally, make BASE[s] point to new_base */ da_set_base (d, s, new_base); } static Bool da_extend_pool (DArray *d, TrieIndex to_index) { void *new_block; TrieIndex new_begin; TrieIndex i; TrieIndex free_tail; if (UNLIKELY (to_index <= 0 || TRIE_INDEX_MAX <= to_index)) return FALSE; if (to_index < d->num_cells) return TRUE; new_block = realloc (d->cells, (to_index + 1) * sizeof (DACell)); if (UNLIKELY (!new_block)) return FALSE; d->cells = (DACell *) new_block; new_begin = d->num_cells; d->num_cells = to_index + 1; /* initialize new free list */ for (i = new_begin; i < to_index; i++) { da_set_check (d, i, -(i + 1)); da_set_base (d, i + 1, -i); } /* merge the new circular list to the old */ free_tail = -da_get_base (d, da_get_free_list (d)); da_set_check (d, free_tail, -new_begin); da_set_base (d, new_begin, -free_tail); da_set_check (d, to_index, -da_get_free_list (d)); da_set_base (d, da_get_free_list (d), -to_index); /* update header cell */ d->cells[0].check = d->num_cells; return TRUE; } /** * @brief Prune the single branch * * @param d : the double-array structure * @param s : the dangling state to prune off * * Prune off a non-separate path up from the final state @a s. * If @a s still has some children states, it does nothing. Otherwise, * it deletes the node and all its parents which become non-separate. */ void da_prune (DArray *d, TrieIndex s) { da_prune_upto (d, da_get_root (d), s); } /** * @brief Prune the single branch up to given parent * * @param d : the double-array structure * @param p : the parent up to which to be pruned * @param s : the dangling state to prune off * * Prune off a non-separate path up from the final state @a s to the * given parent @a p. The prunning stop when either the parent @a p * is met, or a first non-separate node is found. */ void da_prune_upto (DArray *d, TrieIndex p, TrieIndex s) { while (p != s && !da_has_children (d, s)) { TrieIndex parent; parent = da_get_check (d, s); da_free_cell (d, s); s = parent; } } static void da_alloc_cell (DArray *d, TrieIndex cell) { TrieIndex prev, next; prev = -da_get_base (d, cell); next = -da_get_check (d, cell); /* remove the cell from free list */ da_set_check (d, prev, -next); da_set_base (d, next, -prev); } static void da_free_cell (DArray *d, TrieIndex cell) { TrieIndex i, prev; /* find insertion point */ i = -da_get_check (d, da_get_free_list (d)); while (i != da_get_free_list (d) && i < cell) i = -da_get_check (d, i); prev = -da_get_base (d, i); /* insert cell before i */ da_set_check (d, cell, -i); da_set_base (d, cell, -prev); da_set_check (d, prev, -cell); da_set_base (d, i, -cell); } /** * @brief Find first separate node in a sub-trie * * @param d : the double-array structure * @param root : the sub-trie root to search from * @param keybuff : the TrieString buffer for incrementally calcuating key * * @return index to the first separate node; TRIE_INDEX_ERROR on any failure * * Find the first separate node under a sub-trie rooted at @a root. * * On return, @a keybuff is appended with the key characters which walk from * @a root to the separate node. This is for incrementally calculating the * transition key, which is more efficient than later totally reconstructing * key from the given separate node. * * Available since: 0.2.6 */ TrieIndex da_first_separate (DArray *d, TrieIndex root, TrieString *keybuff) { TrieIndex base; TrieIndex c, max_c; while ((base = da_get_base (d, root)) >= 0) { max_c = MIN_VAL (TRIE_CHAR_MAX, d->num_cells - base); for (c = 0; c <= max_c; c++) { if (da_get_check (d, base + c) == root) break; } if (c > max_c) return TRIE_INDEX_ERROR; trie_string_append_char (keybuff, c); root = base + c; } return root; } /** * @brief Find next separate node in a sub-trie * * @param d : the double-array structure * @param root : the sub-trie root to search from * @param sep : the current separate node * @param keybuff : the TrieString buffer for incrementally calcuating key * * @return index to the next separate node; TRIE_INDEX_ERROR if no more * separate node is found * * Find the next separate node under a sub-trie rooted at @a root starting * from the current separate node @a sep. * * On return, @a keybuff is incrementally updated from the key which walks * to previous separate node to the one which walks to the new separate node. * So, it is assumed to be initialized by at least one da_first_separate() * call before. This incremental key calculation is more efficient than later * totally reconstructing key from the given separate node. * * Available since: 0.2.6 */ TrieIndex da_next_separate (DArray *d, TrieIndex root, TrieIndex sep, TrieString *keybuff) { TrieIndex parent; TrieIndex base; TrieIndex c, max_c; while (sep != root) { parent = da_get_check (d, sep); base = da_get_base (d, parent); c = sep - base; trie_string_cut_last (keybuff); /* find next sibling of sep */ max_c = MIN_VAL (TRIE_CHAR_MAX, d->num_cells - base); while (++c <= max_c) { if (da_get_check (d, base + c) == parent) { trie_string_append_char (keybuff, c); return da_first_separate (d, base + c, keybuff); } } sep = parent; } return TRIE_INDEX_ERROR; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/darray.h0000644000175100017510000000574615054046004020126 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * darray.h - Double-array trie structure * Created: 2006-08-11 * Author: Theppitak Karoonboonyanan */ #ifndef __DARRAY_H #define __DARRAY_H #include "triedefs.h" #include "trie-string.h" /** * @file darray.h * @brief Double-array trie structure */ /** * @brief Symbol set structure type */ typedef struct _Symbols Symbols; void symbols_free (Symbols *syms); int symbols_num (const Symbols *syms); TrieChar symbols_get (const Symbols *syms, int index); /** * @brief Double-array structure type */ typedef struct _DArray DArray; DArray * da_new (); DArray * da_fread (FILE *file); void da_free (DArray *d); int da_fwrite (const DArray *d, FILE *file); TrieIndex da_get_root (const DArray *d); TrieIndex da_get_base (const DArray *d, TrieIndex s); TrieIndex da_get_check (const DArray *d, TrieIndex s); void da_set_base (DArray *d, TrieIndex s, TrieIndex val); void da_set_check (DArray *d, TrieIndex s, TrieIndex val); Bool da_walk (const DArray *d, TrieIndex *s, TrieChar c); Symbols * da_output_symbols (const DArray *d, TrieIndex s); /** * @brief Test walkability in double-array structure * * @param d : the double-array structure * @param s : current state * @param c : the input character * * @return boolean indicating walkability * * Test if there is a transition from state @a s with input character @a c. */ /* Bool da_is_walkable (DArray *d, TrieIndex s, TrieChar c); */ #define da_is_walkable(d,s,c) \ (da_get_check ((d), da_get_base ((d), (s)) + (c)) == (s)) TrieIndex da_insert_branch (DArray *d, TrieIndex s, TrieChar c); void da_prune (DArray *d, TrieIndex s); void da_prune_upto (DArray *d, TrieIndex p, TrieIndex s); TrieIndex da_first_separate (DArray *d, TrieIndex root, TrieString *keybuff); TrieIndex da_next_separate (DArray *d, TrieIndex root, TrieIndex sep, TrieString *keybuff); #endif /* __DARRAY_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/dstring-private.h0000644000175100017510000000244715054046004021761 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * dstring-private.h - Dynamic string type * Created: 2012-08-02 * Author: Theppitak Karoonboonyanan */ #ifndef __DSTRING_PRIVATE_H #define __DSTRING_PRIVATE_H #include "typedefs.h" struct _DString { int char_size; int str_len; int alloc_size; void * val; }; #endif /* __DSTRING_PRIVATE_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/dstring.c0000644000175100017510000000766415054046004020312 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * dstring.c - Dynamic string type * Created: 2012-08-01 * Author: Theppitak Karoonboonyanan */ #include "dstring.h" #include "dstring-private.h" #include "trie-private.h" #include #include DString * dstring_new (int char_size, int n_elm) { DString *ds; ds = (DString *) malloc (sizeof (DString)); if (UNLIKELY (!ds)) return NULL; ds->alloc_size = char_size * n_elm; ds->val = malloc (ds->alloc_size); if (!ds->val) { free (ds); return NULL; } ds->char_size = char_size; ds->str_len = 0; return ds; } void dstring_free (DString *ds) { free (ds->val); free (ds); } int dstring_length (const DString *ds) { return ds->str_len; } const void * dstring_get_val (const DString *ds) { return ds->val; } void * dstring_get_val_rw (DString *ds) { return ds->val; } void dstring_clear (DString *ds) { ds->str_len = 0; } static Bool dstring_ensure_space (DString *ds, int size) { if (ds->alloc_size < size) { int re_size = MAX_VAL (ds->alloc_size * 2, size); void *re_ptr = realloc (ds->val, re_size); if (UNLIKELY (!re_ptr)) return FALSE; ds->val = re_ptr; ds->alloc_size = re_size; } return TRUE; } Bool dstring_copy (DString *dst, const DString *src) { if (!dstring_ensure_space (dst, (src->str_len + 1) * src->char_size)) return FALSE; memcpy (dst->val, src->val, (src->str_len + 1) * src->char_size); dst->char_size = src->char_size; dst->str_len = src->str_len; return TRUE; } Bool dstring_append (DString *dst, const DString *src) { if (dst->char_size != src->char_size) return FALSE; if (!dstring_ensure_space (dst, (dst->str_len + src->str_len + 1) * dst->char_size)) { return FALSE; } memcpy ((char *)dst->val + (dst->char_size * dst->str_len), src->val, (src->str_len + 1) * dst->char_size); dst->str_len += src->str_len; return TRUE; } Bool dstring_append_string (DString *ds, const void *data, int len) { if (!dstring_ensure_space (ds, (ds->str_len + len + 1) * ds->char_size)) return FALSE; memcpy ((char *)ds->val + (ds->char_size * ds->str_len), data, ds->char_size * len); ds->str_len += len; return TRUE; } Bool dstring_append_char (DString *ds, const void *data) { if (!dstring_ensure_space (ds, (ds->str_len + 2) * ds->char_size)) return FALSE; memcpy ((char *)ds->val + (ds->char_size * ds->str_len), data, ds->char_size); ds->str_len++; return TRUE; } Bool dstring_terminate (DString *ds) { if (!dstring_ensure_space (ds, (ds->str_len + 2) * ds->char_size)) return FALSE; memset ((char *)ds->val + (ds->char_size * ds->str_len), 0, ds->char_size); return TRUE; } Bool dstring_cut_last (DString *ds) { if (0 == ds->str_len) return FALSE; ds->str_len--; return TRUE; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/dstring.h0000644000175100017510000000345515054046004020311 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * dstring.h - Dynamic string type * Created: 2012-08-01 * Author: Theppitak Karoonboonyanan */ #ifndef __DSTRING_H #define __DSTRING_H #include "typedefs.h" typedef struct _DString DString; DString * dstring_new (int char_size, int n_elm); void dstring_free (DString *ds); int dstring_length (const DString *ds); const void * dstring_get_val (const DString *ds); void * dstring_get_val_rw (DString *ds); void dstring_clear (DString *ds); Bool dstring_copy (DString *dst, const DString *src); Bool dstring_append (DString *dst, const DString *src); Bool dstring_append_string (DString *ds, const void *data, int len); Bool dstring_append_char (DString *ds, const void *data); Bool dstring_terminate (DString *ds); Bool dstring_cut_last (DString *ds); #endif /* __DSTRING_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/fileutils.c0000644000175100017510000000521715054046004020630 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * fileutils.h - File utility functions * Created: 2006-08-15 * Author: Theppitak Karoonboonyanan */ #include #include #include "fileutils.h" /* ==================== BEGIN IMPLEMENTATION PART ==================== */ /*--------------------------------* * FUNCTIONS IMPLEMENTATIONS * *--------------------------------*/ Bool file_read_int32 (FILE *file, int32 *o_val) { unsigned char buff[4]; if (fread (buff, 4, 1, file) == 1) { *o_val = (buff[0] << 24) | (buff[1] << 16) | (buff[2] << 8) | buff[3]; return TRUE; } return FALSE; } Bool file_write_int32 (FILE *file, int32 val) { unsigned char buff[4]; buff[0] = (val >> 24) & 0xff; buff[1] = (val >> 16) & 0xff; buff[2] = (val >> 8) & 0xff; buff[3] = val & 0xff; return (fwrite (buff, 4, 1, file) == 1); } Bool file_read_int16 (FILE *file, int16 *o_val) { unsigned char buff[2]; if (fread (buff, 2, 1, file) == 1) { *o_val = (buff[0] << 8) | buff[1]; return TRUE; } return FALSE; } Bool file_write_int16 (FILE *file, int16 val) { unsigned char buff[2]; buff[0] = val >> 8; buff[1] = val & 0xff; return (fwrite (buff, 2, 1, file) == 1); } Bool file_read_int8 (FILE *file, int8 *o_val) { return (fread (o_val, sizeof (int8), 1, file) == 1); } Bool file_write_int8 (FILE *file, int8 val) { return (fwrite (&val, sizeof (int8), 1, file) == 1); } Bool file_read_chars (FILE *file, char *buff, int len) { return (fread (buff, sizeof (char), len, file) == len); } Bool file_write_chars (FILE *file, const char *buff, int len) { return (fwrite (buff, sizeof (char), len, file) == len); } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/fileutils.h0000644000175100017510000000315115054046004020630 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * fileutils.h - File utility functions * Created: 2006-08-14 * Author: Theppitak Karoonboonyanan */ #ifndef __FILEUTILS_H #define __FILEUTILS_H #include #include Bool file_read_int32 (FILE *file, int32 *o_val); Bool file_write_int32 (FILE *file, int32 val); Bool file_read_int16 (FILE *file, int16 *o_val); Bool file_write_int16 (FILE *file, int16 val); Bool file_read_int8 (FILE *file, int8 *o_val); Bool file_write_int8 (FILE *file, int8 val); Bool file_read_chars (FILE *file, char *buff, int len); Bool file_write_chars (FILE *file, const char *buff, int len); #endif /* __FILEUTILS_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/tail.c0000644000175100017510000003151015054046004017554 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * tail.c - trie tail for keeping suffixes * Created: 2006-08-15 * Author: Theppitak Karoonboonyanan */ #include #include #ifndef _MSC_VER /* for SIZE_MAX */ # include #endif #include #include "tail.h" #include "trie-private.h" #include "fileutils.h" /*----------------------------------* * INTERNAL TYPES DECLARATIONS * *----------------------------------*/ /*-----------------------------------* * PRIVATE METHODS DECLARATIONS * *-----------------------------------*/ static TrieIndex tail_alloc_block (Tail *t); static void tail_free_block (Tail *t, TrieIndex block); /* ==================== BEGIN IMPLEMENTATION PART ==================== */ /*------------------------------------* * INTERNAL TYPES IMPLEMENTATIONS * *------------------------------------*/ /*------------------------------* * PRIVATE DATA DEFINITONS * *------------------------------*/ typedef struct { TrieIndex next_free; TrieData data; TrieChar *suffix; } TailBlock; struct _Tail { TrieIndex num_tails; TailBlock *tails; TrieIndex first_free; }; /*-----------------------------* * METHODS IMPLEMENTAIONS * *-----------------------------*/ #define TAIL_SIGNATURE 0xDFFCDFFC #define TAIL_START_BLOCKNO 1 /* Tail Header: * INT32: signature * INT32: pointer to first free slot * INT32: number of tail blocks * * Tail Blocks: * INT32: pointer to next free block (-1 for allocated blocks) * INT32: data for the key * INT16: length * BYTES[length]: suffix string (no terminating '\0') */ /** * @brief Create a new tail object * * Create a new empty tail object. */ Tail * tail_new () { Tail *t; t = (Tail *) malloc (sizeof (Tail)); if (UNLIKELY (!t)) return NULL; t->first_free = 0; t->num_tails = 0; t->tails = NULL; return t; } /** * @brief Read tail data from file * * @param file : the file to read * * @return a pointer to the openned tail data, NULL on failure * * Read tail data from the opened file, starting from the current * file pointer until the end of tail data block. On return, the * file pointer is left at the position after the read block. */ Tail * tail_fread (FILE *file) { long save_pos; Tail *t; TrieIndex i; uint32 sig; /* check signature */ save_pos = ftell (file); if (!file_read_int32 (file, (int32 *) &sig) || TAIL_SIGNATURE != sig) goto exit_file_read; t = (Tail *) malloc (sizeof (Tail)); if (UNLIKELY (!t)) goto exit_file_read; if (!file_read_int32 (file, &t->first_free) || !file_read_int32 (file, &t->num_tails)) { goto exit_tail_created; } if (t->num_tails > SIZE_MAX / sizeof (TailBlock)) goto exit_tail_created; t->tails = (TailBlock *) malloc (t->num_tails * sizeof (TailBlock)); if (UNLIKELY (!t->tails)) goto exit_tail_created; for (i = 0; i < t->num_tails; i++) { int16 length; if (!file_read_int32 (file, &t->tails[i].next_free) || !file_read_int32 (file, &t->tails[i].data) || !file_read_int16 (file, &length)) { goto exit_in_loop; } t->tails[i].suffix = (TrieChar *) malloc (length + 1); if (UNLIKELY (!t->tails[i].suffix)) goto exit_in_loop; if (length > 0) { if (!file_read_chars (file, (char *)t->tails[i].suffix, length)) { free (t->tails[i].suffix); goto exit_in_loop; } } t->tails[i].suffix[length] = '\0'; } return t; exit_in_loop: while (i > 0) { free (t->tails[--i].suffix); } free (t->tails); exit_tail_created: free (t); exit_file_read: fseek (file, save_pos, SEEK_SET); return NULL; } /** * @brief Free tail data * * @param t : the tail data * * @return 0 on success, non-zero on failure * * Free the given tail data. */ void tail_free (Tail *t) { TrieIndex i; if (t->tails) { for (i = 0; i < t->num_tails; i++) if (t->tails[i].suffix) free (t->tails[i].suffix); free (t->tails); } free (t); } /** * @brief Write tail data * * @param t : the tail data * @param file : the file to write to * * @return 0 on success, non-zero on failure * * Write tail data to the given @a file, starting from the current file * pointer. On return, the file pointer is left after the tail data block. */ int tail_fwrite (const Tail *t, FILE *file) { TrieIndex i; if (!file_write_int32 (file, TAIL_SIGNATURE) || !file_write_int32 (file, t->first_free) || !file_write_int32 (file, t->num_tails)) { return -1; } for (i = 0; i < t->num_tails; i++) { int16 length; if (!file_write_int32 (file, t->tails[i].next_free) || !file_write_int32 (file, t->tails[i].data)) { return -1; } length = t->tails[i].suffix ? strlen ((const char *)t->tails[i].suffix) : 0; if (!file_write_int16 (file, length)) return -1; if (length > 0 && !file_write_chars (file, (char *)t->tails[i].suffix, length)) { return -1; } } return 0; } /** * @brief Get suffix * * @param t : the tail data * @param index : the index of the suffix * * @return pointer to the indexed suffix string. * * Get suffix from tail with given @a index. The returned string is a pointer * to internal storage, which should be accessed read-only by the caller. * No need to free() it. */ const TrieChar * tail_get_suffix (const Tail *t, TrieIndex index) { index -= TAIL_START_BLOCKNO; return LIKELY (index < t->num_tails) ? t->tails[index].suffix : NULL; } /** * @brief Set suffix of existing entry * * @param t : the tail data * @param index : the index of the suffix * @param suffix : the new suffix * * Set suffix of existing entry of given @a index in tail. */ Bool tail_set_suffix (Tail *t, TrieIndex index, const TrieChar *suffix) { index -= TAIL_START_BLOCKNO; if (LIKELY (index < t->num_tails)) { /* suffix and t->tails[index].suffix may overlap; * so, dup it before it's overwritten */ TrieChar *tmp = NULL; if (suffix) { tmp = (TrieChar *) strdup ((const char *)suffix); if (UNLIKELY (!tmp)) return FALSE; } if (t->tails[index].suffix) free (t->tails[index].suffix); t->tails[index].suffix = tmp; return TRUE; } return FALSE; } /** * @brief Add a new suffix * * @param t : the tail data * @param suffix : the new suffix * * @return the index of the newly added suffix, * or TRIE_INDEX_ERROR on failure. * * Add a new suffix entry to tail. */ TrieIndex tail_add_suffix (Tail *t, const TrieChar *suffix) { TrieIndex new_block; new_block = tail_alloc_block (t); if (UNLIKELY (TRIE_INDEX_ERROR == new_block)) return TRIE_INDEX_ERROR; tail_set_suffix (t, new_block, suffix); return new_block; } static TrieIndex tail_alloc_block (Tail *t) { TrieIndex block; if (0 != t->first_free) { block = t->first_free; t->first_free = t->tails[block].next_free; } else { void *new_block; block = t->num_tails; new_block = realloc (t->tails, (t->num_tails + 1) * sizeof (TailBlock)); if (UNLIKELY (!new_block)) return TRIE_INDEX_ERROR; t->tails = (TailBlock *) new_block; ++t->num_tails; } t->tails[block].next_free = -1; t->tails[block].data = TRIE_DATA_ERROR; t->tails[block].suffix = NULL; return block + TAIL_START_BLOCKNO; } static void tail_free_block (Tail *t, TrieIndex block) { TrieIndex i, j; block -= TAIL_START_BLOCKNO; if (block >= t->num_tails) return; t->tails[block].data = TRIE_DATA_ERROR; if (NULL != t->tails[block].suffix) { free (t->tails[block].suffix); t->tails[block].suffix = NULL; } /* find insertion point */ j = 0; for (i = t->first_free; i != 0 && i < block; i = t->tails[i].next_free) j = i; /* insert free block between j and i */ t->tails[block].next_free = i; if (0 != j) t->tails[j].next_free = block; else t->first_free = block; } /** * @brief Get data associated to suffix entry * * @param t : the tail data * @param index : the index of the suffix * * @return the data associated to the suffix entry * * Get data associated to suffix entry @a index in tail data. */ TrieData tail_get_data (const Tail *t, TrieIndex index) { index -= TAIL_START_BLOCKNO; return LIKELY (index < t->num_tails) ? t->tails[index].data : TRIE_DATA_ERROR; } /** * @brief Set data associated to suffix entry * * @param t : the tail data * @param index : the index of the suffix * @param data : the data to set * * @return boolean indicating success * * Set data associated to suffix entry @a index in tail data. */ Bool tail_set_data (Tail *t, TrieIndex index, TrieData data) { index -= TAIL_START_BLOCKNO; if (LIKELY (index < t->num_tails)) { t->tails[index].data = data; return TRUE; } return FALSE; } /** * @brief Delete suffix entry * * @param t : the tail data * @param index : the index of the suffix to delete * * Delete suffix entry from the tail data. */ void tail_delete (Tail *t, TrieIndex index) { tail_free_block (t, index); } /** * @brief Walk in tail with a string * * @param t : the tail data * @param s : the tail data index * @param suffix_idx : pointer to current character index in suffix * @param str : the string to use in walking * @param len : total characters in @a str to walk * * @return total number of characters successfully walked * * Walk in the tail data @a t at entry @a s, from given character position * @a *suffix_idx, using @a len characters of given string @a str. On return, * @a *suffix_idx is updated to the position after the last successful walk, * and the function returns the total number of character succesfully walked. */ int tail_walk_str (const Tail *t, TrieIndex s, short *suffix_idx, const TrieChar *str, int len) { const TrieChar *suffix; int i; short j; suffix = tail_get_suffix (t, s); if (UNLIKELY (!suffix)) return FALSE; i = 0; j = *suffix_idx; while (i < len) { if (str[i] != suffix[j]) break; ++i; /* stop and stay at null-terminator */ if (0 == suffix[j]) break; ++j; } *suffix_idx = j; return i; } /** * @brief Walk in tail with a character * * @param t : the tail data * @param s : the tail data index * @param suffix_idx : pointer to current character index in suffix * @param c : the character to use in walking * * @return boolean indicating success * * Walk in the tail data @a t at entry @a s, from given character position * @a *suffix_idx, using given character @a c. If the walk is successful, * it returns TRUE, and @a *suffix_idx is updated to the next character. * Otherwise, it returns FALSE, and @a *suffix_idx is left unchanged. */ Bool tail_walk_char (const Tail *t, TrieIndex s, short *suffix_idx, TrieChar c) { const TrieChar *suffix; TrieChar suffix_char; suffix = tail_get_suffix (t, s); if (UNLIKELY (!suffix)) return FALSE; suffix_char = suffix[*suffix_idx]; if (suffix_char == c) { if (0 != suffix_char) ++*suffix_idx; return TRUE; } return FALSE; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/tail.h0000644000175100017510000000575015054046004017570 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * tail.h - trie tail for keeping suffixes * Created: 2006-08-12 * Author: Theppitak Karoonboonyanan */ #ifndef __TAIL_H #define __TAIL_H #include "triedefs.h" /** * @file tail.h * @brief trie tail for keeping suffixes */ /** * @brief Double-array structure type */ typedef struct _Tail Tail; Tail * tail_new (); Tail * tail_fread (FILE *file); void tail_free (Tail *t); int tail_fwrite (const Tail *t, FILE *file); const TrieChar * tail_get_suffix (const Tail *t, TrieIndex index); Bool tail_set_suffix (Tail *t, TrieIndex index, const TrieChar *suffix); TrieIndex tail_add_suffix (Tail *t, const TrieChar *suffix); TrieData tail_get_data (const Tail *t, TrieIndex index); Bool tail_set_data (Tail *t, TrieIndex index, TrieData data); void tail_delete (Tail *t, TrieIndex index); int tail_walk_str (const Tail *t, TrieIndex s, short *suffix_idx, const TrieChar *str, int len); Bool tail_walk_char (const Tail *t, TrieIndex s, short *suffix_idx, TrieChar c); /** * @brief Test walkability in tail with a character * * @param t : the tail data * @param s : the tail data index * @param suffix_idx : current character index in suffix * @param c : the character to test walkability * * @return boolean indicating walkability * * Test if the character @a c can be used to walk from given character * position @a suffix_idx of entry @a s of the tail data @a t. */ /* Bool tail_is_walkable_char (Tail *t, TrieIndex s, short suffix_idx, const TrieChar c); */ #define tail_is_walkable_char(t,s,suffix_idx,c) \ (tail_get_suffix ((t), (s)) [suffix_idx] == (c)) #endif /* __TAIL_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/trie-private.h0000644000175100017510000000352415054046004021247 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * trie-private.h - Private utilities for trie implementation * Created: 2007-08-25 * Author: Theppitak Karoonboonyanan */ #ifndef __TRIE_PRIVATE_H #define __TRIE_PRIVATE_H #include /** * @file trie-private.h * @brief Private utilities for trie implementation */ /** * @brief LIKELY and UNLIKELY macros for hinting the compiler * about the expected result of a Boolean expression, for the sake of * optimization */ #if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) #define LIKELY(expr) (__builtin_expect (!!(expr), 1)) #define UNLIKELY(expr) (__builtin_expect (!!(expr), 0)) #else #define LIKELY(expr) (expr) #define UNLIKELY(expr) (expr) #endif /** * @brief Minimum value macro */ #define MIN_VAL(a,b) ((a)<(b)?(a):(b)) /** * @brief Maximum value macro */ #define MAX_VAL(a,b) ((a)>(b)?(a):(b)) #endif /* __TRIE_PRIVATE_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/trie-string.c0000644000175100017510000000503315054046004021073 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * trie-string.c - Dynamic string type for Trie alphabets * Created: 2012-08-02 * Author: Theppitak Karoonboonyanan */ #include "trie-string.h" #include "dstring-private.h" #include "triedefs.h" #include struct _TrieString { DString ds; }; TrieString * trie_string_new (int n_elm) { return (TrieString *) dstring_new (sizeof (TrieChar), n_elm); } void trie_string_free (TrieString *ts) { dstring_free ((DString *)ts); } int trie_string_length (const TrieString *ts) { return dstring_length ((DString *)ts); } const void * trie_string_get_val (const TrieString *ts) { return dstring_get_val ((DString *)ts); } void * trie_string_get_val_rw (TrieString *ts) { return dstring_get_val_rw ((DString *)ts); } void trie_string_clear (TrieString *ts) { dstring_clear ((DString *)ts); } Bool trie_string_copy (TrieString *dst, const TrieString *src) { return dstring_copy ((DString *)dst, (const DString *)src); } Bool trie_string_append (TrieString *dst, const TrieString *src) { return dstring_append ((DString *)dst, (const DString *)src); } Bool trie_string_append_string (TrieString *ts, const TrieChar *str) { return dstring_append_string ((DString *)ts, str, strlen ((const char *)str)); } Bool trie_string_append_char (TrieString *ts, TrieChar tc) { return dstring_append_char ((DString *)ts, &tc); } Bool trie_string_terminate (TrieString *ts) { return dstring_terminate ((DString *)ts); } Bool trie_string_cut_last (TrieString *ts) { return dstring_cut_last ((DString *)ts); } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/trie-string.h0000644000175100017510000000365515054046004021110 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * trie-string.h - Dynamic string type for Trie alphabets * Created: 2012-08-02 * Author: Theppitak Karoonboonyanan */ #ifndef __TRIE_STRING_H #define __TRIE_STRING_H #include "dstring.h" #include "triedefs.h" typedef struct _TrieString TrieString; TrieString * trie_string_new (int n_elm); void trie_string_free (TrieString *ts); int trie_string_length (const TrieString *ts); const void * trie_string_get_val (const TrieString *ts); void * trie_string_get_val_rw (TrieString *ts); void trie_string_clear (TrieString *ts); Bool trie_string_copy (TrieString *dst, const TrieString *src); Bool trie_string_append (TrieString *dst, const TrieString *src); Bool trie_string_append_string (TrieString *ts, const TrieChar *str); Bool trie_string_append_char (TrieString *ts, TrieChar tc); Bool trie_string_terminate (TrieString *ts); Bool trie_string_cut_last (TrieString *ts); #endif /* __TRIE_STRING_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/trie.c0000644000175100017510000007052315054046004017575 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * trie.c - Trie data type and functions * Created: 2006-08-11 * Author: Theppitak Karoonboonyanan */ #include #include #include "trie.h" #include "trie-private.h" #include "fileutils.h" #include "alpha-map.h" #include "alpha-map-private.h" #include "darray.h" #include "tail.h" #include "trie-string.h" /** * @brief Trie structure */ struct _Trie { AlphaMap *alpha_map; DArray *da; Tail *tail; Bool is_dirty; }; /** * @brief TrieState structure */ struct _TrieState { const Trie *trie; /**< the corresponding trie */ TrieIndex index; /**< index in double-array/tail structures */ short suffix_idx; /**< suffix character offset, if in suffix */ short is_suffix; /**< whether it is currently in suffix part */ }; /** * @brief TrieIterator structure */ struct _TrieIterator { const TrieState *root; /**< the state to start iteration from */ TrieState *state; /**< the current state */ TrieString *key; /**< buffer for calculating the entry key */ }; /*------------------------* * INTERNAL FUNCTIONS * *------------------------*/ #define trie_da_is_separate(da,s) (da_get_base ((da), (s)) < 0) #define trie_da_get_tail_index(da,s) (-da_get_base ((da), (s))) #define trie_da_set_tail_index(da,s,v) (da_set_base ((da), (s), -(v))) static TrieState * trie_state_new (const Trie *trie, TrieIndex index, short suffix_idx, short is_suffix); static Bool trie_store_conditionally (Trie *trie, const AlphaChar *key, TrieData data, Bool is_overwrite); static Bool trie_branch_in_branch (Trie *trie, TrieIndex sep_node, const TrieChar *suffix, TrieData data); static Bool trie_branch_in_tail (Trie *trie, TrieIndex sep_node, const TrieChar *suffix, TrieData data); /*-----------------------* * GENERAL FUNCTIONS * *-----------------------*/ /** * @brief Create a new trie * * @param alpha_map : the alphabet set for the trie * * @return a pointer to the newly created trie, NULL on failure * * Create a new empty trie object based on the given @a alpha_map alphabet * set. The trie contents can then be added and deleted with trie_store() and * trie_delete() respectively. * * The created object must be freed with trie_free(). */ Trie * trie_new (const AlphaMap *alpha_map) { Trie *trie; trie = (Trie *) malloc (sizeof (Trie)); if (UNLIKELY (!trie)) return NULL; trie->alpha_map = alpha_map_clone (alpha_map); if (UNLIKELY (!trie->alpha_map)) goto exit_trie_created; trie->da = da_new (); if (UNLIKELY (!trie->da)) goto exit_alpha_map_created; trie->tail = tail_new (); if (UNLIKELY (!trie->tail)) goto exit_da_created; trie->is_dirty = TRUE; return trie; exit_da_created: da_free (trie->da); exit_alpha_map_created: alpha_map_free (trie->alpha_map); exit_trie_created: free (trie); return NULL; } /** * @brief Create a new trie by loading from a file * * @param path : the path to the file * * @return a pointer to the created trie, NULL on failure * * Create a new trie and initialize its contents by loading from the file at * given @a path. * * The created object must be freed with trie_free(). */ Trie * trie_new_from_file (const char *path) { Trie *trie; FILE *trie_file; trie_file = fopen (path, "rb"); if (!trie_file) return NULL; trie = trie_fread (trie_file); fclose (trie_file); return trie; } /** * @brief Create a new trie by reading from an open file * * @param file : the handle of the open file * * @return a pointer to the created trie, NULL on failure * * Create a new trie and initialize its contents by reading from the open * @a file. After reading, the file pointer is left at the end of the trie data. * This can be useful for reading embedded trie index as part of a file data. * * The created object must be freed with trie_free(). * * Available since: 0.2.4 */ Trie * trie_fread (FILE *file) { Trie *trie; trie = (Trie *) malloc (sizeof (Trie)); if (UNLIKELY (!trie)) return NULL; if (NULL == (trie->alpha_map = alpha_map_fread_bin (file))) goto exit_trie_created; if (NULL == (trie->da = da_fread (file))) goto exit_alpha_map_created; if (NULL == (trie->tail = tail_fread (file))) goto exit_da_created; trie->is_dirty = FALSE; return trie; exit_da_created: da_free (trie->da); exit_alpha_map_created: alpha_map_free (trie->alpha_map); exit_trie_created: free (trie); return NULL; } /** * @brief Free a trie object * * @param trie : the trie object to free * * Destruct the @a trie and free its allocated memory. */ void trie_free (Trie *trie) { alpha_map_free (trie->alpha_map); da_free (trie->da); tail_free (trie->tail); free (trie); } /** * @brief Save a trie to file * * @param trie : the trie * * @param path : the path to the file * * @return 0 on success, non-zero on failure * * Create a new file at the given @a path and write @a trie data to it. * If @a path already exists, its contents will be replaced. */ int trie_save (Trie *trie, const char *path) { FILE *file; int res = 0; file = fopen (path, "wb+"); if (!file) return -1; res = trie_fwrite (trie, file); fclose (file); return res; } /** * @brief Write trie data to an open file * * @param trie : the trie * * @param file : the open file * * @return 0 on success, non-zero on failure * * Write @a trie data to @a file which is opened for writing. * After writing, the file pointer is left at the end of the trie data. * This can be useful for embedding trie index as part of a file data. * * Available since: 0.2.4 */ int trie_fwrite (Trie *trie, FILE *file) { if (alpha_map_fwrite_bin (trie->alpha_map, file) != 0) return -1; if (da_fwrite (trie->da, file) != 0) return -1; if (tail_fwrite (trie->tail, file) != 0) return -1; trie->is_dirty = FALSE; return 0; } /** * @brief Check pending changes * * @param trie : the trie object * * @return TRUE if there are pending changes, FALSE otherwise * * Check if the @a trie is dirty with some pending changes and needs saving * to synchronize with the file. */ Bool trie_is_dirty (const Trie *trie) { return trie->is_dirty; } /*------------------------------* * GENERAL QUERY OPERATIONS * *------------------------------*/ /** * @brief Retrieve an entry from trie * * @param trie : the trie * @param key : the key for the entry to retrieve * @param o_data : the storage for storing the entry data on return * * @return boolean value indicating the existence of the entry. * * Retrieve an entry for the given @a key from @a trie. On return, * if @a key is found and @a o_data is not NULL, @a *o_data is set * to the data associated to @a key. */ Bool trie_retrieve (const Trie *trie, const AlphaChar *key, TrieData *o_data) { TrieIndex s; short suffix_idx; const AlphaChar *p; /* walk through branches */ s = da_get_root (trie->da); for (p = key; !trie_da_is_separate (trie->da, s); p++) { TrieIndex tc = alpha_map_char_to_trie (trie->alpha_map, *p); if (TRIE_INDEX_MAX == tc) return FALSE; if (!da_walk (trie->da, &s, (TrieChar) tc)) return FALSE; if (0 == *p) break; } /* walk through tail */ s = trie_da_get_tail_index (trie->da, s); suffix_idx = 0; for ( ; ; p++) { TrieIndex tc = alpha_map_char_to_trie (trie->alpha_map, *p); if (TRIE_INDEX_MAX == tc) return FALSE; if (!tail_walk_char (trie->tail, s, &suffix_idx, (TrieChar) tc)) return FALSE; if (0 == *p) break; } /* found, set the val and return */ if (o_data) *o_data = tail_get_data (trie->tail, s); return TRUE; } /** * @brief Store a value for an entry to trie * * @param trie : the trie * @param key : the key for the entry to retrieve * @param data : the data associated to the entry * * @return boolean value indicating the success of the operation * * Store a @a data for the given @a key in @a trie. If @a key does not * exist in @a trie, it will be appended. If it does, its current data will * be overwritten. */ Bool trie_store (Trie *trie, const AlphaChar *key, TrieData data) { return trie_store_conditionally (trie, key, data, TRUE); } /** * @brief Store a value for an entry to trie only if the key is not present * * @param trie : the trie * @param key : the key for the entry to retrieve * @param data : the data associated to the entry * * @return boolean value indicating the success of the operation * * Store a @a data for the given @a key in @a trie. If @a key does not * exist in @a trie, it will be appended. If it does, the function will * return failure and the existing value will not be touched. * * This can be useful for multi-thread applications, as race condition * can be avoided. * * Available since: 0.2.4 */ Bool trie_store_if_absent (Trie *trie, const AlphaChar *key, TrieData data) { return trie_store_conditionally (trie, key, data, FALSE); } static Bool trie_store_conditionally (Trie *trie, const AlphaChar *key, TrieData data, Bool is_overwrite) { TrieIndex s, t; short suffix_idx; const AlphaChar *p, *sep; /* walk through branches */ s = da_get_root (trie->da); for (p = key; !trie_da_is_separate (trie->da, s); p++) { TrieIndex tc = alpha_map_char_to_trie (trie->alpha_map, *p); if (TRIE_INDEX_MAX == tc) return FALSE; if (!da_walk (trie->da, &s, (TrieChar) tc)) { TrieChar *key_str; Bool res; key_str = alpha_map_char_to_trie_str (trie->alpha_map, p); if (!key_str) return FALSE; res = trie_branch_in_branch (trie, s, key_str, data); free (key_str); return res; } if (0 == *p) break; } /* walk through tail */ sep = p; t = trie_da_get_tail_index (trie->da, s); suffix_idx = 0; for ( ; ; p++) { TrieIndex tc = alpha_map_char_to_trie (trie->alpha_map, *p); if (TRIE_INDEX_MAX == tc) return FALSE; if (!tail_walk_char (trie->tail, t, &suffix_idx, (TrieChar) tc)) { TrieChar *tail_str; Bool res; tail_str = alpha_map_char_to_trie_str (trie->alpha_map, sep); if (!tail_str) return FALSE; res = trie_branch_in_tail (trie, s, tail_str, data); free (tail_str); return res; } if (0 == *p) break; } /* duplicated key, overwrite val if flagged */ if (!is_overwrite) { return FALSE; } tail_set_data (trie->tail, t, data); trie->is_dirty = TRUE; return TRUE; } static Bool trie_branch_in_branch (Trie *trie, TrieIndex sep_node, const TrieChar *suffix, TrieData data) { TrieIndex new_da, new_tail; new_da = da_insert_branch (trie->da, sep_node, *suffix); if (TRIE_INDEX_ERROR == new_da) return FALSE; if ('\0' != *suffix) ++suffix; new_tail = tail_add_suffix (trie->tail, suffix); tail_set_data (trie->tail, new_tail, data); trie_da_set_tail_index (trie->da, new_da, new_tail); trie->is_dirty = TRUE; return TRUE; } static Bool trie_branch_in_tail (Trie *trie, TrieIndex sep_node, const TrieChar *suffix, TrieData data) { TrieIndex old_tail, old_da, s; const TrieChar *old_suffix, *p; /* adjust separate point in old path */ old_tail = trie_da_get_tail_index (trie->da, sep_node); old_suffix = tail_get_suffix (trie->tail, old_tail); if (!old_suffix) return FALSE; for (p = old_suffix, s = sep_node; *p == *suffix; p++, suffix++) { TrieIndex t = da_insert_branch (trie->da, s, *p); if (TRIE_INDEX_ERROR == t) goto fail; s = t; } old_da = da_insert_branch (trie->da, s, *p); if (TRIE_INDEX_ERROR == old_da) goto fail; if ('\0' != *p) ++p; tail_set_suffix (trie->tail, old_tail, p); trie_da_set_tail_index (trie->da, old_da, old_tail); /* insert the new branch at the new separate point */ return trie_branch_in_branch (trie, s, suffix, data); fail: /* failed, undo previous insertions and return error */ da_prune_upto (trie->da, sep_node, s); trie_da_set_tail_index (trie->da, sep_node, old_tail); return FALSE; } /** * @brief Delete an entry from trie * * @param trie : the trie * @param key : the key for the entry to delete * * @return boolean value indicating whether the key exists and is removed * * Delete an entry for the given @a key from @a trie. */ Bool trie_delete (Trie *trie, const AlphaChar *key) { TrieIndex s, t; short suffix_idx; const AlphaChar *p; /* walk through branches */ s = da_get_root (trie->da); for (p = key; !trie_da_is_separate (trie->da, s); p++) { TrieIndex tc = alpha_map_char_to_trie (trie->alpha_map, *p); if (TRIE_INDEX_MAX == tc) return FALSE; if (!da_walk (trie->da, &s, (TrieChar) tc)) return FALSE; if (0 == *p) break; } /* walk through tail */ t = trie_da_get_tail_index (trie->da, s); suffix_idx = 0; for ( ; ; p++) { TrieIndex tc = alpha_map_char_to_trie (trie->alpha_map, *p); if (TRIE_INDEX_MAX == tc) return FALSE; if (!tail_walk_char (trie->tail, t, &suffix_idx, (TrieChar) tc)) return FALSE; if (0 == *p) break; } tail_delete (trie->tail, t); da_set_base (trie->da, s, TRIE_INDEX_ERROR); da_prune (trie->da, s); trie->is_dirty = TRUE; return TRUE; } /** * @brief Enumerate entries in trie * * @param trie : the trie * @param enum_func : the callback function to be called on each key * @param user_data : user-supplied data to send as an argument to @a enum_func * * @return boolean value indicating whether all the keys are visited * * Enumerate all entries in trie. For each entry, the user-supplied * @a enum_func callback function is called, with the entry key and data. * Returning FALSE from such callback will stop enumeration and return FALSE. */ Bool trie_enumerate (const Trie *trie, TrieEnumFunc enum_func, void *user_data) { TrieState *root; TrieIterator *iter; Bool cont = TRUE; root = trie_root (trie); if (UNLIKELY (!root)) return FALSE; iter = trie_iterator_new (root); if (UNLIKELY (!iter)) goto exit_root_created; while (cont && trie_iterator_next (iter)) { AlphaChar *key = trie_iterator_get_key (iter); TrieData data = trie_iterator_get_data (iter); cont = (*enum_func) (key, data, user_data); free (key); } trie_iterator_free (iter); trie_state_free (root); return cont; exit_root_created: trie_state_free (root); return FALSE; } /*-------------------------------* * STEPWISE QUERY OPERATIONS * *-------------------------------*/ /** * @brief Get root state of a trie * * @param trie : the trie * * @return the root state of the trie * * Get root state of @a trie, for stepwise walking. * * The returned state is allocated and must be freed with trie_state_free() */ TrieState * trie_root (const Trie *trie) { return trie_state_new (trie, da_get_root (trie->da), 0, FALSE); } /*----------------* * TRIE STATE * *----------------*/ static TrieState * trie_state_new (const Trie *trie, TrieIndex index, short suffix_idx, short is_suffix) { TrieState *s; s = (TrieState *) malloc (sizeof (TrieState)); if (UNLIKELY (!s)) return NULL; s->trie = trie; s->index = index; s->suffix_idx = suffix_idx; s->is_suffix = is_suffix; return s; } /** * @brief Copy trie state to another * * @param dst : the destination state * @param src : the source state * * Copy trie state data from @a src to @a dst. All existing data in @a dst * is overwritten. */ void trie_state_copy (TrieState *dst, const TrieState *src) { /* May be deep copy if necessary, not the case for now */ *dst = *src; } /** * @brief Clone a trie state * * @param s : the state to clone * * @return an duplicated instance of @a s * * Make a copy of trie state. * * The returned state is allocated and must be freed with trie_state_free() */ TrieState * trie_state_clone (const TrieState *s) { return trie_state_new (s->trie, s->index, s->suffix_idx, s->is_suffix); } /** * @brief Free a trie state * * @param s : the state to free * * Free the trie state. */ void trie_state_free (TrieState *s) { free (s); } /** * @brief Rewind a trie state * * @param s : the state to rewind * * Put the state at root. */ void trie_state_rewind (TrieState *s) { s->index = da_get_root (s->trie->da); s->is_suffix = FALSE; } /** * @brief Walk the trie from the state * * @param s : current state * @param c : key character for walking * * @return boolean value indicating the success of the walk * * Walk the trie stepwise, using a given character @a c. * On return, the state @a s is updated to the new state if successfully walked. */ Bool trie_state_walk (TrieState *s, AlphaChar c) { TrieIndex tc = alpha_map_char_to_trie (s->trie->alpha_map, c); if (UNLIKELY (TRIE_INDEX_MAX == tc)) return FALSE; if (!s->is_suffix) { Bool ret; ret = da_walk (s->trie->da, &s->index, (TrieChar) tc); if (ret && trie_da_is_separate (s->trie->da, s->index)) { s->index = trie_da_get_tail_index (s->trie->da, s->index); s->suffix_idx = 0; s->is_suffix = TRUE; } return ret; } else { return tail_walk_char (s->trie->tail, s->index, &s->suffix_idx, (TrieChar) tc); } } /** * @brief Test walkability of character from state * * @param s : the state to check * @param c : the input character * * @return boolean indicating walkability * * Test if there is a transition from state @a s with input character @a c. */ Bool trie_state_is_walkable (const TrieState *s, AlphaChar c) { TrieIndex tc = alpha_map_char_to_trie (s->trie->alpha_map, c); if (UNLIKELY (TRIE_INDEX_MAX == tc)) return FALSE; if (!s->is_suffix) return da_is_walkable (s->trie->da, s->index, (TrieChar) tc); else return tail_is_walkable_char (s->trie->tail, s->index, s->suffix_idx, (TrieChar) tc); } /** * @brief Get all walkable characters from state * * @param s : the state to get * @param chars : the storage for the result * @param chars_nelm : the size of @a chars[] in number of elements * * @return total walkable characters * * Get the list of all walkable characters from state @a s. At most * @a chars_nelm walkable characters are stored in @a chars[] on return. * * The function returns the actual number of walkable characters from @a s. * Note that this may not equal the number of characters stored in @a chars[] * if @a chars_nelm is less than the actual number. * * Available since: 0.2.6 */ int trie_state_walkable_chars (const TrieState *s, AlphaChar chars[], int chars_nelm) { int syms_num = 0; if (!s->is_suffix) { Symbols *syms = da_output_symbols (s->trie->da, s->index); int i; syms_num = symbols_num (syms); for (i = 0; i < syms_num && i < chars_nelm; i++) { TrieChar tc = symbols_get (syms, i); chars[i] = alpha_map_trie_to_char (s->trie->alpha_map, tc); } symbols_free (syms); } else { const TrieChar *suffix = tail_get_suffix (s->trie->tail, s->index); chars[0] = alpha_map_trie_to_char (s->trie->alpha_map, suffix[s->suffix_idx]); syms_num = 1; } return syms_num; } /** * @brief Check for single path * * @param s : the state to check * * @return boolean value indicating whether it is in a single path * * Check if the given state is in a single path, that is, there is no other * branch from it to leaf. */ Bool trie_state_is_single (const TrieState *s) { return s->is_suffix; } /** * @brief Get data from terminal state * * @param s : a terminal state * * @return the data associated with the terminal state @a s, * or TRIE_DATA_ERROR if @a s is not a terminal state * * Get value from a terminal state of trie. Getting value from a non-terminal * state will result in TRIE_DATA_ERROR. */ TrieData trie_state_get_data (const TrieState *s) { if (!s) { return TRIE_DATA_ERROR; } if (!s->is_suffix) { TrieIndex index = s->index; /* walk a terminal char to get the data from tail */ if (da_walk (s->trie->da, &index, TRIE_CHAR_TERM)) { if (trie_da_is_separate (s->trie->da, index)) { index = trie_da_get_tail_index (s->trie->da, index); return tail_get_data (s->trie->tail, index); } } } else { if (tail_is_walkable_char (s->trie->tail, s->index, s->suffix_idx, TRIE_CHAR_TERM)) { return tail_get_data (s->trie->tail, s->index); } } return TRIE_DATA_ERROR; } /*---------------------* * ENTRY ITERATION * *---------------------*/ /** * @brief Create a new trie iterator * * @param s : the TrieState to start iteration from * * @return a pointer to the newly created TrieIterator, or NULL on failure * * Create a new trie iterator for iterating entries of a sub-trie rooted at * state @a s. * * Use it with the result of trie_root() to iterate the whole trie. * * The created object must be freed with trie_iterator_free(). * * Available since: 0.2.6 */ TrieIterator * trie_iterator_new (TrieState *s) { TrieIterator *iter; iter = (TrieIterator *) malloc (sizeof (TrieIterator)); if (UNLIKELY (!iter)) return NULL; iter->root = s; iter->state = NULL; iter->key = NULL; return iter; } /** * @brief Free a trie iterator * * @param iter : the trie iterator to free * * Destruct the iterator @a iter and free its allocated memory. * * Available since: 0.2.6 */ void trie_iterator_free (TrieIterator *iter) { if (iter->state) { trie_state_free (iter->state); } if (iter->key) { trie_string_free (iter->key); } free (iter); } /** * @brief Move trie iterator to the next entry * * @param iter : an iterator * * @return boolean value indicating the availability of the entry * * Move trie iterator to the next entry. * On return, the iterator @a iter is updated to reference to the new entry * if successfully moved. * * Available since: 0.2.6 */ Bool trie_iterator_next (TrieIterator *iter) { TrieState *s = iter->state; TrieIndex sep; /* first iteration */ if (!s) { s = iter->state = trie_state_clone (iter->root); /* for tail state, we are already at the only entry */ if (s->is_suffix) return TRUE; iter->key = trie_string_new (20); sep = da_first_separate (s->trie->da, s->index, iter->key); if (TRIE_INDEX_ERROR == sep) return FALSE; s->index = sep; return TRUE; } /* no next entry for tail state */ if (s->is_suffix) return FALSE; /* iter->state is a separate node */ sep = da_next_separate (s->trie->da, iter->root->index, s->index, iter->key); if (TRIE_INDEX_ERROR == sep) return FALSE; s->index = sep; return TRUE; } /** * @brief Get key for a trie iterator * * @param iter : an iterator * * @return the allocated key string; NULL on failure * * Get key for the current entry referenced by the trie iterator @a iter. * * The return string must be freed with free(). * * Available since: 0.2.6 */ AlphaChar * trie_iterator_get_key (const TrieIterator *iter) { const TrieState *s; const TrieChar *tail_str; AlphaChar *alpha_key, *alpha_p; s = iter->state; if (!s) return NULL; /* if s is in tail, root == s */ if (s->is_suffix) { tail_str = tail_get_suffix (s->trie->tail, s->index); if (!tail_str) return NULL; tail_str += s->suffix_idx; alpha_key = (AlphaChar *) malloc (sizeof (AlphaChar) * (strlen ((const char *)tail_str) + 1)); alpha_p = alpha_key; } else { TrieIndex tail_idx; int i, key_len; const TrieChar *key_p; tail_idx = trie_da_get_tail_index (s->trie->da, s->index); tail_str = tail_get_suffix (s->trie->tail, tail_idx); if (!tail_str) return NULL; key_len = trie_string_length (iter->key); key_p = trie_string_get_val (iter->key); alpha_key = (AlphaChar *) malloc ( sizeof (AlphaChar) * (key_len + strlen ((const char *)tail_str) + 1) ); alpha_p = alpha_key; for (i = key_len; i > 0; i--) { *alpha_p++ = alpha_map_trie_to_char (s->trie->alpha_map, *key_p++); } } while (*tail_str) { *alpha_p++ = alpha_map_trie_to_char (s->trie->alpha_map, *tail_str++); } *alpha_p = 0; return alpha_key; } /** * @brief Get data for the entry referenced by an iterator * * @param iter : an iterator * * @return the data associated with the entry referenced by iterator @a iter, * or TRIE_DATA_ERROR if @a iter does not reference to a unique entry * * Get value for the entry referenced by an iterator. Getting value from an * un-iterated (or broken for any reason) iterator will result in * TRIE_DATA_ERROR. * * Available since: 0.2.6 */ TrieData trie_iterator_get_data (const TrieIterator *iter) { const TrieState *s = iter->state; TrieIndex tail_index; if (!s) return TRIE_DATA_ERROR; if (!s->is_suffix) { if (!trie_da_is_separate (s->trie->da, s->index)) return TRIE_DATA_ERROR; tail_index = trie_da_get_tail_index (s->trie->da, s->index); } else { tail_index = s->index; } return tail_get_data (s->trie->tail, tail_index); } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/trie.h0000644000175100017510000001614715054046004017604 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * trie.h - Trie data type and functions * Created: 2006-08-11 * Author: Theppitak Karoonboonyanan */ #ifndef __TRIE_H #define __TRIE_H #include #include #ifdef __cplusplus extern "C" { #endif /** * @file trie.h * @brief Trie data type and functions * * Trie is a kind of digital search tree, an efficient indexing method with * O(1) time complexity for searching. Comparably as efficient as hashing, * trie also provides flexibility on incremental matching and key spelling * manipulation. This makes it ideal for lexical analyzers, as well as * spelling dictionaries. * * This library is an implementation of double-array structure for representing * trie, as proposed by Junichi Aoe. The details of the implementation can be * found at http://linux.thai.net/~thep/datrie/datrie.html * * A Trie is associated with an AlphaMap, a map between actual alphabet * characters and the raw characters used to walk through trie. * You can define the alphabet set by adding ranges of character codes * to it before associating it to a trie. And the keys to be added to the trie * must comprise only characters in such ranges. Note that the size of the * alphabet set is limited to 256 (TRIE_CHAR_MAX + 1), and the AlphaMap * will map the alphabet characters to raw codes in the range 0..255 * (0..TRIE_CHAR_MAX). The alphabet character ranges need not be continuous, * but the mapped raw codes will be continuous, for the sake of compactness * of the trie. * * A new Trie can be created in memory using trie_new(), saved to file using * trie_save(), and loaded later with trie_new_from_file(). * It can even be embeded in another file using trie_fwrite() and read back * using trie_fread(). * After use, Trie objects must be freed using trie_free(). * * Operations on trie include: * * - Add/delete entries with trie_store() and trie_delete() * - Retrieve entries with trie_retrieve() * - Walk through trie stepwise with TrieState and its functions * (trie_root(), trie_state_walk(), trie_state_rewind(), * trie_state_clone(), trie_state_copy(), * trie_state_is_walkable(), trie_state_walkable_chars(), * trie_state_is_single(), trie_state_get_data(). * And do not forget to free TrieState objects with trie_state_free() * after use.) * - Enumerate all keys using trie_enumerate() * - Iterate entries using TrieIterator and its functions * (trie_iterator_new(), trie_iterator_next(), trie_iterator_get_key(), * trie_iterator_get_data(). * And do not forget to free TrieIterator objects with trie_iterator_free() * after use.) */ /** * @brief Trie data type */ typedef struct _Trie Trie; /** * @brief Trie enumeration function * * @param key : the key of the entry * @param data : the data of the entry * @param user_data : the user-supplied data on enumerate call * * @return TRUE to continue enumeration, FALSE to stop */ typedef Bool (*TrieEnumFunc) (const AlphaChar *key, TrieData key_data, void *user_data); /** * @brief Trie walking state */ typedef struct _TrieState TrieState; /** * @brief Trie iteration state */ typedef struct _TrieIterator TrieIterator; /*-----------------------* * GENERAL FUNCTIONS * *-----------------------*/ Trie * trie_new (const AlphaMap *alpha_map); Trie * trie_new_from_file (const char *path); Trie * trie_fread (FILE *file); void trie_free (Trie *trie); int trie_save (Trie *trie, const char *path); int trie_fwrite (Trie *trie, FILE *file); Bool trie_is_dirty (const Trie *trie); /*------------------------------* * GENERAL QUERY OPERATIONS * *------------------------------*/ Bool trie_retrieve (const Trie *trie, const AlphaChar *key, TrieData *o_data); Bool trie_store (Trie *trie, const AlphaChar *key, TrieData data); Bool trie_store_if_absent (Trie *trie, const AlphaChar *key, TrieData data); Bool trie_delete (Trie *trie, const AlphaChar *key); Bool trie_enumerate (const Trie *trie, TrieEnumFunc enum_func, void *user_data); /*-------------------------------* * STEPWISE QUERY OPERATIONS * *-------------------------------*/ TrieState * trie_root (const Trie *trie); /*----------------* * TRIE STATE * *----------------*/ TrieState * trie_state_clone (const TrieState *s); void trie_state_copy (TrieState *dst, const TrieState *src); void trie_state_free (TrieState *s); void trie_state_rewind (TrieState *s); Bool trie_state_walk (TrieState *s, AlphaChar c); Bool trie_state_is_walkable (const TrieState *s, AlphaChar c); int trie_state_walkable_chars (const TrieState *s, AlphaChar chars[], int chars_nelm); /** * @brief Check for terminal state * * @param s : the state to check * * @return boolean value indicating whether it is a terminal state * * Check if the given state is a terminal state. A terminal state is a trie * state that terminates a key, and stores a value associated with it. */ #define trie_state_is_terminal(s) trie_state_is_walkable((s),TRIE_CHAR_TERM) Bool trie_state_is_single (const TrieState *s); /** * @brief Check for leaf state * * @param s : the state to check * * @return boolean value indicating whether it is a leaf state * * Check if the given state is a leaf state. A leaf state is a terminal state * that has no other branch. */ #define trie_state_is_leaf(s) \ (trie_state_is_single(s) && trie_state_is_terminal(s)) TrieData trie_state_get_data (const TrieState *s); /*----------------------* * ENTRY ITERATION * *----------------------*/ TrieIterator * trie_iterator_new (TrieState *s); void trie_iterator_free (TrieIterator *iter); Bool trie_iterator_next (TrieIterator *iter); AlphaChar * trie_iterator_get_key (const TrieIterator *iter); TrieData trie_iterator_get_data (const TrieIterator *iter); #ifdef __cplusplus } #endif #endif /* __TRIE_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/triedefs.h0000644000175100017510000000415415054046004020441 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * triedefs.h - General typedefs for trie * Created: 2006-08-11 * Author: Theppitak Karoonboonyanan */ #ifndef __TRIEDEFS_H #define __TRIEDEFS_H #include /** * @file triedefs.h * @brief General typedefs for trie */ /** * @brief Alphabet character type for use as input/output strings of trie keys */ typedef uint32 AlphaChar; /** * @brief Error value for alphabet character */ #define ALPHA_CHAR_ERROR (~(AlphaChar)0) /** * @brief Raw character type mapped into packed set from AlphaChar, * for use in actual trie transition calculations */ typedef unsigned char TrieChar; /** * @brief Trie terminator character */ #define TRIE_CHAR_TERM '\0' #define TRIE_CHAR_MAX 255 /** * @brief Type of index into Trie double-array and tail structures */ typedef int32 TrieIndex; /** * @brief Trie error index */ #define TRIE_INDEX_ERROR 0 /** * @brief Maximum trie index value */ #define TRIE_INDEX_MAX 0x7fffffff /** * @brief Type of value associated to trie entries */ typedef int32 TrieData; /** * @brief Trie error data */ #define TRIE_DATA_ERROR -1 #endif /* __TRIEDEFS_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/datrie/typedefs.h0000644000175100017510000000647515054046004020467 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2006 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * typedefs.h - general types * Created : 11 Aug 2006 * Author : Theppitak Karoonboonyanan */ #ifndef __TYPEDEFS_H #define __TYPEDEFS_H #include typedef enum { FALSE = 0, TRUE = 1 } Bool; # if UCHAR_MAX == 0xff # ifndef UINT8_TYPEDEF # define UINT8_TYPEDEF typedef unsigned char uint8; # endif /* UINT8_TYPEDEF */ # endif /* UCHAR_MAX */ # if SCHAR_MAX == 0x7f # ifndef INT8_TYPEDEF # define INT8_TYPEDEF typedef signed char int8; # endif /* INT8_TYPEDEF */ # endif /* SCHAR_MAX */ # if UINT_MAX == 0xffff # ifndef UINT16_TYPEDEF # define UINT16_TYPEDEF typedef unsigned int uint16; # endif /* UINT16_TYPEDEF */ # endif /* UINT_MAX */ # if INT_MAX == 0x7fff # ifndef INT16_TYPEDEF # define INT16_TYPEDEF typedef int int16; # endif /* INT16_TYPEDEF */ # endif /* INT_MAX */ # if USHRT_MAX == 0xffff # ifndef UINT16_TYPEDEF # define UINT16_TYPEDEF typedef unsigned short uint16; # endif /* UINT16_TYPEDEF */ # endif /* USHRT_MAX */ # if SHRT_MAX == 0x7fff # ifndef INT16_TYPEDEF # define INT16_TYPEDEF typedef short int16; # endif /* INT16_TYPEDEF */ # endif /* SHRT_MAX */ # if UINT_MAX == 0xffffffff # ifndef UINT32_TYPEDEF # define UINT32_TYPEDEF typedef unsigned int uint32; # endif /* UINT32_TYPEDEF */ # endif /* UINT_MAX */ # if INT_MAX == 0x7fffffff # ifndef INT32_TYPEDEF # define INT32_TYPEDEF typedef int int32; # endif /* INT32_TYPEDEF */ # endif /* INT_MAX */ # if ULONG_MAX == 0xffffffff # ifndef UINT32_TYPEDEF # define UINT32_TYPEDEF typedef unsigned long uint32; # endif /* UINT32_TYPEDEF */ # endif /* ULONG_MAX */ # if LONG_MAX == 0x7fffffff # ifndef INT32_TYPEDEF # define INT32_TYPEDEF typedef long int32; # endif /* INT32_TYPEDEF */ # endif /* LONG_MAX */ # ifndef UINT8_TYPEDEF # error "uint8 type is undefined!" # endif # ifndef INT8_TYPEDEF # error "int8 type is undefined!" # endif # ifndef UINT16_TYPEDEF # error "uint16 type is undefined!" # endif # ifndef INT16_TYPEDEF # error "int16 type is undefined!" # endif # ifndef UINT32_TYPEDEF # error "uint32 type is undefined!" # endif # ifndef INT32_TYPEDEF # error "int32 type is undefined!" # endif typedef uint8 byte; typedef uint16 word; typedef uint32 dword; #endif /* __TYPEDEFS_H */ /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5671265 datrie-0.8.3/libdatrie/tests/0000755000175100017510000000000015054046505016357 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/test_file.c0000644000175100017510000000753115054046004020501 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2013 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * test_file.c - Test for datrie file operations * Created: 2013-10-16 * Author: Theppitak Karoonboonyanan */ #include #include "utils.h" #include #include #define TRIE_FILENAME "test.tri" static Bool trie_enum_mark_rec (const AlphaChar *key, TrieData key_data, void *user_data) { Bool *is_failed = (Bool *)user_data; TrieData src_data; src_data = dict_src_get_data (key); if (TRIE_DATA_ERROR == src_data) { printf ("Extra entry in file: key '%ls', data %d.\n", key, key_data); *is_failed = TRUE; } else if (src_data != key_data) { printf ("Data mismatch for: key '%ls', expected %d, got %d.\n", key, src_data, key_data); *is_failed = TRUE; } else { dict_src_set_data (key, TRIE_DATA_READ); } return TRUE; } int main () { Trie *test_trie; DictRec *dict_p; Bool is_failed; msg_step ("Preparing trie"); test_trie = en_trie_new (); if (!test_trie) { printf ("Failed to allocate test trie.\n"); goto err_trie_not_created; } /* add/remove some words */ for (dict_p = dict_src; dict_p->key; dict_p++) { if (!trie_store (test_trie, dict_p->key, dict_p->data)) { printf ("Failed to add key '%ls', data %d.\n", dict_p->key, dict_p->data); goto err_trie_created; } } /* save & close */ msg_step ("Saving trie to file"); unlink (TRIE_FILENAME); /* error ignored */ if (trie_save (test_trie, TRIE_FILENAME) != 0) { printf ("Failed to save trie to file '%s'.\n", TRIE_FILENAME); goto err_trie_created; } trie_free (test_trie); /* reload from file */ msg_step ("Reloading trie from the saved file"); test_trie = trie_new_from_file (TRIE_FILENAME); if (!test_trie) { printf ("Failed to reload saved trie from '%s'.\n", TRIE_FILENAME); goto err_trie_saved; } /* enumerate & check */ msg_step ("Checking trie contents"); is_failed = FALSE; /* mark entries found in file */ if (!trie_enumerate (test_trie, trie_enum_mark_rec, (void *)&is_failed)) { printf ("Failed to enumerate trie file contents.\n"); goto err_trie_saved; } /* check for unmarked entries, (i.e. missed in file) */ for (dict_p = dict_src; dict_p->key; dict_p++) { if (dict_p->data != TRIE_DATA_READ) { printf ("Entry missed in file: key '%ls', data %d.\n", dict_p->key, dict_p->data); is_failed = TRUE; } } if (is_failed) { printf ("Errors found in trie saved contents.\n"); goto err_trie_saved; } unlink (TRIE_FILENAME); trie_free (test_trie); return 0; err_trie_saved: unlink (TRIE_FILENAME); err_trie_created: trie_free (test_trie); err_trie_not_created: return 1; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/test_iterator.c0000644000175100017510000001007715054046004021412 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2013 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * test_iterator.c - Test for datrie iterator operations * Created: 2013-10-16 * Author: Theppitak Karoonboonyanan */ #include #include "utils.h" #include #include int main () { Trie *test_trie; DictRec *dict_p; TrieState *trie_root_state; TrieIterator *trie_it; Bool is_failed; msg_step ("Preparing trie"); test_trie = en_trie_new (); if (!test_trie) { fprintf (stderr, "Fail to create test trie\n"); goto err_trie_not_created; } /* store */ msg_step ("Adding data to trie"); for (dict_p = dict_src; dict_p->key; dict_p++) { if (!trie_store (test_trie, dict_p->key, dict_p->data)) { printf ("Failed to add key '%ls', data %d.\n", dict_p->key, dict_p->data); goto err_trie_created; } } /* iterate & check */ msg_step ("Iterating and checking trie contents"); trie_root_state = trie_root (test_trie); if (!trie_root_state) { printf ("Failed to get trie root state\n"); goto err_trie_created; } trie_it = trie_iterator_new (trie_root_state); if (!trie_it) { printf ("Failed to get trie iterator\n"); goto err_trie_root_created; } is_failed = FALSE; while (trie_iterator_next (trie_it)) { AlphaChar *key; TrieData key_data, src_data; key = trie_iterator_get_key (trie_it); if (!key) { printf ("Failed to get key from trie iterator\n"); is_failed = TRUE; continue; } key_data = trie_iterator_get_data (trie_it); if (TRIE_DATA_ERROR == key_data) { printf ("Failed to get data from trie iterator for key '%ls'\n", key); is_failed = TRUE; } /* mark entries found in trie */ src_data = dict_src_get_data (key); if (TRIE_DATA_ERROR == src_data) { printf ("Extra entry in trie: key '%ls', data %d.\n", key, key_data); is_failed = TRUE; } else if (src_data != key_data) { printf ("Data mismatch for: key '%ls', expected %d, got %d.\n", key, src_data, key_data); is_failed = TRUE; } else { dict_src_set_data (key, TRIE_DATA_READ); } free (key); } /* check for unmarked entries, (i.e. missed in trie) */ for (dict_p = dict_src; dict_p->key; dict_p++) { if (dict_p->data != TRIE_DATA_READ) { printf ("Entry missed in trie: key '%ls', data %d.\n", dict_p->key, dict_p->data); is_failed = TRUE; } } if (is_failed) { printf ("Errors found in trie iteration.\n"); goto err_trie_it_created; } trie_iterator_free (trie_it); trie_state_free (trie_root_state); trie_free (test_trie); return 0; err_trie_it_created: trie_iterator_free (trie_it); err_trie_root_created: trie_state_free (trie_root_state); err_trie_created: trie_free (test_trie); err_trie_not_created: return 1; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/test_nonalpha.c0000644000175100017510000000532415054046004021360 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2014 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * test_nonalpha.c - Test for datrie behaviors on non-alphabet inputs * Created: 2014-01-06 * Author: Theppitak Karoonboonyanan */ #include #include "utils.h" #include const AlphaChar *nonalpha_src[] = { (AlphaChar *)L"a6acus", (AlphaChar *)L"a5acus", NULL }; int main () { Trie *test_trie; DictRec *dict_p; const AlphaChar **nonalpha_key; TrieData trie_data; Bool is_fail; msg_step ("Preparing trie"); test_trie = en_trie_new (); if (!test_trie) { fprintf (stderr, "Fail to create test trie\n"); goto err_trie_not_created; } /* store */ msg_step ("Adding data to trie"); for (dict_p = dict_src; dict_p->key; dict_p++) { if (!trie_store (test_trie, dict_p->key, dict_p->data)) { printf ("Failed to add key '%ls', data %d.\n", dict_p->key, dict_p->data); goto err_trie_created; } } /* test storing keys with non-alphabet chars */ is_fail = FALSE; for (nonalpha_key = nonalpha_src; *nonalpha_key; nonalpha_key++) { if (trie_retrieve (test_trie, *nonalpha_key, &trie_data)) { printf ("False duplication on key '%ls', with existing data %d.\n", *nonalpha_key, trie_data); is_fail = TRUE; } if (trie_store (test_trie, *nonalpha_key, TRIE_DATA_UNREAD)) { printf ("Wrongly added key '%ls' containing non-alphanet char\n", *nonalpha_key); is_fail = TRUE; } } if (is_fail) goto err_trie_created; trie_free (test_trie); return 0; err_trie_created: trie_free (test_trie); err_trie_not_created: return 1; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/test_null_trie.c0000644000175100017510000000527015054046004021555 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2015 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * test_null_trie.c - Test for datrie iteration on empty trie * Created: 2015-04-21 * Author: Theppitak Karoonboonyanan */ #include #include "utils.h" #include #include int main () { Trie *test_trie; TrieState *trie_root_state; TrieIterator *trie_it; Bool is_failed; msg_step ("Preparing empty trie"); test_trie = en_trie_new (); if (!test_trie) { fprintf (stderr, "Fail to create test trie\n"); goto err_trie_not_created; } /* iterate & check */ msg_step ("Iterating"); trie_root_state = trie_root (test_trie); if (!trie_root_state) { printf ("Failed to get trie root state\n"); goto err_trie_created; } trie_it = trie_iterator_new (trie_root_state); if (!trie_it) { printf ("Failed to get trie iterator\n"); goto err_trie_root_created; } is_failed = FALSE; while (trie_iterator_next (trie_it)) { AlphaChar *key; printf ("Got entry from empty trie, which is weird!\n"); key = trie_iterator_get_key (trie_it); if (key) { printf ("Got key from empty trie, which is weird! (key='%ls')\n", key); is_failed = TRUE; free (key); } } if (is_failed) { printf ("Errors found in empty trie iteration.\n"); goto err_trie_it_created; } trie_iterator_free (trie_it); trie_state_free (trie_root_state); trie_free (test_trie); return 0; err_trie_it_created: trie_iterator_free (trie_it); err_trie_root_created: trie_state_free (trie_root_state); err_trie_created: trie_free (test_trie); err_trie_not_created: return 1; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/test_store-retrieve.c0000644000175100017510000001434015054046004022535 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2013 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * test_store-retrieve.c - Test for datrie store/retrieve operations * Created: 2013-10-16 * Author: Theppitak Karoonboonyanan */ #include #include "utils.h" #include #include #include int main () { Trie *test_trie; DictRec *dict_p; TrieData trie_data; Bool is_failed; int n_entries, n_dels, i; TrieState *trie_root_state; TrieIterator *trie_it; msg_step ("Preparing trie"); test_trie = en_trie_new (); if (!test_trie) { fprintf (stderr, "Fail to create test trie\n"); goto err_trie_not_created; } /* store */ msg_step ("Adding data to trie"); for (dict_p = dict_src; dict_p->key; dict_p++) { if (!trie_store (test_trie, dict_p->key, dict_p->data)) { printf ("Failed to add key '%ls', data %d.\n", dict_p->key, dict_p->data); goto err_trie_created; } } /* retrieve */ msg_step ("Retrieving data from trie"); is_failed = FALSE; for (dict_p = dict_src; dict_p->key; dict_p++) { if (!trie_retrieve (test_trie, dict_p->key, &trie_data)) { printf ("Failed to retrieve key '%ls'.\n", dict_p->key); is_failed = TRUE; } if (trie_data != dict_p->data) { printf ("Wrong data for key '%ls'; expected %d, got %d.\n", dict_p->key, dict_p->data, trie_data); is_failed = TRUE; } } if (is_failed) { printf ("Trie store/retrieval test failed.\n"); goto err_trie_created; } /* delete */ msg_step ("Deleting some entries from trie"); n_entries = dict_src_n_entries (); srand (time (NULL)); for (n_dels = n_entries/3 + 1; n_dels > 0; n_dels--) { /* pick an undeleted entry */ do { i = rand () % n_entries; } while (TRIE_DATA_READ == dict_src[i].data); printf ("Deleting '%ls'\n", dict_src[i].key); if (!trie_delete (test_trie, dict_src[i].key)) { printf ("Failed to delete '%ls'\n", dict_src[i].key); is_failed = TRUE; } dict_src[i].data = TRIE_DATA_READ; } if (is_failed) { printf ("Trie deletion test failed.\n"); goto err_trie_created; } /* retrieve */ msg_step ("Retrieving data from trie again after deletions"); for (dict_p = dict_src; dict_p->key; dict_p++) { /* skip deleted entries */ if (TRIE_DATA_READ == dict_p->data) continue; if (!trie_retrieve (test_trie, dict_p->key, &trie_data)) { printf ("Failed to retrieve key '%ls'.\n", dict_p->key); is_failed = TRUE; } if (trie_data != dict_p->data) { printf ("Wrong data for key '%ls'; expected %d, got %d.\n", dict_p->key, dict_p->data, trie_data); is_failed = TRUE; } } if (is_failed) { printf ("Trie retrival-after-deletion test failed.\n"); goto err_trie_created; } /* enumerate & check */ msg_step ("Iterating trie contents after deletions"); trie_root_state = trie_root (test_trie); if (!trie_root_state) { printf ("Failed to get trie root state\n"); goto err_trie_created; } trie_it = trie_iterator_new (trie_root_state); if (!trie_it) { printf ("Failed to get trie iterator\n"); goto err_trie_root_created; } while (trie_iterator_next (trie_it)) { AlphaChar *key; TrieData key_data, src_data; key = trie_iterator_get_key (trie_it); if (!key) { printf ("Failed to get key from trie iterator\n"); is_failed = TRUE; continue; } key_data = trie_iterator_get_data (trie_it); if (TRIE_DATA_ERROR == key_data) { printf ("Failed to get data from trie iterator for key '%ls'\n", key); is_failed = TRUE; } /* mark entries found in trie */ src_data = dict_src_get_data (key); if (TRIE_DATA_ERROR == src_data) { printf ("Extra entry in trie: key '%ls', data %d.\n", key, key_data); is_failed = TRUE; } else if (src_data != key_data) { printf ("Data mismatch for: key '%ls', expected %d, got %d.\n", key, src_data, key_data); is_failed = TRUE; } else { dict_src_set_data (key, TRIE_DATA_READ); } free (key); } /* check for unmarked entries, (i.e. missed in trie) */ for (dict_p = dict_src; dict_p->key; dict_p++) { if (dict_p->data != TRIE_DATA_READ) { printf ("Entry missed in trie: key '%ls', data %d.\n", dict_p->key, dict_p->data); is_failed = TRUE; } } if (is_failed) { printf ("Errors found in trie iteration after deletions.\n"); goto err_trie_it_created; } trie_iterator_free (trie_it); trie_state_free (trie_root_state); trie_free (test_trie); return 0; err_trie_it_created: trie_iterator_free (trie_it); err_trie_root_created: trie_state_free (trie_root_state); err_trie_created: trie_free (test_trie); err_trie_not_created: return 1; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/test_term_state.c0000644000175100017510000000716415054046004021733 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2018 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * test_term_state.c - Test data retrieval from terminal state * Created: 2018-03-29 * Author: Theppitak Karoonboonyanan */ #include #include "utils.h" #include #include /* * Test trie * * (1) -a-> (2) -b-> (3) -#-> [4] {data=1} * | * +---c-> (5) -#-> [6] {data=2} * */ int main () { Trie *test_trie; TrieState *trie_state; TrieData data; Bool is_failed; msg_step ("Preparing trie"); test_trie = en_trie_new (); if (!test_trie) { printf ("Fail to create test trie\n"); goto err_trie_not_created; } /* populate trie */ msg_step ("Populating trie with test set"); if (!trie_store (test_trie, (AlphaChar *)L"ab", 1)) { printf ("Failed to add key 'ab', data 1.\n"); goto err_trie_created; } if (!trie_store (test_trie, (AlphaChar *)L"abc", 2)) { printf ("Failed to add key 'abc', data 2.\n"); goto err_trie_created; } is_failed = FALSE; /* try retrieving data */ msg_step ("Preparing root state"); trie_state = trie_root (test_trie); if (!trie_state) { printf ("Failed to get trie root state\n"); goto err_trie_created; } msg_step ("Try walking from root with 'a'"); if (!trie_state_walk (trie_state, (AlphaChar)L'a')) { printf ("Failed to walk from root with 'a'.\n"); is_failed = TRUE; } data = trie_state_get_data (trie_state); if (data != TRIE_DATA_ERROR) { printf ("Retrieved data at 'a' is %d, not %d.\n", data, TRIE_DATA_ERROR); is_failed = TRUE; } msg_step ("Try walking further with 'b'"); if (!trie_state_walk (trie_state, (AlphaChar)L'b')) { printf ("Failed to continue walking with 'b'.\n"); is_failed = TRUE; } data = trie_state_get_data (trie_state); if (data != 1) { printf ("Retrieved data for key 'ab' is %d, not 1.\n", data); is_failed = TRUE; } msg_step ("Try walking further with 'c'"); if (!trie_state_walk (trie_state, (AlphaChar)L'c')) { printf ("Failed to continue walking with 'c'.\n"); is_failed = TRUE; } data = trie_state_get_data (trie_state); if (data != 2) { printf ("Retrieved data for key 'abc' is %d, not 2.\n", data); is_failed = TRUE; } trie_state_free (trie_state); if (is_failed) { printf ("Errors found in terminal state data retrieval.\n"); goto err_trie_created; } trie_free (test_trie); return 0; err_trie_created: trie_free (test_trie); err_trie_not_created: return 1; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/test_walk.c0000644000175100017510000004353215054046004020521 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2013 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * test_walk.c - Test for datrie walking operations * Created: 2013-10-16 * Author: Theppitak Karoonboonyanan */ #include #include "utils.h" #include /* * Sample trie in http://linux.thai.net/~thep/datrie/datrie.html * * +---o-> (3) -o-> (4) -l-> [5] * | * | +---i-> (7) -z-> (8) -e-> [9] * | | * (1) -p-> (2) -r-> (6) -e-> (10) -v-> (11) -i-> (12) -e-> (13) -w-> [14] * | | * | +---p-> (15) -a-> (16) -r-> (17) -e-> [18] * | * +---o-> (19) -d-> (20) -u-> (21) -c-> (22) -e-> [23] * | * +---g-> (24) -r-> (25) -e-> (26) -s-> (27) -s-> [28] * */ DictRec walk_dict[] = { {(AlphaChar *)L"pool", TRIE_DATA_UNREAD}, {(AlphaChar *)L"prize", TRIE_DATA_UNREAD}, {(AlphaChar *)L"preview", TRIE_DATA_UNREAD}, {(AlphaChar *)L"prepare", TRIE_DATA_UNREAD}, {(AlphaChar *)L"produce", TRIE_DATA_UNREAD}, {(AlphaChar *)L"progress", TRIE_DATA_UNREAD}, {(AlphaChar *)NULL, TRIE_DATA_ERROR}, }; static Bool is_walkables_include (AlphaChar c, const AlphaChar *walkables, int n_elm) { while (n_elm > 0) { if (walkables[--n_elm] == c) return TRUE; } return FALSE; } static void print_walkables (const AlphaChar *walkables, int n_elm) { int i; printf ("{"); for (i = 0; i < n_elm; i++) { if (i > 0) { printf (", "); } printf ("'%lc'", walkables[i]); } printf ("}"); } #define ALPHABET_SIZE 256 int main () { Trie *test_trie; DictRec *dict_p; TrieState *s, *t, *u; AlphaChar walkables[ALPHABET_SIZE]; int n; Bool is_failed; TrieData data; msg_step ("Preparing trie"); test_trie = en_trie_new (); if (!test_trie) { fprintf (stderr, "Fail to create test trie\n"); goto err_trie_not_created; } /* store */ for (dict_p = walk_dict; dict_p->key; dict_p++) { if (!trie_store (test_trie, dict_p->key, dict_p->data)) { printf ("Failed to add key '%ls', data %d.\n", dict_p->key, dict_p->data); goto err_trie_created; } } printf ( "Now the trie structure is supposed to be:\n" "\n" " +---o-> (3) -o-> (4) -l-> [5]\n" " |\n" " | +---i-> (7) -z-> (8) -e-> [9]\n" " | |\n" "(1) -p-> (2) -r-> (6) -e-> (10) -v-> (11) -i-> (12) -e-> (13) -w-> [14]\n" " | |\n" " | +---p-> (15) -a-> (16) -r-> (17) -e-> [18]\n" " |\n" " +---o-> (19) -d-> (20) -u-> (21) -c-> (22) -e-> [23]\n" " |\n" " +---g-> (24) -r-> (25) -e-> (26) -s-> (27) -s-> [28]\n" "\n" ); /* walk */ msg_step ("Test walking"); s = trie_root (test_trie); if (!s) { printf ("Failed to get trie root state\n"); goto err_trie_created; } msg_step ("Test walking with 'p'"); if (!trie_state_is_walkable (s, L'p')) { printf ("Trie state is not walkable with 'p'\n"); goto err_trie_state_s_created; } if (!trie_state_walk (s, L'p')) { printf ("Failed to walk with 'p'\n"); goto err_trie_state_s_created; } msg_step ("Now at (2), walkable chars should be {'o', 'r'}"); is_failed = FALSE; n = trie_state_walkable_chars (s, walkables, ALPHABET_SIZE); if (2 != n) { printf ("Walkable chars should be exactly 2, got %d\n", n); is_failed = TRUE; } if (!is_walkables_include (L'o', walkables, n)) { printf ("Walkable chars do not include 'o'\n"); is_failed = TRUE; } if (!is_walkables_include (L'r', walkables, n)) { printf ("Walkable chars do not include 'r'\n"); is_failed = TRUE; } if (is_failed) { printf ("Walkables = "); print_walkables (walkables, n); printf ("\n"); goto err_trie_state_s_created; } msg_step ("Try walking from (2) with 'o' to (3)"); t = trie_state_clone (s); if (!t) { printf ("Failed to clone trie state\n"); goto err_trie_state_s_created; } if (!trie_state_walk (t, L'o')) { printf ("Failed to walk from (2) with 'o' to (3)\n"); goto err_trie_state_t_created; } if (!trie_state_is_single (t)) { printf ("(3) should be single, but isn't.\n"); goto err_trie_state_t_created; } msg_step ("Try walking from (3) with 'o' to (4)"); if (!trie_state_walk (t, L'o')) { printf ("Failed to walk from (3) with 'o' to (4)\n"); goto err_trie_state_t_created; } if (!trie_state_is_single (t)) { printf ("(4) should be single, but isn't.\n"); goto err_trie_state_t_created; } msg_step ("Try walking from (4) with 'l' to (5)"); if (!trie_state_walk (t, L'l')) { printf ("Failed to walk from (4) with 'l' to (5)\n"); goto err_trie_state_t_created; } if (!trie_state_is_terminal (t)) { printf ("(5) should be terminal, but isn't.\n"); goto err_trie_state_t_created; } /* get key & data */ msg_step ("Try getting data from (5)"); data = trie_state_get_data (t); if (TRIE_DATA_ERROR == data) { printf ("Failed to get data from (5)\n"); goto err_trie_state_t_created; } if (TRIE_DATA_UNREAD != data) { printf ("Mismatched data from (5), expected %d, got %d\n", TRIE_DATA_UNREAD, data); goto err_trie_state_t_created; } /* walk s from (2) with 'r' to (6) */ msg_step ("Try walking from (2) with 'r' to (6)"); if (!trie_state_walk (s, L'r')) { printf ("Failed to walk from (2) with 'r' to (6)\n"); goto err_trie_state_t_created; } msg_step ("Now at (6), walkable chars should be {'e', 'i', 'o'}"); is_failed = FALSE; n = trie_state_walkable_chars (s, walkables, ALPHABET_SIZE); if (3 != n) { printf ("Walkable chars should be exactly 3, got %d\n", n); is_failed = TRUE; } if (!is_walkables_include (L'e', walkables, n)) { printf ("Walkable chars do not include 'e'\n"); is_failed = TRUE; } if (!is_walkables_include (L'i', walkables, n)) { printf ("Walkable chars do not include 'i'\n"); is_failed = TRUE; } if (!is_walkables_include (L'o', walkables, n)) { printf ("Walkable chars do not include 'o'\n"); is_failed = TRUE; } if (is_failed) { printf ("Walkables = "); print_walkables (walkables, n); printf ("\n"); goto err_trie_state_t_created; } /* walk from s (6) with "ize" */ msg_step ("Try walking from (6) with 'i' to (7)"); trie_state_copy (t, s); if (!trie_state_walk (t, L'i')) { printf ("Failed to walk from (6) with 'i' to (7)\n"); goto err_trie_state_t_created; } msg_step ("Try walking from (7) with 'z' to (8)"); if (!trie_state_walk (t, L'z')) { printf ("Failed to walk from (7) with 'z' to (8)\n"); goto err_trie_state_t_created; } if (!trie_state_is_single (t)) { printf ("(7) should be single, but isn't.\n"); goto err_trie_state_t_created; } msg_step ("Try walking from (8) with 'e' to (9)"); if (!trie_state_walk (t, L'e')) { printf ("Failed to walk from (8) with 'e' to (9)\n"); goto err_trie_state_t_created; } if (!trie_state_is_terminal (t)) { printf ("(9) should be terminal, but isn't.\n"); goto err_trie_state_t_created; } msg_step ("Try getting data from (9)"); data = trie_state_get_data (t); if (TRIE_DATA_ERROR == data) { printf ("Failed to get data from (9)\n"); goto err_trie_state_t_created; } if (TRIE_DATA_UNREAD != data) { printf ("Mismatched data from (9), expected %d, got %d\n", TRIE_DATA_UNREAD, data); goto err_trie_state_t_created; } /* walk from u = s (6) with 'e' to (10) */ msg_step ("Try walking from (6) with 'e' to (10)"); u = trie_state_clone (s); if (!u) { printf ("Failed to clone trie state\n"); goto err_trie_state_t_created; } if (!trie_state_walk (u, L'e')) { printf ("Failed to walk from (6) with 'e' to (10)\n"); goto err_trie_state_u_created; } /* walkable chars from (10) should be {'p', 'v'} */ msg_step ("Now at (10), walkable chars should be {'p', 'v'}"); is_failed = FALSE; n = trie_state_walkable_chars (u, walkables, ALPHABET_SIZE); if (2 != n) { printf ("Walkable chars should be exactly 2, got %d\n", n); is_failed = TRUE; } if (!is_walkables_include (L'p', walkables, n)) { printf ("Walkable chars do not include 'p'\n"); is_failed = TRUE; } if (!is_walkables_include (L'v', walkables, n)) { printf ("Walkable chars do not include 'v'\n"); is_failed = TRUE; } if (is_failed) { printf ("Walkables = "); print_walkables (walkables, n); printf ("\n"); goto err_trie_state_u_created; } /* walk from u (10) with "view" */ msg_step ("Try walking from (10) with 'v' to (11)"); trie_state_copy (t, u); if (!trie_state_walk (t, L'v')) { printf ("Failed to walk from (10) with 'v' to (11)\n"); goto err_trie_state_u_created; } if (!trie_state_is_single (t)) { printf ("(11) should be single, but isn't.\n"); goto err_trie_state_u_created; } msg_step ("Try walking from (11) with 'i' to (12)"); if (!trie_state_walk (t, L'i')) { printf ("Failed to walk from (11) with 'i' to (12)\n"); goto err_trie_state_u_created; } msg_step ("Try walking from (12) with 'e' to (13)"); if (!trie_state_walk (t, L'e')) { printf ("Failed to walk from (12) with 'e' to (13)\n"); goto err_trie_state_u_created; } msg_step ("Try walking from (13) with 'w' to (14)"); if (!trie_state_walk (t, L'w')) { printf ("Failed to walk from (13) with 'w' to (14)\n"); goto err_trie_state_u_created; } if (!trie_state_is_terminal (t)) { printf ("(14) should be terminal, but isn't.\n"); goto err_trie_state_u_created; } msg_step ("Try getting data from (14)"); data = trie_state_get_data (t); if (TRIE_DATA_ERROR == data) { printf ("Failed to get data from (14)\n"); goto err_trie_state_u_created; } if (TRIE_DATA_UNREAD != data) { printf ("Mismatched data from (14), expected %d, got %d\n", TRIE_DATA_UNREAD, data); goto err_trie_state_u_created; } /* walk from u (10) with "pare" */ msg_step ("Try walking from (10) with 'p' to (15)"); trie_state_copy (t, u); if (!trie_state_walk (t, L'p')) { printf ("Failed to walk from (10) with 'p' to (15)\n"); goto err_trie_state_u_created; } if (!trie_state_is_single (t)) { printf ("(15) should be single, but isn't.\n"); goto err_trie_state_u_created; } msg_step ("Try walking from (15) with 'a' to (16)"); if (!trie_state_walk (t, L'a')) { printf ("Failed to walk from (15) with 'a' to (16)\n"); goto err_trie_state_u_created; } msg_step ("Try walking from (16) with 'r' to (17)"); if (!trie_state_walk (t, L'r')) { printf ("Failed to walk from (16) with 'r' to (17)\n"); goto err_trie_state_u_created; } msg_step ("Try walking from (17) with 'e' to (18)"); if (!trie_state_walk (t, L'e')) { printf ("Failed to walk from (17) with 'e' to (18)\n"); goto err_trie_state_u_created; } if (!trie_state_is_terminal (t)) { printf ("(18) should be terminal, but isn't.\n"); goto err_trie_state_u_created; } msg_step ("Try getting data from (18)"); data = trie_state_get_data (t); if (TRIE_DATA_ERROR == data) { printf ("Failed to get data from (18)\n"); goto err_trie_state_u_created; } if (TRIE_DATA_UNREAD != data) { printf ("Mismatched data from (18), expected %d, got %d\n", TRIE_DATA_UNREAD, data); goto err_trie_state_u_created; } trie_state_free (u); /* walk s from (6) with 'o' to (19) */ msg_step ("Try walking from (6) with 'o' to (19)"); if (!trie_state_walk (s, L'o')) { printf ("Failed to walk from (6) with 'o' to (19)\n"); goto err_trie_state_t_created; } msg_step ("Now at (19), walkable chars should be {'d', 'g'}"); is_failed = FALSE; n = trie_state_walkable_chars (s, walkables, ALPHABET_SIZE); if (2 != n) { printf ("Walkable chars should be exactly 2, got %d\n", n); is_failed = TRUE; } if (!is_walkables_include (L'd', walkables, n)) { printf ("Walkable chars do not include 'd'\n"); is_failed = TRUE; } if (!is_walkables_include (L'g', walkables, n)) { printf ("Walkable chars do not include 'g'\n"); is_failed = TRUE; } if (is_failed) { printf ("Walkables = "); print_walkables (walkables, n); printf ("\n"); goto err_trie_state_t_created; } /* walk from s (19) with "duce" */ msg_step ("Try walking from (19) with 'd' to (20)"); trie_state_copy (t, s); if (!trie_state_walk (t, L'd')) { printf ("Failed to walk from (19) with 'd' to (20)\n"); goto err_trie_state_t_created; } if (!trie_state_is_single (t)) { printf ("(20) should be single, but isn't.\n"); goto err_trie_state_t_created; } msg_step ("Try walking from (20) with 'u' to (21)"); if (!trie_state_walk (t, L'u')) { printf ("Failed to walk from (20) with 'u' to (21)\n"); goto err_trie_state_t_created; } msg_step ("Try walking from (21) with 'c' to (22)"); if (!trie_state_walk (t, L'c')) { printf ("Failed to walk from (21) with 'c' to (22)\n"); goto err_trie_state_t_created; } msg_step ("Try walking from (22) with 'e' to (23)"); if (!trie_state_walk (t, L'e')) { printf ("Failed to walk from (22) with 'e' to (23)\n"); goto err_trie_state_t_created; } if (!trie_state_is_terminal (t)) { printf ("(23) should be terminal, but isn't.\n"); goto err_trie_state_t_created; } msg_step ("Try getting data from (23)"); data = trie_state_get_data (t); if (TRIE_DATA_ERROR == data) { printf ("Failed to get data from (23)\n"); goto err_trie_state_t_created; } if (TRIE_DATA_UNREAD != data) { printf ("Mismatched data from (23), expected %d, got %d\n", TRIE_DATA_UNREAD, data); goto err_trie_state_t_created; } trie_state_free (t); /* walk from s (19) with "gress" */ msg_step ("Try walking from (19) with 'g' to (24)"); if (!trie_state_walk (s, L'g')) { printf ("Failed to walk from (19) with 'g' to (24)\n"); goto err_trie_state_s_created; } if (!trie_state_is_single (s)) { printf ("(24) should be single, but isn't.\n"); goto err_trie_state_s_created; } msg_step ("Try walking from (24) with 'r' to (25)"); if (!trie_state_walk (s, L'r')) { printf ("Failed to walk from (24) with 'r' to (25)\n"); goto err_trie_state_s_created; } msg_step ("Try walking from (25) with 'e' to (26)"); if (!trie_state_walk (s, L'e')) { printf ("Failed to walk from (25) with 'e' to (26)\n"); goto err_trie_state_s_created; } msg_step ("Try walking from (26) with 's' to (27)"); if (!trie_state_walk (s, L's')) { printf ("Failed to walk from (26) with 's' to (27)\n"); goto err_trie_state_s_created; } msg_step ("Try walking from (27) with 's' to (28)"); if (!trie_state_walk (s, L's')) { printf ("Failed to walk from (27) with 's' to (28)\n"); goto err_trie_state_s_created; } if (!trie_state_is_terminal (s)) { printf ("(28) should be terminal, but isn't.\n"); goto err_trie_state_s_created; } msg_step ("Try getting data from (28)"); data = trie_state_get_data (s); if (TRIE_DATA_ERROR == data) { printf ("Failed to get data from (28)\n"); goto err_trie_state_s_created; } if (TRIE_DATA_UNREAD != data) { printf ("Mismatched data from (28), expected %d, got %d\n", TRIE_DATA_UNREAD, data); goto err_trie_state_s_created; } trie_state_free (s); trie_free (test_trie); return 0; err_trie_state_u_created: trie_state_free (u); err_trie_state_t_created: trie_state_free (t); err_trie_state_s_created: trie_state_free (s); err_trie_created: trie_free (test_trie); err_trie_not_created: return 1; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/utils.c0000644000175100017510000001152415054046004017660 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2013 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * utils.c - Utility functions for datrie test cases * Created: 2013-10-16 * Author: Theppitak Karoonboonyanan */ #include #include "utils.h" /*---------------------* * Debugging helpers * *---------------------*/ void msg_step (const char *msg) { printf ("=> %s...\n", msg); } /*-------------------------* * Trie creation helpers * *-------------------------*/ static AlphaMap * en_alpha_map_new () { AlphaMap *en_map; en_map = alpha_map_new (); if (!en_map) goto err_map_not_created; if (alpha_map_add_range (en_map, 0x0061, 0x007a) != 0) goto err_map_created; return en_map; err_map_created: alpha_map_free (en_map); err_map_not_created: return NULL; } Trie * en_trie_new () { AlphaMap *en_map; Trie *en_trie; en_map = en_alpha_map_new (); if (!en_map) goto err_map_not_created; en_trie = trie_new (en_map); if (!en_trie) goto err_map_created; alpha_map_free (en_map); return en_trie; err_map_created: alpha_map_free (en_map); err_map_not_created: return NULL; } /*---------------------------* * Dict source for testing * *---------------------------*/ DictRec dict_src[] = { {(AlphaChar *)L"a", TRIE_DATA_UNREAD}, {(AlphaChar *)L"abacus", TRIE_DATA_UNREAD}, {(AlphaChar *)L"abandon", TRIE_DATA_UNREAD}, {(AlphaChar *)L"accident", TRIE_DATA_UNREAD}, {(AlphaChar *)L"accredit", TRIE_DATA_UNREAD}, {(AlphaChar *)L"algorithm", TRIE_DATA_UNREAD}, {(AlphaChar *)L"ammonia", TRIE_DATA_UNREAD}, {(AlphaChar *)L"angel", TRIE_DATA_UNREAD}, {(AlphaChar *)L"angle", TRIE_DATA_UNREAD}, {(AlphaChar *)L"azure", TRIE_DATA_UNREAD}, {(AlphaChar *)L"bat", TRIE_DATA_UNREAD}, {(AlphaChar *)L"bet", TRIE_DATA_UNREAD}, {(AlphaChar *)L"best", TRIE_DATA_UNREAD}, {(AlphaChar *)L"home", TRIE_DATA_UNREAD}, {(AlphaChar *)L"house", TRIE_DATA_UNREAD}, {(AlphaChar *)L"hut", TRIE_DATA_UNREAD}, {(AlphaChar *)L"king", TRIE_DATA_UNREAD}, {(AlphaChar *)L"kite", TRIE_DATA_UNREAD}, {(AlphaChar *)L"name", TRIE_DATA_UNREAD}, {(AlphaChar *)L"net", TRIE_DATA_UNREAD}, {(AlphaChar *)L"network", TRIE_DATA_UNREAD}, {(AlphaChar *)L"nut", TRIE_DATA_UNREAD}, {(AlphaChar *)L"nutshell", TRIE_DATA_UNREAD}, {(AlphaChar *)L"quality", TRIE_DATA_UNREAD}, {(AlphaChar *)L"quantum", TRIE_DATA_UNREAD}, {(AlphaChar *)L"quantity", TRIE_DATA_UNREAD}, {(AlphaChar *)L"quartz", TRIE_DATA_UNREAD}, {(AlphaChar *)L"quick", TRIE_DATA_UNREAD}, {(AlphaChar *)L"quiz", TRIE_DATA_UNREAD}, {(AlphaChar *)L"run", TRIE_DATA_UNREAD}, {(AlphaChar *)L"tape", TRIE_DATA_UNREAD}, {(AlphaChar *)L"test", TRIE_DATA_UNREAD}, {(AlphaChar *)L"what", TRIE_DATA_UNREAD}, {(AlphaChar *)L"when", TRIE_DATA_UNREAD}, {(AlphaChar *)L"where", TRIE_DATA_UNREAD}, {(AlphaChar *)L"which", TRIE_DATA_UNREAD}, {(AlphaChar *)L"who", TRIE_DATA_UNREAD}, {(AlphaChar *)L"why", TRIE_DATA_UNREAD}, {(AlphaChar *)L"zebra", TRIE_DATA_UNREAD}, {(AlphaChar *)NULL, TRIE_DATA_ERROR}, }; int dict_src_n_entries () { return sizeof (dict_src) / sizeof (dict_src[0]) - 1; } TrieData dict_src_get_data (const AlphaChar *key) { const DictRec *dict_p; for (dict_p = dict_src; dict_p->key; dict_p++) { if (alpha_char_strcmp (dict_p->key, key) == 0) { return dict_p->data; } } return TRIE_DATA_ERROR; } int dict_src_set_data (const AlphaChar *key, TrieData data) { DictRec *dict_p; for (dict_p = dict_src; dict_p->key; dict_p++) { if (alpha_char_strcmp (dict_p->key, key) == 0) { dict_p->data = data; return 0; } } return -1; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tests/utils.h0000644000175100017510000000337215054046004017667 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * libdatrie - Double-Array Trie Library * Copyright (C) 2013 Theppitak Karoonboonyanan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * utils.h - Utility functions for datrie test cases * Created: 2013-10-16 * Author: Theppitak Karoonboonyanan */ #include /*---------------------* * Debugging helpers * *---------------------*/ void msg_step (const char *msg); /*-------------------------* * Trie creation helpers * *-------------------------*/ Trie * en_trie_new (); /*---------------------------* * Dict source for testing * *---------------------------*/ typedef struct _DictRec DictRec; struct _DictRec { AlphaChar *key; TrieData data; }; #define TRIE_DATA_UNREAD 1 #define TRIE_DATA_READ 2 extern DictRec dict_src[]; int dict_src_n_entries (); TrieData dict_src_get_data (const AlphaChar *key); int dict_src_set_data (const AlphaChar *key, TrieData data); /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5671594 datrie-0.8.3/libdatrie/tools/0000755000175100017510000000000015054046505016355 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384260.0 datrie-0.8.3/libdatrie/tools/trietool.c0000644000175100017510000004004315054046004020355 0ustar00tcaswelltcaswell/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * trietool.c - Trie manipulation tool * Created: 2006-08-15 * Author: Theppitak Karoonboonyanan */ #include #include #include #include #include #include #if defined(HAVE_LOCALE_CHARSET) # include #elif defined (HAVE_LANGINFO_CODESET) # include # define locale_charset() nl_langinfo(CODESET) #endif #include #include #include #include /* iconv encoding name for AlphaChar string */ #define ALPHA_ENC "UCS-4LE" #define N_ELEMENTS(a) (sizeof(a)/sizeof((a)[0])) typedef struct { const char *path; const char *trie_name; iconv_t to_alpha_conv; iconv_t from_alpha_conv; Trie *trie; } ProgEnv; static void init_conv (ProgEnv *env); static size_t conv_to_alpha (ProgEnv *env, const char *in, AlphaChar *out, size_t out_size); static size_t conv_from_alpha (ProgEnv *env, const AlphaChar *in, char *out, size_t out_size); static void close_conv (ProgEnv *env); static int prepare_trie (ProgEnv *env); static int close_trie (ProgEnv *env); static int decode_switch (int argc, char *argv[], ProgEnv *env); static int decode_command (int argc, char *argv[], ProgEnv *env); static int command_add (int argc, char *argv[], ProgEnv *env); static int command_add_list (int argc, char *argv[], ProgEnv *env); static int command_delete (int argc, char *argv[], ProgEnv *env); static int command_delete_list (int argc, char *argv[], ProgEnv *env); static int command_query (int argc, char *argv[], ProgEnv *env); static int command_list (int argc, char *argv[], ProgEnv *env); static void usage (const char *prog_name, int exit_status); static char *string_trim (char *s); int main (int argc, char *argv[]) { int i; ProgEnv env; int ret; env.path = "."; init_conv (&env); i = decode_switch (argc, argv, &env); if (i == argc) usage (argv[0], EXIT_FAILURE); env.trie_name = argv[i++]; if (prepare_trie (&env) != 0) exit (EXIT_FAILURE); ret = decode_command (argc - i, argv + i, &env); if (close_trie (&env) != 0) exit (EXIT_FAILURE); close_conv (&env); return ret; } static void init_conv (ProgEnv *env) { const char *prev_locale; const char *locale_codeset; prev_locale = setlocale (LC_CTYPE, ""); locale_codeset = locale_charset(); setlocale (LC_CTYPE, prev_locale); env->to_alpha_conv = iconv_open (ALPHA_ENC, locale_codeset); env->from_alpha_conv = iconv_open (locale_codeset, ALPHA_ENC); } static size_t conv_to_alpha (ProgEnv *env, const char *in, AlphaChar *out, size_t out_size) { char *in_p = (char *) in; char *out_p = (char *) out; size_t in_left = strlen (in); size_t out_left = out_size * sizeof (AlphaChar); size_t res; const unsigned char *byte_p; assert (sizeof (AlphaChar) == 4); /* convert to UCS-4LE */ res = iconv (env->to_alpha_conv, (char **) &in_p, &in_left, &out_p, &out_left); if (res == (size_t) -1) return res; /* convert UCS-4LE to AlphaChar string */ res = 0; for (byte_p = (const unsigned char *) out; res < out_size && byte_p + 3 < (unsigned char*) out_p; byte_p += 4) { out[res++] = byte_p[0] | (byte_p[1] << 8) | (byte_p[2] << 16) | (byte_p[3] << 24); } if (res < out_size) { out[res] = 0; } return res; } static size_t conv_from_alpha (ProgEnv *env, const AlphaChar *in, char *out, size_t out_size) { size_t in_left = alpha_char_strlen (in) * sizeof (AlphaChar); size_t res; assert (sizeof (AlphaChar) == 4); /* convert AlphaChar to UCS-4LE */ for (res = 0; in[res]; res++) { unsigned char b[4]; b[0] = in[res] & 0xff; b[1] = (in[res] >> 8) & 0xff; b[2] = (in[res] >> 16) & 0xff; b[3] = (in[res] >> 24) & 0xff; memcpy ((char *) &in[res], b, 4); } /* convert UCS-4LE to locale codeset */ res = iconv (env->from_alpha_conv, (char **) &in, &in_left, &out, &out_size); *out = 0; return res; } static void close_conv (ProgEnv *env) { iconv_close (env->to_alpha_conv); iconv_close (env->from_alpha_conv); } static int prepare_trie (ProgEnv *env) { char buff[256]; snprintf (buff, sizeof (buff), "%s/%s.tri", env->path, env->trie_name); env->trie = trie_new_from_file (buff); if (!env->trie) { FILE *sbm; AlphaMap *alpha_map; snprintf (buff, sizeof (buff), "%s/%s.abm", env->path, env->trie_name); sbm = fopen (buff, "r"); if (!sbm) { fprintf (stderr, "Cannot open alphabet map file %s\n", buff); return -1; } alpha_map = alpha_map_new (); while (fgets (buff, sizeof (buff), sbm)) { int b, e; /* read the range * format: [b,e] * where: b = begin char, e = end char; both in hex values */ if (sscanf (buff, " [ %x , %x ] ", &b, &e) != 2) continue; if (b > e) { fprintf (stderr, "Range begin (%x) > range end (%x)\n", b, e); continue; } alpha_map_add_range (alpha_map, b, e); } env->trie = trie_new (alpha_map); alpha_map_free (alpha_map); fclose (sbm); } return 0; } static int close_trie (ProgEnv *env) { if (trie_is_dirty (env->trie)) { char path[256]; snprintf (path, sizeof (path), "%s/%s.tri", env->path, env->trie_name); if (trie_save (env->trie, path) != 0) { fprintf (stderr, "Cannot save trie to %s\n", path); return -1; } } trie_free (env->trie); return 0; } static int decode_switch (int argc, char *argv[], ProgEnv *env) { int opt_idx; for (opt_idx = 1; opt_idx < argc && *argv[opt_idx] == '-'; opt_idx++) { if (strcmp (argv[opt_idx], "-h") == 0 || strcmp (argv[opt_idx], "--help") == 0) { usage (argv[0], EXIT_FAILURE); } else if (strcmp (argv[opt_idx], "-V") == 0 || strcmp (argv[opt_idx], "--version") == 0) { printf ("%s\n", VERSION); exit (EXIT_FAILURE); } else if (strcmp (argv[opt_idx], "-p") == 0 || strcmp (argv[opt_idx], "--path") == 0) { env->path = argv[++opt_idx]; } else if (strcmp (argv[opt_idx], "--") == 0) { ++opt_idx; break; } else { fprintf (stderr, "Unknown option: %s\n", argv[opt_idx]); exit (EXIT_FAILURE); } } return opt_idx; } static int decode_command (int argc, char *argv[], ProgEnv *env) { int opt_idx; for (opt_idx = 0; opt_idx < argc; opt_idx++) { if (strcmp (argv[opt_idx], "add") == 0) { ++opt_idx; opt_idx += command_add (argc - opt_idx, argv + opt_idx, env); } else if (strcmp (argv[opt_idx], "add-list") == 0) { ++opt_idx; opt_idx += command_add_list (argc - opt_idx, argv + opt_idx, env); } else if (strcmp (argv[opt_idx], "delete") == 0) { ++opt_idx; opt_idx += command_delete (argc - opt_idx, argv + opt_idx, env); } else if (strcmp (argv[opt_idx], "delete-list") == 0) { ++opt_idx; opt_idx += command_delete_list (argc - opt_idx, argv + opt_idx, env); } else if (strcmp (argv[opt_idx], "query") == 0) { ++opt_idx; opt_idx += command_query (argc - opt_idx, argv + opt_idx, env); } else if (strcmp (argv[opt_idx], "list") == 0) { ++opt_idx; opt_idx += command_list (argc - opt_idx, argv + opt_idx, env); } else { fprintf (stderr, "Unknown command: %s\n", argv[opt_idx]); return EXIT_FAILURE; } } return EXIT_SUCCESS; } static int command_add (int argc, char *argv[], ProgEnv *env) { int opt_idx; opt_idx = 0; while (opt_idx < argc) { const char *key; AlphaChar key_alpha[256]; TrieData data; key = argv[opt_idx++]; data = (opt_idx < argc) ? atoi (argv[opt_idx++]) : TRIE_DATA_ERROR; conv_to_alpha (env, key, key_alpha, N_ELEMENTS (key_alpha)); if (!trie_store (env->trie, key_alpha, data)) { fprintf (stderr, "Failed to add entry '%s' with data %d\n", key, data); } } return opt_idx; } static int command_add_list (int argc, char *argv[], ProgEnv *env) { const char *enc_name, *input_name; int opt_idx; iconv_t saved_conv; FILE *input; char line[256]; enc_name = 0; opt_idx = 0; saved_conv = env->to_alpha_conv; if (strcmp (argv[0], "-e") == 0 || strcmp (argv[0], "--encoding") == 0) { if (++opt_idx >= argc) { fprintf (stderr, "add-list option \"%s\" requires encoding name", argv[0]); return opt_idx; } enc_name = argv[opt_idx++]; } if (opt_idx >= argc) { fprintf (stderr, "add-list requires input word list file name\n"); return opt_idx; } input_name = argv[opt_idx++]; if (enc_name) { iconv_t conv = iconv_open (ALPHA_ENC, enc_name); if ((iconv_t) -1 == conv) { fprintf (stderr, "Conversion from \"%s\" to \"%s\" is not supported.\n", enc_name, ALPHA_ENC); return opt_idx; } env->to_alpha_conv = conv; } input = fopen (input_name, "r"); if (!input) { fprintf (stderr, "add-list: Cannot open input file \"%s\"\n", input_name); goto exit_iconv_openned; } while (fgets (line, sizeof line, input)) { char *key, *data; AlphaChar key_alpha[256]; TrieData data_val; key = string_trim (line); if ('\0' != *key) { /* find key boundary */ for (data = key; *data && !strchr ("\t,", *data); ++data) ; /* mark key ending and find data begin */ if ('\0' != *data) { *data++ = '\0'; while (isspace (*data)) ++data; } /* decode data */ data_val = ('\0' != *data) ? atoi (data) : TRIE_DATA_ERROR; /* store the key */ conv_to_alpha (env, key, key_alpha, N_ELEMENTS (key_alpha)); if (!trie_store (env->trie, key_alpha, data_val)) fprintf (stderr, "Failed to add key '%s' with data %d.\n", key, data_val); } } fclose (input); exit_iconv_openned: if (enc_name) { iconv_close (env->to_alpha_conv); env->to_alpha_conv = saved_conv; } return opt_idx; } static int command_delete (int argc, char *argv[], ProgEnv *env) { int opt_idx; for (opt_idx = 0; opt_idx < argc; opt_idx++) { AlphaChar key_alpha[256]; conv_to_alpha (env, argv[opt_idx], key_alpha, N_ELEMENTS (key_alpha)); if (!trie_delete (env->trie, key_alpha)) { fprintf (stderr, "No entry '%s'. Not deleted.\n", argv[opt_idx]); } } return opt_idx; } static int command_delete_list (int argc, char *argv[], ProgEnv *env) { const char *enc_name, *input_name; int opt_idx; iconv_t saved_conv; FILE *input; char line[256]; enc_name = 0; opt_idx = 0; saved_conv = env->to_alpha_conv; if (strcmp (argv[0], "-e") == 0 || strcmp (argv[0], "--encoding") == 0) { if (++opt_idx >= argc) { fprintf (stderr, "delete-list option \"%s\" requires encoding name", argv[0]); return opt_idx; } enc_name = argv[opt_idx++]; } if (opt_idx >= argc) { fprintf (stderr, "delete-list requires input word list file name\n"); return opt_idx; } input_name = argv[opt_idx++]; if (enc_name) { iconv_t conv = iconv_open (ALPHA_ENC, enc_name); if ((iconv_t) -1 == conv) { fprintf (stderr, "Conversion from \"%s\" to \"%s\" is not supported.\n", enc_name, ALPHA_ENC); return opt_idx; } env->to_alpha_conv = conv; } input = fopen (input_name, "r"); if (!input) { fprintf (stderr, "delete-list: Cannot open input file \"%s\"\n", input_name); goto exit_iconv_openned; } while (fgets (line, sizeof line, input)) { char *p; p = string_trim (line); if ('\0' != *p) { AlphaChar key_alpha[256]; conv_to_alpha (env, p, key_alpha, N_ELEMENTS (key_alpha)); if (!trie_delete (env->trie, key_alpha)) { fprintf (stderr, "No entry '%s'. Not deleted.\n", p); } } } fclose (input); exit_iconv_openned: if (enc_name) { iconv_close (env->to_alpha_conv); env->to_alpha_conv = saved_conv; } return opt_idx; } static int command_query (int argc, char *argv[], ProgEnv *env) { AlphaChar key_alpha[256]; TrieData data; if (argc == 0) { fprintf (stderr, "query: No key specified.\n"); return 0; } conv_to_alpha (env, argv[0], key_alpha, N_ELEMENTS (key_alpha)); if (trie_retrieve (env->trie, key_alpha, &data)) { printf ("%d\n", data); } else { fprintf (stderr, "query: Key '%s' not found.\n", argv[0]); } return 1; } static Bool list_enum_func (const AlphaChar *key, TrieData key_data, void *user_data) { ProgEnv *env = (ProgEnv *) user_data; char key_locale[1024]; conv_from_alpha (env, key, key_locale, N_ELEMENTS (key_locale)); printf ("%s\t%d\n", key_locale, key_data); return TRUE; } static int command_list (int argc, char *argv[], ProgEnv *env) { trie_enumerate (env->trie, list_enum_func, (void *) env); return 0; } static void usage (const char *prog_name, int exit_status) { printf ("%s - double-array trie manipulator\n", prog_name); printf ("Usage: %s [OPTION]... TRIE CMD ARG ...\n", prog_name); printf ( "Options:\n" " -p, --path DIR set trie directory to DIR [default=.]\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" "\n" "Commands:\n" " add WORD DATA ...\n" " Add WORD with DATA to trie\n" " add-list [OPTION] LISTFILE\n" " Add words and data listed in LISTFILE to trie\n" " Options:\n" " -e, --encoding ENC specify character encoding of LISTFILE\n" " delete WORD ...\n" " Delete WORD from trie\n" " delete-list [OPTION] LISTFILE\n" " Delete words listed in LISTFILE from trie\n" " Options:\n" " -e, --encoding ENC specify character encoding of LISTFILE\n" " query WORD\n" " Query WORD data from trie\n" " list\n" " List all words in trie\n" ); exit (exit_status); } static char * string_trim (char *s) { char *p; /* skip leading white spaces */ while (*s && isspace (*s)) ++s; /* trim trailing white spaces */ p = s + strlen (s) - 1; while (isspace (*p)) --p; *++p = '\0'; return s; } /* vi:ts=4:ai:expandtab */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/pyproject.toml0000644000175100017510000000012215054046003016156 0ustar00tcaswelltcaswell[build-system] requires = [ "setuptools>=40.8.0", "wheel", "Cython" ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5679386 datrie-0.8.3/setup.cfg0000644000175100017510000000007715054046505015103 0ustar00tcaswelltcaswell[aliases] test = pytest [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384536.0 datrie-0.8.3/setup.py0000755000175100017510000000454115054046430014774 0ustar00tcaswelltcaswell#! /usr/bin/env python """Super-fast, efficiently stored Trie for Python.""" import glob import os from setuptools import setup, Extension from Cython.Build import cythonize LIBDATRIE_DIR = 'libdatrie' LIBDATRIE_FILES = sorted(glob.glob(os.path.join(LIBDATRIE_DIR, "datrie", "*.c"))) DESCRIPTION = __doc__ LONG_DESCRIPTION = open('README.rst').read() + open('CHANGES.rst').read() LICENSE = 'LGPLv2+' CLASSIFIERS = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)', 'Programming Language :: Cython', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Scientific/Engineering :: Information Analysis', 'Topic :: Text Processing :: Linguistic' ] ext_modules = cythonize( 'src/datrie.pyx', 'src/cdatrie.pxd', 'src/stdio_ext.pxd', annotate=True, include_path=[os.path.join(os.path.dirname(os.path.abspath(__file__)), "src")], language_level=2 ) for m in ext_modules: m.include_dirs=[LIBDATRIE_DIR] setup(name="datrie", version="0.8.3", description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='Mikhail Korobov', author_email='kmike84@gmail.com', license=LICENSE, url='https://github.com/kmike/datrie', classifiers=CLASSIFIERS, libraries=[('datrie', { "sources": LIBDATRIE_FILES, "include_dirs": [LIBDATRIE_DIR]})], ext_modules=ext_modules, python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", setup_requires=['Cython>=0.28'], tests_require=["pytest", "hypothesis"]) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5672681 datrie-0.8.3/src/0000755000175100017510000000000015054046505014045 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/src/cdatrie.pxd0000644000175100017510000000536115054046003016173 0ustar00tcaswelltcaswell# cython: profile=False from libc cimport stdio cdef extern from "../libdatrie/datrie/triedefs.h": ctypedef int AlphaChar # it should be utf32 letter ctypedef unsigned char TrieChar # 1 byte ctypedef int TrieIndex ctypedef int TrieData # int cdef extern from "../libdatrie/datrie/alpha-map.h": ctypedef struct AlphaMap: pass AlphaMap * alpha_map_new() void alpha_map_free (AlphaMap *alpha_map) AlphaMap * alpha_map_clone (AlphaMap *a_map) int alpha_map_add_range (AlphaMap *alpha_map, AlphaChar begin, AlphaChar end) int alpha_char_strlen (AlphaChar *str) cdef extern from "../libdatrie/datrie/trie.h": ctypedef struct Trie: pass ctypedef struct TrieState: pass ctypedef struct TrieIterator: pass ctypedef int TrieData ctypedef bint (*TrieEnumFunc) (AlphaChar *key, TrieData key_data, void *user_data) int TRIE_CHAR_TERM int TRIE_DATA_ERROR # ========== GENERAL FUNCTIONS ========== Trie * trie_new (AlphaMap *alpha_map) Trie * trie_new_from_file (char *path) Trie * trie_fread (stdio.FILE *file) void trie_free (Trie *trie) int trie_save (Trie *trie, char *path) int trie_fwrite (Trie *trie, stdio.FILE *file) bint trie_is_dirty (Trie *trie) # =========== GENERAL QUERY OPERATIONS ========= bint trie_retrieve (Trie *trie, AlphaChar *key, TrieData *o_data) bint trie_store (Trie *trie, AlphaChar *key, TrieData data) bint trie_store_if_absent (Trie *trie, AlphaChar *key, TrieData data) bint trie_delete (Trie *trie, AlphaChar *key) bint trie_enumerate (Trie *trie, TrieEnumFunc enum_func, void *user_data); # ======== STEPWISE QUERY OPERATIONS ======== TrieState * trie_root (Trie *trie) # ========= TRIE STATE =============== TrieState * trie_state_clone (TrieState *s) void trie_state_copy (TrieState *dst, TrieState *src) void trie_state_free (TrieState *s) void trie_state_rewind (TrieState *s) bint trie_state_walk (TrieState *s, AlphaChar c) bint trie_state_is_walkable (TrieState *s, AlphaChar c) bint trie_state_is_terminal(TrieState * s) bint trie_state_is_single (TrieState *s) bint trie_state_is_leaf(TrieState* s) TrieData trie_state_get_data (TrieState *s) TrieData trie_state_get_data (TrieState *s) # ============== ITERATION =================== TrieIterator* trie_iterator_new (TrieState *s) void trie_iterator_free (TrieIterator *iter) bint trie_iterator_next (TrieIterator *iter) AlphaChar * trie_iterator_get_key (TrieIterator *iter) TrieData trie_iterator_get_data (TrieIterator *iter) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/src/datrie.pyx0000644000175100017510000010527515054046003016062 0ustar00tcaswelltcaswell# cython: profile=False """ Cython wrapper for libdatrie. """ from cpython.version cimport PY_MAJOR_VERSION from cython.operator import dereference as deref from libc.stdlib cimport malloc, free from libc cimport stdio from libc cimport string cimport stdio_ext cimport cdatrie import itertools import warnings import sys import tempfile try: from collections.abc import MutableMapping except ImportError: from collections import MutableMapping try: import cPickle as pickle except ImportError: import pickle class DatrieError(Exception): pass RAISE_KEY_ERROR = object() RERAISE_KEY_ERROR = object() DELETED_OBJECT = object() cdef class BaseTrie: """ Wrapper for libdatrie's trie. Keys are unicode strings, values are integers -2147483648 <= x <= 2147483647. """ cdef AlphaMap alpha_map cdef cdatrie.Trie *_c_trie def __init__(self, alphabet=None, ranges=None, AlphaMap alpha_map=None, _create=True): """ For efficiency trie needs to know what unicode symbols it should be able to store so this constructor requires either ``alphabet`` (a string/iterable with all allowed characters), ``ranges`` (a list of (begin, end) pairs, e.g. [('a', 'z')]) or ``alpha_map`` (:class:`datrie.AlphaMap` instance). """ if self._c_trie is not NULL: return if not _create: return if alphabet is None and ranges is None and alpha_map is None: raise ValueError( "Please provide alphabet, ranges or alpha_map argument.") if alpha_map is None: alpha_map = AlphaMap(alphabet, ranges) self.alpha_map = alpha_map self._c_trie = cdatrie.trie_new(alpha_map._c_alpha_map) if self._c_trie is NULL: raise MemoryError() def __dealloc__(self): if self._c_trie is not NULL: cdatrie.trie_free(self._c_trie) def update(self, other=(), **kwargs): if PY_MAJOR_VERSION == 2: if kwargs: raise TypeError("keyword arguments are not supported.") if hasattr(other, "keys"): for key in other: self[key] = other[key] else: for key, value in other: self[key] = value for key in kwargs: self[key] = kwargs[key] def clear(self): cdef AlphaMap alpha_map = self.alpha_map.copy() _c_trie = cdatrie.trie_new(alpha_map._c_alpha_map) if _c_trie is NULL: raise MemoryError() cdatrie.trie_free(self._c_trie) self._c_trie = _c_trie cpdef bint is_dirty(self): """ Returns True if the trie is dirty with some pending changes and needs saving to synchronize with the file. """ return cdatrie.trie_is_dirty(self._c_trie) def save(self, path): """ Saves this trie. """ with open(path, "wb", 0) as f: self.write(f) def write(self, f): """ Writes a trie to a file. File-like objects without real file descriptors are not supported. """ f.flush() cdef stdio.FILE* f_ptr = stdio_ext.fdopen(f.fileno(), "w") if f_ptr == NULL: raise IOError("Can't open file descriptor") cdef int res = cdatrie.trie_fwrite(self._c_trie, f_ptr) if res == -1: raise IOError("Can't write to file") stdio.fflush(f_ptr) @classmethod def load(cls, path): """ Loads a trie from file. """ with open(path, "rb", 0) as f: return cls.read(f) @classmethod def read(cls, f): """ Creates a new Trie by reading it from file. File-like objects without real file descriptors are not supported. # XXX: does it work properly in subclasses? """ cdef BaseTrie trie = cls(_create=False) trie._c_trie = _load_from_file(f) return trie def __reduce__(self): with tempfile.NamedTemporaryFile() as f: self.write(f) f.seek(0) state = f.read() return BaseTrie, (None, None, None, False), state def __setstate__(self, bytes state): assert self._c_trie is NULL with tempfile.NamedTemporaryFile() as f: f.write(state) f.flush() f.seek(0) self._c_trie = _load_from_file(f) def __setitem__(self, unicode key, cdatrie.TrieData value): self._setitem(key, value) cdef void _setitem(self, unicode key, cdatrie.TrieData value): cdef cdatrie.AlphaChar* c_key = new_alpha_char_from_unicode(key) try: cdatrie.trie_store(self._c_trie, c_key, value) finally: free(c_key) def __getitem__(self, unicode key): return self._getitem(key) def get(self, unicode key, default=None): try: return self._getitem(key) except KeyError: return default cdef cdatrie.TrieData _getitem(self, unicode key) except -1: cdef cdatrie.TrieData data cdef cdatrie.AlphaChar* c_key = new_alpha_char_from_unicode(key) try: found = cdatrie.trie_retrieve(self._c_trie, c_key, &data) finally: free(c_key) if not found: raise KeyError(key) return data def __contains__(self, unicode key): cdef cdatrie.AlphaChar* c_key = new_alpha_char_from_unicode(key) try: return cdatrie.trie_retrieve(self._c_trie, c_key, NULL) finally: free(c_key) def __delitem__(self, unicode key): self._delitem(key) def pop(self, unicode key, default=None): try: value = self[key] self._delitem(key) return value except KeyError: return default cpdef bint _delitem(self, unicode key) except -1: """ Deletes an entry for the given key from the trie. Returns boolean value indicating whether the key exists and is removed. """ cdef cdatrie.AlphaChar* c_key = new_alpha_char_from_unicode(key) try: found = cdatrie.trie_delete(self._c_trie, c_key) finally: free(c_key) if not found: raise KeyError(key) @staticmethod cdef int len_enumerator(cdatrie.AlphaChar *key, cdatrie.TrieData key_data, void *counter_ptr): (counter_ptr)[0] += 1 return True def __len__(self): cdef int counter = 0 cdatrie.trie_enumerate(self._c_trie, (self.len_enumerator), &counter) return counter def __richcmp__(self, other, int op): if op == 2: # == if other is self: return True elif not isinstance(other, BaseTrie): return False for key in self: if self[key] != other[key]: return False # XXX this can be written more efficiently via explicit iterators. return len(self) == len(other) elif op == 3: # != return not (self == other) raise TypeError("unorderable types: {0} and {1}".format( self.__class__, other.__class__)) def setdefault(self, unicode key, cdatrie.TrieData value): return self._setdefault(key, value) cdef cdatrie.TrieData _setdefault(self, unicode key, cdatrie.TrieData value): cdef cdatrie.AlphaChar* c_key = new_alpha_char_from_unicode(key) cdef cdatrie.TrieData data try: found = cdatrie.trie_retrieve(self._c_trie, c_key, &data) if found: return data else: cdatrie.trie_store(self._c_trie, c_key, value) return value finally: free(c_key) def iter_prefixes(self, unicode key): ''' Returns an iterator over the keys of this trie that are prefixes of ``key``. ''' cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() cdef int index = 1 try: for char in key: if not cdatrie.trie_state_walk(state, char): return if cdatrie.trie_state_is_terminal(state): yield key[:index] index += 1 finally: cdatrie.trie_state_free(state) def iter_prefix_items(self, unicode key): ''' Returns an iterator over the items (``(key,value)`` tuples) of this trie that are associated with keys that are prefixes of ``key``. ''' cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() cdef int index = 1 try: for char in key: if not cdatrie.trie_state_walk(state, char): return if cdatrie.trie_state_is_terminal(state): # word is found yield key[:index], cdatrie.trie_state_get_data(state) index += 1 finally: cdatrie.trie_state_free(state) def iter_prefix_values(self, unicode key): ''' Returns an iterator over the values of this trie that are associated with keys that are prefixes of ``key``. ''' cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() try: for char in key: if not cdatrie.trie_state_walk(state, char): return if cdatrie.trie_state_is_terminal(state): yield cdatrie.trie_state_get_data(state) finally: cdatrie.trie_state_free(state) def prefixes(self, unicode key): ''' Returns a list with keys of this trie that are prefixes of ``key``. ''' cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() cdef list result = [] cdef int index = 1 try: for char in key: if not cdatrie.trie_state_walk(state, char): break if cdatrie.trie_state_is_terminal(state): result.append(key[:index]) index += 1 return result finally: cdatrie.trie_state_free(state) cpdef suffixes(self, unicode prefix=u''): """ Returns a list of this trie's suffixes. If ``prefix`` is not empty, returns only the suffixes of words prefixed by ``prefix``. """ cdef bint success cdef list res = [] cdef BaseState state = BaseState(self) if prefix is not None: success = state.walk(prefix) if not success: return res cdef BaseIterator iter = BaseIterator(state) while iter.next(): res.append(iter.key()) return res def prefix_items(self, unicode key): ''' Returns a list of the items (``(key,value)`` tuples) of this trie that are associated with keys that are prefixes of ``key``. ''' return self._prefix_items(key) cdef list _prefix_items(self, unicode key): cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() cdef list result = [] cdef int index = 1 try: for char in key: if not cdatrie.trie_state_walk(state, char): break if cdatrie.trie_state_is_terminal(state): # word is found result.append( (key[:index], cdatrie.trie_state_get_data(state)) ) index += 1 return result finally: cdatrie.trie_state_free(state) def prefix_values(self, unicode key): ''' Returns a list of the values of this trie that are associated with keys that are prefixes of ``key``. ''' return self._prefix_values(key) cdef list _prefix_values(self, unicode key): cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() cdef list result = [] try: for char in key: if not cdatrie.trie_state_walk(state, char): break if cdatrie.trie_state_is_terminal(state): # word is found result.append(cdatrie.trie_state_get_data(state)) return result finally: cdatrie.trie_state_free(state) def longest_prefix(self, unicode key, default=RAISE_KEY_ERROR): """ Returns the longest key in this trie that is a prefix of ``key``. If the trie doesn't contain any prefix of ``key``: - if ``default`` is given, returns it, - otherwise raises ``KeyError``. """ cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() cdef int index = 0, last_terminal_index = 0 try: for ch in key: if not cdatrie.trie_state_walk(state, ch): break index += 1 if cdatrie.trie_state_is_terminal(state): last_terminal_index = index if not last_terminal_index: if default is RAISE_KEY_ERROR: raise KeyError(key) return default return key[:last_terminal_index] finally: cdatrie.trie_state_free(state) def longest_prefix_item(self, unicode key, default=RAISE_KEY_ERROR): """ Returns the item (``(key,value)`` tuple) associated with the longest key in this trie that is a prefix of ``key``. If the trie doesn't contain any prefix of ``key``: - if ``default`` is given, returns it, - otherwise raises ``KeyError``. """ return self._longest_prefix_item(key, default) cdef _longest_prefix_item(self, unicode key, default=RAISE_KEY_ERROR): cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() cdef int index = 0, last_terminal_index = 0, data try: for ch in key: if not cdatrie.trie_state_walk(state, ch): break index += 1 if cdatrie.trie_state_is_terminal(state): last_terminal_index = index data = cdatrie.trie_state_get_data(state) if not last_terminal_index: if default is RAISE_KEY_ERROR: raise KeyError(key) return default return key[:last_terminal_index], data finally: cdatrie.trie_state_free(state) def longest_prefix_value(self, unicode key, default=RAISE_KEY_ERROR): """ Returns the value associated with the longest key in this trie that is a prefix of ``key``. If the trie doesn't contain any prefix of ``key``: - if ``default`` is given, return it - otherwise raise ``KeyError`` """ return self._longest_prefix_value(key, default) cdef _longest_prefix_value(self, unicode key, default=RAISE_KEY_ERROR): cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() cdef int data = 0 cdef char found = 0 try: for ch in key: if not cdatrie.trie_state_walk(state, ch): break if cdatrie.trie_state_is_terminal(state): found = 1 data = cdatrie.trie_state_get_data(state) if not found: if default is RAISE_KEY_ERROR: raise KeyError(key) return default return data finally: cdatrie.trie_state_free(state) def has_keys_with_prefix(self, unicode prefix): """ Returns True if any key in the trie begins with ``prefix``. """ cdef cdatrie.TrieState* state = cdatrie.trie_root(self._c_trie) if state == NULL: raise MemoryError() try: for char in prefix: if not cdatrie.trie_state_walk(state, char): return False return True finally: cdatrie.trie_state_free(state) cpdef items(self, unicode prefix=None): """ Returns a list of this trie's items (``(key,value)`` tuples). If ``prefix`` is not None, returns only the items associated with keys prefixed by ``prefix``. """ cdef bint success cdef list res = [] cdef BaseState state = BaseState(self) if prefix is not None: success = state.walk(prefix) if not success: return res cdef BaseIterator iter = BaseIterator(state) if prefix is None: while iter.next(): res.append((iter.key(), iter.data())) else: while iter.next(): res.append((prefix+iter.key(), iter.data())) return res def __iter__(self): cdef BaseIterator iter = BaseIterator(BaseState(self)) while iter.next(): yield iter.key() cpdef keys(self, unicode prefix=None): """ Returns a list of this trie's keys. If ``prefix`` is not None, returns only the keys prefixed by ``prefix``. """ cdef bint success cdef list res = [] cdef BaseState state = BaseState(self) if prefix is not None: success = state.walk(prefix) if not success: return res cdef BaseIterator iter = BaseIterator(state) if prefix is None: while iter.next(): res.append(iter.key()) else: while iter.next(): res.append(prefix+iter.key()) return res cpdef values(self, unicode prefix=None): """ Returns a list of this trie's values. If ``prefix`` is not None, returns only the values associated with keys prefixed by ``prefix``. """ cdef bint success cdef list res = [] cdef BaseState state = BaseState(self) if prefix is not None: success = state.walk(prefix) if not success: return res cdef BaseIterator iter = BaseIterator(state) while iter.next(): res.append(iter.data()) return res cdef _index_to_value(self, cdatrie.TrieData index): return index cdef class Trie(BaseTrie): """ Wrapper for libdatrie's trie. Keys are unicode strings, values are Python objects. """ cdef list _values def __init__(self, alphabet=None, ranges=None, AlphaMap alpha_map=None, _create=True): """ For efficiency trie needs to know what unicode symbols it should be able to store so this constructor requires either ``alphabet`` (a string/iterable with all allowed characters), ``ranges`` (a list of (begin, end) pairs, e.g. [('a', 'z')]) or ``alpha_map`` (:class:`datrie.AlphaMap` instance). """ self._values = [] super(Trie, self).__init__(alphabet, ranges, alpha_map, _create) def __reduce__(self): with tempfile.NamedTemporaryFile() as f: self.write(f) pickle.dump(self._values, f) f.seek(0) state = f.read() return Trie, (None, None, None, False), state def __setstate__(self, bytes state): assert self._c_trie is NULL with tempfile.NamedTemporaryFile() as f: f.write(state) f.flush() f.seek(0) self._c_trie = _load_from_file(f) self._values = pickle.load(f) def __getitem__(self, unicode key): cdef cdatrie.TrieData index = self._getitem(key) return self._values[index] def get(self, unicode key, default=None): cdef cdatrie.TrieData index try: index = self._getitem(key) return self._values[index] except KeyError: return default def __setitem__(self, unicode key, object value): cdef cdatrie.TrieData next_index = len(self._values) cdef cdatrie.TrieData index = self._setdefault(key, next_index) if index == next_index: self._values.append(value) # insert else: self._values[index] = value # update def setdefault(self, unicode key, object value): cdef cdatrie.TrieData next_index = len(self._values) cdef cdatrie.TrieData index = self._setdefault(key, next_index) if index == next_index: self._values.append(value) # insert return value else: return self._values[index] # lookup def __delitem__(self, unicode key): # XXX: this could be faster (key is encoded twice here) cdef cdatrie.TrieData index = self._getitem(key) self._values[index] = DELETED_OBJECT self._delitem(key) def write(self, f): """ Writes a trie to a file. File-like objects without real file descriptors are not supported. """ super(Trie, self).write(f) pickle.dump(self._values, f) @classmethod def read(cls, f): """ Creates a new Trie by reading it from file. File-like objects without real file descriptors are not supported. """ cdef Trie trie = super(Trie, cls).read(f) trie._values = pickle.load(f) return trie cpdef items(self, unicode prefix=None): """ Returns a list of this trie's items (``(key,value)`` tuples). If ``prefix`` is not None, returns only the items associated with keys prefixed by ``prefix``. """ # the following code is # # [(k, self._values[v]) for (k,v) in BaseTrie.items(self, prefix)] # # but inlined for speed. cdef bint success cdef list res = [] cdef BaseState state = BaseState(self) if prefix is not None: success = state.walk(prefix) if not success: return res cdef BaseIterator iter = BaseIterator(state) if prefix is None: while iter.next(): res.append((iter.key(), self._values[iter.data()])) else: while iter.next(): res.append((prefix+iter.key(), self._values[iter.data()])) return res cpdef values(self, unicode prefix=None): """ Returns a list of this trie's values. If ``prefix`` is not None, returns only the values associated with keys prefixed by ``prefix``. """ # the following code is # # [self._values[v] for v in BaseTrie.values(self, prefix)] # # but inlined for speed. cdef list res = [] cdef BaseState state = BaseState(self) cdef bint success if prefix is not None: success = state.walk(prefix) if not success: return res cdef BaseIterator iter = BaseIterator(state) while iter.next(): res.append(self._values[iter.data()]) return res def longest_prefix_item(self, unicode key, default=RAISE_KEY_ERROR): """ Returns the item (``(key,value)`` tuple) associated with the longest key in this trie that is a prefix of ``key``. If the trie doesn't contain any prefix of ``key``: - if ``default`` is given, returns it, - otherwise raises ``KeyError``. """ cdef res = self._longest_prefix_item(key, RERAISE_KEY_ERROR) if res is RERAISE_KEY_ERROR: # error if default is RAISE_KEY_ERROR: raise KeyError(key) return default return res[0], self._values[res[1]] def longest_prefix_value(self, unicode key, default=RAISE_KEY_ERROR): """ Returns the value associated with the longest key in this trie that is a prefix of ``key``. If the trie doesn't contain any prefix of ``key``: - if ``default`` is given, return it - otherwise raise ``KeyError`` """ cdef res = self._longest_prefix_value(key, RERAISE_KEY_ERROR) if res is RERAISE_KEY_ERROR: # error if default is RAISE_KEY_ERROR: raise KeyError(key) return default return self._values[res] def prefix_items(self, unicode key): ''' Returns a list of the items (``(key,value)`` tuples) of this trie that are associated with keys that are prefixes of ``key``. ''' return [(k, self._values[v]) for (k, v) in self._prefix_items(key)] def iter_prefix_items(self, unicode key): for k, v in super(Trie, self).iter_prefix_items(key): yield k, self._values[v] def prefix_values(self, unicode key): ''' Returns a list of the values of this trie that are associated with keys that are prefixes of ``key``. ''' return [self._values[v] for v in self._prefix_values(key)] def iter_prefix_values(self, unicode key): for v in super(Trie, self).iter_prefix_values(key): yield self._values[v] cdef _index_to_value(self, cdatrie.TrieData index): return self._values[index] cdef class _TrieState: cdef cdatrie.TrieState* _state cdef BaseTrie _trie def __cinit__(self, BaseTrie trie): self._state = cdatrie.trie_root(trie._c_trie) if self._state is NULL: raise MemoryError() self._trie = trie def __dealloc__(self): if self._state is not NULL: cdatrie.trie_state_free(self._state) cpdef walk(self, unicode to): cdef bint res for ch in to: if not self.walk_char( ch): return False return True cdef bint walk_char(self, cdatrie.AlphaChar char): """ Walks the trie stepwise, using a given character ``char``. On return, the state is updated to the new state if successfully walked. Returns boolean value indicating the success of the walk. """ return cdatrie.trie_state_walk(self._state, char) cpdef copy_to(self, _TrieState state): """ Copies trie state to another """ cdatrie.trie_state_copy(state._state, self._state) cpdef rewind(self): """ Puts the state at root """ cdatrie.trie_state_rewind(self._state) cpdef bint is_terminal(self): return cdatrie.trie_state_is_terminal(self._state) cpdef bint is_single(self): return cdatrie.trie_state_is_single(self._state) cpdef bint is_leaf(self): return cdatrie.trie_state_is_leaf(self._state) def __unicode__(self): return u"data:%d, term:%s, leaf:%s, single: %s" % ( self.data(), self.is_terminal(), self.is_leaf(), self.is_single(), ) def __repr__(self): return self.__unicode__() # XXX: this is incorrect under Python 2.x cdef class BaseState(_TrieState): """ cdatrie.TrieState wrapper. It can be used for custom trie traversal. """ cpdef int data(self): return cdatrie.trie_state_get_data(self._state) cdef class State(_TrieState): def __cinit__(self, Trie trie): # this is overriden for extra type check self._state = cdatrie.trie_root(trie._c_trie) if self._state is NULL: raise MemoryError() self._trie = trie cpdef data(self): cdef cdatrie.TrieData data = cdatrie.trie_state_get_data(self._state) return self._trie._index_to_value(data) cdef class _TrieIterator: cdef cdatrie.TrieIterator* _iter cdef _TrieState _root def __cinit__(self, _TrieState state): self._root = state # prevent garbage collection of state self._iter = cdatrie.trie_iterator_new(state._state) if self._iter is NULL: raise MemoryError() def __dealloc__(self): if self._iter is not NULL: cdatrie.trie_iterator_free(self._iter) cpdef bint next(self): return cdatrie.trie_iterator_next(self._iter) cpdef unicode key(self): cdef cdatrie.AlphaChar* key = cdatrie.trie_iterator_get_key(self._iter) try: return unicode_from_alpha_char(key) finally: free(key) cdef class BaseIterator(_TrieIterator): """ cdatrie.TrieIterator wrapper. It can be used for custom datrie.BaseTrie traversal. """ cpdef cdatrie.TrieData data(self): return cdatrie.trie_iterator_get_data(self._iter) cdef class Iterator(_TrieIterator): """ cdatrie.TrieIterator wrapper. It can be used for custom datrie.Trie traversal. """ def __cinit__(self, State state): # this is overriden for extra type check self._root = state # prevent garbage collection of state self._iter = cdatrie.trie_iterator_new(state._state) if self._iter is NULL: raise MemoryError() cpdef data(self): cdef cdatrie.TrieData data = cdatrie.trie_iterator_get_data(self._iter) return self._root._trie._index_to_value(data) cdef (cdatrie.Trie* ) _load_from_file(f) except NULL: cdef int fd = f.fileno() cdef stdio.FILE* f_ptr = stdio_ext.fdopen(fd, "r") if f_ptr == NULL: raise IOError() cdef cdatrie.Trie* trie = cdatrie.trie_fread(f_ptr) if trie == NULL: raise DatrieError("Can't load trie from stream") cdef int f_pos = stdio.ftell(f_ptr) f.seek(f_pos) return trie #cdef (cdatrie.Trie*) _load_from_file(path) except NULL: # str_path = path.encode(sys.getfilesystemencoding()) # cdef char* c_path = str_path # cdef cdatrie.Trie* trie = cdatrie.trie_new_from_file(c_path) # if trie is NULL: # raise DatrieError("Can't load trie from file") # # return trie # ============================ AlphaMap & utils ================================ cdef class AlphaMap: """ Alphabet map. For sparse data compactness, the trie alphabet set should be continuous, but that is usually not the case in general character sets. Therefore, a map between the input character and the low-level alphabet set for the trie is created in the middle. You will have to define your input character set by listing their continuous ranges of character codes creating a trie. Then, each character will be automatically assigned internal codes of continuous values. """ cdef cdatrie.AlphaMap *_c_alpha_map def __cinit__(self): self._c_alpha_map = cdatrie.alpha_map_new() def __dealloc__(self): if self._c_alpha_map is not NULL: cdatrie.alpha_map_free(self._c_alpha_map) def __init__(self, alphabet=None, ranges=None, _create=True): if not _create: return if ranges is not None: for range in ranges: self.add_range(*range) if alphabet is not None: self.add_alphabet(alphabet) cdef AlphaMap copy(self): cdef AlphaMap clone = AlphaMap(_create=False) clone._c_alpha_map = cdatrie.alpha_map_clone(self._c_alpha_map) if clone._c_alpha_map is NULL: raise MemoryError() return clone def add_alphabet(self, alphabet): """ Adds all chars from iterable to the alphabet set. """ for begin, end in alphabet_to_ranges(alphabet): self._add_range(begin, end) def add_range(self, begin, end): """ Add a range of character codes from ``begin`` to ``end`` to the alphabet set. ``begin`` - the first character of the range; ``end`` - the last character of the range. """ self._add_range(ord(begin), ord(end)) cpdef _add_range(self, cdatrie.AlphaChar begin, cdatrie.AlphaChar end): if begin > end: raise DatrieError('range begin > end') code = cdatrie.alpha_map_add_range(self._c_alpha_map, begin, end) if code != 0: raise MemoryError() cdef cdatrie.AlphaChar* new_alpha_char_from_unicode(unicode txt): """ Converts Python unicode string to libdatrie's AlphaChar* format. libdatrie wants null-terminated array of 4-byte LE symbols. The caller should free the result of this function. """ cdef int txt_len = len(txt) cdef int size = (txt_len + 1) * sizeof(cdatrie.AlphaChar) # allocate buffer cdef cdatrie.AlphaChar* data = malloc(size) if data is NULL: raise MemoryError() # Copy text contents to buffer. # XXX: is it safe? The safe alternative is to decode txt # to utf32_le and then use memcpy to copy the content: # # py_str = txt.encode('utf_32_le') # cdef char* c_str = py_str # string.memcpy(data, c_str, size-1) # # but the following is much (say 10x) faster and this # function is really in a hot spot. cdef int i = 0 for char in txt: data[i] = char i+=1 # Buffer must be null-terminated (last 4 bytes must be zero). data[txt_len] = 0 return data cdef unicode unicode_from_alpha_char(cdatrie.AlphaChar* key, int len=0): """ Converts libdatrie's AlphaChar* to Python unicode. """ cdef int length = len if length == 0: length = cdatrie.alpha_char_strlen(key)*sizeof(cdatrie.AlphaChar) cdef char* c_str = key return c_str[:length].decode('utf_32_le') def to_ranges(lst): """ Converts a list of numbers to a list of ranges:: >>> numbers = [1,2,3,5,6] >>> list(to_ranges(numbers)) [(1, 3), (5, 6)] """ for a, b in itertools.groupby(enumerate(lst), lambda t: t[1] - t[0]): b = list(b) yield b[0][1], b[-1][1] def alphabet_to_ranges(alphabet): for begin, end in to_ranges(sorted(map(ord, iter(alphabet)))): yield begin, end def new(alphabet=None, ranges=None, AlphaMap alpha_map=None): warnings.warn('datrie.new is deprecated; please use datrie.Trie.', DeprecationWarning) return Trie(alphabet, ranges, alpha_map) MutableMapping.register(Trie) MutableMapping.register(BaseTrie) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/src/stdio_ext.pxd0000644000175100017510000000014615054046003016556 0ustar00tcaswelltcaswellfrom libc cimport stdio cdef extern from "stdio.h" nogil: stdio.FILE *fdopen(int fd, char *mode) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5674653 datrie-0.8.3/tests/0000755000175100017510000000000015054046505014420 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/tests/__init__.py0000644000175100017510000000007615054046003016525 0ustar00tcaswelltcaswell# -*- coding: utf-8 -*- from __future__ import absolute_import././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/tests/test_iteration.py0000644000175100017510000000416615054046003020027 0ustar00tcaswelltcaswell# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import string import datrie WORDS = ['producers', 'pool', 'prepare', 'preview', 'prize', 'produce', 'producer', 'progress'] def _trie(): trie = datrie.Trie(ranges=[(chr(0), chr(127))]) for index, word in enumerate(WORDS, 1): trie[word] = index return trie def test_base_trie_data(): trie = datrie.BaseTrie(string.printable) trie['x'] = 1 trie['xo'] = 2 state = datrie.BaseState(trie) state.walk('x') it = datrie.BaseIterator(state) it.next() assert it.data() == 1 state.walk('o') it = datrie.BaseIterator(state) it.next() assert it.data() == 2 def test_next(): trie = _trie() state = datrie.State(trie) it = datrie.Iterator(state) values = [] while it.next(): values.append(it.data()) assert len(values) == 8 assert values == [2, 3, 4, 5, 6, 7, 1, 8] def test_next_non_root(): trie = _trie() state = datrie.State(trie) state.walk('pr') it = datrie.Iterator(state) values = [] while it.next(): values.append(it.data()) assert len(values) == 7 assert values == [3, 4, 5, 6, 7, 1, 8] def test_next_tail(): trie = _trie() state = datrie.State(trie) state.walk('poo') it = datrie.Iterator(state) values = [] while it.next(): values.append(it.data()) assert values == [2] def test_keys(): trie = _trie() state = datrie.State(trie) it = datrie.Iterator(state) keys = [] while it.next(): keys.append(it.key()) assert keys == sorted(WORDS) def test_keys_non_root(): trie = _trie() state = datrie.State(trie) state.walk('pro') it = datrie.Iterator(state) keys = [] while it.next(): keys.append(it.key()) assert keys == ['duce', 'ducer', 'ducers', 'gress'] def test_keys_tail(): trie = _trie() state = datrie.State(trie) state.walk('pro') it = datrie.Iterator(state) keys = [] while it.next(): keys.append(it.key()) assert keys == ['duce', 'ducer', 'ducers', 'gress'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/tests/test_random.py0000644000175100017510000000333515054046003017306 0ustar00tcaswelltcaswell# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import pickle import string import datrie import hypothesis.strategies as st from hypothesis import given printable_strings = st.lists(st.text(string.printable)) @given(printable_strings) def test_contains(words): trie = datrie.Trie(string.printable) for i, word in enumerate(set(words)): trie[word] = i + 1 for i, word in enumerate(set(words)): assert word in trie assert trie[word] == trie.get(word) == i + 1 @given(printable_strings) def test_len(words): trie = datrie.Trie(string.printable) for i, word in enumerate(set(words)): trie[word] = i assert len(trie) == len(set(words)) @given(printable_strings) def test_pickle_unpickle(words): trie = datrie.Trie(string.printable) for i, word in enumerate(set(words)): trie[word] = i trie = pickle.loads(pickle.dumps(trie)) for i, word in enumerate(set(words)): assert word in trie assert trie[word] == i @given(printable_strings) def test_pop(words): words = set(words) trie = datrie.Trie(string.printable) for i, word in enumerate(words): trie[word] = i for i, word in enumerate(words): assert trie.pop(word) == i assert trie.pop(word, 42) == trie.get(word, 42) == 42 @given(printable_strings) def test_clear(words): words = set(words) trie = datrie.Trie(string.printable) for i, word in enumerate(words): trie[word] = i assert len(trie) == len(words) trie.clear() assert not trie assert len(trie) == 0 # make sure the trie works afterwards. for i, word in enumerate(words): trie[word] = i assert trie[word] == i ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/tests/test_state.py0000644000175100017510000000077015054046003017146 0ustar00tcaswelltcaswell# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import datrie def _trie(): trie = datrie.Trie(ranges=[(chr(0), chr(127))]) trie['f'] = 1 trie['fo'] = 2 trie['fa'] = 3 trie['faur'] = 4 trie['fauxiiiip'] = 5 trie['fauzox'] = 10 trie['fauzoy'] = 20 return trie def test_trie_state(): trie = _trie() state = datrie.State(trie) state.walk('f') assert state.data() == 1 state.walk('o') assert state.data() == 2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/tests/test_trie.py0000644000175100017510000002753415054046003017000 0ustar00tcaswelltcaswell# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import pickle import random import string import sys import tempfile import datrie import pytest def test_trie(): trie = datrie.Trie(string.printable) assert trie.is_dirty() assert 'foo' not in trie assert 'Foo' not in trie trie['foo'] = '5' assert 'foo' in trie assert trie['foo'] == '5' trie['Foo'] = 10 assert trie['Foo'] == 10 assert trie['foo'] == '5' del trie['foo'] assert 'foo' not in trie assert 'Foo' in trie assert trie['Foo'] == 10 with pytest.raises(KeyError): trie['bar'] def test_trie_invalid_alphabet(): t = datrie.Trie('abc') t['a'] = 'a' t['b'] = 'b' t['c'] = 'c' for k in 'abc': assert t[k] == k with pytest.raises(KeyError): t['d'] with pytest.raises(KeyError): t['e'] def test_trie_save_load(): fd, fname = tempfile.mkstemp() trie = datrie.Trie(string.printable) trie['foobar'] = 1 trie['foovar'] = 2 trie['baz'] = 3 trie['fo'] = 4 trie['Foo'] = 'vasia' trie.save(fname) del trie trie2 = datrie.Trie.load(fname) assert trie2['foobar'] == 1 assert trie2['baz'] == 3 assert trie2['fo'] == 4 assert trie2['foovar'] == 2 assert trie2['Foo'] == 'vasia' def test_save_load_base(): fd, fname = tempfile.mkstemp() trie = datrie.BaseTrie(alphabet=string.printable) trie['foobar'] = 1 trie['foovar'] = 2 trie['baz'] = 3 trie['fo'] = 4 trie.save(fname) trie2 = datrie.BaseTrie.load(fname) assert trie2['foobar'] == 1 assert trie2['baz'] == 3 assert trie2['fo'] == 4 assert trie2['foovar'] == 2 def test_trie_file_io(): fd, fname = tempfile.mkstemp() trie = datrie.BaseTrie(string.printable) trie['foobar'] = 1 trie['foo'] = 2 extra_data = ['foo', 'bar'] with open(fname, "wb", 0) as f: pickle.dump(extra_data, f) trie.write(f) pickle.dump(extra_data, f) with open(fname, "rb", 0) as f: extra_data2 = pickle.load(f) trie2 = datrie.BaseTrie.read(f) extra_data3 = pickle.load(f) assert extra_data2 == extra_data assert extra_data3 == extra_data assert trie2['foobar'] == 1 assert trie2['foo'] == 2 assert len(trie2) == len(trie) def test_trie_unicode(): # trie for lowercase Russian characters trie = datrie.Trie(ranges=[('а', 'я')]) trie['а'] = 1 trie['б'] = 2 trie['аб'] = 'vasia' assert trie['а'] == 1 assert trie['б'] == 2 assert trie['аб'] == 'vasia' def test_trie_ascii(): trie = datrie.Trie(string.ascii_letters) trie['x'] = 1 trie['y'] = 'foo' trie['xx'] = 2 assert trie['x'] == 1 assert trie['y'] == 'foo' assert trie['xx'] == 2 def test_trie_items(): trie = datrie.Trie(string.ascii_lowercase) trie['foo'] = 10 trie['bar'] = 'foo' trie['foobar'] = 30 assert trie.values() == ['foo', 10, 30] assert trie.items() == [('bar', 'foo'), ('foo', 10), ('foobar', 30)] assert trie.keys() == ['bar', 'foo', 'foobar'] def test_trie_iter(): trie = datrie.Trie(string.ascii_lowercase) assert list(trie) == [] trie['foo'] = trie['bar'] = trie['foobar'] = 42 assert list(trie) == ['bar', 'foo', 'foobar'] def test_trie_comparison(): trie = datrie.Trie(string.ascii_lowercase) assert trie == trie assert trie == datrie.Trie(string.ascii_lowercase) other = datrie.Trie(string.ascii_lowercase) trie['foo'] = 42 other['foo'] = 24 assert trie != other other['foo'] = trie['foo'] assert trie == other other['bar'] = 42 assert trie != other with pytest.raises(TypeError): trie < other # same for other comparisons def test_trie_update(): trie = datrie.Trie(string.ascii_lowercase) trie.update([("foo", 42)]) assert trie["foo"] == 42 trie.update({"bar": 123}) assert trie["bar"] == 123 if sys.version_info[0] == 2: with pytest.raises(TypeError): trie.update(bar=24) else: trie.update(bar=24) assert trie["bar"] == 24 def test_trie_suffixes(): trie = datrie.Trie(string.ascii_lowercase) trie['pro'] = 1 trie['prof'] = 2 trie['product'] = 3 trie['production'] = 4 trie['producer'] = 5 trie['producers'] = 6 trie['productivity'] = 7 assert trie.suffixes('pro') == [ '', 'ducer', 'ducers', 'duct', 'duction', 'ductivity', 'f' ] def test_trie_len(): trie = datrie.Trie(string.ascii_lowercase) words = ['foo', 'f', 'faa', 'bar', 'foobar'] for word in words: trie[word] = None assert len(trie) == len(words) # Calling len on an empty trie caused segfault, see #17 on GitHub. trie = datrie.Trie(string.ascii_lowercase) assert len(trie) == 0 def test_setdefault(): trie = datrie.Trie(string.ascii_lowercase) assert trie.setdefault('foo', 5) == 5 assert trie.setdefault('foo', 4) == 5 assert trie.setdefault('foo', 5) == 5 assert trie.setdefault('bar', 'vasia') == 'vasia' assert trie.setdefault('bar', 3) == 'vasia' assert trie.setdefault('bar', 7) == 'vasia' class TestPrefixLookups(object): def _trie(self): trie = datrie.Trie(string.ascii_lowercase) trie['foo'] = 10 trie['bar'] = 20 trie['foobar'] = 30 trie['foovar'] = 40 trie['foobarzartic'] = None return trie def test_trie_keys_prefix(self): trie = self._trie() assert trie.keys('foobarz') == ['foobarzartic'] assert trie.keys('foobarzart') == ['foobarzartic'] assert trie.keys('foo') == ['foo', 'foobar', 'foobarzartic', 'foovar'] assert trie.keys('foobar') == ['foobar', 'foobarzartic'] assert trie.keys('') == [ 'bar', 'foo', 'foobar', 'foobarzartic', 'foovar' ] assert trie.keys('x') == [] def test_trie_items_prefix(self): trie = self._trie() assert trie.items('foobarz') == [('foobarzartic', None)] assert trie.items('foobarzart') == [('foobarzartic', None)] assert trie.items('foo') == [ ('foo', 10), ('foobar', 30), ('foobarzartic', None), ('foovar', 40) ] assert trie.items('foobar') == [('foobar', 30), ('foobarzartic', None)] assert trie.items('') == [ ('bar', 20), ('foo', 10), ('foobar', 30), ('foobarzartic', None), ('foovar', 40) ] assert trie.items('x') == [] def test_trie_values_prefix(self): trie = self._trie() assert trie.values('foobarz') == [None] assert trie.values('foobarzart') == [None] assert trie.values('foo') == [10, 30, None, 40] assert trie.values('foobar') == [30, None] assert trie.values('') == [20, 10, 30, None, 40] assert trie.values('x') == [] class TestPrefixSearch(object): WORDS = ['producers', 'producersz', 'pr', 'pool', 'prepare', 'preview', 'prize', 'produce', 'producer', 'progress'] def _trie(self): trie = datrie.Trie(string.ascii_lowercase) for index, word in enumerate(self.WORDS, 1): trie[word] = index return trie def test_trie_iter_prefixes(self): trie = self._trie() trie['pr'] = 'foo' prefixes = trie.iter_prefixes('producers') assert list(prefixes) == ['pr', 'produce', 'producer', 'producers'] no_prefixes = trie.iter_prefixes('vasia') assert list(no_prefixes) == [] values = trie.iter_prefix_values('producers') assert list(values) == ['foo', 8, 9, 1] no_prefixes = trie.iter_prefix_values('vasia') assert list(no_prefixes) == [] items = trie.iter_prefix_items('producers') assert next(items) == ('pr', 'foo') assert next(items) == ('produce', 8) assert next(items) == ('producer', 9) assert next(items) == ('producers', 1) no_prefixes = trie.iter_prefix_items('vasia') assert list(no_prefixes) == [] def test_trie_prefixes(self): trie = self._trie() prefixes = trie.prefixes('producers') assert prefixes == ['pr', 'produce', 'producer', 'producers'] values = trie.prefix_values('producers') assert values == [3, 8, 9, 1] items = trie.prefix_items('producers') assert items == [('pr', 3), ('produce', 8), ('producer', 9), ('producers', 1)] assert trie.prefixes('vasia') == [] assert trie.prefix_values('vasia') == [] assert trie.prefix_items('vasia') == [] def test_has_keys_with_prefix(self): trie = self._trie() for word in self.WORDS: assert trie.has_keys_with_prefix(word) assert trie.has_keys_with_prefix(word[:-1]) assert trie.has_keys_with_prefix('p') assert trie.has_keys_with_prefix('poo') assert trie.has_keys_with_prefix('pr') assert trie.has_keys_with_prefix('priz') assert not trie.has_keys_with_prefix('prizey') assert not trie.has_keys_with_prefix('ops') assert not trie.has_keys_with_prefix('progn') def test_longest_prefix(self): trie = self._trie() for word in self.WORDS: assert trie.longest_prefix(word) == word assert trie.longest_prefix('pooler') == 'pool' assert trie.longest_prefix('producers') == 'producers' assert trie.longest_prefix('progressor') == 'progress' assert trie.longest_prefix('paol', default=None) is None assert trie.longest_prefix('p', default=None) is None assert trie.longest_prefix('z', default=None) is None with pytest.raises(KeyError): trie.longest_prefix('z') def test_longest_prefix_bug(self): trie = self._trie() assert trie.longest_prefix("print") == "pr" assert trie.longest_prefix_value("print") == 3 assert trie.longest_prefix_item("print") == ("pr", 3) def test_longest_prefix_item(self): trie = self._trie() for index, word in enumerate(self.WORDS, 1): assert trie.longest_prefix_item(word) == (word, index) assert trie.longest_prefix_item('pooler') == ('pool', 4) assert trie.longest_prefix_item('producers') == ('producers', 1) assert trie.longest_prefix_item('progressor') == ('progress', 10) dummy = (None, None) assert trie.longest_prefix_item('paol', default=dummy) == dummy assert trie.longest_prefix_item('p', default=dummy) == dummy assert trie.longest_prefix_item('z', default=dummy) == dummy with pytest.raises(KeyError): trie.longest_prefix_item('z') def test_longest_prefix_value(self): trie = self._trie() for index, word in enumerate(self.WORDS, 1): assert trie.longest_prefix_value(word) == index assert trie.longest_prefix_value('pooler') == 4 assert trie.longest_prefix_value('producers') == 1 assert trie.longest_prefix_value('progressor') == 10 assert trie.longest_prefix_value('paol', default=None) is None assert trie.longest_prefix_value('p', default=None) is None assert trie.longest_prefix_value('z', default=None) is None with pytest.raises(KeyError): trie.longest_prefix_value('z') def test_trie_fuzzy(): russian = 'абвгдеёжзиклмнопрстуфхцчъыьэюя' alphabet = russian.upper() + string.ascii_lowercase words = list({ "".join(random.choice(alphabet) for x in range(random.randint(8, 16))) for y in range(1000) }) trie = datrie.Trie(alphabet) enumerated_words = list(enumerate(words)) for index, word in enumerated_words: trie[word] = index assert len(trie) == len(words) random.shuffle(enumerated_words) for index, word in enumerated_words: assert word in trie, word assert trie[word] == index, (word, index) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/tox-bench.ini0000644000175100017510000000013015054046003015631 0ustar00tcaswelltcaswell[tox] envlist = py27,py34,py35,py36,py37 [testenv] commands= python bench/speed.py ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/tox.ini0000644000175100017510000000017615054046003014566 0ustar00tcaswelltcaswell[tox] envlist = py27,py34,py35,py36,py37,py38 [testenv] deps = hypothesis pytest cython commands= py.test [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1756384580.5674987 datrie-0.8.3/travis/0000755000175100017510000000000015054046505014566 5ustar00tcaswelltcaswell././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/travis/build-wheels.sh0000755000175100017510000000076415054046003017511 0ustar00tcaswelltcaswell#!/bin/bash set -e -x # Compile wheels for PYBIN in /opt/python/*/bin; do "${PYBIN}/pip" install -r /io/dev-requirements.txt "${PYBIN}/pip" wheel /io/ -w wheelhouse/ done # Bundle external shared libraries into the wheels for whl in wheelhouse/*.whl; do auditwheel repair "$whl" --plat $PLAT -w /io/wheelhouse/ done # Install packages and test for PYBIN in /opt/python/*/bin/; do "${PYBIN}/pip" install datrie --no-index -f /io/wheelhouse "${PYBIN}/python" -m pytest /io/ done ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1756384259.0 datrie-0.8.3/update_c.sh0000755000175100017510000000010415054046003015365 0ustar00tcaswelltcaswell#!/bin/sh cython src/datrie.pyx src/cdatrie.pxd src/stdio_ext.pxd -a