diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b49feed --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.8) +project(utilities VERSION 1.0.0) + +find_package(cpp REQUIRED) +cpp_option(BUILD_TESTS ON) + +add_subdirectory(utilities) + +if(BUILD_TESTS) + cpp_find_or_build_dependency(NAME Catch2 URL github.com/catchorg/Catch2) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..f7a47e3 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,15 @@ +def buildModuleMatrix = [ + "GCC 7.1.0":("cmake gcc/7.1.0") + ] +node{ + def nwxJenkins + stage('Import Jenkins Commands'){ + sh """ + da_url=https://raw.githubusercontent.com/NWChemEx-Project/ + da_url+=DeveloperTools/master/ci/Jenkins/nwxJenkins.groovy + wget \${da_url} + """ + nwxJenkins=load("nwxJenkins.groovy") + } + nwxJenkins.commonSteps(buildModuleMatrix, "Utilities") +} diff --git a/LICENSE b/LICENSE index 261eeb9..f49a4e1 100644 --- a/LICENSE +++ b/LICENSE @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d099ff --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +**NOTE: This repository is part of the NWChemEx Project** + +Utilities Repository +====================== + +The C++ standard library as well as the STL are great for providing many of the +features one needs to write a code. However, they leave much to be desired. +This library is intended to be our "Boost" in that it's a series of +extensions to the standard libraries for functionality we commonly want. + +At the moment this functionality can be expressed as falling into one of three +sub libraries: + +- IterTools: Classes designed to make iteration easier. This includes base + classes for writing your own iterators as well as classes for + iterating over common situations such as: + - All permutations of a sequence + - All combinations of a sequence +- Mathematician : Math functionality beyond that of the STL including: + - Binomial coefficients + - Multinomial coefficients +- TypeTraits : Structs to make meta-template programming easier + - Extended type intraspection (*i.e.* determining if a type has a member) + +Building Utilities +-------------------- + +Utilities is built using [CPP](https://github.com/CMakePackagingProject/CMakePackagingProject.git), +hence the first step is to build and install CPP if you have not done so +already. Then building Utilities can be accomplished by: + +```bash +git clone https://github.com/NWChemEx-Project/Utilities.git +cd Utilities +cmake -H. -Bbuild -DCMAKE_PREFIX_PATH= \ + -DCMAKE_INSTALL_PREFIX= +cd build +cmake --build . +#May need to run as an admin depending on where you are installing +cmake --build . --target install +``` + +Note that the configure will appear to hang when it gets to Catch2. +This is because it is building Catch2. Building of Catch2 can be +avoided by disabling tests (*i.e.*, passing `-DBUILD_TESTS=OFF` to the first +invocation of `cmake`) or by providing CMake with an already built version of +Catch2 by passing `-DCatch2_ROOT=/path/to/catch2` to the first invocation of +`cmake`. diff --git a/bin/MakeForEach.py b/bin/MakeForEach.py new file mode 100644 index 0000000..e2bf4aa --- /dev/null +++ b/bin/MakeForEach.py @@ -0,0 +1,86 @@ +import os +file_name = os.path.join("..", "utilities", "macros", "for_each.hpp") +depth = 25 +max_args = 1 + +def print_n(f, start, end): + for itr in range(start, end): + f.write(" _{},".format(itr)) + +def print_fex_y(f, x, y): + f.write("#define _fe{}_{}(_call,".format(x, y)) + print_n(f, 0, x+1) + f.write(" ...)") + +with open(file_name,'w') as f: + f.write("#pragma once\n\n") + f.write("/** @file ForEach.hpp\n") + f.write(" *\n") + f.write(" * This header file is auto-generated by MakeForEach.py\n") + f.write(" * To allow more looping regenerate this file with a higher\n") + f.write(" * value of \"depth\".\n") + f.write(" *\n") + f.write(" * Includes C-Preprocessor macros for iterating over a list\n") + f.write(" * of arguments and applying a macro to them. The contents of\n") + f.write(" * this header are borrowed from StackOverflow.\n") + f.write(" * Of the various macros in here only the following are\n") + f.write(" * intended for use:\n") + f.write(" * - CALL_MACRO_X_FOR_EACH : Calls a macro for each argument \n") + f.write(" * passed to the list\n") + f.write(" * - CALL_MACRO_X_FOR_EACH1: Calls a macro with one bound\n") + f.write(" * argument for each argument in the list\n") + f.write(" */\n") + + f.write("/**\n") + f.write(" * @brief Returns the N-th argument of a list.\n") + f.write(" *\n") + f.write(" * By padding what's to the left and what's to the right of\n") + f.write(" * element N, this macro can be made to arbitrarily select an\n") + f.write(" * element from a list. To select say the third argument of a\n") + f.write(" * list you simply put M-3 placeholders in front of it where M\n") + f.write(" * is the maximum number written out here ({}).\n".format(depth)) + f.write(" */\n") + f.write("#define _GET_NTH_ARG(") + print_n(f, 1, depth+1) + f.write(" N, ...) N\n\n") + + for i in range(max_args+1): + f.write("/// Guts of the CALL_MACRO_X_FOR_EACH{} macro\n".format(i)) + f.write("///{\n") + print_fex_y(f, i, 0) + f.write("\n") + print_fex_y(f, i, 1) + f.write(" _call(") + print_n(f, 0, i) + f.write("_{})\n".format(i)) + for j in range(2, depth): + print_fex_y(f, i, j) + f.write(" _call(") + print_n(f, 0, i) + f.write("_{}) _fe{}_{}(_call, ".format(i, i, j-1)) + print_n(f, 0, i) + f.write(" __VA_ARGS__)\n") + f.write("///}\n\n") + + for i in range(max_args+1): + f.write("/**\n") + f.write(" * @brief Applies a macro to each argument in a list\n") + f.write(" *\n") + f.write(" * @param[in] x The macro to apply\n") + for j in range(i): + f.write(" * @param[in] _{} The {}-th argument to bind\n".format( + j, j)) + f.write(" * @param[in] ... The list of arguments to apply @p x to\n") + f.write(" */\n") + if i ==0: + f.write("#define CALL_MACRO_X_FOR_EACH(_call, ...)\ \n") + else: + f.write("#define CALL_MACRO_X_FOR_EACH{}(_call,".format(i)) + print_n(f, 0, i) + f.write("...)\ \n") + f.write(" _GET_NTH_ARG(\"ignored\", __VA_ARGS__,") + for j in range(depth-1, 1, -1): + f.write(" _fe{}_{},".format(i, j)) + f.write(" _fe{}_0)(_call,".format(i)) + print_n(f, 0, i) + f.write(" __VA_ARGS__)\n") diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..8098011 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = Utilities +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c3788f0 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,22 @@ +Building the documentation +========================== + +Building is done by running: + +``` +python3 build_docs.py +``` + +in the `SDE/docs` directory. If this runs successfully you will get two +additional directories: `build` and `doxyoutput`. The actual documentation can +be viewed by opening `docs/build/html/index.html` in a web browser. + +If the build did not run successfully check that you have installed the +following Python packages (all available in `pip`): + +- sphinx_rtd_theme (The Read-The-Docs theme for sphinx) +- sphinx (The thing that makes the documenation) +- breathe (Dependency of exhale, may get installed by it) +- exhale (The thing that turns Doxygen output into ReST) + +You also will need Doxygen. diff --git a/docs/source/IntroToCombinatorics.md b/docs/source/IntroToCombinatorics.md new file mode 100644 index 0000000..5e553f7 --- /dev/null +++ b/docs/source/IntroToCombinatorics.md @@ -0,0 +1,217 @@ +Introduction To Combinatorics +============================= + +The purpose of this page is to gather some notes and musings regarding +combinatorics that hopefully helps other people out as they peruse the functions +and classes in UtililitesEx. This page also contains details pertaining to the +algorithms behind our implementations. + +Contents +-------- + +- [Combinations From Permutations](#combinations-from-permutations) +- [Indexing Permutations Without Repetition](#indexing-permutations-without-repetition) +- [Indexing Permutations With Repetition](#indexing-permutations-with-repetition) + +Combinations From Permutations +------------------------------ + +The STL provides routines `prev_permutation` and `next_permutation` that can +generate each unique permutation of an \f$n\f$ element sequence, \f$S\f$ with a +complexity \f$\mathcal{O}(n)\f$ (which is the same complexity as doing it +with \f$n\f$ nested loops). It does not however provide the corresponding +generators for combinations. Any combination, \f$C\f$, of \f$S\f$ can be +represented by a sequence \f$P\equiv\left[ x_1,x_2,\ldots,x_N\right]\f$ where +\f$x_i=1\f$ (true) if the \f$i\f$-th element of \f$S\f$ is in \f$C\f$ and +\f$x_i=0\f$ (false) otherwise. Hence, we can generate all possible +combinations of \f$n\f$ objects, taken \f$k\f$ at a time, by iterating over all +unique permutations of \f$k\f$ 1s and \f$n-k\f$ 0s. The resulting algorithm +will then scale the same as the STL permutation routines. + +1. Fill a vector, \f$P\f$ with \f$k\f$ 1s followed by \f$n-k\f$ 0s. +2. First combination is the first \f$k\f$ elements + - Time: 0 (it's the input) +3. Compute next permutation of \f$P\f$ + - Time: \f$\mathcal{O}(n)\f$ +4. Fill combination container based off \f$P\f$ + - Time: \f$\mathcal{O}(n)\f$ +5. Repeat 3 and 4, \f${n\choose k}-2\f$ more times + - Time: \f$\mathcal{O}(n^k)\f$ + +Now let us consider combinations with repetition. To that end, assume we want +all combinations with repetitions of an \f$n\f$ element sequence \f$S\f$, +such that we take \f$k\f$ elements at a time. As with our analysis of +combinations without repeats, for a given combination with repetition, \f$C\f$, +we can again define an \f$n\f$ element sequence \f$P\equiv\left[ x_1,x_2, +\ldots,x_N\right]\f$ where \f$x_i\f$ is now the number of times the +\f$i\f$-th element of \f$S\f$ appears in \f$C\f$ (technically a +generalization of above). Of course this again implies the sum of the +\f$x_i\f$s is equal to \f$k\f$; however, now they are not restricted to 0s +and 1s. Hence its not enough to take all permutations of \f$P\f$, like it +was before. This is easily seen by setting \f$x_i=k\f$ and taking all +permutations. The resulting set is the \f$N\f$ combinations that can be +formed by repeating the same element \f$k\f$ times. This neglects say the +combination where the first element appears \f$k-1\f$ times and the second +element appears \f$k\f$ times. In other words, we need to "unclump" the +\f$x_i\f$s and then take permutations. To this end we can instead think of +\f$P\f$ as being \f$x_1\f$ 1s, a separator, \f$x_2\f$ 1s, a separator, ..., +and then \f$x_n\f$ 1s. Accounting for the separators this gives us a +permutation of \f$k\f$ 1s and \f$n-1\f$ separators. The set of all possible +unique permutations of \f$k\f$ 1s and \f$n-1\f$ separators (say 0s) then can be +used to generate the set of combinations with repetition. + +1. Fill a vector, \f$P\f$ with \f$k\f$ 1s and \f$n-1\f$ 0s + - Time \f$\mathcal{O}(n+k)\f$ +2. First combination is first element \f$k\f$ times + - Time \f$\mathcal{O}(k)\f$ +3. Next permutation of \f$P\f$ + - Time \f$\mathcal{O}(n+k)\f$ +4. Count 1s and form combinations + - Time \f$\mathcal{O}(n+k)\f$ +5. Repeat 3 and 4, \f${n+k-1\choose k}-2\f$ more times + - Time: \f$\mathcal{O}(n^k+k^k)\f$ + +Indexing Permutations Without Repetition +---------------------------------------- + +Given a sequence of length \f$N\f$ there are \f$N!\f$ permutations. Given a +means of ordering the permutations (here assumed to be lexicographical order), +the goal is to be able to generate the \f$i^\text{th}\f$ permutation on +demand (*i.e.* without permuting \f$i\f$-1 times). To this end let us denote +the elements of our input sequence as the numbers 0 to \f$N\f$-1. We can +immediately write down all permutations: +``` +0,1,2,3,...,(N-2),(N-1) +0,1,2,3,...,(N-1),(N-2) +... +(N-1),(N-2),...,3,2,1,0 +``` +Remember these are sequences and not numbers. We need a unique means of mapping +each of these sequences to a single unique number. It should be clear that +simply removing the commas is not sufficient (for example the sequences +0,1,2,3,...11,12,13,... and 0,12,3,...11,1,2,13... would both be identical). +Progress can be made by making the \f$i^\text{th}\f$ digit representative of +which element was chosen. To this end we aim to devise a number system in which +the \f$i^\text{th}\f$ digit from the left tells us which element was the +\f$i^\text{th}\f$ chosen element of the permutation. Mapping of a number in +this number system to decimal can then be accomplished by the usual +conversions (the value in decimal, \f$D\f$, is given by +\f[ +D=\sum_{i=0}^N x_ib_i, +\f] +where \f$x_i\f$ is the \f$i^\text{th}\f$ digit and \f$b_i\f$ is the value of the +\f$i^\text{th}\f$ place). + +To establish our number system we start from the left. There are \f$N\f$ +choices for the value of the zeroth digit. Hence we can encode its value as +a single number in base \f$N\f$. Once we've picked the zeroth digit we have +\f$N\f$-1 choices for the first digit and can thus encode its value in base +\f$N\f$-1 (*N.B.* this means value of the \f$i^\text{th}\f$ digit is always +determined with respect to elements remaining in the sequence and that the +elements are renumbered after each choice, but not reordered). This leads to +the general observation that the \f$i^\text{th}\f$ digit of the number can +be expressed in base \f$N-i\f$. Now counting from the right this means there +is only 1 choice for the zeroth digit (0), two choices for first digit (0, 1), +three choices for the second digit (0, 1, 2) and so on. Consequentially, +counting in our number system we get: +``` +0000 +0010 +0100 +0110 +0200 +0210 +1000 +1010 +1100 +1110 +1200 +1210 +2000 +... +``` +What we see is that in order to increment the \f$i^\text{th}\f$ digit requires +\f$i!\f$ increments. Hence the \f$i^\text{th}\f$ digit is in the \f$i!\f$ +place (by analogy to the ones, tens, hundreds, *etc.* places). This is the +definition of the factorial number system (FNS). + +The FNS makes it straightforward to index permutations of length \f$N\f$ when +all values in the sequence are unique. One converts the permutation to its FNS +value, then maps that value to decimal. To convert to the FNS one takes the +decimal number and divides off 1; the remainder is the zeroth digit (from +the right). Next, divide 2 off the integer quotient of the previous division; +the remainder is the first digit. Then, divide 3 off the integer quotient +of the previous division, the remainder is the second digit and so on and so +forth. Hence the \f$i^\text{th}\f$ digit is the remainder obtained by dividing +the integer quotient of the (\f$i\f$-1)\f$^\text{th}\f$ step by \f$i\f$+1. + +Indexing Permutations With Repetition +------------------------------------- + +The internet is all too quick to tell you how to index permutations without +repeats, but information on how to do it when there are repeats is not so +forthcoming. This section devises such a scheme. As with indexing permutations +with all unique elements we again assume we wish to generate the permutations in +lexicographical order. + +Given a sequence of length \f$N\f$, where there are \f$M\f$ unique elements, +the \f$i^\text{th}\f$ one appearing \f$m_i\f$ times, there are +\f[ +{{N} \choose {m_0, m_1, \ldots, m_M}} = \frac{N!}{\prod_{i=1}^Mm_i!} +\f] +unique permutations. Again our purpose is to devise a number system in which +the \f$i^\text{th}\f$ digit tells us which of the \f$N\f$ elements was the +\f$i^\text{th}\f$ element chosen from the sequence. + +The procedure for establishing the value of the number in this number system is +the same as for the case where we had no repeats aside from the fact that we, by +convention, always pull the first occurrence of a repeated element from the +sequence. The result is a number in the FNS; however, not all values less than +\f$N!\f$ will appear when iterating over permutations (when multiple values +in the FNS map to the same permutation we only obtain the smallest value). What +does differ from the case with no repeats is the value of the places, more over +the values of the places depends on the permutation. + +To derive the values of the places let us assume our permutation is +\f$\lbrace P\rbrace\f$. This sequence is of length \f$N\f$, with the +\f$i^\text{th}\f$ element being denoted \f$P_i\f$. Starting from \f$P_0\f$ we +can assume that by analogy with the no-repeat case it would have taken a +number of permutations equal to the number of permutations of the sequence +\f$\lbrace P_1,\ldots, P_N\rbrace\f$ to increment this place; however, if +\f$P_0\f$ appears more than once in \f$\lbrace P\rbrace\f$ we actually will get +here quicker. This is perhaps clearer with an +example. Consider the permutation \f$\lbrace 3,4,4\rbrace\f$, there are three +unique permutations \f$\lbrace 3,4,4\rbrace\f$, \f$\lbrace 4,3,4 \rbrace\f$, and +\f$\lbrace 4,4,3\rbrace\f$. Respectively these map to \f$000_!\f$, \f$100_!\f$, +and \f$110_!\f$ (the subscript "!" reminds us these are values in the FNS). If +we look at the second two values we conclude that the first digit will change +after \f$\frac{2!}{1!1!}=2\f$ iterations. However, if instead we consider the +first value we (correctly) conclude that it will change after +\f$\frac{2!}{2!}=1\f$ iterations. So where'd we go wrong in deducing the place +value of the first digit in the second two numbers? The \f$2!\f$ ultimately +assumed we'd also have to iterate over the value \f$\lbrace 0,1,0\rbrace\f$; +however mapping this to a permutation shows that this is just +\f$\lbrace 3,4,4\rbrace\f$, a permutation we've already counted. Ultimately, +what we really want is the number of permutations of length \f$N\f$ that start +with \f$P_0\f$, which is just: +\f[ +\frac{{{N} \choose {m_0, m_1, \ldots, m_M}}}{N}= +\frac{(N-1)!}{\prod_{i=1}^M m_i!}. +\f] +] + +The final trick to this scenario is going from decimal back to FNS. For this +we'll need the original sequence in lexicographical order, we'll call it +\f$\lbrace S\rbrace\f$, let us assume that the number in decimal we are +attempting to convert from is given by \f$D\f$. Furthermore let the number of +permutations that start with \f$S_i\f$ be \f$P(S_i)\f$. Then we can compute the +FNS value by: + +1. Set \f$P=0\f$ +2. Loop over \f$S_i\in \lbrace S\rbrace\f$ + 3. Compute \f$P(S_i)\f$ + 4. Set \f$P+=P(S_i)\f$ + 5. \f$D \stackrel{?}{\lt} P\f$ + - Yes: The current digit is \f$i\f$ + - Remove \f$S_i\f$ from \f$S\f$ and return to step 1 + - No: next iteration diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..f427efc --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +import os + + +# -- Project information ----------------------------------------------------- + +project = u'Utilities' +copyright = u'2019, NWChemEx Team' +author = u'NWChemEx Team' +src_dir = u'utilities' + +# The short X.Y version +version = u'1.0.0' +# The full version, including alpha/beta/rc tags +release = u'1.0.0alpha' + +############################################################################## +# Shouldn't need to change anything below this point # +############################################################################## + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.githubpages', + 'breathe', + 'exhale' +] +dir_path = os.path.dirname(os.path.realpath(__file__)) +doc_path = os.path.dirname(dir_path) +root_path = os.path.dirname(doc_path) + +breathe_default_project = project +breathe_projects = { project : os.path.join(doc_path, "doxyoutput", "xml")} + +exhale_args = { + "containmentFolder" : "./api", + "rootFileName" : "library_root.rst", + "rootFileTitle" : "Library API", + "doxygenStripFromPath" : "..", + "createTreeView" : True, + "exhaleExecutesDoxygen" : True, + "exhaleDoxygenStdin" : "INPUT = " + os.path.join(root_path, project, + "detail_") +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = [] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = project + 'doc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, project + '.tex',project + ' Documentation', author, 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, project.lower(), project + ' Documentation', [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, project, project + ' Documentation', + author, project, 'One line description of project.', 'Miscellaneous'), +] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..6242b15 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,9 @@ +Welcome to Utilities' documentation! +==================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + api/library_root + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..f3d7dde --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,26 @@ +set(sources test_main.cpp + cartesian_product.cpp + case_insensitive_map.cpp + combinations.cpp + combinatorics.cpp + enumerate.cpp + integer_utils.cpp + iterator_types.cpp + permutations.cpp + range.cpp + range_container.cpp + math_set.cpp + smart_enum.cpp + static_string.cpp + timer.cpp + tuple_utilities.cpp + type_traits_extensions.cpp + zip.cpp +) + +cpp_add_executable( + test_utilities + SOURCES ${sources} + DEPENDS utilities Catch2::Catch2 +) +add_test(NAME test_utilities COMMAND test_utilities) diff --git a/tests/cartesian_product.cpp b/tests/cartesian_product.cpp new file mode 100644 index 0000000..42785fb --- /dev/null +++ b/tests/cartesian_product.cpp @@ -0,0 +1,51 @@ +#include +#include +#include + +using namespace utilities; + +template +void check_state(CP_t& c, vector_type&& corr) { + REQUIRE(c.size() == corr.size()); + if(c.size()) + REQUIRE(c.begin() != c.end()); + else + REQUIRE(c.begin() == c.end()); + for(auto x : Enumerate(c)) { + auto i = std::get<0>(x); + auto val = std::get<1>(x); + REQUIRE(corr[i] == val); + } +} + +TEST_CASE("CartesianProduct") { + SECTION("Empty") { + auto c = CartesianProduct(); + check_state(c, std::vector>{}); + } + + SECTION("Single container") { + std::vector l{1, 2, 3}; + std::vector> corr{{1}, {2}, {3}}; + auto c = CartesianProduct(l); + check_state(c, corr); + } + + SECTION("Two containers") { + std::vector l{1, 2, 3}; + std::vector> corr{{1, 1}, {1, 2}, {1, 3}, + {2, 1}, {2, 2}, {2, 3}, + {3, 1}, {3, 2}, {3, 3}}; + auto c = CartesianProduct(l, l); + check_state(c, corr); + } + + SECTION("Two different containers") { + std::vector l{1, 2, 3}; + std::vector l2{1, 3}; + std::vector> corr{{1, 1}, {1, 3}, {2, 1}, + {2, 3}, {3, 1}, {3, 3}}; + auto c = CartesianProduct(l, l2); + check_state(c, corr); + } +} diff --git a/tests/case_insensitive_map.cpp b/tests/case_insensitive_map.cpp new file mode 100644 index 0000000..1a06613 --- /dev/null +++ b/tests/case_insensitive_map.cpp @@ -0,0 +1,94 @@ +#include +#include + +using namespace utilities; + +// Note that the actual CaseInsensitiveMap is just a typedef so this test +// focuses on the less than comparer which actually bestows the case +// insensitive property. + +TEST_CASE("CaseInsensitiveLess_::LetterComparer_") { + detail_::CaseInsensitiveLess_::LetterComparer_ fxn; + + SECTION("Both lowercase") { + REQUIRE(fxn('a', 'b')); + REQUIRE(!fxn('b', 'a')); + REQUIRE(!fxn('a', 'a')); + } + + SECTION("Left uppercase, right lower") { + REQUIRE(fxn('A', 'b')); + REQUIRE(!fxn('B', 'a')); + REQUIRE(!fxn('A', 'a')); + } + + SECTION("Left lowercase, right upper") { + REQUIRE(fxn('a', 'B')); + REQUIRE(!fxn('b', 'A')); + REQUIRE(!fxn('a', 'A')); + } + + SECTION("Both uppercase") { + REQUIRE(fxn('A', 'B')); + REQUIRE(!fxn('B', 'A')); + REQUIRE(!fxn('A', 'A')); + } +} + +TEST_CASE("CaseInsensitiveLess_") { + detail_::CaseInsensitiveLess_ fxn; + SECTION("Both lowercase") { + REQUIRE(fxn("abcde", "bcdef")); + REQUIRE(!fxn("bcdef", "abcde")); + REQUIRE(!fxn("abcde", "abcde")); + } + + SECTION("Left uppercase, right lower") { + REQUIRE(fxn("ABCDE", "bcdef")); + REQUIRE(!fxn("BCDEF", "abcde")); + REQUIRE(!fxn("ABCDE", "abcde")); + } + + SECTION("Left lowercase, right upper") { + REQUIRE(fxn("abcde", "BCDEF")); + REQUIRE(!fxn("bcdef", "ABCDE")); + REQUIRE(!fxn("abcde", "ABCDE")); + } + + SECTION("Both uppercase") { + REQUIRE(fxn("ABCDE", "BCDEF")); + REQUIRE(!fxn("BCDEF", "ABCDE")); + REQUIRE(!fxn("ABCDE", "ABCDE")); + } + + SECTION("Mixed case") { + REQUIRE(fxn("aB", "bC")); + REQUIRE(fxn("aB", "Bc")); + REQUIRE(fxn("Ab", "bC")); + REQUIRE(fxn("Ab", "Cb")); + } +} + +TEST_CASE("CaseInsensitiveMap") { + CaseInsensitiveMap a_map; + + SECTION("Before add") { + REQUIRE(!a_map.count("abc")); + REQUIRE(!a_map.count("Abc")); + REQUIRE(!a_map.count("ABc")); + REQUIRE(!a_map.count("AbC")); + REQUIRE(!a_map.count("aBC")); + REQUIRE(!a_map.count("ABC")); + } + + a_map["abc"] = 2; + + SECTION("After add") { + REQUIRE(a_map.count("abc")); + REQUIRE(a_map.count("Abc")); + REQUIRE(a_map.count("ABc")); + REQUIRE(a_map.count("AbC")); + REQUIRE(a_map.count("aBC")); + REQUIRE(a_map.count("ABC")); + } +} diff --git a/tests/combinations.cpp b/tests/combinations.cpp new file mode 100644 index 0000000..289b5ba --- /dev/null +++ b/tests/combinations.cpp @@ -0,0 +1,130 @@ +#include +#include + +using namespace utilities; +using vector_t = std::vector; + +template +using comb_itr = detail_::CombinationItr; + +std::vector> corr_combs{ + {{}}, // k=0 + {{1}, {2}, {3}}, // k=1 + {{1, 2}, {1, 3}, {2, 3}}, // k=2 + {{1, 2, 3}} // k=3 +}; + +std::vector> corr_combs_wr{ + {{}}, // k=0 + {{1}, {2}}, // k=1 + {{1, 1}, {1, 2}, {2, 2}}, // k=2 + {{1, 1, 1}, {1, 1, 2}, {1, 2, 2}, {2, 2, 2}}, // k=3 + {{1, 1, 1, 1}, {1, 1, 1, 2}, {1, 1, 2, 2}, {1, 2, 2, 2}, {2, 2, 2, 2}} // k=4 +}; + +template +void check_state(comb_itr start, const comb_itr& end, + const vector_t& orig, const std::vector& corr) { + if(corr.size() != 0) + REQUIRE(start != end); + else + REQUIRE(start == end); + size_t counter = 0; + while(start != end) { + for(std::size_t i = 0; i < corr.size(); ++i) { + comb_itr tmp{orig, corr[i].size(), false}; + for(std::size_t j = 0; j < i; ++j) ++tmp; + const long dx = static_cast(i) - counter; + REQUIRE(start.distance_to(tmp) == dx); + comb_itr copy{start}; + REQUIRE(copy.advance(dx) == tmp); + } + REQUIRE(*start++ == corr[counter++]); + } +} +TEST_CASE("Combinations") { + SECTION("Default Ctor w/o Repeat") { + comb_itr c0; + check_state(c0, c0, {}, {}); + } + + SECTION("Default Ctor w Repeat") { + comb_itr c0; + check_state(c0, c0, {}, {}); + } + + SECTION("Combinations") { + std::vector s0{1, 2, 3}; + for(size_t k = 0; k < 4; ++k) { + SECTION("{1, 2, 3} choose " + std::to_string(k)) { + comb_itr c0(s0, k, false); + comb_itr c1(s0, k, true); + check_state(c0, c1, s0, corr_combs[k]); + } + } + } + SECTION("Ctors w/o Repeat") { + std::vector s0{1, 2, 3}; + comb_itr c0(s0, 2, false); + SECTION("Copy Ctor") { + comb_itr c2{c0}; + REQUIRE(c2 == c0); + } + SECTION("Copy assignment") { + comb_itr c2; + REQUIRE(c2 != c0); + c2 = c0; + REQUIRE(c2 == c0); + } + SECTION("Move Ctor") { + comb_itr c2{c0}; + comb_itr c3{std::move(c0)}; + REQUIRE(c3 == c2); + } + SECTION("Move assignment") { + comb_itr c2; + REQUIRE(c2 != c0); + comb_itr c3{c0}; + c2 = std::move(c3); + REQUIRE(c2 == c0); + } + } + + SECTION("Combinations With Repeat") { + std::vector s0{1, 2}; + for(size_t k = 0; k <= 4; ++k) { + SECTION("{1, 2} multichoose " + std::to_string(k)) { + comb_itr c0(s0, k, false); + comb_itr c1(s0, k, true); + check_state(c0, c1, s0, corr_combs_wr[k]); + } + } + } + + SECTION("Ctors w/Repeat") { + std::vector s0{1, 2}; + comb_itr c0(s0, 2, false); + SECTION("Copy Ctor") { + comb_itr c2{c0}; + REQUIRE(c2 == c0); + } + SECTION("Copy assignment") { + comb_itr c2; + REQUIRE(c2 != c0); + c2 = c0; + REQUIRE(c2 == c0); + } + SECTION("Move Ctor") { + comb_itr c2{c0}; + comb_itr c3{std::move(c0)}; + REQUIRE(c3 == c2); + } + SECTION("Move assignment") { + comb_itr c2; + REQUIRE(c2 != c0); + comb_itr c3{c0}; + c2 = std::move(c3); + REQUIRE(c2 == c0); + } + } +} diff --git a/tests/combinatorics.cpp b/tests/combinatorics.cpp new file mode 100644 index 0000000..eb7db00 --- /dev/null +++ b/tests/combinatorics.cpp @@ -0,0 +1,189 @@ +#include +#include + +using namespace utilities; +using set_type = std::vector; +using fns_type = std::deque; + +TEST_CASE("Binomial coefficient") { + REQUIRE(binomial_coefficient(0, 0) == 1); + REQUIRE(binomial_coefficient(0, 1) == 0); + REQUIRE(binomial_coefficient(4, 0) == 1); + REQUIRE(binomial_coefficient(35, 4) == 52360); + REQUIRE_THROWS_AS(binomial_coefficient(99999, 88), + std::overflow_error); +} + +TEST_CASE("Multinomial coefficient") { + REQUIRE(multinomial_coefficient(set_type{}) == 1); + REQUIRE(multinomial_coefficient(set_type{5}) == 1); + REQUIRE(multinomial_coefficient(set_type{5, 6, 7}) == 14702688); + REQUIRE(multinomial_coefficient(set_type{4, 31}) == 52360); +} + +TEST_CASE("N Permutations") { + REQUIRE(n_permutations(set_type{}) == 1); + REQUIRE(n_permutations(set_type{0}) == 1); + REQUIRE(n_permutations(set_type{0, 1}) == 2); + REQUIRE(n_permutations(set_type{0, 0}) == 1); + REQUIRE(n_permutations(set_type{0, 1, 2}) == 6); + REQUIRE(n_permutations(set_type{0, 1, 1}) == 3); + REQUIRE(n_permutations(set_type{0, 1, 2, 3}) == 24); + REQUIRE(n_permutations(set_type{0, 1, 1, 2}) == 12); + REQUIRE(n_permutations(set_type{0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 2, 2}) == 14702688); +} + +TEST_CASE("Factorial Number System Free Functions") { + SECTION("Empty sequence {}") { + set_type orig; + fns_type fns; + REQUIRE(permutation_to_fns(orig, orig) == fns); + REQUIRE(fns_to_permutation(fns, orig) == orig); + REQUIRE(fns_place_values(orig) == fns); + REQUIRE(decimal_to_fns(0, orig) == fns); + REQUIRE(decimal_to_permutation(0, orig) == orig); + REQUIRE(permutation_to_decimal(orig, orig) == 0); + } + + SECTION("{1}") { + set_type orig({1}); + fns_type fns({0}); + fns_type values({1}); + REQUIRE(permutation_to_fns(orig, orig) == fns); + REQUIRE(fns_to_permutation(fns, orig) == orig); + REQUIRE(fns_place_values(orig) == values); + REQUIRE(decimal_to_fns(0, orig) == fns); + REQUIRE(decimal_to_permutation(0, orig) == orig); + REQUIRE(permutation_to_decimal(orig, orig) == 0); + } + + SECTION("Sequence {1,2,3} (no repeats)") { + std::vector perms( + {{1, 2, 3}, {1, 3, 2}, {2, 1, 3}, {2, 3, 1}, {3, 1, 2}, {3, 2, 1}}); + std::vector fns_s( + {{0, 0, 0}, {0, 1, 0}, {1, 0, 0}, {1, 1, 0}, {2, 0, 0}, {2, 1, 0}}); + + SECTION("Permutation 2 FNS") { + for(size_t i = 0; i < 6; ++i) { + auto corr = fns_s[i]; + auto comp = permutation_to_fns(perms[i], perms[0]); + REQUIRE(corr == comp); + } + } + + SECTION("FNS 2 Permutation") { + for(size_t i = 0; i < 6; ++i) { + auto corr = perms[i]; + auto comp = fns_to_permutation(fns_s[i], perms[0]); + REQUIRE(corr == comp); + } + } + + SECTION("FNS place values") { + fns_type corr({2, 1, 1}); + for(size_t i = 0; i < 6; ++i) { + auto comp = fns_place_values(perms[i]); + REQUIRE(corr == comp); + } + } + + SECTION("Decimal to FNS") { + for(size_t i = 0; i < 6; ++i) { + auto corr = fns_s[i]; + auto comp = decimal_to_fns(i, perms[0]); + REQUIRE(corr == comp); + } + } + + SECTION("Permutation to Decimal") { + for(size_t i = 0; i < 6; ++i) { + auto comp = permutation_to_decimal(perms[i], perms[0]); + REQUIRE(comp == i); + } + } + SECTION("Decimal to Permutation") { + for(size_t i = 0; i < 6; ++i) { + auto comp = decimal_to_permutation(i, perms[0]); + REQUIRE(comp == perms[i]); + } + } + } + SECTION("Permuation {1,3,3,7} (with repeats)") { + std::vector perms({{1, 3, 3, 7}, + {1, 3, 7, 3}, + {1, 7, 3, 3}, + {3, 1, 3, 7}, + {3, 1, 7, 3}, + {3, 3, 1, 7}, + {3, 3, 7, 1}, + {3, 7, 1, 3}, + {3, 7, 3, 1}, + {7, 1, 3, 3}, + {7, 3, 1, 3}, + {7, 3, 3, 1}}); + + std::vector fns_s({{0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 2, 0, 0}, + {1, 0, 0, 0}, + {1, 0, 1, 0}, + {1, 1, 0, 0}, + {1, 1, 1, 0}, + {1, 2, 0, 0}, + {1, 2, 1, 0}, + {3, 0, 0, 0}, + {3, 1, 0, 0}, + {3, 1, 1, 0}}); + + SECTION("Permutation to FNS") { + for(size_t i = 0; i < 12; ++i) { + auto comp = permutation_to_fns(perms[i], perms[0]); + auto corr = fns_s[i]; + REQUIRE(comp == corr); + } + } + + SECTION("FNS place values") { + std::vector vals({{3, 1, 1, 1}, + {3, 1, 1, 1}, + {3, 1, 1, 1}, + {3, 2, 1, 1}, + {3, 2, 1, 1}, + {3, 2, 1, 1}, + {3, 2, 1, 1}, + {3, 2, 1, 1}, + {3, 2, 1, 1}, + {3, 1, 1, 1}, + {3, 1, 1, 1}, + {3, 1, 1, 1}}); + for(size_t i = 0; i < 12; ++i) { + auto comp = fns_place_values(perms[i]); + auto corr = vals[i]; + REQUIRE(comp == corr); + } + } + + SECTION("Decimal to FNS") { + for(size_t i = 0; i < 12; ++i) { + auto comp = decimal_to_fns(i, perms[0]); + auto corr = fns_s[i]; + REQUIRE(comp == corr); + } + } + SECTION("Decimal to permutation") { + for(size_t i = 0; i < 12; ++i) { + auto comp = decimal_to_permutation(i, perms[0]); + auto corr = perms[i]; + REQUIRE(comp == corr); + } + } + SECTION("Permutation to decimal") { + for(size_t i = 0; i < 12; ++i) { + auto comp = permutation_to_decimal(perms[i], perms[0]); + auto corr = i; + REQUIRE(comp == corr); + } + } + } +} diff --git a/tests/enumerate.cpp b/tests/enumerate.cpp new file mode 100644 index 0000000..6bdb13e --- /dev/null +++ b/tests/enumerate.cpp @@ -0,0 +1,15 @@ +#include +#include +#include + +using namespace utilities; + +TEST_CASE("Enumerate") { + std::vector test{1.1, 2.2, 3.3}; + std::size_t counter = 0; + for(auto x : Enumerate(test)) { + REQUIRE(std::get<0>(x) == counter); + REQUIRE(std::get<1>(x) == test[counter]); + ++counter; + } +} diff --git a/tests/integer_utils.cpp b/tests/integer_utils.cpp new file mode 100644 index 0000000..f8e0461 --- /dev/null +++ b/tests/integer_utils.cpp @@ -0,0 +1,39 @@ +#include +#include + +using namespace utilities; + +TEST_CASE("UnsignedSubtract") { + SECTION("Default types") { + std::size_t n1{3}; + std::size_t n2{2}; + long n1mn2{1}; + long n2mn1{-1}; + REQUIRE(UnsignedSubtract(n1, n2) == n1mn2); + REQUIRE(UnsignedSubtract(n2, n1) == n2mn1); + } + SECTION("Shorten return") { + std::size_t n1{3}; + std::size_t n2{2}; + int n1mn2{1}; + int n2mn1{-1}; + REQUIRE(UnsignedSubtract(n1, n2) == n1mn2); + REQUIRE(UnsignedSubtract(n2, n1) == n2mn1); + } + SECTION("Shorten input") { + unsigned n1{3}; + unsigned n2{2}; + long n1mn2{1}; + long n2mn1{-1}; + REQUIRE(UnsignedSubtract(n1, n2) == n1mn2); + REQUIRE(UnsignedSubtract(n2, n1) == n2mn1); + } + SECTION("Shorten both") { + unsigned n1{3}; + unsigned n2{2}; + int n1mn2{1}; + int n2mn1{-1}; + REQUIRE(UnsignedSubtract(n1, n2) == n1mn2); + REQUIRE(UnsignedSubtract(n2, n1) == n2mn1); + } +} diff --git a/tests/iterator_types.cpp b/tests/iterator_types.cpp new file mode 100644 index 0000000..b95669f --- /dev/null +++ b/tests/iterator_types.cpp @@ -0,0 +1,172 @@ +#include +#include +#include + +using namespace utilities; +using namespace detail_; + +struct Iterator : public InputIteratorBase { + int value_ = 0; + + Iterator& increment() { + ++value_; + return *this; + } + + const int& dereference() const { return value_; } + + bool are_equal(const Iterator& other) const noexcept { + return value_ == other.value_; + } +}; + +struct Iterator2 : public InputIteratorBase {}; + +TEST_CASE("InputIterator base class") { + Iterator itr; + SECTION("Satisfies iterator concept") { + bool is_itr = is_input_iterator::value; + REQUIRE(is_itr); + } + SECTION("Can be dereferenced in non-const state") { + int& value = *itr; + REQUIRE(value == 0); + REQUIRE(&value == &itr.value_); + } + SECTION("Can be dereferenced in a const state") { + const int& value = *const_cast(itr); // NOLINT + REQUIRE(value == 0); + REQUIRE(&value == &itr.value_); + } + SECTION("Can be prefix-incremented") { + auto& rv = ++itr; + REQUIRE(&rv == &itr); + REQUIRE(*rv == 1); + + SECTION("Equality works") { REQUIRE(rv == itr); } + } + SECTION("Can be post-incremented") { + auto rv = itr++; + REQUIRE(*rv == 0); + REQUIRE(&rv.value_ != &itr.value_); + REQUIRE(*itr == 1); + SECTION("Inequality works") { REQUIRE(rv != itr); } + } + SECTION("Const is obeyed") { + bool is_const = + std::is_same::value; + REQUIRE(is_const); + } +} + +struct BidirectionalIterator + : public BidirectionalIteratorBase { + int value_ = 0; + + BidirectionalIterator& increment() { + ++value_; + return *this; + } + + BidirectionalIterator& decrement() { + --value_; + return *this; + } + + const int& dereference() const { return value_; } + + bool are_equal(const BidirectionalIterator& other) const noexcept { + return value_ == other.value_; + } +}; + +TEST_CASE("BidirectionalIterator base class") { + BidirectionalIterator itr; + SECTION("Satisfies iterator concept") { + bool is_itr = is_bidirectional_iterator::value; + REQUIRE(is_itr); + } + SECTION("Can be prefix decremented") { + BidirectionalIterator& rv = --itr; + REQUIRE(*itr == -1); + REQUIRE(&rv == &itr); + } + SECTION("Can be postfix decremented") { + BidirectionalIterator rv = itr--; + REQUIRE(&rv.value_ != &itr.value_); + REQUIRE(*rv == 0); + REQUIRE(*itr == -1); + } +} + +struct RandomAccessIterator + : public RandomAccessIteratorBase { + int value_ = 0; + + RandomAccessIterator& increment() { + ++value_; + return *this; + } + + RandomAccessIterator& decrement() { + --value_; + return *this; + } + + const int& dereference() const { return value_; } + + bool are_equal(const RandomAccessIterator& other) const noexcept { + return value_ == other.value_; + } + + long int distance_to(const RandomAccessIterator& other) const noexcept { + return other.value_ - value_; + } + + RandomAccessIterator& advance(long int n) { + value_ += n; + return *this; + } +}; + +TEST_CASE("RandomAccessIterator base class") { + RandomAccessIterator itr; + SECTION("Satisfies iterator concept") { + bool is_itr = is_random_access_iterator::value; + REQUIRE(is_itr); + } + SECTION("Order comparisons work") { + RandomAccessIterator itr2; + RandomAccessIterator itr3; + itr3.value_ = 10; + REQUIRE(itr < itr3); + REQUIRE(itr <= itr2); + REQUIRE(itr3 > itr); + REQUIRE(itr2 >= itr); + } + SECTION("Advancing works") { + RandomAccessIterator& rv = (itr += 10); + REQUIRE(&rv == &itr); + REQUIRE(rv.value_ == 10); + } + SECTION("Advance and copy works") { + RandomAccessIterator rv = (itr + 10); + REQUIRE(itr.value_ == 0); + REQUIRE(rv.value_ == 10); + } + SECTION("Go backwards works") { + RandomAccessIterator& rv = (itr -= 10); + REQUIRE(&rv == &itr); + REQUIRE(itr.value_ == -10); + } + SECTION("Go backwards and copy works") { + RandomAccessIterator rv = (itr - 10); + REQUIRE(itr.value_ == 0); + REQUIRE(rv.value_ == -10); + } + SECTION("Random access works") { + int rv = itr[100]; + REQUIRE(itr.value_ == 0); + REQUIRE(rv == 100); + } +} diff --git a/tests/math_set.cpp b/tests/math_set.cpp new file mode 100644 index 0000000..3258825 --- /dev/null +++ b/tests/math_set.cpp @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include + +using namespace utilities; + +template +void check_state(MathSet& set, + std::array values) { + REQUIRE(set.size() == N); + REQUIRE(set.empty() == (N == 0)); + if(N) + REQUIRE(set.begin() != set.end()); + else + REQUIRE(set.begin() == set.end()); + for(auto& x : Zip(set, values)) { + REQUIRE(std::get<0>(x) == std::get<1>(x)); + } +} + +TEST_CASE("MathSet") { + SECTION("Is a container") { REQUIRE(is_container>::value); } + + SECTION("Empty and trivial sets") { + MathSet empty_set; + + SECTION("Default constructor") { check_state<0>(empty_set, {}); } + + MathSet set_11; + SECTION("Empty equality") { REQUIRE(set_11 == empty_set); } + + set_11.insert(1.1); + + SECTION("Adding an element") { check_state<1>(set_11, {1.1}); } + + SECTION("Comparisons empty and trivial") { + REQUIRE(set_11 != empty_set); + REQUIRE(empty_set < set_11); + REQUIRE(empty_set <= set_11); + REQUIRE(set_11 > empty_set); + REQUIRE(set_11 >= empty_set); + REQUIRE(!(empty_set > set_11)); + REQUIRE(!(empty_set >= set_11)); + REQUIRE(!(set_11 < empty_set)); + REQUIRE(!(set_11 <= empty_set)); + } + + SECTION("Copy constructor") { + MathSet set2_11(set_11); + check_state<1>(set2_11, {1.1}); + } + + SECTION("Assignment operator") { + empty_set = set_11; + check_state<1>(empty_set, {1.1}); + } + + SECTION("Move constructor") { + MathSet set2_11(std::move(set_11)); + check_state<1>(set2_11, {1.1}); + } + + SECTION("Move assignment") { + empty_set = std::move(set_11); + check_state<1>(empty_set, {1.1}); + } + } + + SECTION("Non-trivial sets") { + MathSet set1({1.1, 2.2, 3.3}); + MathSet set2({2.2, 4.4, 5.5, 6.6}); + + SECTION("Initializer list constructor") { + check_state<3>(set1, {1.1, 2.2, 3.3}); + check_state<4>(set2, {2.2, 4.4, 5.5, 6.6}); + } + + SECTION("Comparisons w/ empty_set") { + MathSet empty_set; + REQUIRE(empty_set != set1); + REQUIRE(empty_set < set1); + REQUIRE(empty_set <= set1); + REQUIRE(set1 > empty_set); + REQUIRE(set1 >= empty_set); + } + + SECTION("Union assignment") { + auto& pset1 = (set1.union_assign(set2)); + REQUIRE(&pset1 == &set1); + check_state<6>(set1, {1.1, 2.2, 3.3, 4.4, 5.5, 6.6}); + + SECTION("Non-trivial comparisons") { + REQUIRE(set2 < set1); + REQUIRE(set2 <= set1); + REQUIRE(set1 > set2); + REQUIRE(set1 >= set2); + REQUIRE(set2 != set1); + REQUIRE(set2 >= set2); + REQUIRE(set2 <= set2); + } + } + + SECTION("Take union") { + auto rv = set2.take_union(set1); + check_state<4>(set2, {2.2, 4.4, 5.5, 6.6}); + check_state<6>(rv, {1.1, 2.2, 3.3, 4.4, 5.5, 6.6}); + } + + SECTION("Intersection assign") { + auto& pset1 = (set1.intersection_assign(set2)); + REQUIRE(&pset1 == &set1); + check_state<1>(set1, {2.2}); + } + + SECTION("Intersection") { + auto rv = set1.intersection(set2); + check_state<3>(set1, {1.1, 2.2, 3.3}); + check_state<1>(rv, {2.2}); + } + + SECTION("Difference assign") { + auto& pset1 = (set1.difference_assign(set2)); + REQUIRE(&pset1 == &set1); + check_state<2>(set1, {1.1, 3.3}); + } + + SECTION("Difference") { + auto rv = set1.difference(set2); + check_state<3>(set1, {1.1, 2.2, 3.3}); + check_state<2>(rv, {1.1, 3.3}); + } + + SECTION("Symmetric difference assign") { + auto& pset2 = (set2.symmetric_difference_assign(set1)); + REQUIRE(&pset2 == &set2); + check_state<5>(set2, {1.1, 3.3, 4.4, 5.5, 6.6}); + } + + SECTION("Symmetric difference") { + auto rv = set2.symmetric_difference(set1); + check_state<4>(set2, {2.2, 4.4, 5.5, 6.6}); + check_state<5>(rv, {1.1, 3.3, 4.4, 5.5, 6.6}); + } + } +} diff --git a/tests/permutations.cpp b/tests/permutations.cpp new file mode 100644 index 0000000..1759495 --- /dev/null +++ b/tests/permutations.cpp @@ -0,0 +1,108 @@ +#include +#include +#include + +using namespace utilities; +using set_type = std::vector; +using perm_itr = detail_::PermutationItr; + +void check_state(perm_itr& b, const perm_itr& end, + const std::vector& corr, size_t off = 0) { + if(corr.empty()) + REQUIRE(b == end); + else + REQUIRE(b != end); + long counter = 0; + while(b != end) { + // Check that from each permutation the distance to every other perm + // is good (subtract off cause logic won't work if temp starts less + // than b) + for(size_t pstart = 0; pstart < corr.size() - off; ++pstart) { + perm_itr temp{corr[pstart], 0}; + long dx = static_cast(pstart) - counter; + + REQUIRE(b.distance_to(temp) == dx); + perm_itr copyb{b}; + REQUIRE(*(copyb.advance(dx)) == *temp); + } + REQUIRE(*b++ == corr[counter++]); + } + // b is at end, this gets it to point at the last permutation + --b; + for(size_t i = corr.size(); i > 0; --i) { REQUIRE(*b-- == corr[i - 1]); } +} + +TEST_CASE("Permutations") { + SECTION("Empty Permutation") { + perm_itr p0; + check_state(p0, perm_itr{}, {}); + } + + SECTION("Permutations of empty set") { + set_type empty{}; + perm_itr p0{empty, 0}; + perm_itr p1{empty, 1}; + check_state(p0, p1, {empty}); + } + + SECTION("Permutations of set of unique elements") { + set_type s0{1, 2, 3}; + perm_itr p0{s0, 0}; + perm_itr p1{s0, 6}; + SECTION("Correctness") { + check_state(p0, p1, + {set_type{1, 2, 3}, set_type{1, 3, 2}, + set_type{2, 1, 3}, set_type{2, 3, 1}, + set_type{3, 1, 2}, set_type{3, 2, 1}}); + } + SECTION("Copy Ctor") { + perm_itr p2{p0}; + REQUIRE(p2 == p0); + } + SECTION("Copy Assignment") { + perm_itr p2; + REQUIRE(p2 != p0); + p2 = p0; + REQUIRE(p2 == p0); + } + SECTION("Move Ctor") { + perm_itr corr{p0}; + perm_itr p2{std::move(p0)}; + REQUIRE(p2 == corr); + } + SECTION("Move Assignment") { + perm_itr p2; + perm_itr corr{p0}; + REQUIRE(p2 != p0); + p2 = std::move(p0); + REQUIRE(p2 == corr); + } + } + + SECTION("Permutations of set of non-unique elements") { + set_type s0{1, 2, 2}; + perm_itr p0{s0, 0}; + perm_itr p1{s0, 3}; + check_state(p0, p1, + {set_type{1, 2, 2}, set_type{2, 1, 2}, set_type{2, 2, 1}}); + } + + SECTION("Permutations from not lexicographically smallest") { + set_type s0{1, 3, 2}; + perm_itr p0{s0, 0}; + perm_itr p1{s0, 6}; + check_state(p0, p1, + {set_type{1, 3, 2}, set_type{2, 1, 3}, set_type{2, 3, 1}, + set_type{3, 1, 2}, set_type{3, 2, 1}, set_type{1, 2, 3}}, + 1); + } + + SECTION("Permutations of set of non-unique elements from not " + "lexicographically smallest") { + set_type s0{2, 1, 2}; + perm_itr p0{s0, 0}; + perm_itr p1{s0, 3}; + check_state( + p0, p1, {set_type{2, 1, 2}, set_type{2, 2, 1}, set_type{1, 2, 2}}, 1); + } +} diff --git a/tests/range.cpp b/tests/range.cpp new file mode 100644 index 0000000..850c09d --- /dev/null +++ b/tests/range.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +using namespace utilities; + +template +void check_state(container rng, std::initializer_list val_in) { + std::vector vals(val_in); + constexpr auto max_i = + std::numeric_limits::max(); + REQUIRE(rng.size() == vals.size()); + REQUIRE(rng.max_size() == max_i); + if(vals.size()) { + REQUIRE(rng.begin() != rng.end()); + REQUIRE(rng.cbegin() != rng.cend()); + } else { + REQUIRE(rng.begin() == rng.end()); + REQUIRE(rng.cbegin() == rng.cend()); + } + + // Normal iterators + for(auto& x : Zip(rng, vals)) { REQUIRE(std::get<0>(x) == std::get<1>(x)); } +} + +TEST_CASE("Range") { + SECTION("Is a container") { + REQUIRE(is_container>::value); + } + + SECTION("Range default constructor") { + auto rng1 = Range(0); + check_state(rng1, {}); + } + + SECTION("Simple range [0,5)") { + auto rng1 = Range(5); + check_state(rng1, {0, 1, 2, 3, 4}); + } + + SECTION("Moderate range [1,6)") { + auto rng1 = Range(1, 6); + check_state(rng1, {1, 2, 3, 4, 5}); + } + + SECTION("Hard range [1,7) by 2s") { + auto rng1 = Range(1, 7, 2); + check_state(rng1, {1, 3, 5}); + } + + SECTION("Reverse range") { + auto rng1 = Range(8, 2, -2); + check_state(rng1, {8, 6, 4}); + } + + SECTION("Can be used in a foreach loop") { + std::size_t counter = 0; + for(auto x : Range(5)) { + REQUIRE(x == counter); + ++counter; + } + REQUIRE(counter == 5); + } +} diff --git a/tests/range_container.cpp b/tests/range_container.cpp new file mode 100644 index 0000000..8ba73e4 --- /dev/null +++ b/tests/range_container.cpp @@ -0,0 +1,79 @@ +#include +#include +#include + +using namespace utilities; +using iterator_type = typename std::vector::const_iterator; +using container_type = detail_::RangeContainer; + +void check_state(container_type& C, const std::vector& contents) { + REQUIRE(C.size() == contents.size()); + REQUIRE(C.max_size() == std::numeric_limits::max()); + REQUIRE(C.empty() == (contents.begin() == contents.end())); + auto corr_begin = contents.begin(); + for(auto x : C) REQUIRE(*corr_begin++ == x); +} + +TEST_CASE("range_container") { + SECTION("Is a container") { REQUIRE(is_container::value); } + + SECTION("Default Ctor") { + std::vector empty{}; + container_type C; + check_state(C, empty); + + SECTION("Equality") { + container_type C2; + REQUIRE(C == C2); + } + } + + SECTION("Range Ctor w/Empty Range") { + std::vector empty{}; + container_type C{empty.begin(), empty.end(), empty.size()}; + check_state(C, empty); + + SECTION("Equality w/default Ctor") { + container_type C2; + REQUIRE(C == C2); + } + } + + SECTION("Range Ctor") { + std::vector nonempty{1, 2, 3}; + container_type C{nonempty.begin(), nonempty.end(), nonempty.size()}; + check_state(C, nonempty); + + SECTION("Inequality") { + container_type C2; + REQUIRE(C != C2); + } + SECTION("Copy Construct") { + container_type C2{C}; + check_state(C2, nonempty); + REQUIRE(C2 == C); + } + SECTION("Copy Assignment") { + container_type C2{}; + REQUIRE(C2 != C); + C2 = C; + REQUIRE(C2 == C); + } + SECTION("Move Ctor") { + container_type C2{std::move(C)}; + check_state(C2, nonempty); + } + SECTION("Move Assignment") { + container_type C2; + REQUIRE(C2 != C); + C2 = std::move(C); + check_state(C2, nonempty); + } + SECTION("Swap") { + container_type C2; + C2.swap(C); + check_state(C, {}); + check_state(C2, nonempty); + } + } +} diff --git a/tests/smart_enum.cpp b/tests/smart_enum.cpp new file mode 100644 index 0000000..32f8175 --- /dev/null +++ b/tests/smart_enum.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +using namespace utilities; + +DECLARE_SmartEnum(Fruit, apple, pear, banana, grape); + +/* I'm not sure if it's a compiler bug with Intel, but it won't let me use + * the enums as constexpr despite them being initialized as such... + */ + +TEST_CASE("SmartEnum") { + /* Intel no like-y + constexpr Fruit fuji = Fruit::apple; + constexpr Fruit gala = Fruit::apple; + constexpr Fruit bosc = Fruit::pear; + static_assert(fuji == gala); + static_assert(fuji != bosc); + static_assert(fuji < bosc); + static_assert(fuji <= gala); + static_assert(bosc > fuji); + static_assert(fuji >= gala); + */ + + Fruit fuji = Fruit::apple; + Fruit gala = Fruit::apple; + Fruit bosc = Fruit::pear; + + REQUIRE(fuji == gala); + REQUIRE(fuji != bosc); + + // This uses the fact that enums are sorted alphabetically by the instance + + REQUIRE(fuji < bosc); + REQUIRE(fuji <= gala); + REQUIRE(bosc > fuji); + REQUIRE(fuji >= gala); + + std::stringstream ss; + ss << fuji; + + std::stringstream corr_ss; + corr_ss << "apple"; + + REQUIRE(ss.str() == corr_ss.str()); +} diff --git a/tests/static_string.cpp b/tests/static_string.cpp new file mode 100644 index 0000000..8ec008d --- /dev/null +++ b/tests/static_string.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +// Note we use static_assert to assert that it's working at compile-time and we +// have to use assert in the check_string function because REQUIRE is not +// constexpr + +using namespace utilities; + +template +constexpr bool check_string(const StaticString& str, const char (&corr)[N]) { + assert(str.size() == N - 1); + for(std::size_t i = 0; i < N - 1; ++i) assert(str[i] == corr[i]); + StaticString temp_corr(corr); + assert(str == temp_corr); + StaticString temp_not_corr("No strings use this value"); + assert(str != temp_not_corr); + constexpr StaticString very_late_in_alphabet("zzzzzzzzzzzzzzzzzzzzzzzzzzz"); + assert(str < very_late_in_alphabet); + assert(str <= str); + assert(str <= very_late_in_alphabet); + assert(very_late_in_alphabet > str); + assert(str >= str); + assert(very_late_in_alphabet >= str); + constexpr StaticString null(""); + assert(str >= null); + assert(null <= str); + if(N != 1) { + assert(str > null); + assert(null < str); + } + return true; +} + +TEST_CASE("Null StaticString") { + constexpr StaticString null{""}; + static_assert(check_string(null, "")); + REQUIRE(check_string(null, "")); +} + +TEST_CASE("No Spaces StaticString") { + constexpr StaticString hello{"hello"}; + static_assert(check_string(hello, "hello")); + REQUIRE(check_string(hello, "hello")); +} + +TEST_CASE("Spaces StaticString") { + constexpr StaticString hello_world{"hello world"}; + static_assert(check_string(hello_world, "hello world")); + REQUIRE(check_string(hello_world, "hello world")); + std::stringstream ss_corr; + ss_corr << "hello world"; + std::stringstream ss; + ss << hello_world; + REQUIRE(ss.str() == ss_corr.str()); +} diff --git a/tests/test_main.cpp b/tests/test_main.cpp new file mode 100644 index 0000000..4ed06df --- /dev/null +++ b/tests/test_main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/tests/timer.cpp b/tests/timer.cpp new file mode 100644 index 0000000..794881a --- /dev/null +++ b/tests/timer.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +TEST_CASE("Timer::record") { + std::chrono::milliseconds time2sleep(5); + utilities::Timer t; + std::this_thread::sleep_for(time2sleep); + t.record("sleep 5 ms"); + REQUIRE(t.at("sleep 5 ms") >= time2sleep); +} + +TEST_CASE("Timer::reset") { + std::chrono::milliseconds time2sleep(5); + utilities::Timer t; + std::this_thread::sleep_for(time2sleep); + t.reset(); + t.record("no sleep"); + REQUIRE(t.at("no sleep") < time2sleep); +} + +TEST_CASE("Timer::time_it") { + std::chrono::milliseconds time2sleep(5); + utilities::Timer t; + t.time_it("sleep 5 ms", [=]() { std::this_thread::sleep_for(time2sleep); }); + REQUIRE(t.at("sleep 5 ms") >= time2sleep); +} + +TEST_CASE("Timer::operator[]") { + utilities::Timer t; + REQUIRE_THROWS_AS(t["not a key"], std::out_of_range); +} + +TEST_CASE("Printing a timer") { + std::chrono::milliseconds time2sleep(5); + utilities::Timer t; + std::this_thread::sleep_for(time2sleep); + t.record("sleep 5 ms"); + std::stringstream ss; + ss << t << std::endl; + auto found = ss.str().find("sleep 5 ms : 0 h 0 m 0 s 5 ms"); + REQUIRE(found != std::string::npos); +} diff --git a/tests/tuple_utilities.cpp b/tests/tuple_utilities.cpp new file mode 100644 index 0000000..baa2041 --- /dev/null +++ b/tests/tuple_utilities.cpp @@ -0,0 +1,132 @@ +#include +#include +#include + +using namespace utilities; + +// Just returns the value given to it +struct functor1 { + template + T run(T in) const noexcept { + return in; + } +}; + +// Returns the value of calling size() +struct functor2 { + template + auto run(T in) const noexcept { + return in.size(); + } +}; + +// Sums the second element of each input +struct functor3 { + template + int run(int val, T in) const noexcept { + return val + in[1]; + } +}; + +// Sums the second element of each element +struct functor4 { + template + auto run(lhs_type lhs, rhs_type rhs) const noexcept { + return lhs[1] + rhs[1]; + } +}; + +// Returns true if the element is an 'a' +struct functor5 { + template + bool run(tuple_type in) const noexcept { + return in == 'a'; + } +}; + +TEST_CASE("Helper Types") { + REQUIRE(detail_::recursion_done<2, std::tuple>::value); + REQUIRE(!detail_::recursion_done<1, std::tuple>::value); + REQUIRE(!detail_::recursion_not_done<2, std::tuple>::value); + REQUIRE(detail_::recursion_not_done<1, std::tuple>::value); +} + +TEST_CASE("apply_functor") { + SECTION("Empty tuple") { + auto my_tuple = std::make_tuple(); + auto result = tuple_transform(my_tuple, functor1()); + REQUIRE(result == my_tuple); + } + + SECTION("Simple functor and simple tuple") { + auto my_tuple = std::make_tuple(1, 'a', 4.5); + auto result = tuple_transform(my_tuple, functor1()); + REQUIRE(std::get<0>(result) == 1); + REQUIRE(std::get<1>(result) == 'a'); + REQUIRE(std::get<2>(result) == 4.5); + } + + SECTION("More complicated functor and tuple") { + auto my_tuple = std::make_tuple(std::vector({1, 2, 3}), + std::array({'a', 'b'})); + auto result = tuple_transform(my_tuple, functor2()); + REQUIRE(std::get<0>(result) == 3); + REQUIRE(std::get<1>(result) == 2); + } + + SECTION("Constant tuple") { + std::tuple my_tuple({3, 4}); + const auto& cmy_tuple = + const_cast&>(my_tuple); // NOLINT + auto result = tuple_transform(cmy_tuple, functor1()); + REQUIRE(std::get<0>(result) == 3); + REQUIRE(std::get<1>(result) == 4); + } +} + +TEST_CASE("reduce_tuple") { + SECTION("Empty tuple") { + auto my_tuple = std::make_tuple(); + auto sum = tuple_accumulate(my_tuple, functor3(), 100); + REQUIRE(sum == 100); + } + + SECTION("Non-trivial tuple") { + auto my_tuple = std::make_tuple(std::vector({1, 2, 3}), + std::vector({6, 2, 9})); + auto sum = tuple_accumulate(my_tuple, functor3(), 0); + REQUIRE(sum == 4); + } +} + +TEST_CASE("combine_tuples") { + SECTION("Trivial tuples") { + auto lhs_tuple = std::make_tuple(); + auto rhs_tuple = std::make_tuple(); + auto sum = tuple_transform(lhs_tuple, rhs_tuple, functor4()); + REQUIRE(sum == lhs_tuple); + } + + SECTION("Non-trivial tuples") { + auto lhs_tuple = std::make_tuple(std::array({1, 2, 3}), + std::array({6, 7})); + auto sum = tuple_transform(lhs_tuple, lhs_tuple, functor4()); + REQUIRE(std::get<0>(sum) == 4); + REQUIRE(std::get<1>(sum) == 14); + } +} + +TEST_CASE("find if") { + SECTION("Empty tuple") { + auto t = std::make_tuple(); + REQUIRE(tuple_find_if(t, functor5{}) == 0); + } + SECTION("Non-trivial tuple w/o a true") { + auto t = std::make_tuple('b', 'c', 'd'); + REQUIRE(tuple_find_if(t, functor5{}) == 3); + } + SECTION("Tuple w/ true") { + auto t = std::make_tuple('b', 'a', 'c'); + REQUIRE(tuple_find_if(t, functor5{}) == 1); + } +} diff --git a/tests/type_traits_extensions.cpp b/tests/type_traits_extensions.cpp new file mode 100644 index 0000000..0cafeef --- /dev/null +++ b/tests/type_traits_extensions.cpp @@ -0,0 +1,177 @@ +#include +#include +#include + +using namespace utilities; + +struct Struct1 { + void begin(); + void swap(Struct1&); + bool operator!=(const Struct1&); + int operator*(); + int operator++(int); + int operator[](int); + int operator+=(int); + int operator-=(int); + int operator<=(int); + int operator>=(int); +}; +struct Struct2 { + using value_type = int; + bool operator==(const Struct2&); + int operator++(); + Struct2* operator->(); + int operator--(); + int operator--(int); + int operator+(int); + int operator-(int); + int operator<(int); + int operator>(int); +}; + +TEST_CASE("type_traits library extensions") { + REQUIRE(Negation::value); + + SECTION("Has types check") { + const bool has_type_false = has_type_value_type::value; + const bool has_type_true = has_type_value_type::value; + REQUIRE(!has_type_false); + REQUIRE(has_type_true); + } + + SECTION("Has function checks") { + const bool has_fxn_false = has_begin::value; + const bool has_fxn_true = has_begin::value; + REQUIRE(!has_fxn_false); + REQUIRE(has_fxn_true); + } + + // Operators are a bit tricky so actually check each one + SECTION("Has operation checks") { + const bool equal_to_false = has_equal_to::value; + const bool equal_to_true = has_equal_to::value; + REQUIRE(!equal_to_false); + REQUIRE(equal_to_true); + + bool not_equal_to_false = has_not_equal_to::value; + bool not_equal_to_true = has_not_equal_to::value; + REQUIRE(!not_equal_to_false); + REQUIRE(not_equal_to_true); + + bool dereference_false = has_dereference::value; + bool dereference_true = has_dereference::value; + REQUIRE(!dereference_false); + REQUIRE(dereference_true); + + bool arrow_false = has_arrow::value; + bool arrow_true = has_arrow::value; + REQUIRE(!arrow_false); + REQUIRE(arrow_true); + + bool prefix_inc_false = has_prefix_increment::value; + bool prefix_inc_true = has_prefix_increment::value; + REQUIRE(!prefix_inc_false); + REQUIRE(prefix_inc_true); + + bool postfix_inc_false = has_postfix_increment::value; + bool postfix_inc_true = has_postfix_increment::value; + REQUIRE(!postfix_inc_false); + REQUIRE(postfix_inc_true); + + bool prefix_dec_false = has_prefix_decrement::value; + bool prefix_dec_true = has_prefix_decrement::value; + REQUIRE(!prefix_dec_false); + REQUIRE(prefix_dec_true); + + bool postfix_dec_false = has_postfix_decrement::value; + bool postfix_dec_true = has_postfix_decrement::value; + REQUIRE(!postfix_dec_false); + REQUIRE(postfix_dec_true); + + bool is_idx_false = is_indexable::value; + bool is_idx_true = is_indexable::value; + REQUIRE(!is_idx_false); + REQUIRE(is_idx_true); + + bool has_lte_true = has_less_than_equal::value; + bool has_lte_false = has_less_than_equal::value; + REQUIRE(!has_lte_false); + REQUIRE(has_lte_true); + + bool has_gte_true = has_greater_than_equal::value; + bool has_gte_false = has_greater_than_equal::value; + REQUIRE(!has_gte_false); + REQUIRE(has_gte_true); + + bool has_lt_true = has_less_than::value; + bool has_lt_false = has_less_than::value; + REQUIRE(!has_lt_false); + REQUIRE(has_lt_true); + + bool has_gt_true = has_greater_than::value; + bool has_gt_false = has_greater_than::value; + REQUIRE(!has_gt_false); + REQUIRE(has_gt_true); + + bool has_inc_by_true = has_increment_by::value; + bool has_inc_by_false = has_increment_by::value; + REQUIRE(!has_inc_by_false); + REQUIRE(has_inc_by_true); + + bool has_dec_by_true = has_decrement_by::value; + bool has_dec_by_false = has_decrement_by::value; + REQUIRE(!has_dec_by_false); + REQUIRE(has_dec_by_true); + + bool has_plus_true = has_plus::value; + bool has_plus_false = has_plus::value; + REQUIRE(!has_plus_false); + REQUIRE(has_plus_true); + + bool has_minus_true = has_minus::value; + bool has_minus_false = has_minus::value; + REQUIRE(!has_minus_false); + REQUIRE(has_minus_true); + } + + SECTION("Container concept checks") { + const bool is_container_false = is_container::value; + const bool is_container_true = is_container>::value; + REQUIRE(!is_container_false); + REQUIRE(is_container_true); + } + + SECTION("Iterator concept checks") { + using iterator = typename std::vector::iterator; + const bool is_iterator_false = is_iterator::value; + const bool is_iterator_true = is_iterator::value; + + REQUIRE(!is_iterator_false); + REQUIRE(is_iterator_true); + + SECTION("InputIterator") { + const bool input_iterator_false = is_input_iterator::value; + const bool input_iterator_true = is_input_iterator::value; + REQUIRE(!input_iterator_false); + REQUIRE(input_iterator_true); + + SECTION("BidirectionalIterator") { + const bool bid_iterator_false = + is_bidirectional_iterator::value; + const bool bid_iterator_true = + is_bidirectional_iterator::value; + REQUIRE(!bid_iterator_false); + REQUIRE(bid_iterator_true); + + SECTION("RandomAccessIterator") { + const bool ra_itr_false = + is_random_access_iterator::value; + const bool ra_itr_true = + is_random_access_iterator::value; + REQUIRE(!ra_itr_false); + REQUIRE(ra_itr_true); + } + } + } + } +} diff --git a/tests/zip.cpp b/tests/zip.cpp new file mode 100644 index 0000000..2b67b56 --- /dev/null +++ b/tests/zip.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include + +using namespace utilities; + +template +void check_state(T& zipper, std::size_t size) { + REQUIRE(zipper.size() == size); + const bool is_empty = (size == 0); + REQUIRE(zipper.empty() == is_empty); + if(is_empty) + REQUIRE(zipper.begin() == zipper.end()); + else + REQUIRE(zipper.begin() != zipper.end()); +} + +TEST_CASE("Zip") { + SECTION("Empty zip") { + auto test_container = Zip(); + check_state(test_container, 0); + } + + SECTION("Single container") { + std::vector numbers{1, 2}; + auto test_container = Zip(numbers); + check_state(test_container, 2); + + auto itr = test_container.begin(); + auto end = test_container.end(); + + SECTION("Test iteration") { + REQUIRE(std::get<0>(*itr) == 1); + auto& pitr = (++itr); + REQUIRE(&pitr == &itr); + REQUIRE(itr != end); + REQUIRE(std::get<0>(*itr) == 2); + ++itr; + REQUIRE(itr == end); + } + } + + SECTION("Two same size arrays") { + std::vector numbers({1, 2, 3}); + std::array letters({'a', 'b', 'c'}); + auto test_container = Zip(numbers, letters); + check_state(test_container, 3); + + SECTION("Foreach loop") { + std::size_t counter = 0; + for(auto& x : test_container) { + SECTION("Iteration test") { + REQUIRE(std::get<0>(x) == numbers[counter]); + REQUIRE(std::get<1>(x) == letters.at(counter)); + } + ++counter; + } + } + } + + SECTION("Different size arrays") { + std::vector numbers({1, 2, 3}); + std::vector letters({'a', 'b'}); + auto test_container = Zip(numbers, letters); + check_state(test_container, 2); + std::size_t counter = 0; + for(auto& x : test_container) { + SECTION("Iteration test") { + REQUIRE(std::get<0>(x) == numbers[counter]); + REQUIRE(std::get<1>(x) == letters[counter]); + } + ++counter; + } + } + + SECTION("Different size arrays (one empty)") { + std::vector numbers({1, 2, 3}); + std::vector letters; + auto test_container = Zip(numbers, letters); + check_state(test_container, 0); + } + + SECTION("Const containers") { + const std::vector numbers{1, 2, 3}; + const std::vector letters{'a', 'b', 'c'}; + auto test_container = Zip(numbers, letters); + check_state(test_container, 3); + } + + // SECTION("References to elements"){ + // const std::vector numbers{1, 2, 3}; + // const std::vector letters{'a', 'b', 'c'}; + // std::size_t counter = 0; + // for(auto& i : Zip(numbers, letters)){ + // REQUIRE(&(std::get<0>(i)) == &(numbers[counter])); + // REQUIRE(&(std::get<1>(i)) == &(letters[counter])); + // } + //} +} diff --git a/utilities/CMakeLists.txt b/utilities/CMakeLists.txt new file mode 100644 index 0000000..b3f2252 --- /dev/null +++ b/utilities/CMakeLists.txt @@ -0,0 +1,32 @@ +set( + UTILITIES_HEADERS containers/case_insensitive_map.hpp + containers/math_set.hpp + iter_tools/cartesian_product.hpp + iter_tools/combinations.hpp + iter_tools/enumerate.hpp + iter_tools/permutations.hpp + iter_tools/range.hpp + iter_tools/range_container.hpp + iter_tools/tuple_container.hpp + iter_tools/zip.hpp + macros/for_each.hpp + mathematician/combinatorics.hpp + mathematician/integer_utils.hpp + type_traits/iterator_types.hpp + type_traits/tuple_utilities.hpp + type_traits/type_traits_extensions.hpp + iter_tools.hpp + smart_enum.hpp + static_string.hpp + timer.hpp +) + +set(UTILITIES_SRCS mathematician/combinatorics.cpp) + +cpp_add_library( + utilities + SOURCES ${UTILITIES_SRCS} + INCLUDES ${UTILITIES_HEADERS} +) +cpp_install(TARGETS utilities) + diff --git a/utilities/README.md b/utilities/README.md new file mode 100644 index 0000000..b745471 --- /dev/null +++ b/utilities/README.md @@ -0,0 +1,15 @@ +Utilities Source Root +======================= + +The current directory is the root of the Utilities source tree. The source +itself is separated into four subtypes for the moment: + +- Containers These are classes that implement various non-standard container +types. +- IterTools These are classes that aid in condensing multiple loops or +complicated looping patterns into a single, simple foreach loop. +- Mathematician This directory contains things an omnipotent mathematician +would know (and isn't in the STL or handled by TAMM). This includes things +like factorials, binomial coefficients, *etc.* +- TypeTraits These are various template structures meant to facilitate +meta-template programming. diff --git a/utilities/containers/case_insensitive_map.hpp b/utilities/containers/case_insensitive_map.hpp new file mode 100644 index 0000000..35a8485 --- /dev/null +++ b/utilities/containers/case_insensitive_map.hpp @@ -0,0 +1,68 @@ +#pragma once +#include // For lexicographical_compare +#include // For tolower +#include +#include + +namespace utilities { +namespace detail_ { + +/** + * @brief Implements a case insensitive less-than operation for two + * std::strings. + * + * @note This is shamelessly stolen from StackOverflow's topic: + * "Making map find operation case insensitive" + */ +struct CaseInsensitiveLess_ { + /// Letter by letter case-insensitive functor + struct LetterComparer_ { + /** + * @brief Compares two characters in a case-insensitive way. + * @param c1 The first + * @param c2 + * @return True if lowercase @p c1 is less than lowercase @p c2 and + * false otherwise + * @throw ??? Throws if the locale backend throws (I am unable to + * find a list of what could cause that). Strong throw guarantee. + */ + bool operator()(const unsigned char& c1, + const unsigned char& c2) const { + return std::tolower(c1) < std::tolower(c2); + } + }; + + /** + * @brief Compares two strings in a case insensitive manner. + * + * Ultimately this will use LetterComparer_ to compare the strings + * lexicographically letter by letter. + * @param s1 The first string + * @param s2 The second string + * @return True if lowercase s1 comes before lowercase s2. False otherwise. + * @throw ??? Throws if LetterComparer_'s operator() throws. Strong throw + * guarantee. + */ + bool operator()(const std::string& s1, const std::string& s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), LetterComparer_()); + } +}; + +} // namespace detail_ + +/** + * @brief A case-insensitive std::map (the keys are assumed to be + * std::string otherwise it doesn't make a whole lot of sense to do a + * case-insensitive compare...) + * + * This class is really just a partial specialization of std::map so it's + * API is simply that of std::map. + * + * @tparam T the type of values you are putting into the map + */ +template +using CaseInsensitiveMap = + std::map; + +} // namespace utilities diff --git a/utilities/containers/math_set.hpp b/utilities/containers/math_set.hpp new file mode 100644 index 0000000..eb5fdc8 --- /dev/null +++ b/utilities/containers/math_set.hpp @@ -0,0 +1,458 @@ +#pragma once +#include //For set_intersection and the like +#include + +namespace utilities { + +/** + * @brief Implements an std::unordered_set-like object except that it knows + * about union, etc. + * + * @note We actually use std::set to avoid the need for requiring the elements + * to be hashable. + * @note I'm not convinced that the various operations are optimal. It may be + * easier to do them directly with the members of std::set. Also switching to + * a sorted std::vector could possibly speed things up by taking advantage of + * the fact that it wouldn't be random memory access. + * + * @tparam element_type The type of the elements in the set. Must be copyable. + */ +template +class MathSet { + private: + /// Typedef of implementing container for easy modification later + using container_type = std::set; + + public: + /// The type of an element inside this set + using value_type = typename container_type::value_type; + + /// The type of a reference to an element in this set + using reference = typename container_type::reference; + + /// The type of a read-only reference to an element in this set + using const_reference = typename container_type::const_reference; + + /// The type of an iterator to this set + using iterator = typename container_type::iterator; + + /// The type of an iterator to a read-only instance of this set + using const_iterator = typename container_type::const_iterator; + + /// The type of the difference between two iterators + using difference_type = typename container_type::difference_type; + + /// The type of index or an offset + using size_type = typename container_type::size_type; + + /** @brief Constructs an instance that is the empty set. + * + * @throw None. No throw guarantee. + */ + MathSet() = default; + + /** + * @brief Deep copies another MathSet instance. + * + * @param[in] rhs The MathSet instance to deep copy. + * @throw std::bad_alloc if their is insufficient memory for the copy. + * Strong throw guarantee. + * @throw ??? If the copy constructor of element_type throws. Strong throw + * guarantee. + */ + MathSet(const MathSet& /*rhs*/) = default; + + /** + * @brief Takes ownership of another MathSet instance's state. + * + * @param[in] rhs The MathSet instance to take ownership of. After this + * call @p rhs will be in a valid, but undefined state. + * @throw None. No throw guarantee. + */ + MathSet(MathSet&&) noexcept = default; + + /** + * @brief Initializes the set from an initializer list + * + * @param il The intializer list being used to set the initial state of the + * container. + * + * @throw std::bad_alloc if there is insufficient memory for copying the + * elements. Strong throw guarantee. + * + */ + MathSet(std::initializer_list il) : elements_(il) {} + + /** @brief Frees up memory associated with the current instance. + * + * After this call all iterators to this class are invalidated. + * + * @throw None. No throw guarantee. + */ + ~MathSet() noexcept = default; + + /** + * @brief Makes the current instance a deep copy of another instance. + * + * @param[in] rhs The instance to deep copy. + * + * @return The current instance now containing a deep copy of @p rhs 's + * state. + * + * @throw std::bad_alloc if there is insufficient memory for the copy. + * Strong throw guarantee. + * + * @throw ??? if the element's copy constructor throws. Strong throw + * guarantee. + * + */ + MathSet& operator=(const MathSet&) = default; + + /** @brief Takes ownership of another MathSet instance's state. + * + * @param[in] rhs The MathSet instance to take ownership of. After this + * call @p rhs is in a valid, but otherwise undefined state. + * + * @return The current instance containing @p rhs 's state. + * @throw None. No throw guarantee. + */ + MathSet& operator=(MathSet&&) noexcept = default; + + /** @brief Creates an iterator that points to the first element in this set. + * + * @return An iterator that points to the first element of this set. The + * iterator satisfies bidirectional iterator. + * @throw None. No throw guarantee. + */ + iterator begin() noexcept { return elements_.begin(); } + + /** @brief Creates an iterator that points to the first element in this set + * that can not be used to modify the elements of the set. + * + * @copydetails begin() + */ + const_iterator begin() const { return elements_.cbegin(); } + + ///@copydoc begin()const + const_iterator cbegin() const { return elements_.cbegin(); } + + /** @brief Creates an iterator that points to just past the last element in + * this set. + * + * @return An iterator that points to just past the last element of this + * set. The iterator satisfies bidirectional iterator. + * @throw None. No throw guarantee. + */ + iterator end() { return elements_.end(); } + + /** @brief Creates an iterator that points to just past the last element + * in this set that can not be used to modify the elements of the set. + * + * @copydetails end() + */ + const_iterator end() const { return elements_.cend(); } + + ///@copydoc end()const + const_iterator cend() const { return elements_.cend(); } + + template + auto insert(input_element&& ei) { + return elements_.insert(std::forward(ei)); + } + + /** + * @brief Returns the number of times an element appears in the current set. + * + * Since an element can only appear once in a set the return value of this + * function is a boolean (true if the element is present and false + * otherwise). + * + * @param ei The element to look for. + * @return True if @p ei is contained within the current set and false + * otherwise. + * + * @throw None. No throw guarantee. + * + */ + bool count(const element_type& ei) const noexcept { + return elements_.count(ei); + } + + /** + * @brief Returns the number of elements in the current set. + * + * @return The number of elements currently in the set. + * @throw None. No throw guarantee. + */ + size_type size() const noexcept { return elements_.size(); } + + /** + * @brief Returns the theoretical maximum number of elements this container + * can accomodate ignoring hardware considerations. + * + * @return The maximum number of elements that can be added to this + * container (theoretically). + * @throw None. No throw guarantee. + */ + size_type max_size() const noexcept { return elements_.max_size(); } + + /** + * @brief Returns true if the current instance is the empty set. + * + * @return True if the current instance is the empty set and false if it + * contains at least one element. + * @throw None. No throw guarantee. + */ + bool empty() const noexcept { return elements_.empty(); } + + /** + * @brief Makes the current instance the union of itself and another + * MathSet. + * + * The union of two sets @f$x@f$ and @f$y@f$ is a third set @f$z@f$ which + * contains all elements that are present in @f$x@f$ and/or in @f$y@f$. + * Note that a set will contain at most one copy of a given element. + * + * @param rhs The set we are taking a union with. + * @return The current instance set to the union of itself and @p rhs. + * @throw std::bad_alloc if there is insufficient memory to copy elements + * over. Weak throw guarantee. + */ + MathSet& union_assign(const MathSet& rhs) { + auto end_itr = end(); + for(const auto& x : rhs) end_itr = elements_.insert(end_itr, x); + return *this; + } + + /** + * @brief Returns the union of this MathSet instance and another. + * + * This function behaves the same as union_assign() except that the + * result is a new instance. + * + * @note union is a protected keyword in the C++ language and hence is not a + * valid member function name. + * + * @throw std::bad_alloc if there is insufficient memory to copy the + * elements of this and @p rhs to a common container. Strong throw + * guarantee. + */ + MathSet take_union(const MathSet& rhs) const { + return MathSet(*this).union_assign(rhs); + } + + /** + * @brief Makes this the intersection of itself and another set. + * + * The intersection of a set @f$x@f$ and a set @f$y@f$ is the set of all + * elements appearing in both @f$x@f$ and @f$y@f$. + * + * @return the current instance set to the intersection of itself and @p + * rhs. + * + * @throw std::bad_alloc if there is insufficient memory to copy common + * elements. Strong throw guarantee. + */ + MathSet& intersection_assign(const MathSet& rhs) { + container_type temp; + auto inserter = std::inserter(temp, temp.end()); + std::set_intersection(begin(), end(), rhs.begin(), rhs.end(), inserter); + elements_.swap(temp); + return *this; + } + + /** + * @brief Creates a new MathSet that is the intersection of this and rhs + * + * This function is basically the same as intersection_assign aside from the + * fact that it makes a new instance. + * + * @param rhs The set to take the intersection with. + * @return A new MathSet containing the intersection of this and @p rhs. + * @throw std::bad_alloc if there is insufficient memory to make the new + * set. Strong throw guarantee. + */ + MathSet intersection(const MathSet& rhs) const { + return MathSet(*this).intersection_assign(rhs); + } + + /** @brief Makes the current set the set difference of itself and another + * set. + * + * The set @f$x/y@f$ is the set difference of sets @f$x@f$ and @f$y@f$ + * and is all of the elements in @f$x@f$ that are not in @f$y@f$. + * + * @param rhs The set to take the difference with. + * @return The current instance less elements common to @p rhs. + * @throw std::bad_alloc if there is insufficient memory to copy common + * elements over. + */ + MathSet& difference_assign(const MathSet& rhs) { + container_type temp; + auto inserter = std::inserter(temp, temp.end()); + std::set_difference(begin(), end(), rhs.begin(), rhs.end(), inserter); + elements_.swap(temp); + return *this; + } + + /** + * @brief Returns the set difference of this and another set. + * + * This function is basically identical to set_difference_assign aside from + * the fact that the result is put into a new instance. + * + * @param rhs The set to take the difference with. + * @return A new instance containing the elements that are in this set and + * not in @p rhs. + * @throw std::bad_alloc if there is insufficient memory to copy. + */ + MathSet difference(const MathSet& rhs) const { + return MathSet(*this).difference_assign(rhs); + } + + /** @brief Makes the current set the symmetric set difference of itself and + * another set. + * + * The symmetric set difference of sets @f$x@f$ and @f$y@f$ is the set of + * all elements appearing in either @f$x@f$ or @f$y@f$, but not in both. + * + * @param rhs The set to take the symmetric difference with. + * @return The current instance less elements common to @p rhs plus unique + * elements of @p rhs. + * @throw std::bad_alloc if there is insufficient memory to copy common + * elements over. + */ + MathSet& symmetric_difference_assign(const MathSet& rhs) { + container_type temp; + auto inserter = std::inserter(temp, temp.end()); + std::set_symmetric_difference(begin(), end(), rhs.begin(), rhs.end(), + inserter); + elements_.swap(temp); + return *this; + } + + /** + * @brief Returns the set difference of this and another set. + * + * This function is basically identical to set_difference_assign aside from + * the fact that the result is put into a new instance. + * + * @param rhs The set to take the difference with. + * @return A new instance containing the elements that are in this set and + * not in @p rhs. + * @throw std::bad_alloc if there is insufficient memory to copy. + */ + MathSet symmetric_difference(const MathSet& rhs) const { + return MathSet(*this).symmetric_difference_assign(rhs); + } + + /** @brief Returns true if the current MathSet is a proper subset of @p rhs. + * + * In order for a set @f$x@f$ to be a proper subset of a set @f$y@f$ every + * element of @f$x@f$ must be contained in @f$y@f$ and furthermore there + * must exist at least one element in @f$y@f$ that does not exist in + * @f$x@f$. + * + * @warning A value of false doesn't mean that the current instance is a + * superset of @p rhs. + * + * @param rhs The MathSet to compare to. + * @return True if the current instance is a proper subset of @p rhs. + * @throw None. No throw guarantee. + */ + bool operator<(const MathSet& rhs) const noexcept { + if(size() >= + rhs.size()) // Can't be subset if we don't have less elements + return false; + return operator<=(rhs); + } + + /** + * @brief Returns whether or not the current MathSet is a proper superset of + * another MathSet. + * + * Given two sets @f$x@f$ and @f$y@f$, @f$x@f$ is a proper superset of + * @f$y@f$ if and only if all elements of @f$y@f$ are also in @f$x@f$ and + * all there exists at least one element in @f$x@f$ that is not in @f$y@f$. + * + * @param rhs The instance to compare against. + * @return True if the current set is a proper superset of @p rhs and false + * otherwise. + * @throw None. No throw guarantee. + */ + bool operator>(const MathSet& rhs) const noexcept { + return rhs < *this; // X superset Y means Y subset of X + } + + /** @brief Returns whether or not the current MathSet is subset of another + * MathSet. + * + * The difference between a subset and a proper subset is that all sets are + * also subsets of themselves (i.e. subset allows for equality whereas a + * proper subset is strictly less than). + * + * @param rhs The set to compare against. + * @return True if the current instance is a subset of @p rhs and false if + * there is at least one element that is not in @p rhs. + * @throw None. No throw guarantee. + */ + bool operator<=(const MathSet& rhs) const noexcept { + if(size() > rhs.size()) // Can't be subset with more elements + return false; + for(auto& x : *this) + if(!rhs.count(x)) return false; + return true; + } + + /** @brief Returns whether or not the current MathSet is a superset of + * another MathSet. + * + * The difference between a superset and a proper superset is that all sets + * are also supersets of themselves (i.e. superset allows for equality + * whereas proper superset is strictly greater than). + * + * @param rhs The set to compare against. + * @return True if the current instance is a superset of @p rhs and false if + * there is at least one element in @p rhs that is not in the current + * instance. + * @throw None. No throw guarantee. + */ + bool operator>=(const MathSet& rhs) const noexcept { + return rhs <= (*this); + } + + /** + * @brief Determines if two MathSet instances are equivalent. + * + * We define two MathSet instances to be equal if they are both supersets + * and subsets of eachother. + * + * @param rhs The set to compare against. + * @return True if all elements in this set are in @p rhs and vice versa. + * @throw None. No throw guarantee. + */ + bool operator==(const MathSet& rhs) const noexcept { + return elements_ == rhs.elements_; + } + + /** + * @brief Used to determine if two sets are different. + * + * Two sets are different if there exists at least one element in one that + * is not in the other. + * + * @param rhs The set to compare against. + * @return True if there is an element in this set or @p rhs that is not in + * the other. + * @throw None. No throw guarantee. + */ + bool operator!=(const MathSet& rhs) const noexcept { + return !((*this) == rhs); + } + + private: + /// The actual set of elements + std::set elements_ = {}; +}; + +} // namespace utilities diff --git a/utilities/iter_tools.hpp b/utilities/iter_tools.hpp new file mode 100644 index 0000000..b6dae63 --- /dev/null +++ b/utilities/iter_tools.hpp @@ -0,0 +1,15 @@ +#pragma once +/** @file IterTools.hpp + * + * This file is a convenience header for the IterTools sublibrary of + * Utilities. + * + * @note This header-file meant for client use only and should not be included + * in any file within the Utilities project. This is to provide the + * client a means of avoiding unnecessary includes. + */ +#include +#include +#include +#include +#include diff --git a/utilities/iter_tools/cartesian_product.hpp b/utilities/iter_tools/cartesian_product.hpp new file mode 100644 index 0000000..7677e39 --- /dev/null +++ b/utilities/iter_tools/cartesian_product.hpp @@ -0,0 +1,125 @@ +#pragma once +#include "utilities/iter_tools/tuple_container.hpp" + +namespace utilities { +namespace detail_ { + +/// Functor that computes the number of elements in the CartesianProduct +struct CartSizeFunctor { + /// Empty case is caught by base class + static constexpr std::size_t initial_value = 1L; + + template + auto run(std::size_t curr_min, T&& container) const { + return curr_min * container.size(); + } +}; + +/// Functor used to increment the CartesianProduct iterator +struct CartIncrementFunctor { + /// Functor that returns true if an iterator is at the end + template + struct AtEnd { + const iterator_type& end; + AtEnd(const iterator_type& da_end) : end(da_end) {} + + template + bool run(T&& elem) const noexcept { + return elem == std::get(end); + } + }; + + /// Functor that resets all indices starting with @p turn_on + template + struct Reseter { + const iterator_type& start; + std::size_t turn_on = 0; + Reseter(const iterator_type& da_start, std::size_t on) : + start(da_start), + turn_on(on) {} + + template + auto run(T&& itr) { + return (I >= turn_on) ? std::get(start) : itr; + } + }; + + /// Functor that increments @p turn_on + struct Incrementer { + std::size_t turn_on = 0; + Incrementer(std::size_t on) : turn_on(on) {} + + template + auto run(T&& itr) { + return (I == turn_on) ? ++itr : itr; + } + }; + + /// The function called by TupleContainer + template + void run(const IteratorType& start, const IteratorType& end, + IteratorType& value, std::index_sequence) { + constexpr std::size_t nelems = sizeof...(I); + std::size_t idx = nelems; + while(true) { + if(!idx) break; // idx==0 means we have no indices left to try + // Increment the right most element that we know isn't at end + auto temp_value = tuple_transform(value, Incrementer(idx - 1)); + // Now get first index at end + auto new_idx = tuple_find_if(temp_value, AtEnd(end)); + // If new_idx hasn't changed (i.e. is still idx) that was a good inc + if(new_idx == idx) { + value = tuple_transform(temp_value, + Reseter(start, idx)); + break; + } + idx = new_idx; // not good means new_idx has moved down one + } + } +}; + +} // namespace detail_ + +/** @brief Wrapper function that makes a CartesianProduct container. + * + * The purpose/usage of the resulting object is perhaps best explained with an + * example. Consider the following snippet. + * + * @code + * std::vector list1({1,2,3}); + * std::vector list2({2,3}); + * for(auto& x : CartesianProduct(list1.begin(),list2.begin()) + * std::cout<< std::get<0>(x) << " " << std::get<1>(x) < +auto CartesianProduct(ContainerTypes&&... containers) { + return detail_::TupleContainerImpl< + detail_::CartIncrementFunctor, + std::remove_reference_t...>( + detail_::CartSizeFunctor{}, std::forward(containers)...); +} + +} // namespace utilities diff --git a/utilities/iter_tools/combinations.hpp b/utilities/iter_tools/combinations.hpp new file mode 100644 index 0000000..c49ff5c --- /dev/null +++ b/utilities/iter_tools/combinations.hpp @@ -0,0 +1,289 @@ +#pragma once +#include "utilities/iter_tools/permutations.hpp" +#include //is_permutation +#include //max size of size_t +#include //for tie + +namespace utilities { +namespace detail_ { + +/** @brief The main implementation of the CombinationImpl class. This iterator + * does all the work. + * + * This class is written in terms of the PermutationItr class. As a + * technical note we invert the usual 0=false, 1=true mapping to get the + * unique permutations back in lexicographical order. This class can be used + * alone as a generator, but is intended to be used with a RangeContainer + * instance so that it can be used in a range-based for loop. + * + * @tparam SequenceType The type of a generated combination. Should satisfy + * the concept of random access container. + * @tparam repeat Should we allow repeated elements while forming combinations? + * + * @todo C++17 allows inheriting default ctors. + */ +template +class CombinationItr : public detail_::RandomAccessIteratorBase< + CombinationItr, SequenceType> { + /// The type of an instance of this class, defined for sanity + using my_type = CombinationItr; + /// The type of the base class, again for sanity. + using base_type = detail_::RandomAccessIteratorBase; + + public: + /// Typedefs forwarded from the base class + ///@{ + using value_type = typename base_type::value_type; + using const_reference = typename base_type::const_reference; + using size_type = typename base_type::size_type; + using difference_type = typename base_type::difference_type; + ///@} + + /** + * @brief Makes an instance that points to an empty container. + * + * The resulting iterator will iterate over all combinations of the empty + * range, of which there are none. This is different than iterating over + * all empty combinations, of which there is 1. + * + * @throw ??? Throws if SequenceType's default ctor throws. Strong throw + * guarantee. + */ + CombinationItr() = default; + + /** @brief Makes a new combination iterator over a given sequence. + * + * @param[in] input_set The set to iterate over. + * @param[in] k The number of objects chosen at a time. @p k is + * assumed in the range [0,len(input_set)] if repeats are + * not allowed, otherwise it may be any value. + * @param[in] at_end True if this is the end iterator + * @throws std::bad_alloc If the copy fails because of lack of memory. + * Strong throw guarantee. + * @throws ??? If SequenceType's copy constructor throws. + */ + CombinationItr(const_reference input_set, size_type k, bool at_end) : + set_(input_set), + comb_(k), + current_perm_() { + const size_type n = input_set.size(); + // k==n==0 is possible and leads to -1 (technically ok, -1 choose + // 0=1...) + const size_type eff_size = (n || k ? n + k - 1 : 0); + std::vector temp(!repeat ? n : eff_size, true); + for(size_t i = 0; i < k; ++i) temp[i] = false; + auto perms = Permutations(temp); + current_perm_ = (at_end ? perms.end() : perms.begin()); + update_comb(); + } + + /** @brief Returns a read-only version of the element currently pointed to + * by this iterator. + * + * + * @return The element being pointed to. + * @throws None No throw guarantee. + */ + const_reference dereference() const noexcept { return comb_; } + + /** @brief Makes the iterator point to the next Combination. + * + * Combinations are ordered lexicographically and "next" follows from + * this convention. + * + * @warning Incrementing beyond the end of the container is allowed; + * however, dereferencing the corresponding iterator is + * undefined behavior. + * + * @return The iterator after incrementing + * @throws None No throw guarantee. + */ + CombinationItr& increment() noexcept { + ++current_perm_; + update_comb(); + return *this; + } + + /** @brief Makes the iterator point to the previous Combination. + * + * Combinations are ordered lexicographically and "previous" follows + * from this convention. + * + * @warning Decrementing beyond the beginning of the container is + * allowed; however, dereferencing the corresponding iterator + * is undefined behavior. + * + * @return The iterator after decrementing + * @throws None No throw guarantee. + */ + CombinationItr& decrement() noexcept { + --current_perm_; + update_comb(); + return *this; + } + + /** @brief Moves the current iterator @p n iterations + * + * @param[in] n The number of iterations to move the iterator. Can + * be either forward or backward. + * @returns The current iterator pointing at the element @p n + * iterations away. + * @throws ??? if update_comb() throws same throw guarantee. + * + */ + CombinationItr& advance(difference_type n) { + current_perm_ += n; + update_comb(); + return *this; + } + + /** Compares two CombinationItrs for exact equality + * + * Exact equality is defined as pointing to the same Combination, + * having the same starting Combination, and having both wrapped (or + * not wrapped). + * + * @param[in] other The iterator to compare to. + * @return True if this iterator is exactly the same as @p other + * @throws None No throw guarantee. We assume that SequenceType's equality + * operator is also no throw. + */ + bool are_equal(const CombinationItr& other) const noexcept { + return std::tie(set_, current_perm_) == + std::tie(other.set_, other.current_perm_); + } + + /** @brief Returns the distance between this iterator and another + * + * @param[in] rhs The iterator we want the distance to. + * @returns the distance between the two iterators + * @throw None No throw guarantee + */ + difference_type distance_to(const CombinationItr& rhs) const noexcept { + return current_perm_ - rhs.current_perm_; + } + + /** + * @brief Exchanges the current iterator's state with that of another + * instance. + * + * @param rhs The iterator to exchange state with. After this call @p rhs + * will contain the current iterator's state. + * @throw ??? throws if swapping SequenceType instances throws. If a + * throw occurs the guarantee is weak at best. If SequenceType is + * no throw this function is no throw. + */ + void swap(CombinationItr& rhs) { + std::swap(set_, rhs.set_); + std::swap(comb_, rhs.comb_); + std::swap(current_perm_, rhs.current_perm_); + } + + private: + /// A copy of the parent's set + value_type set_; + + /// The current combination + value_type comb_; + + /// The current permutation + PermutationItr> current_perm_; + + /** + * @brief Updates comb_ to be consistent with current_perm_ + * + * @throw ??? Throws if SequenceType's operator[] or size() member + * function throws. Guarantee is weak unless both member functions + * are no throw, then it is also no throw. + */ + void update_comb() { + const auto& p = *current_perm_; + for(size_type i = 0, counter = 0, bar_count = 0; i < p.size(); ++i) { + if(!p[i]) + comb_[counter++] = set_[!repeat ? i : bar_count]; + else + ++bar_count; + if(counter == comb_.size()) // Early termination, rest are false + break; + } + } + +}; // End class CombinationItr + +} // End namespace detail_ + +/** @brief Makes a container for holding all combinations of a sequence with + * repeats. + * + * Given a sequence of @f$N@f$ elements, there are @f${N+m-1\choose m}@f$ + * combinations of @f$m@f$ elements if repeats are allowed. The class + * resulting from this function will simulate a container filled with all of + * them. It should be noted that the resulting class does not actually contain + * all the elements, but rather the elements are generated on the fly. + * Owing to the underlying algorithm, the order of combinations is + * lexicographic in the positions of the elements NOT the values of the + * elements (if the elements in the input sequence are sorted in lexicographic + * order then the order will also be lexicographic in the elements). + * + * @tparam container_type The sequence for which we are generating + * combinations. Should satisfy the concept of container. + * @param container The sequence of elements for which combinations should be + * formed. + * @param k The number of elements per combination. @p k should be in the + * range [0, container.size()). + * @throws std::bad_alloc if the container can not be copied. Strong throw + * guarantee. + * @throws std::overflow_error if computing the size causes the binomial + * coefficient algorithm to overflow. Strong throw guarantee. + * @throws ??? if the copy constructor of SequenceType throws for any reason. + * Strong throw guarantee. + */ +template +auto CombinationsWithRepeat(container_type&& container, std::size_t k) { + using iterator_type = + detail_::CombinationItr, true>; + return detail_::RangeContainer{ + iterator_type{std::forward(container), k, false}, + iterator_type{std::forward(container), k, true}, + binomial_coefficient(container.size() + k - 1, k)}; +} + +/** @brief Makes a container for holding all combinations of a sequence of + * numbers without repeats. + * + * Given a sequence of @f$N@f$ elements, there are @f${N \choose m}@f$ possible + * ways to choose @f$m@f$ elements. The class + * resulting from this function will simulate a container filled with all of + * them. It should be noted that the resulting class does not actually contain + * all the elements, but rather the elements are generated on the fly. + * Owing to the underlying algorithm, the order of combinations is + * lexicographic in the positions of the elements NOT the values of the + * elements (if the elements in the input sequence are sorted in lexicographic + * order then the order will also be lexicographic in the elements). + * + * @tparam container_type The sequence for which we are generating + * combinations. Should satisfy the concept of container. + * @param container The sequence of elements for which we are generating all + * combinations of length @p k. + * @param k The number of elements in any generated combination. @p k should + * be in the range [0, container.size()). + * @return A container instance suitable for inclusion in a range-based for + * loop. + * @throws std::bad_alloc if the container can not be copied. Strong throw + * guarantee. + * @throws std::overflow_error if computing the size causes the binomial + * coefficient algorithm to overflow. Strong throw guarantee. + * @throws ??? if the copy constructor of SequenceType throws for any reason. + * Strong throw guarantee. + */ +template +auto Combinations(container_type&& container, std::size_t k) { + using iterator_type = + detail_::CombinationItr, false>; + return detail_::RangeContainer{ + iterator_type{std::forward(container), k, false}, + iterator_type{std::forward(container), k, true}, + binomial_coefficient(container.size(), k)}; +} + +} // End namespace utilities diff --git a/utilities/iter_tools/enumerate.hpp b/utilities/iter_tools/enumerate.hpp new file mode 100644 index 0000000..9e0d183 --- /dev/null +++ b/utilities/iter_tools/enumerate.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "utilities/iter_tools/range.hpp" +#include "utilities/iter_tools/zip.hpp" + +namespace utilities { + +template +auto Enumerate(container_type&& c) { + auto r = Range(c.size()); + return Zip(r, std::forward(c)); +} + +} // namespace utilities diff --git a/utilities/iter_tools/permutations.hpp b/utilities/iter_tools/permutations.hpp new file mode 100644 index 0000000..b7f2255 --- /dev/null +++ b/utilities/iter_tools/permutations.hpp @@ -0,0 +1,300 @@ +#pragma once +#include "utilities/iter_tools/range_container.hpp" +#include "utilities/mathematician/combinatorics.hpp" +#include "utilities/mathematician/integer_utils.hpp" +#include "utilities/type_traits/iterator_types.hpp" +#include //For is_permutation, next/prev permutation +#include //For maximum value of size_t +#include //For std::tie + +namespace utilities { +namespace detail_ { + +/** @brief The class that actually implements the guts of generating + * permutations. + * + * This class is actually written in terms of the next/prev permutation + * generators included in the STL. + * + * @section Technical notes + * - Since next/prev permutation mutate the container they operate on, this + * class actually stores a deep copy of the container it is forming + * permutations of so as to avoid altering the original container. + * - next/prev permutation wrap around on themselves. For this reason we + * need to maintain how many times we've iterated so as to know when we have + * finished. + * + * + * @tparam SequenceType the type of the input sequence and the resulting + * permutations. Should satisfy the concept of sequence. + * @todo C++17 allows for inheriting default ctors. + */ +template +class PermutationItr + : public detail_::RandomAccessIteratorBase, + SequenceType> { + /// Type of this class (to simplify defining base_type) + using my_type = PermutationItr; + + /// Type of the base class (to simplify scoping types) + using base_type = detail_::RandomAccessIteratorBase; + + public: + /// Brings some of base class's typedefs into scope + ///@{ + using value_type = typename base_type::value_type; + using const_reference = typename base_type::const_reference; + using difference_type = typename base_type::difference_type; + using size_type = typename base_type::size_type; + ///@} + + /** @brief Makes an iterator that iterates over nothing. + * + * This iterator is capable of iterating over the empty range. + * + * @note A default iterator is not the same as an iterator over the + * empty set. The former iterates over an empty range, whereas the latter + * iterates over permutations of the empty set (for which there is one + * element, the empty set). + * + * @throws ??? if SequenceType's default ctor throws. Same guarantee as + * SequenceType's default ctor. + */ + PermutationItr() = default; + + /** @brief Makes an instance that will generate permutations of an input + * sequence. + * + * This ctor will make a deep copy of the input sequence so as to avoid + * mutating it. Consequentially, all state is stored in this + * iterator and the iterator is valid even if the input container goes out + * of scope. + * + * @param[in] input_set The set to iterate over. + * @param[in] offset Which permutation to start with. Offset should be + * in the range [0, size) where permutations are number + * lexicographically starting from the input sequence and ending when + * the original sequence is regenerated. + * @throws ??? If SequenceType's copy constructor throws. Strong throw + * guarantee. + * @throws ??? If permutation_to_decimal throws. Strong throw guarantee. + */ + PermutationItr(const_reference input_set, size_type offset) : + orig_set_(input_set), + sorted_orig_([&]() { + value_type temp(input_set); // I guess by value is still const... + std::sort(temp.begin(), temp.end()); + return temp; + }()), + set_(input_set), + offset_(offset), + dx_(permutation_to_decimal(input_set, sorted_orig_)) {} + + /** @brief Allows access to the current permutation. + * + * In accordance with usual C++ practice the element is returned by + * reference. However, any changes made to the element will be overridden + * when the iterator is incremented or decremented. + * + * @note The base class will use this function to implement both the + * read-only and the read/write dereference operation via const_cast. + * + * @return The element being pointed to. + * @throws None. No throw guarantee. + */ + const_reference dereference() const override { return set_; } + + /** @brief Makes the iterator point to the next permutation. + * + * Permutations are ordered lexicographically and "next" follows from + * this convention. If the current permutation is the lexicographically + * greatest permutation the next permutation is the lexicographically + * lowest permutation (such behavior is possible if the input sequence is + * not sorted in lexicographical order to begin with). + * + * @warning Incrementing beyond the last permutation is allowed; + * however, dereferencing the corresponding iterator is + * undefined behavior. + * + * @return The iterator by reference after incrementing the permutation. + * @throws ??? if SequenceType's begin() or end() function throws or if + * std::next_permutation throws given the resulting iterators. + * Same throw guarantee as the throwing function. + */ + PermutationItr& increment() { + std::next_permutation(set_.begin(), set_.end()); + ++offset_; + return *this; + } + + /** @biref Compares two PermutationItrs for exact equality. + * + * Exact equality is defined as: + * 1. Containing the same permutation. + * 2. Having the same starting permutation. + * 3. Having both wrapped or not wrapped. + * - Relevant for comparing the first permutation to the one just past + * the end (which is the same permutation) + * + * @par Implementation Notes + * - It suffices to check orig_set_ and not dx_ and sorted_orig_ + * as the latter two are determined by orig_set_ and are not changed + * during the course of the iterator's lifetime. + * + * @param[in] rhs The iterator to compare to. + * @return True if this iterator is exactly the same as @p rhs + * @throws None No throw guarantee. + */ + bool are_equal(const PermutationItr& rhs) const noexcept { + return std::tie(orig_set_, set_, offset_) == + std::tie(rhs.orig_set_, rhs.set_, rhs.offset_); + } + + /** @brief Makes the iterator point to the previous permutation. + * + * PermutationsImpl are ordered lexicographically and "previous" follows + * from this convention. If the current permutation is the + * lexicographically lowest permutation, decrementing will generate the + * lexicographically highest permutation. This class is such that + * decrementing an iterator that is just past the end, will generate the + * last permutation. + * + * @warning Decrementing beyond the input sequence is allowed, but + * dereferencing the resulting state is undefined behavior. + * + * @return The iterator after decrementing + * @throws ??? if SequenceType's begin() or end() member functions throw or + * if prev_permutation throws with the resulting iterators. Same + * guarantee as the throwing function. + */ + PermutationItr& decrement() { + std::prev_permutation(set_.begin(), set_.end()); + --offset_; + return *this; + } + + /** @brief Advances the current iterator @p n iterations. + * + * This function can be used to skip permutations either in the forward + * (positive @p n values) or backwards (@p negative n values) directions. + * + * @param[in] n The number of iterations to move the iterator. Positive + * @p n produce lexicographically larger permutations whereas + * negative @p n produces lexicographically smaller + * permutations. + * @returns The current iterator pointing at the element @p n + * iterations away. + * @throws std::bad_alloc if decimal_to_permutation has insufficient + * memory to complete. Strong throw guarantee. + * + */ + PermutationItr& advance(difference_type n) { + set_ = decimal_to_permutation(offset_ + dx_ + n, sorted_orig_); + offset_ += n; // After above call for strong throw guarantee + return *this; + } + + /** @brief Returns the number of permutations between this and @p rhs + * + * This is actually a lot more complicated then it sounds owing to the fact + * that permutations are allowed to start from sequences that are not + * sorted lexicographically. To that end, we need to get the absolute + * offset (relative to the lexicographically least permutation) for both + * this iterator and @p rhs. If we let @f$\Delta X@f$ be the offset of + * this iterator's initial permutation and @f$\Delta X'@f$ be the offset of + * @p rhs's initial permutation, then the total shift of this iterator is + * @f$\Delta Y = \Delta X + offset_@f$ and that of @p rhs is @f$\Delta + * Y' = \Delta X' + rhs.offset_@f$. Consequentially the total distance + * from this iterator to @p rhs is: + * @f[ + * \Delta Y' - \Delta Y =\Delta X'- \Delta X+ rhs.offset_ -offset_ + * @f] + * + * @param[in] rhs The iterator to compare against. It is assumed that + * @p rhs's state is contained with this instance's range. + * @return The number of permutations between this and other + * @throws std::invalid_argument if @p rhs did not start from the same + * sequence. Strong throw guarantee. + * + * + */ + difference_type distance_to(const PermutationItr& rhs) const { + difference_type ddx = UnsignedSubtract(rhs.dx_, dx_); + difference_type doff = UnsignedSubtract(rhs.offset_, offset_); + return ddx + doff; + } + + /** + * @brief Swaps the state of the current instance with that of another. + * + * + * @param rhs the instance to swap with. After the operation it will + * contain the state of the current instance. + * @throw ??? if SequenceType's swap function throws. Guarantee is no throw + * if SequenceType's swap is also no throw. Otherwise it is weak at + * best. + */ + void swap(PermutationItr& rhs) { + std::swap(orig_set_, rhs.orig_set_); + std::swap(sorted_orig_, rhs.sorted_orig_); + std::swap(set_, rhs.set_); + std::swap(offset_, rhs.offset_); + std::swap(dx_, rhs.dx_); + } + + private: + /// A copy of the parent's set, doesn't get modified + value_type orig_set_; + + /// A copy of the set, sorted lexicographically + value_type sorted_orig_; + + /// A copy of the parent's set, modified by next/prev permutation + value_type set_; + + /// The number of increments from the first call + size_type offset_ = 0; + + /// Number of increments orig_set_ is from lexicographically lowest seq. + size_type dx_ = 0; + +}; // End class PermutationItr +} // namespace detail_ + +/** + * @brief Makes a container "filled" with all unique permutations of a given + * sequence. + * + * This is a convenience function for making a container filled with all + * unique permutations of a given sequence such that the types of the container + * (and consequentially the returned permutations) are inferred automatically. + * It should be noted that the returned container does not actually contain all + * unique permutations of the sequence, rather permutations are generated on + * the fly. Ultimately it relies on std::next_permutation/std::prev_permutation + * and thus can only generate unique permutations. + * + * @note Permutations are generated in lexicographical order, wrapping around + * when necessary, until the original sequence is regenerated. + * + * @note If for some reason you want all permutations of a sequence (and not + * just the unique ones) it suffices to put the numbers 0 to the length of your + * sequence minus 1 in a container and treat the resulting permutations as the + * indices. + * + * @tparam container_type The type of the original sequence and also the type of + * the generated permutations. + * @param container The container to form all unique permutations of. + * @return A container filled with all unique permutations of @p container. + * @throw ??? If @p container_type 's copy ctor throws. + */ +template +auto Permutations(container_type&& container) { + using raw_container_t = std::decay_t; + using iterator_type = detail_::PermutationItr; + const auto nperms = n_permutations(std::forward(container)); + return detail_::RangeContainer{ + iterator_type{std::forward(container), 0}, + iterator_type{std::forward(container), nperms}, nperms}; +} + +} // namespace utilities diff --git a/utilities/iter_tools/range.hpp b/utilities/iter_tools/range.hpp new file mode 100644 index 0000000..8149841 --- /dev/null +++ b/utilities/iter_tools/range.hpp @@ -0,0 +1,323 @@ +#pragma once +#include "utilities/iter_tools/range_container.hpp" +#include "utilities/type_traits/iterator_types.hpp" + +namespace utilities { +namespace detail_ { + +/** @brief Implements a generator that produces all elements in a sequence + * + * This class is the guts of the Range semantics. It basically holds the + * start, end, increment, and current value and keeps adding or subtracting + * increment from the current value until end is reached. No checks to make + * sure we haven't stepped out of the range are ever performed. + * + * Note that care needs to be taken with the arithmetic in case we have a mix + * of signed and unsigned quantities. + * + * @tparam element_type The type of the value in the sequence. Assumed to be + * a numeric POD type. + */ +template +class RangeItr + : public RandomAccessIteratorBase, element_type> { + private: + /// Typedef of the base type for sanity + using base_type = + detail_::RandomAccessIteratorBase, element_type>; + + public: + /// Pulls the const_reference typedef into scope + using const_reference = const element_type&; + + /// Pulls the difference_type typedef into scope + using typename base_type::difference_type; + + /** + * @brief Makes a default RangeItr instance. + * + * By default a RangeItr instance is capable of iteratring over the empty + * range. + * + * @throw ??? Throws if the default constructors of element_type throw. + * Strong throw guarantee. + */ + RangeItr() = default; + + /** + * @brief Deep copies another RangItr instance. + * + * @param[in] rhs The RangeItr to deep-copy. + * @throw ??? If either element_type's or difference_type's copy + * constructors throw. Strong throw guarantee. + */ + RangeItr(const RangeItr& /*rhs*/) = default; + + /** + * @brief Takes ownership of another RangeItr instance's state. + * @param[in] rhs The RangeItr to take ownership of. After this function is + * called @p rhs is in a valid, but otherwise undefined state. + * @throw ??? Throws if either element_type's or difference_type's copy + * constructors throw. Strong throw guarantee. + */ + RangeItr(RangeItr&& /*rhs*/) = default; + + /** + * @brief Frees up memory associated with this iterator. + * @throw None. No throw guarantee. + */ + ~RangeItr() noexcept = default; + + /** + * @brief Assigns a deep copy of another RangeItr to the current instance. + * + * @param[in] rhs The RangeItr instance to deep-copy. + * @return The current instance containing a deep-copy of @p rhs's state. + * @throw ??? Throws if element_type's or difference_type's copy + * constructors throw. Strong throw guarantee. + */ + RangeItr& operator=(const RangeItr& /*rhs*/) = default; + + /** + * @brief Assigns a deep copy of another RangeItr to the current instance. + * @param[in] rhs The RangeItr instance to take ownership of. After the + * call + * @p rhs is in a valid, but otherwise undefined state. + * @return The current instance containing @p rhs's state. + * @throw ??? Throws if element_type's or difference_type's move assignment + * operator throws. Strong throw guarantee. + */ + RangeItr& operator=(RangeItr&& /*rhs*/) = default; + + /** + * @brief The primary constructor for the RangItr class. + * + * This constructor is ultimately called by the Range function to provide + * the desired API. + * + * @param start The value the range starts at. + * @param stop The value just after the last value in the range + * @param increment The amount to increment by each iteration. + * + * @throw ??? If copying element_type or difference_type throws. Strong + * throw guarantee. + */ + RangeItr(element_type start, element_type stop, difference_type increment) : + start_(start), + stop_(stop), + increment_(increment), + value_(start_) {} + + private: + /// Allows base class to implement random access iterator + friend base_type; + + /// Implements operator*() + const_reference dereference() const override { return value_; } + + /// Implements operator++ + RangeItr& increment() override { return advance(increment_); } + + /// Implements operator-- + RangeItr& decrement() override { return advance(-1 * increment_); } + + /// Implements operator+= + RangeItr& advance(difference_type adv) override { + adv > 0 ? value_ += adv : value_ -= -1 * adv; + return *this; + } + + /// Implements itr1 - itr2 + difference_type distance_to(const RangeItr& rhs) const noexcept { + const bool is_positive = rhs.value_ > value_; + difference_type abs_diff = + is_positive ? rhs.value_ - value_ : value_ - rhs.value_; + return is_positive ? abs_diff : -1 * abs_diff; + } + + /** @brief Implements iterator equality comparisons. + * + * Two RangeItrs are defined to be equal if they currently point to the + * same value regardless of whether or no that value resides in the + * same container. + * + * @param rhs The RangeItr to compare to. + * @return True if this and @p rhs point to the same value and false + * otherwise. + * @throw None. No throw guarantee. + */ + bool are_equal(const RangeItr& rhs) const noexcept { + return value_ == rhs.value_; + } + + /// The first value in our range + element_type start_ = 0; + + /// The last value in our range + element_type stop_ = 0; + + /// The amount to increment by + difference_type increment_ = 1; + + /// The current value of the iterator + element_type value_ = 0; +}; + +/** @brief Simulates a container filled with all the values in a range. + * + * The Range class is a C++ implementation of Python's range generator. This + * object behaves as if it is a container filled with all numeric values in a + * range [start, stop) that are separated by some defined amount (by default + * 1). + * + * Like many of the containers in the IterTools sublibrary, this container does + * not actually store all of the values in the range, but rather computes them + * on the fly. It's worth noting that this class does not assume that the + * range is integral (although it does default to it). That is it is possible + * to generate ranges like: + * + * @verbatim + * 1.1, 1.2, 1.3, ..., 1.9 + * @endverbatim + * + * with a call like: + * @code + * Range(1.1, 2.0, 0.1); + * @endcode + * + * The Range class satisfies the C++ concept of container. + * + * @tparam element_type The type of the numbers inside this container. Must + * satisfy the concept of copyable. + */ +template +class RangeImpl + : public detail_::RangeContainer> { + private: + /// Typedef of base class for sanity + using base_type = detail_::RangeContainer>; + + public: + using typename base_type::difference_type; + + /** + * @brief Makes an empty range. + * + * @throw ??? Throws if element_type's default constructor throws. Strong + * throw guarantee. + */ + RangeImpl() = default; + + /** + * @brief Makes the current container be filled with a deep copy of another + * RangeImpl container. + * + * @param[in] rhs The container to copy. + * @throw ??? Throws if the copy constructor of the iterator throws. Strong + * throw guarantee. + */ + RangeImpl(const RangeImpl& /*rhs*/) = default; + + /** + * @brief Takes ownership of another RangeImpl instance. + * + * @param[in] rhs The instance to take ownership of. After this call the + * instance is in a valid, but otherwise undefined state. + * @throw ??? Throws if the move constructor of the iterator throws. + */ + RangeImpl(RangeImpl&& /*rhs*/) = default; + + /** + * @brief Frees up memory associated with the current RangeImpl instance. + * + * Note because the iterators maintain all state iterators to this container + * remain valid even after the container has been removed from scope. + * + * @throw None. No throw guarantee. + */ + ~RangeImpl() noexcept = default; + + /** + * @brief Assigns a deep-copy of another RangeImpl to the current instance. + * + * @param[in] rhs The instance to copy. + * @return The current instance containing a deep-copy of @p rhs's state + * @throw ??? Throws if the copy constructor of the iterator throws. Strong + * throw guarantee. + */ + RangeImpl& operator=(const RangeImpl& /*rhs*/) = default; + + /** + * @brief Takes ownership of another RangeImpl instance's state. + * + * @param[in] rhs The instance to take ownership of. After this call @p rhs + * is in a valid, but otherwise undefined state. + * + * @return The current instance containing @p rhs's state. + * @throw ??? Throws if the move constructor of the iterator throws. Strong + * throw guarantee. + */ + RangeImpl& operator=(RangeImpl&& /*rhs*/) noexcept = default; + + /** + * @brief The most useful constructor for the RangeContainer class. + * + * Makes a RangeContainer holding all values [start, stop). The difference + * between subsequent values is determined by @p increment + * + * @note It is assumed that start+n*increment == stop for some integer n. It + * is undefined behavior if this is not true. + * + * @param start The first value in the sequence. + * @param stop The value to stop before. + * @param increment The number to add to generate the next value in the + * sequence. Can be negative to make a sequence countdown. + */ + RangeImpl(element_type start, element_type stop, + difference_type increment = 1) : + base_type(RangeItr(start, stop, increment), + RangeItr(stop, stop, increment), + stop > start ? (stop - start) / increment : + (start - stop) / (-1 * increment)) {} +}; + +} // namespace detail_ + +/** @brief Wrapper function for making a RangeImpl object. + * @relates detail_::RangeImpl + * + * This wrapper exists so that the user doesn't have to specify the template + * parameter for the type, but rather it can be determined automatically. + * + * @tparam element_type The type of the elements in the produced range. Must + * be copyable. + * @param stop The value [0, stop) for the produced range + * @return A container with the resulting range. + * @throw ??? Throws if RangeImpl's constructor throws. Strong throw guarantee. + */ +template +auto Range(element_type stop) { + return detail_::RangeImpl(0, stop, 1); +} + +/** @brief Wrapper function for making a RangeImpl object. + * @relates detail_::RangeImpl + * + * This wrapper exists so that the user doesn't have to specify the template + * parameter for the type, but rather it can be determined automatically. + * + * @tparam element_type The type of the elements in the produced range. Must + * be copyable. + * @param start The first value in the range. + * @param stop Just past the last value in the desired range + * @param increment The amount to increase each value by + * + * @return A container with the resulting range. + * @throw ??? Throws if RangeImpl's constructor throws. Strong throw guarantee. + */ +template +auto Range(element_type start, element_type stop, element_type increment = 1) { + return detail_::RangeImpl(start, stop, increment); +} + +} // namespace utilities diff --git a/utilities/iter_tools/range_container.hpp b/utilities/iter_tools/range_container.hpp new file mode 100644 index 0000000..683c212 --- /dev/null +++ b/utilities/iter_tools/range_container.hpp @@ -0,0 +1,222 @@ +#pragma once +#include // For std::numeric_limits +#include // For std::tie +#include + +namespace utilities { +namespace detail_ { + +/** @brief Simulates a container filled all elements in a range. + * + * This class is largely code factorization from the various IterTools + * containers. It contains no actual state aside from copies of the starting + * and ending iterators (i.e. it does not actually store every element in the + * range like it pretends to). Consequentially all iterators to this container + * are constant iterators as they can not mutate the container. + * + * This class implements most of the container API for the derived class. The + * derived class is only responsible for the constructors. That said instances + * of RangeContainers are never meant to be instantiated by the user. + * + * @todo It's possible (maybe with a bit of tweaking) to remove some of the + * boilerplate in IterTools classes using this class as a base class. + * + * @tparam IteratorType The type of iterator used to generate the elements. We + * presently assume it at least meets the concept of bidirectional iterator. + */ +template +class RangeContainer { + public: + /// The type of the elements contained within this container + using value_type = typename IteratorType::value_type; + + /// The type of a reference to an element in this container + using reference = value_type&; + + /// The type of a reference to a read-only element in this container + using const_reference = const value_type&; + + /// The type of an iterator over this container. + using iterator = IteratorType; + + /// The type of an iterator that only reads the elements of this container. + using const_iterator = IteratorType; + + /// The type of the difference between two elements of this container + using difference_type = long int; + + /// The type of an offset or index to an element in this container + using size_type = std::size_t; + + /** @brief Constructs an empty container. + * + * @throw ??? Throws if the default constructor for the iterator throws. + * Strong throw guarantee. + */ + RangeContainer() = default; + + /** @brief The primarily useful constructor capable of making a container + * holding the specified range. + * + * @param start_itr An iterator pointing to the first element in the + * container. + * @param end_itr An iterator pointing to just past the last element in the + * container. + * @param size_in The number of elements between end_itr and start_itr. + * @throw ??? If IteratorType's copy constructor throws. Strong throw + * guarantee. + */ + RangeContainer(iterator start_itr, iterator end_itr, size_type size_in) : + start_(start_itr), + end_(end_itr), + size_(size_in) {} + + /** + * @brief Returns the @p i -th element of the container. + * @param i the index of the desired element. + * @return a copy of the @p i -th element. + * @throw ??? if either the copy constructor or std::advance throw. Same + * guarantee as IteratorType's copy ctor. + */ + value_type operator[](size_type i) const { + const_iterator copy_of_start(start_); + std::advance(copy_of_start, i); + return *copy_of_start; + } + + /** @brief Returns an iterator to first element of the container. + * + * All the state of the container is contained within the iterator and thus + * the iterator remains valid even if the container goes out of scope. + * + * @return An iterator pointing to the first element of this container. + * @throw ??? Throws if the iterator's copy constructor throws. Same + * guarantee as IteratorType's copy constructor. + */ + iterator begin() { return start_; } + + ///@copydoc begin() + const_iterator begin() const { return start_; } + + ///@copydoc begin() + const_iterator cbegin() const { return start_; } + + /** + * @brief Returns an iterator that points to just past the last element of + * the container. + * + * The entire state of the container is kept in the iterators, hence the + * resulting iterator is valid even if the container goes out of scope. + * + * @return An iterator set to the element just past the last element of the + * array. + * + * @throw ??? Throws if the iterator's copy constructor throws. Same + * guarantee as IteratorType's copy ctor. + */ + iterator end() { return end_; } + + ///@copydoc end() + const_iterator end() const { return end_; } + + ///@copydoc end() + const_iterator cend() const { return end_; } + + /** @brief Returns the number of elements in this container. + * + * + * @return The number of elements in this container. + * @throw None. No throw guarantee. + */ + size_type size() const noexcept { return size_; } + + /** + * @brief The theoretical maximum size of the container as determined by + * known software and hardware limitations. + * + * Since this container does not actually store its contents, but rather + * generates them on the fly, the maximum number of elements this container + * can hold is only bound by the requirement that the size of the container + * be indexable, which is the same as the maximum value of size_t. + * + * @return The theoretical maximum number of elements this container can + * hold. + * + * @throw None. No throw guarantee. + */ + static constexpr size_type max_size() { + return std::numeric_limits::max(); + } + + /** + * @brief Used to check if a container "contains" elements. + * + * Since RangeContainer instances do not actually contain their elements + * this check is really ensuring that the range of elements traversed by the + * iterators is non-empty. + * + * @return True if the container is empty and false otherwise. + * @throw ??? if the equality operator of IteratorType throws. Same + * guarantee as IteratorType's equality operator. + */ + bool empty() const { return start_ == end_; } + + /** + * @brief Compares two RangeContainer instances for equality. + * + * Equality of two RangeContainer instances is defined as having internal + * iterators that compare equal. + * + * @param rhs The RangeContainer instance to compare to. + * @return True if the two instances are equivalent and false otherwise. + * @throw ??? Throws if the equality comparison operator of the iterator + * throws. Strong throw guarantee. + */ + bool operator==(const RangeContainer& rhs) const { + return std::tie(start_, end_) == std::tie(rhs.start_, rhs.end_); + } + + /** + * @brief Determines if two RangeContainer instances are different. + * + * Equality of RangeContainer instances is defined as wrapping iterators + * that compare equal. + * + * @param rhs The container to compare to. + * @return True if either iterator in this class differs from the + * corresponding iterator in @p rhs. + * @throw ??? Throws if operator==() of the iterators throws. Same + * guarantee as equality operator. + */ + bool operator!=(const RangeContainer& rhs) const { + return !((*this) == rhs); + } + + /** + * @brief Swaps the contents of two RangeContainer instances. + * + * @param rhs The container to swap this one with. After the swap @p rhs + * will contain the contents of the current instance. + * @throw ??? if std::swap(IteratorType, IteratorType) throws. Is no throw + * guarantee if swapping IteratorType instances is no throw, + * otherwise it's weak at best. + */ + void swap(RangeContainer& rhs) { + std::swap(start_, rhs.start_); + std::swap(end_, rhs.end_); + std::swap(size_, rhs.size_); + } + + private: + /// An iterator pointing to the first element + iterator start_; + + /// An iterator to just past the last element + iterator end_; + + /// The number of elements between @p start_ and @p end_ + size_type size_ = 0; +}; + +} // namespace detail_ +} // namespace utilities diff --git a/utilities/iter_tools/tuple_container.hpp b/utilities/iter_tools/tuple_container.hpp new file mode 100644 index 0000000..19ebee7 --- /dev/null +++ b/utilities/iter_tools/tuple_container.hpp @@ -0,0 +1,417 @@ +#pragma once +#include "utilities/type_traits/iterator_types.hpp" +#include "utilities/type_traits/tuple_utilities.hpp" //Includes tuple always +#include //For std::min +#include + +namespace utilities { +namespace detail_ { + +/// Forward declaration of TupleContainerImpl for meta-programming +template +class TupleContainerImpl; + +/// Struct which contains value==false if T is not a TupleContainerImpl instance +template +struct IsTupleContainerImpl : std::false_type {}; + +/// Specialization of IsTupleContainerImpl to trigger value == true +template +struct IsTupleContainerImpl> + : std::true_type {}; + +/// This will allow us to use iterators which are actually raw pointers +template +struct GetValueType { + using type = typename T::value_type; +}; + +/// Partial specialization to catch raw pointers +template +struct GetValueType { + using type = T; +}; + +template +using IteratorType = + std::conditional_t>::value, + typename T::iterator, typename T::const_iterator>; + +/** @brief Simulates a container filled with some set of tuples formed from a + * series of containers. + * + * This class is primarily code factorization for iterating over a series + * of containers. + * + * @note This class is the implementation of the innards of the TupleContainer + function, + * but for all intents and purposes it is TupleContainer. The TupleContainer + function only exists so + * the usage syntax is not: + * @code + * for(auto& x : + TupleContainer(iterator1,iterator2,...)) + * { + * ... + * } + * @endcode + * + * + * @note At the moment the values returned in the tuples are copies and not the + * actual ones in the zipped containers. Everything is by reference + * until apply_functor_to_tuple is called to modify the buffer of the + * iterator. + * @tparam IncrementFunctor The type of a functor that can be used to compute + * the next tuple in the sequence. Must contain a member function with + * the signature: + * @code + * template + * void run(const IteratorType& start, const IteratorType& end, + IteratorType& value, std::index_sequence); + * @endcode + * where start and end are tuples containing iterators to the original + * containers as generated by calling begin and end respectively, value + * is a tuple containing the current value of the iterators (and should + * be updated). + * + * @tparam ContainerTypes The types of the containers that will be zipped + * together. + * + */ +template +class TupleContainerImpl { + private: + /// Forward declaration of the actual iterator type + class TupleContainerIterator; + + /// The type of a tuple holding iterators to the containers + using iterator_tuple = std::tuple...>; + + public: + /// The type of an element in this container. + using value_type = std::tuple>::type>...>; + /// The type of a reference to an element of this container + using reference = value_type&; + /// The type of a read-only element of this container + using const_reference = const value_type&; + /// The type of an iterator to this container (can't actually modify) + using iterator = TupleContainerIterator; + /// The type of a read-only iterator to this container + using const_iterator = TupleContainerIterator; + /// The type of the difference between two pointers + using difference_type = long int; + /// The type of an index or offset into this container + using size_type = std::size_t; + + /** @brief Constructs a container filled with no tuples taken from no + * containers. + * + * @throw None. No throw guarantee. + */ + TupleContainerImpl() noexcept = default; + + /** @brief Constructs a new TupleContainerImpl by grabbing the begin and + * end iterators of a container + * @tparam SizeFunctor The type of a functor that will be used to compute + * the number of elements in this container. Must have a static + * member variable named @p initial_value containing the initial + * value for the accumulate and must have a function named run with + * the following interface: + @code + template + auto run(std::size_t curr_size, T&& container) const; + @endcode + where @p curr_size is the current value of the accumulate and + @p container is the next container whose size is to be included. + The function should return the updated value of the current size. + * @tparam InputTypes The types of the containers. + * @param containers The actual container instances to zip. Note that the + * iterators stored in this class are tied to the original containers + * still and thus their validity is also tied to those containers. + * + * @throw ??? This constructor will call the size(), begin() and end() + * members of each container if any of those member functions + * throw, then this constructor will also throw. Strong throw + * guarantee. + */ + template + TupleContainerImpl(SizeFunctor f, InputTypes&&... containers) : + size_( + sizeof...(InputTypes) ? + tuple_accumulate(std::tie(std::forward(containers)...), f, + SizeFunctor::initial_value) : + 0L), + begin_(!size_, std::forward(containers)...), + end_(true, std::forward(containers)...) {} + + /** @brief Makes a copy of the current container. + * + * It should be noted that this is a deep copy of the TupleContainerImpl + * container, which means that all internal state will be deep copied. The + * internal state will not be copied recursively though (which in turn + * means that the tuple copies will still be filled with shallow copies of + * the input containers). + * + * @param rhs The container to copy. + * @throw None. No throw guarantee. Can't be noexcept b/c tuple copy + * constructor is not noexcept; however, + * + */ + TupleContainerImpl(const TupleContainerImpl& /*rhs*/) = default; + + /** @brief Takes ownership of another container. + * + * @param rhs The container to take over. After this call @p rhs is in a + * valid, but otherwise undefined state. + * @throw None. No throw guarantee. + * + */ + TupleContainerImpl(TupleContainerImpl&& /*rhs*/) = default; + + /** @brief Deletes the current instance. + * + * This doesn't delete the containers that were zipped together. + * + * @throw None. No throw guarantee. + */ + ~TupleContainerImpl() = default; + + /** @brief Assigns a deep copy of another TupleContainerImpl to this one. + * + * The same comments as the copy constructor apply here, namely that the + * result still contains shallow copies of the actual containers. + * + * @param rhs The TupleContainerImpl to copy the state of. + * + * @throw None. No throw guarantee. Can't be marked as noexcept because + * tuple copy constructor is not noexcept + */ + TupleContainerImpl& operator=(const TupleContainerImpl& /*rhs*/) = default; + + /** @brief Assigns another TupleContainerImpl's state to this one. + * + * @param rhs The TupleContainerImpl to take the state of. After this call + * @p rhs is in a valid, but otherwise undefined state. + * @return The current instance containing @p rhs 's state. + * @throw None. No throw guarantee. + */ + TupleContainerImpl& operator=(TupleContainerImpl&& /*rhs*/) = default; + + /** @brief Returns the number of tuples in this container. + * + * Given a list of containers such that the @f$i@f$-th container contains + * @f$N_i@f$ elements. This function returns the smallest @f$N_i@f$ as + * that is the maximum number of iterations possible. + * + * @return The number of tuples in this container. + * @throw None. No throw guarantee. + */ + size_type size() const noexcept { return size_; } + + /** @brief Returns the maximum number of elements this container can + * possibly hold ignoring hardware considerations. + * + * At the moment this function simply returns the maximum of @p size_type. + * + * @return The maximum number of elements this container can possibly hold + * ignoring hardware considerations. + * + * @throw None. No throw guarantee. + */ + constexpr static size_type max_size() noexcept { + return std::numeric_limits::max(); + } + + /** + * @brief Returns true if the current instance contains no elements + * + * @return True if the current instance holds no elements and false if it + * holds even one element. + * @throw None. No throw guarantee. + */ + bool empty() const noexcept { return !size_; } + + /** + * @brief Creates an iterator that points to the first tuple of elements in + * the zipped containers. + * + * @return An iterator pointing to first tuple of elements inside the + * zipped container. + */ + iterator begin() { return begin_; } + + ///@copydoc begin() + const_iterator cbegin() const { return begin_; } + + /** + * @brief Creates an iterator that points to just past the last tuple of + * elements in the zipped containers. + * + * @return An iterator pointing to just past the last tuple of elements + * inside the zipped container. + */ + iterator end() { return end_; } + + ///@copydoc end() + const_iterator cend() const { return end_; } + + /** + * @brief Determines if two TupleContainerImpl instances are holding the + * same elements. + * + * Two TupleContainerImpl instances are assumed equal if they have the same + * first and last elements (size is checked for good measure). Given that + * incrementing is implemented via functor, which is part of the type, this + * should be sufficient to ensure the same sequence will be generated. + * + * @tparam OtherContainers The types of the other containers. Must satisfy + * the same concepts as ContainerTypes. + * @param rhs The actual TupleContainerImpl instance to compare to. + * @return True if the the two instances hold the same elements. + * @throw None. No throw guarantee. + */ + template + bool operator==(const TupleContainerImpl& rhs) const + noexcept { + return std::tie(begin_, end_, size_) == + std::tie(rhs.begin_, rhs.end_, rhs.size_); + } + + /** + * @brief Determines if two TupleContainerImpl instances are different + * containers. + * + * This function uses the same definition of equality as operator== + * + * @tparam OtherContainers The types of the other containers. Must satisfy + * the same concepts as ContainerTypes. + * @param rhs The actual TupleContainerImpl instance to compare to. + * @return True if the the two instances are different containers + * false if the container instances are the same. + * @throw None. No throw guarantee. + */ + template + bool operator!=(const TupleContainerImpl& rhs) const + noexcept { + return !((*this) == rhs); + } + + private: + /// The number of containers we are zipping together + constexpr static std::size_t ncontainers_ = sizeof...(ContainerTypes); + + /// The number of iterations in this container + std::size_t size_ = 0; + + /// Points to start of each container + TupleContainerIterator begin_; + + /// Points to end of each container + TupleContainerIterator end_; + + /** @brief The class responsible for iterating over the containers stored in + * a TupleContainerImpl instance. + * + * TupleContainerIterator instances store iterators to the zipped + * containers and are only valid so long as said containers are in scope. + * Since the actual iterators are stored within the TupleContainerIterator + * instance, TupleContainerIterators remain valid even if the creating + * TupleContainerImpl goes out of scope. + * + */ + class TupleContainerIterator + : public detail_::InputIteratorBase { + private: + /// Allows TupleContainerImpl to create an iterator + friend class TupleContainerImpl; + /// Allows the base class to call the implementation functions + friend class detail_::InputIteratorBase; + + /** @brief Creates a TupleContainerIterator instance. + * + * The TupleContainerIterator keeps a copy of the iterators internally + * and thus is valid even if the TupleContainerImpl goes out of scope. + * If any of the containers being iterated over go out of scope the + * TupleContainerIterator is invalidated. + * + * @param containers The tuple of containers to iterate over. + * @param at_end If true the resulting iterator will represent an + * iterator just past the last element, otherwise it's pointing to + * the first element. + */ + template + TupleContainerIterator(bool at_end, InputTypes&&... containers) : + start_(tuple_transform( + std::tie(std::forward(containers)...), CallBegin())), + end_(tuple_transform( + std::tie(std::forward(containers)...), CallEnd())), + value_(!at_end ? start_ : end_), + buffer_(!at_end ? tuple_transform(value_, Derefer()) : value_type()) { + } + + /// Implements the means by which this class can be dereferenced + reference dereference() { return buffer_; } + + /// Implements the mechanism for dereferencing a read-only iterator + const_reference dereference() const { return buffer_; } + + /// Implements the mechansim for incrementing this iterator + TupleContainerIterator& increment() noexcept { + IncrementFunctor f; + f.run(start_, end_, value_, + std::make_index_sequence()); + if(value_ != end_) buffer_ = tuple_transform(value_, Derefer()); + return *this; + } + + /// Implements the mechanism for checking if this iterator equals + /// another + bool are_equal(const TupleContainerIterator& rhs) const noexcept { + return value_ == rhs.value_; + } + + /// A tuple containing the starting iterators to the containers + iterator_tuple start_; + + /// A tuple containing the ending iterators of the containers + iterator_tuple end_; + + /// The tuple containing our current iterators + iterator_tuple value_; + + /// The actual element + value_type buffer_; + + /// Functor for calling begin() on all containers + struct CallBegin { + template + auto run(T&& container) { + return container.begin(); + } + }; + + /// Functor for calling end() on all containers + struct CallEnd { + template + auto run(T&& container) const { + return container.end(); + } + }; + + /// Functor for derefrencing an argument + struct Derefer { + template + auto& run(T&& itr) const { + return *itr; + } + }; + + public: + TupleContainerIterator() noexcept = default; + }; +}; +} // End namespace detail_ + +} // namespace utilities diff --git a/utilities/iter_tools/zip.hpp b/utilities/iter_tools/zip.hpp new file mode 100644 index 0000000..2d8963c --- /dev/null +++ b/utilities/iter_tools/zip.hpp @@ -0,0 +1,88 @@ +#pragma once +#include "utilities/iter_tools/tuple_container.hpp" +#include +// For numeric_limits +namespace utilities { +namespace detail_ { + +/// Functor for calling the size member of each container. +struct ZipSizeFunctor { + static constexpr std::size_t initial_value = + std::numeric_limits::max(); + + template + auto run(std::size_t curr_min, T&& container) const { + return std::min(curr_min, container.size()); + } +}; + +struct ZipIncrementFunctor { + /// Functor for finding if any element is true via reduction + struct AnyTrue { + template + bool run(bool val, T&& element) const { + return val || element; + } + }; + struct Comparer { + template + auto run(lhs_type&& lhs, rhs_type&& rhs) const { + return lhs == rhs; + } + }; + template + void run(const IteratorType& start, const IteratorType& end, + IteratorType& value, std::index_sequence) { + value = std::make_tuple((++std::get(value))...); + auto at_end = tuple_transform(value, end, Comparer()); + bool done = tuple_accumulate(at_end, AnyTrue(), false); + if(done) value = end; + } +}; + +} // namespace detail_ + +/** @brief Wrapper function that makes a Zip container. + * + * The purpose/usage of the resulting object is perhaps best explained with an + * example. Consider the following snippet. + * + * @code + * std::vector list1({1,2,3,4}); + * std::vector list2({2,3,4,5}); + * for(auto& x : Zip(list1.begin(),list2.begin()) + * std::cout<< std::get<0>(x) << " " << std::get<1>(x) < +auto Zip(ContainerTypes&&... containers) { + return detail_::TupleContainerImpl< + detail_::ZipIncrementFunctor, std::remove_reference_t...>( + detail_::ZipSizeFunctor{}, std::forward(containers)...); +} + +} // namespace utilities diff --git a/utilities/macros/for_each.hpp b/utilities/macros/for_each.hpp new file mode 100644 index 0000000..e188f68 --- /dev/null +++ b/utilities/macros/for_each.hpp @@ -0,0 +1,131 @@ +#pragma once + +/** @file ForEach.hpp + * + * This header file is auto-generated by MakeForEach.py + * To allow more looping regenerate this file with a higher + * value of "depth". + * + * Includes C-Preprocessor macros for iterating over a list + * of arguments and applying a macro to them. The contents of + * this header are borrowed from StackOverflow. + * Of the various macros in here only the following are + * intended for use: + * - CALL_MACRO_X_FOR_EACH : Calls a macro for each argument + * passed to the list + * - CALL_MACRO_X_FOR_EACH1: Calls a macro with one bound + * argument for each argument in the list + */ +/** + * @brief Returns the N-th argument of a list. + * + * By padding what's to the left and what's to the right of + * element N, this macro can be made to arbitrarily select an + * element from a list. To select say the third argument of a + * list you simply put M-3 placeholders in front of it where M + * is the maximum number written out here (25). + */ +#define _GET_NTH_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \ + _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, \ + _25, N, ...) \ + N + +/// Guts of the CALL_MACRO_X_FOR_EACH0 macro +///{ +#define _fe0_0(_call, _0, ...) +#define _fe0_1(_call, _0, ...) _call(_0) +#define _fe0_2(_call, _0, ...) _call(_0) _fe0_1(_call, __VA_ARGS__) +#define _fe0_3(_call, _0, ...) _call(_0) _fe0_2(_call, __VA_ARGS__) +#define _fe0_4(_call, _0, ...) _call(_0) _fe0_3(_call, __VA_ARGS__) +#define _fe0_5(_call, _0, ...) _call(_0) _fe0_4(_call, __VA_ARGS__) +#define _fe0_6(_call, _0, ...) _call(_0) _fe0_5(_call, __VA_ARGS__) +#define _fe0_7(_call, _0, ...) _call(_0) _fe0_6(_call, __VA_ARGS__) +#define _fe0_8(_call, _0, ...) _call(_0) _fe0_7(_call, __VA_ARGS__) +#define _fe0_9(_call, _0, ...) _call(_0) _fe0_8(_call, __VA_ARGS__) +#define _fe0_10(_call, _0, ...) _call(_0) _fe0_9(_call, __VA_ARGS__) +#define _fe0_11(_call, _0, ...) _call(_0) _fe0_10(_call, __VA_ARGS__) +#define _fe0_12(_call, _0, ...) _call(_0) _fe0_11(_call, __VA_ARGS__) +#define _fe0_13(_call, _0, ...) _call(_0) _fe0_12(_call, __VA_ARGS__) +#define _fe0_14(_call, _0, ...) _call(_0) _fe0_13(_call, __VA_ARGS__) +#define _fe0_15(_call, _0, ...) _call(_0) _fe0_14(_call, __VA_ARGS__) +#define _fe0_16(_call, _0, ...) _call(_0) _fe0_15(_call, __VA_ARGS__) +#define _fe0_17(_call, _0, ...) _call(_0) _fe0_16(_call, __VA_ARGS__) +#define _fe0_18(_call, _0, ...) _call(_0) _fe0_17(_call, __VA_ARGS__) +#define _fe0_19(_call, _0, ...) _call(_0) _fe0_18(_call, __VA_ARGS__) +#define _fe0_20(_call, _0, ...) _call(_0) _fe0_19(_call, __VA_ARGS__) +#define _fe0_21(_call, _0, ...) _call(_0) _fe0_20(_call, __VA_ARGS__) +#define _fe0_22(_call, _0, ...) _call(_0) _fe0_21(_call, __VA_ARGS__) +#define _fe0_23(_call, _0, ...) _call(_0) _fe0_22(_call, __VA_ARGS__) +#define _fe0_24(_call, _0, ...) _call(_0) _fe0_23(_call, __VA_ARGS__) +///} + +/// Guts of the CALL_MACRO_X_FOR_EACH1 macro +///{ +#define _fe1_0(_call, _0, _1, ...) +#define _fe1_1(_call, _0, _1, ...) _call(_0, _1) +#define _fe1_2(_call, _0, _1, ...) _call(_0, _1) _fe1_1(_call, _0, __VA_ARGS__) +#define _fe1_3(_call, _0, _1, ...) _call(_0, _1) _fe1_2(_call, _0, __VA_ARGS__) +#define _fe1_4(_call, _0, _1, ...) _call(_0, _1) _fe1_3(_call, _0, __VA_ARGS__) +#define _fe1_5(_call, _0, _1, ...) _call(_0, _1) _fe1_4(_call, _0, __VA_ARGS__) +#define _fe1_6(_call, _0, _1, ...) _call(_0, _1) _fe1_5(_call, _0, __VA_ARGS__) +#define _fe1_7(_call, _0, _1, ...) _call(_0, _1) _fe1_6(_call, _0, __VA_ARGS__) +#define _fe1_8(_call, _0, _1, ...) _call(_0, _1) _fe1_7(_call, _0, __VA_ARGS__) +#define _fe1_9(_call, _0, _1, ...) _call(_0, _1) _fe1_8(_call, _0, __VA_ARGS__) +#define _fe1_10(_call, _0, _1, ...) _call(_0, _1) _fe1_9(_call, _0, __VA_ARGS__) +#define _fe1_11(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_10(_call, _0, __VA_ARGS__) +#define _fe1_12(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_11(_call, _0, __VA_ARGS__) +#define _fe1_13(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_12(_call, _0, __VA_ARGS__) +#define _fe1_14(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_13(_call, _0, __VA_ARGS__) +#define _fe1_15(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_14(_call, _0, __VA_ARGS__) +#define _fe1_16(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_15(_call, _0, __VA_ARGS__) +#define _fe1_17(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_16(_call, _0, __VA_ARGS__) +#define _fe1_18(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_17(_call, _0, __VA_ARGS__) +#define _fe1_19(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_18(_call, _0, __VA_ARGS__) +#define _fe1_20(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_19(_call, _0, __VA_ARGS__) +#define _fe1_21(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_20(_call, _0, __VA_ARGS__) +#define _fe1_22(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_21(_call, _0, __VA_ARGS__) +#define _fe1_23(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_22(_call, _0, __VA_ARGS__) +#define _fe1_24(_call, _0, _1, ...) \ + _call(_0, _1) _fe1_23(_call, _0, __VA_ARGS__) +///} + +/** + * @brief Applies a macro to each argument in a list + * + * @param[in] x The macro to apply + * @param[in] ... The list of arguments to apply @p x to + */ +#define CALL_MACRO_X_FOR_EACH(_call, ...) \ + \ + _GET_NTH_ARG("ignored", __VA_ARGS__, _fe0_24, _fe0_23, _fe0_22, _fe0_21, \ + _fe0_20, _fe0_19, _fe0_18, _fe0_17, _fe0_16, _fe0_15, \ + _fe0_14, _fe0_13, _fe0_12, _fe0_11, _fe0_10, _fe0_9, _fe0_8, \ + _fe0_7, _fe0_6, _fe0_5, _fe0_4, _fe0_3, _fe0_2, _fe0_0) \ + (_call, __VA_ARGS__) +/** + * @brief Applies a macro to each argument in a list + * + * @param[in] x The macro to apply + * @param[in] _0 The 0-th argument to bind + * @param[in] ... The list of arguments to apply @p x to + */ +#define CALL_MACRO_X_FOR_EACH1(_call, _0, ...) \ + \ + _GET_NTH_ARG("ignored", __VA_ARGS__, _fe1_24, _fe1_23, _fe1_22, _fe1_21, \ + _fe1_20, _fe1_19, _fe1_18, _fe1_17, _fe1_16, _fe1_15, \ + _fe1_14, _fe1_13, _fe1_12, _fe1_11, _fe1_10, _fe1_9, _fe1_8, \ + _fe1_7, _fe1_6, _fe1_5, _fe1_4, _fe1_3, _fe1_2, _fe1_0) \ + (_call, _0, __VA_ARGS__) diff --git a/utilities/mathematician/combinatorics.cpp b/utilities/mathematician/combinatorics.cpp new file mode 100644 index 0000000..d348d6d --- /dev/null +++ b/utilities/mathematician/combinatorics.cpp @@ -0,0 +1,37 @@ +#include "utilities/mathematician/combinatorics.hpp" +#include //For accumulate +#include + +namespace utilities { +namespace detail_ { + +template<> +std::size_t BCImpl::eval(std::size_t n, std::size_t k) { + if(k == 0) return 1; + if(k > n) return 0; + // Guaranteed to fit in size_t,VER use pascal's rule + if(n < sizeof(std::size_t) * 8) + return BCImpl::eval(n - 1, k - 1) + + BCImpl::eval(n - 1, k); + throw std::overflow_error("I haven't coded up n>64"); +} + +template<> +std::size_t MCImpl::eval(const std::vector& ks) { + std::size_t result = 1; + std::size_t numerator = std::accumulate(ks.begin(), ks.end(), 0); + for(auto k : ks) { + for(size_t i = 1; i <= k; ++i) { + result *= numerator; + result = (result - result % i) / i; + numerator -= 1; + } + } + return result; +} + +template class BCImpl; +template class MCImpl; + +} // namespace detail_ +} // namespace utilities diff --git a/utilities/mathematician/combinatorics.hpp b/utilities/mathematician/combinatorics.hpp new file mode 100644 index 0000000..c62ae65 --- /dev/null +++ b/utilities/mathematician/combinatorics.hpp @@ -0,0 +1,364 @@ +#pragma once +#include //For sort and count +#include //For returns of fns functions +#include //For inner_product and accumulate +#include + +/** @file Combinatorics.hpp + * + * Free functions for things that are vaguely combinatorial in nature. + * + * The current contents of this file are: + * - binomial_coefficient : calculates binomial coefficients + * - multinomial_coefficient : calculates multinomial coefficients + * - n_permutations : calculates the number of permutations of a sequence + * - permutation_to_fns : maps a permutation to its representation in the\n + * factorial number system + * - fns_to_permutation : reverse mapping of permutation_to_fns + * - fns_place_values : returns the place values in the factorial number\n + * system for a given permutation. + * - decimal_to_fns: Maps a value in decimal to its corresponding\n + * representation in the factorial number system + * - permutation_to_decimal : wrapper over fns intermediate + * - decimal_to_permutation : reverse mapping of permutation_to_decimal + */ + +/// Main namespace for the Utilities library +namespace utilities { +/// Name space for things not mean to see the light of day +namespace detail_ { + +/// Struct that actually implements the binomial coefficient +template +struct BCImpl { + /// Returns @p n choose @p k + T eval(std::size_t n, std::size_t k); +}; + +/// Struct that actually implements the multinomial coefficient +template +struct MCImpl { + /// Returns sum(ks) chooses k0, k1, k2,... + T eval(const std::vector& ks); +}; + +} // namespace detail_ + +/** @brief Computes the binomial coefficient @f${n\choose k}@f$ + * + * Depending on the requested return type a different algorithm is used. If + * the user requests an integral return type an algorithm based on Pascal's + * triangle will be used. If the user requests a floating point return type it + * will then be assumed that the user is fine with loosing precision *i.e.* the + * value will be returned in scientific notation with the significand + * truncated to the precision of the floating point value. + * + * @tparam T The desired return type. + * @param n top number of the bc *i.e.* number of objects to pick from + * @param k bottom number of the bc *i.e.* number of objects to pick at a time + * @return The requested value of the binomial coefficient + * @throws std::overflow_error if the requested binomial coefficient can not be + * held in the requested return type. + */ +template +T binomial_coefficient(std::size_t n, std::size_t k) { + return detail_::BCImpl().eval(n, k); +} + +/** @brief Computes the multinomial coefficient + * @f${{\sum k_i} \choose {\prod k_i!}}@f$ + * + * Given @f$N@f$ observations taken from @f$M@f$ unique choices such that the + * @f$i^\text{th}@f$ unique choice is observed @f$m_i@f$ times, the multinomial + * coefficient @f${ N \choose {\prod_{i=1}^M m_i!}}@f$, is the number of + * possible orderings for those observations. + * + * @param[in] ks The number of times each unique observation occurs. + * @tparam T The desired return type. + * @return sum of @p ks choose k0!,k1!,... + * @throws None. No throw guarantee. + * + */ + +template +T multinomial_coefficient(const std::vector& ks) noexcept { + return detail_::MCImpl().eval(ks); +} + +/** @brief Given a sequence returns the number of unique permutations + * + * This function is primarily code factorization. Basically, given a sequence + * we can't assume all elements are unique so we can't assume its just the + * length of the sequence factorial. Rather, we need to figure out the + * number of times each unique element appears in the sequence and then use + * that information to compute the appropriate multinomial coefficient. + * + * @par Memory + * The present algorithm will make a copy of @p seq and an + * std::vector of the length of @p seq. + * + * @param[in] seq The sequence for which we wish to know the number of + * permutations. + * @tparam container_type The type of @p seq. Must satisfy the sequence + concept. + * @return The number of unique permutations of a sequence + * @throws std::bad_alloc if there is not enough memory. Strong throw + * guarantee. + */ +template +std::size_t n_permutations(const container_type& seq); + +/** @brief Given a permutation this function maps that permutation to its value + * in the factorial number system. + * + * For a description of the factorial number system see the Intro to + * Combinatorics section of the manual. + * + * @par Memory + * The current algorithm is recursive and thus at a depth @f$i@f$ will require + * two copies of @p perm (except they contain @f$i-1@f$ less elements). Memory + * for the return type will also be allocated. + * + * @tparam container_type The type of the sequences + * @param perm A permutation of @p original + * @param original The original sequence from which @p perm was generated + * @return The value of @p perm in the factorial number system. + * @throws std::bad_alloc if memory allocation fails. Strong throw guarantee. + */ +template +std::deque permutation_to_fns(const container_type& perm, + const container_type& original); + +/** @brief Converts a value in the factorial number system to the equivalent + * permutation. + * + * @par Memory + * + * This call ultimately will call decimal_to_fns and therefore incurs its + * memory overhead as well as that of the return value. + * + * @param[in] fns The value in the factorial number system to convert to a + * permutation. + * @param[in] original The original permutation. + * @returns The requested permutation. + * @throws std::bad_alloc if there is insufficient memory to complete the + * operation. + */ +template +container_type fns_to_permutation(const std::deque& fns, + const container_type& original); + +/** @brief Given a permutation of a sequence computes the values of the places + * in the number. + * + * For a description of the factorial number system see the Intro to + * Combinatorics section of the manual. Since for a general permutation the + * values of the places depends on the permutation this function will compute + * the place values. + * + * @par Memory + * At the moment the internal algorithm is recursive requiring a copy of @p perm + * at each depth @f$i@f$; the copy will contain @f$i@f$ less elements than + * @p perm. Additionally a call to n_permutations will be made at each depth. + * Finally, memory for the return type will be allocated. + * + * @tparam container_type The type of the sequences + * @param perm The current permutation + * @return The value of the places in the factorial number system + * @throws std::bad_alloc if any memory allocation fails. Strong throw + * guarantee. + */ +template +std::deque fns_place_values(const container_type& perm); + +/** @brief Given a decimal value and the original sequence, computes the + * corresponding value in the factorial number system. + * + * Mapping a number in the factorial number system (FNS) to decimal is done by + * taking the inner product of the number's digits and the place values. This + * function is the inverse mapping, taking a value in decimal and returning the + * value in the FNS. + * + * @par Memory + * The current algorithm requires two copies of @p perm and a call to + * n_permutations. Additionally memory for the return will be allocated. + * + * @param[in] D The requested permutation. @p D must be in the range + * [0, \f$N\f$) where \f$N\f$ is the number of permutations of @p perm. + * @param[in] perm The original permutation. + * @tparam container_type The type of @p perm. Must satisfy the concept of a + * container. + * @returns The value of the @p D -th permutation in the factorial number + * system. + * @throws std::bad_alloc if memory allocation fails. Strong throw guarantee. + * + */ +template +std::deque decimal_to_fns(std::size_t D, + const container_type& perm); + +/** @brief A convenience function for bypassing the FNS intermediate. + * + * @par Memory + * This is ultimately a thin wrapper around permutation_to_fns and + * fns_place_values. It thus has their combined memory footprints. + * + * @tparam container_type The type of the two sequences. + * @param perm The permutation for which we want the decimal representation + * @param original The original sequence + * @return The decimal representation of the permutation. + * @throws std::bad_alloc if either of the two sub calls run out of memory. + */ +template +std::size_t permutation_to_decimal(const container_type& perm, + const container_type& original) { + auto fns = permutation_to_fns(perm, original); + auto values = fns_place_values(perm); + return std::inner_product(fns.begin(), fns.end(), values.begin(), 0); +} + +/** @brief A convenience function for going from decimal to the corresponding + * permutation. + * + * @par Memory + * + * Ultimately this calls fns_to_permutation and decimal_to_fns and thus has the + * same footprints. + * + * @tparam container_type The type of the original sequence. + * @param[in] n The index of the requested permutation + * @param[in] original The original sequence + * @returns The requested permutation + * @throws std::bad_alloc if either of the two subroutines run out of memory + */ +template +container_type decimal_to_permutation(std::size_t n, + const container_type& original) { + return fns_to_permutation(decimal_to_fns(n, original), original); +} + +////////////////////////////////// Implementations ///////////////////////////// + +template +std::size_t n_permutations(const container_type& seq) { + container_type temp(seq); + std::vector counts; + counts.reserve(seq.size()); + std::sort(temp.begin(), temp.end()); + auto ei = temp.begin(); + while(ei != temp.end()) { + const auto n_ei = std::count(ei, temp.end(), *ei); + counts.push_back(n_ei); + ei += n_ei; + } + return multinomial_coefficient(counts); +} + +// Note: Without assuming a container type we have to rely on iterators +template +std::deque permutation_to_fns(const container_type& perm, + const container_type& original) { + // Recursion end + if(perm.size() == 0) return std::deque({}); + if(perm.size() == 1) return std::deque({0}); + + // Strip off 1st element and put remaining elements in new container + auto perm_itr = perm.begin(); + auto ei = *perm_itr; + container_type sub_perm(++perm_itr, perm.end()); + + // Get digit in FNS and fill new_orig with all elements aside from it + std::size_t digit = 0; + container_type new_orig; + auto orig_itr = original.begin(); + for(std::size_t i = 0; i < original.size(); ++i) { + auto ej = *orig_itr++; + if(ej == ei) { + digit = i; + while(orig_itr != original.end()) { + new_orig.insert(new_orig.end(), *orig_itr++); + } + break; + } + new_orig.insert(new_orig.end(), ej); + } + auto rv = permutation_to_fns(sub_perm, new_orig); + rv.push_front(digit); + return rv; +} + +template +container_type fns_to_permutation(const std::deque& fns, + const container_type& original) { + container_type temp(original); + container_type rv; + for(auto x : fns) { + auto ei_itr = temp.begin(); + container_type temp2; + for(std::size_t i = 0; i < temp.size(); ++i) { + if(i != x) + temp2.insert(temp2.end(), *ei_itr); + else + rv.insert(rv.end(), *ei_itr); + ++ei_itr; + } + temp.swap(temp2); + } + return rv; +} + +template +std::deque fns_place_values(const container_type& perm) { + // Recursion end + if(perm.size() == 0) return std::deque({}); + if(perm.size() == 1) return std::deque({1}); + if(perm.size() == 2) return std::deque({1, 1}); + + // Strip off 1st element and put remaining elements in new container + auto perm_itr = perm.begin(); + // auto ei=*perm_itr; + container_type sub_perm(++perm_itr, perm.end()); + + auto rv = fns_place_values(sub_perm); + rv.push_front(n_permutations(perm) / perm.size()); + return rv; +} + +template +std::deque decimal_to_fns(std::size_t D, + const container_type& orig) { + container_type new_orig(orig.begin(), orig.end()); + std::sort(new_orig.begin(), new_orig.end()); + std::deque rv; + + while(new_orig.begin() != + new_orig.end()) // We pop an element out per iteration + { + auto curr_guess = new_orig.begin(); + std::size_t total_perms = 0; + std::size_t digit_i = 0; + while(curr_guess != new_orig.end()) { + auto ei = *curr_guess; + // New container w/o element i + container_type temp(new_orig.begin(), curr_guess++); + temp.insert(temp.end(), curr_guess, new_orig.end()); + + const auto P_ei = n_permutations(temp); + total_perms += P_ei; + if(D < total_perms) { + rv.push_back(digit_i); + new_orig.swap(temp); + D = D - (total_perms - P_ei); + break; + } + // We know the element isn't ei, but ei may still be in temp... + auto n_ei = std::count(temp.begin(), temp.end(), ei); + digit_i += 1 + n_ei; + std::advance(curr_guess, n_ei); + } + } + + return rv; +} + +} // namespace utilities diff --git a/utilities/mathematician/integer_utils.hpp b/utilities/mathematician/integer_utils.hpp new file mode 100644 index 0000000..14208a7 --- /dev/null +++ b/utilities/mathematician/integer_utils.hpp @@ -0,0 +1,42 @@ +#pragma once +#include // For size_t +#include // For is_integral + +/** @file IntegerUtils.hpp + * + * This file contains utilities for working with POD types that are integral. + * In particular it includes: + * + * - UnsignedSubtract : Subtracts two unsigned values resulting in a signed + * type + * + */ + +namespace utilities { + +/** + * @brief Given two integer type instances this function will take their + * difference in a manner that will not lead to an overflow and will generate + * the correct sign on the result. + * + * @tparam SignedType The type of the result. Should be a signed integral POD + * type. + * @tparam UnsignedType The type of the input values. Should be an unsigned + * integral POD type. + * @param lhs The number of the left of the minus sign. + * @param rhs The number on the right of the minus sign. + * @return The signed difference between @p lhs and @p rhs. + * @throw None. No throw guarantee. + */ +template +SignedType UnsignedSubtract(const UnsignedType& lhs, + const UnsignedType& rhs) noexcept { + static_assert(std::is_integral::value, "Must be integral " + "type"); + static_assert(std::is_integral::value, "Must be integral type"); + if(lhs > rhs) return static_cast(lhs - rhs); + SignedType negative1{-1}; + return negative1 * static_cast(rhs - lhs); +} + +} // namespace utilities diff --git a/utilities/smart_enum.hpp b/utilities/smart_enum.hpp new file mode 100644 index 0000000..76a76ef --- /dev/null +++ b/utilities/smart_enum.hpp @@ -0,0 +1,242 @@ +#pragma once +#include "utilities/macros/for_each.hpp" +#include "utilities/static_string.hpp" +#include + +namespace utilities { + +/** @brief A class to implement smart enumerations, based on strings + * + * I stole the basic design of this class from + * [here](http://www.drdobbs.com/when-enum-just-isnt-enough-enumeration-c + * /184403955). That said the actual implementation is a bit different in that + * I am not concerned with being able to iterate over all the possibilities. + * This means I can dodge the weird static `std::set` instance. It also + * means I can get the entire class to be a compile-time literal. + */ +template +class SmartEnum { +private: + /// The type of this class, for convenience + using my_type = SmartEnum; + +public: + /// Intel won't let this be private + template + constexpr explicit SmartEnum(const char (&str)[N]) : value_(str) {} + + /** + * @brief Compares two SmartEnums for equality. + * + * All SmartEnum comparison operators defer to the underlying StaticString + * instance and thus this class uses the same definition of equality. + * + * @param rhs The SmartEnum to compare to. + * + * @return True if the two SmartEnum instances are the same and false + * otherwise. + * + * @throw None. No throw guarantee. + * + * @par Complexity: + * Worst case linear in the length of the underlying string + * + * @par Data Races: + * None. All SmartEnums are read-only at run-time. + */ + constexpr bool operator==(const my_type& rhs) const noexcept { + return value_ == rhs.value_; + } + + /** + * @brief Compares two SmartEnums for inequality. + * + * All SmartEnum comparison operators defer to the underlying StaticString + * instance and thus this class uses the same definition of inequality. + * + * @param rhs The SmartEnum to compare to. + * + * @return True if the two SmartEnum instances are different and false + * otherwise. + * + * @throw None. No throw guarantee. + * + * @par Complexity: + * Worst case linear in the length of the underlying string + * + * @par Data Races: + * None. All SmartEnums are read-only at run-time. + */ + constexpr bool operator!=(const my_type& rhs) const noexcept { + return value_ != rhs.value_; + } + + /** + * @brief Determines if the current SmartEnum is less than another instance. + * + * All SmartEnum comparison operators defer to the underlying StaticString + * instance and thus this class uses the same definition of less than. + * + * @param rhs The SmartEnum to compare to. + * + * @return True if the current SmartEnum instance is less than and + * false otherwise. + * + * @throw None. No throw guarantee. + * + * @par Complexity: + * Worst case linear in the length of the underlying string + * + * @par Data Races: + * None. All SmartEnums are read-only at run-time. + */ + constexpr bool operator<(const my_type& rhs) const noexcept { + return value_ < rhs.value_; + } + + /** + * @brief Determines if the current SmartEnum is less than or equal to + * another instance. + * + * All SmartEnum comparison operators defer to the underlying StaticString + * instance and thus this class uses the same definition of less than or + * equal to. + * + * @param rhs The SmartEnum to compare to. + * + * @return True if the current SmartEnum instance is less than or equal to + * @p rhs and false otherwise. + * + * @throw None. No throw guarantee. + * + * @par Complexity: + * Worst case linear in the length of the underlying string + * + * @par Data Races: + * None. All SmartEnums are read-only at run-time. + */ + constexpr bool operator<=(const my_type& rhs) const noexcept { + return value_ <= rhs.value_; + } + + /** + * @brief Determines if the current SmartEnum is greater than another + * instance. + * + * All SmartEnum comparison operators defer to the underlying StaticString + * instance and thus this class uses the same definition of greater than. + * + * @param rhs The SmartEnum to compare to. + * + * @return True if the current SmartEnum instance is greater than and + * false otherwise. + * + * @throw None. No throw guarantee. + * + * @par Complexity: + * Worst case linear in the length of the underlying string + * + * @par Data Races: + * None. All SmartEnums are read-only at run-time. + */ + constexpr bool operator>(const my_type& rhs) const noexcept { + return value_ > rhs.value_; + } + + /** + * @brief Determines if the current SmartEnum is greater than or equal to + * another instance. + * + * All SmartEnum comparison operators defer to the underlying StaticString + * instance and thus this class uses the same definition of greater than or + * equal to. + * + * @param rhs The SmartEnum to compare to. + * + * @return True if the current SmartEnum instance is greater than or equal + * to @p rhs, and false otherwise. + * + * @throw None. No throw guarantee. + * + * @par Complexity: + * Worst case linear in the length of the underlying string + * + * @par Data Races: + * None. All SmartEnums are read-only at run-time. + */ + constexpr bool operator>=(const my_type& rhs) const noexcept { + return value_ >= rhs.value_; + } + + /** + * @brief Allows the enum to be implicitly converted to a C-style string. + * + * + * @return The current enum as a C-style string. + * @throw None. No throw guarantee. + * @par Complexity: + * Constant. + * @par Data Races: + * None. All SmartEnums are read-only at run-time. + */ + constexpr operator const char*() const noexcept { return value_; } + +private: + /// The value this particular enum holds + StaticString value_; + +protected: + /// Constructor, protected cause you won't ever call it outside derived + /// class +}; + +} // namespace utilities + +/// Implementation detail for declaring the enum instances in DECLARE_SmartEnum +#define _ADD_SMARTENUM(name) static const T name; + +/// Implementation detail for defining the enum instances in DECLARE_SmartEnum +#define _DEFINE_SMARTENUM(enum_name, name) \ + template \ + constexpr T enum_name##Impl::name(#name); + +/** + * @brief Convenience function for declaring a SmartEnum set. + * + * This macro hides the boilerplate for declaring a new set of SmartEnum + * instances. It relies on the CALL_MACRO_X_FOR_EACH macro which has a hard + * coded limit of the maximum number of variable arguments it can handle (this + * is an artifact of the fact that C macros can't be called recursively). + * + * Usage to make a set of SmartEnum "Fruits" which contains apple, pear, and + * orange: + * @code + * DECLARE_SmartEnum(Fruits, apple, pear, orange); + * @endcode + * + * @param[in] enum_name This will become the name of the class which holds your + * enum (Fruits in the example above) + * @param[in] ... These are the values of your enum. (apple, pear, orange in the + * example above). + * + * Notes on the actual implementation: + * - We actually create a templated trampoline class enum_nameImpl which allows + * us to define the static member variables in a header without fear of + * multiple definition reprisal because templates are immune to the ODR. + * + * + */ +#define DECLARE_SmartEnum(enum_name, ...) \ + template \ + class enum_name##Impl : public utilities::SmartEnum { \ + public: \ + using utilities::SmartEnum::SmartEnum; \ + \ + public: \ + CALL_MACRO_X_FOR_EACH(_ADD_SMARTENUM, __VA_ARGS__) \ + }; \ + CALL_MACRO_X_FOR_EACH1(_DEFINE_SMARTENUM, enum_name, __VA_ARGS__) \ + class enum_name : public enum_name##Impl { \ + public: \ + using enum_name##Impl::enum_name##Impl; \ + } diff --git a/utilities/static_string.hpp b/utilities/static_string.hpp new file mode 100644 index 0000000..ba7ecce --- /dev/null +++ b/utilities/static_string.hpp @@ -0,0 +1,190 @@ +#pragma once +#include +#include + +namespace utilities { + +/** + * @brief A class capable of holding a compile-time manipulatable string. + * + * Particularly when working with enums we often want string semantics. This + * class allows us to define an instance like: + * + * ```.cpp + * constexpr StaticString value1{"value 1"}; + * ``` + * that is usable like an enum with only compile-time overhead. + * + * As a general note this class is really only intended to be used for + * compile-time strings (in fact I don't see how it could be used with + * dynamically allocated strings). For this reason all function documentation + * is written assuming it will be called at compile-time. In particular this + * means that all functions are thread-safe and noexcept. + * + * @nosubgrouping + */ +class StaticString { +public: + /** + * @brief Wraps a string literal (a compile-time string) in a more C++-like + * API. + * + * + * @tparam Length the compile-time length of the string to wrap (will + * include the `'\n'` character). + * @param str the string literal to wrap. + * + * @par Complexity: + * Constant. + * @par Data Races: + * None. + * @throws None. No throw guarantee. + * + */ + template + constexpr StaticString(const char (&str)[Length]) noexcept : + begin_(str), + size_(Length - 1) {} + + /** + * @brief Returns the i-th character of the string. + * + * This function assumes that @p i is in bounds. If @p i is not in bounds + * you're likely to get a compiler error; however, this is not guaranteed. + * + * @param i the offset of the requested character. @p i should be in the + * range [0, N) where @p N is the length of the string as returned + * by `size()`. + * @return The requested character. + * @par Complexity: + * Constant. + * @par Data Races: + * None. + * @throws None. No throw guarantee. + */ + constexpr char operator[](unsigned i) const noexcept { return begin_[i]; } + + /** + * @brief An accessor wrapping the length of the string. + * + * @return The length of the string not counting the `'\n'` character. + * @throw None. No throw guarantee. + * @par Complexity: + * Constant. + * @par Data Races: + * None. + */ + constexpr std::size_t size() const noexcept { return size_; } + + /** + * @name String comparisons + * @brief Functions for determining if strings are equal, less than, ... + * + * Two strings are equal if they are the same length and the i-th character + * is the same for both strings, for all i. + * + * A string is less than another string if it compares lexicographically + * lower than the other string. Note this comparison is done character by + * character and will only be alphabetic if all characters have the same + * case. In ASCII upper case numbers are first and thus will come first + * "alphabetically". + * + * @param rhs The string to compare to. + * @return True if the comparison is true and false otherwise. + * @par Complexity: + * Worst case linear in the length of the string. + * @par Data Races: + * None. + * @throws None. All comparisons offer the no throw guarantee. + */ + ///@{ + constexpr bool operator==(const StaticString& rhs) const noexcept { + if(size_ != rhs.size()) return false; + for(std::size_t i = 0; i < size_; ++i) // know they are same size + if((*this)[i] != rhs[i]) return false; + return true; + } + + constexpr bool operator!=(const StaticString& rhs) const noexcept { + return !((*this) == rhs); + } + + constexpr bool operator<(const StaticString& rhs) const noexcept { + const bool am_smaller = (size() < rhs.size()); + const std::size_t min_size = (am_smaller ? size_ : rhs.size()); + for(std::size_t i = 0; i < min_size; ++i) { + const char li = (*this)[i]; + const char ri = rhs[i]; + if(li < ri) + return true; + else if(li > ri) + return false; + // must be that li == ri + } + // At this point either they are the same up the length of the shortest + // string. Thus we simply need to know which is shorter. + return am_smaller; + } + + constexpr bool operator>(const StaticString& rhs) const { + return rhs < *this; + } + + constexpr bool operator<=(const StaticString& rhs) const { + return (*this) < rhs || (*this) == rhs; + } + + constexpr bool operator>=(const StaticString& rhs) const noexcept { + return rhs <= *this; + } + ///@} + + /** + * @brief Implicitly casts the current instance to a C-style string. + * + * This user-defined conversion allows the current instance to be used + * anywhere a C-style string could be. + * + * @return The current instance as a C-style string. + * @throws None. No throw guarantee. + * @par Complexity: + * Constant. + * @par Data Races: + * None. + */ + constexpr operator const char*() const noexcept { return begin_; } + +private: + /// A pointer to the start of the string + const char* begin_; + + /// The length of the string (not including the `'\n'` character. + std::size_t size_; +}; // StaticString + +} // namespace utilities + +///** +// * @brief Overloads the std::ostream so that it can print the string +// * +// * It should be noted that this function is **NOT** constexpr. That is to say +// * the string will not be printed at compile-time, but at run-time. +// * +// * @param[in,out] os The ostream to add @p str to. +// * @param[in] str The StaticString instance to print out. +// * @return @p os in a manner ammenable for operator chaining. +// * @throw std::failure if writing to the stream fails for any reason. Weak +// * throw guarantee regarding the state of @p os. +// * @par Complexity: +// * Presumably linear in the length of the string, but not certain. +// * @par Data Races: +// * Modifies @p os and data races may ensure if @p os is concurrently modified +// or +// * accessed. @p str can is a compile-time constant and thus is immune to data +// * races. +// */ +// std::ostream& operator<<(std::ostream& os, const utilities::StaticString& +// str) { +// for(std::size_t i = 0; i < str.size(); ++i) os << str[i]; +// return os; +//} diff --git a/utilities/timer.hpp b/utilities/timer.hpp new file mode 100644 index 0000000..1bb3753 --- /dev/null +++ b/utilities/timer.hpp @@ -0,0 +1,207 @@ +#pragma once +#include "utilities/containers/case_insensitive_map.hpp" +#include +#include +#include + +namespace utilities { + +/** @brief Facilitates timing one or more functions. + * + * The primary purpose of this class is to time one or more functions. Timing + * of a single function is done by: + * ``` + * Timer t; + * t.time_it("Description of function", fxn_to_time, arg1, arg2, ...); + * ``` + * + * Alternatively you can do: + * + * ``` + * Timer t; + * fxn_to_time(arg1, arg2, ...); + * t.record("Description of function"); + * ``` + * The latter syntax facilitates timing multiple functions together (simply + * hold off on calling `record` until you have run all the functions you want + * to time). + * + * The timer keeps track of each of the timings that it has run and stores them + * in a map-like manner where the description can be used to retrieve the time + * a function took. + */ +class Timer { +private: + /// The type of clock we use internally + using clock_type = std::chrono::high_resolution_clock; + /// The type the clock uses to record time + using time_point = typename clock_type::time_point; + +public: + /// The type of a descriptions + using key_type = std::string; + /// The duration that passed between time points, the value_type + using duration = typename clock_type::duration; + + /** @brief Creates and starts a timer. + * + * In the spirit of RAII, this class immediately starts a timer upon + * construction. Hence the user should create a timer before the first + * function they want to time. + * + * @throw none No throw guarantee. + * + */ + Timer() noexcept : m_started_(clock_type::now()) {} + + /** @brief Called to manually note the time that has passed since the timer + * started counting. + * + * This function is intended for use when the user wants to time multiple + * functions as one, or it is inconvenient to pass the function to + * `time_it`. + * + * @note This call will reset the timer so that it is ready for the next + * timing. + * + * @param[in] desc A description of what was just timed. + * @throw std::bad_alloc if there is insufficient memory to record the + * timing. Strong throw guarantee. + */ + void record(key_type desc); + + /** @brief Times the provided call back + * + * This function will time the provided call back and save the result + * internally. + * + * @note This function will reset the timer after the call. + * + * @tparam Fxn The type of the call back + * @tparam Args The types of the arguments that will be forwarded to the + * callback + * @param[in] desc A description of the callback that is being timed. + * @param[in] fxn The actual callback to run + * @param[in] args The arguments to forward to the callback. + * + * @throw ??? If the callback throws. Strong throw guarantee. + * @throw std::bac_alloc if there is insufficient memory to record the + * timing. Strong throw guarantee. + */ + template + void time_it(key_type desc, Fxn&& fxn, Args&&... args); + + /** @brief Resets how long the timer has been running for. + * + * This function restarts the timer. All recorded times are still + * preserved. This function is primarily used internally, but is of use to + * the user if the user has run functions since the last timing, and the + * timing of those functions is not of interest. + * + * @throw none No throw guarantee. + */ + void reset() noexcept { m_started_ = clock_type::now(); } + + /** @name Duration accessors + * + * @brief Allows the user to manually retrieve the duration of a timed + * function. + * + * This function can be used to retrieve how long a particular timed + * section ran for. The result is read-only. + * + * @param[in] desc The timing the user is interested in. + * @return How the long the requested operation ran for as an object of type + * @p duration. + * @throw std::out_of_range if @p desc is not a valid key. + */ + ///@{ + auto& at(const key_type& desc) const { return m_times_.at(desc); } + auto& operator[](const key_type& desc) const { return at(desc); } + ///@} + + /** @name Iterators + * + * @note All iterators are only good so long as the Timer instance that + * created them is. + * + * @return An iterator pointing at the first timing result (for begin and + * cbegin) or an iterator just past the last timing result (for + * end and cend). + * + * @throw none All iterator creation methods are no throw gurantee. + */ + ///@{ + auto begin() const noexcept { return m_times_.begin(); } + auto end() const noexcept { return m_times_.end(); } + auto cbegin() const noexcept { return begin(); } + auto cend() const noexcept { return end(); } + ///@} +private: + /// code factorization for recording a timing + void record_(key_type desc, time_point t1, time_point t2); + + /// When we started timing + time_point m_started_; + + /// The timings that this instance has recorded + utilities::CaseInsensitiveMap m_times_; +}; + +/** @brief Wraps printing a Timer instance out + * + * @relates Timer + * + * This will print out all timings contained within a particular Timer + * instance. Each timing will be of the format: + * ``` + * : h m s ms + * ``` + * where `desc` is the description the user provided, `x` is the number of + * hours that the function took, `y` is the number of minutes, `z` is the + * number of seconds, and `w` is the number of milliseconds. The total time is + * the sum of `x`, `y`, `z`, and `w`. + * + * @param[in] os The std::ostream object to print @p t to. + * @param[in] t The timer instance to print out. + * @return @p os containing the string representation of @p t. + * @throw ??? Throws if os::operator<< throws. Weak throw guarantee (@p os + * will be in a valid, but otherwise undefined state). + */ +inline std::ostream& operator<<(std::ostream& os, const Timer& t); + +//------------------------------Implementations-------------------------------- + +inline void Timer::record(std::string desc) { + /// Capture the time 1st in order to be slightly more accurate + auto t2 = clock_type::now(); + record_(std::move(desc), m_started_, t2); +} + +template +void Timer::time_it(std::string desc, Fxn&& fxn, Args&&... args) { + auto t1 = clock_type::now(); + fxn(std::forward(args)...); + auto t2 = clock_type::now(); + record_(std::move(desc), t1, t2); +} + +inline void Timer::record_(std::string desc, time_point t1, time_point t2) { + m_times_.emplace(std::move(desc), t2 - t1); + reset(); +} + +inline std::ostream& operator<<(std::ostream& os, const Timer& t) { + for(const auto& [desc, dt] : t) { + using namespace std::chrono; + auto h = duration_cast(dt); + auto m = duration_cast(dt) - h; + auto s = duration_cast(dt) - h - m; + auto ms = duration_cast(dt) - h - m - s; + os << desc << " : " << h.count() << " h " << m.count() << " m " + << s.count() << " s " << ms.count() << " ms" << std::endl; + } + return os; +} + +} // namespace utilities diff --git a/utilities/type_traits/iterator_types.hpp b/utilities/type_traits/iterator_types.hpp new file mode 100644 index 0000000..0f7806a --- /dev/null +++ b/utilities/type_traits/iterator_types.hpp @@ -0,0 +1,438 @@ +#pragma once +#include + +/** @file IteratorTypes.hpp + * + * This file contains base classes that will remove much of the boiler + * plate associated with iterators. + * + * How to use these classes is probably best exhibited by example. For that + * purpose see TestIteratorTypes.cpp. These classes are implemented in a + * somewhat straightforward manner using CRTP. It also should be noted that + * Boost has similar implementations available and is trying to actively get + * their implementations into the STL. If that happens we likely will want to + * switch. + * + * + * Contents: + * - InputIteratorBase : Base class for an input iterator + * - BidirectionalIteratorBase : Base class for a bidirectional iterator + * - RandomAccessIteratorBase : Base class for a random access iterator + */ + +namespace utilities { +namespace detail_ { + +/** @brief This class is designed to facilitate making your own input iterator + * class. + * + * To use this class to quickly define an input iterator you'll need to define: + * - increment + * - dereference + * - are_equal + * + * @tparam ParentType The type of the iterator we are implementing. + * @tparam ValueType The type of the elements you are storing in your + * container. + * @tparam SizeType The type of an index into your container. Default: + * std::size_t. + * @tparam DifferenceType The type of the difference between two SizeType + * instances. Default: long int. + */ +template +struct InputIteratorBase { + /// The type of an element returned by this iterator. + using value_type = ValueType; + + /// The type of a mutable reference to an element + using reference = value_type&; + + /// The type of a non-mutable reference to an element + using const_reference = const value_type&; + + /// The type of a pointer to an element + using pointer = value_type*; + + /// The type of a pointer to a non-mutable element + using const_pointer = const value_type*; + + /// The type of the index + using size_type = SizeType; + + /// The type of the difference between two indices + using difference_type = DifferenceType; + + /// The concept tag this iterator obeys + using iterator_category = std::input_iterator_tag; + + /// Implement this so we can increment your iterator + virtual ParentType& increment() = 0; + + /// Implement this so we can dereference your iterator + virtual const_reference dereference() const = 0; + + /// Implement to provide equality comparisons + virtual bool are_equal(const ParentType& rhs) const noexcept = 0; + + /** @brief Allows access to the element currently pointed at by this + * iterator. + * + * This function simply calls dereference. + * + * @return The element this iterator currently points to. + * @throws exception if dereference throws. + */ + const_reference operator*() const { return dereference(); } + + ///@copydoc operator*()const + reference operator*() { + const_reference temp = dereference(); + return const_cast(temp); // NOLINT + } + + /** @brief Provides access to an element's member functions directly. + * + * @returns The address of the current element for use with the arrow + * operator. + * @throws exception if the dereference operation throws. + */ + const_pointer operator->() const { return &(operator*()); } + + /** @brief Provides access to an element's member functions directly. + * + * If the user specified the type of the element to be const then this + * ultimately will still return a const pointer + * + * @returns The address of the current element for use with the arrow + * operator. + * @throws exception if the dereference operation throws. + */ + pointer operator->() { return &(operator*()); } + + /** @brief Implements prefix increment. + * + * Increments an iterator before returning the current value. This + * operation is implemented by calling increment. + * + * @returns The iterator pointing to the value resulting from the + * increment. + * @throws exception if increment throws + * + */ + ParentType& operator++() { return increment(); } + + /** @brief Implements postfix increment. + * + * Increments an iterator after returning the current value. This + * operation is implemented in terms of the copy constructor and the prefix + * increment operator. + * + * @returns A copy of the current iterator pointing to the element before + * the increment. + * @throws exception if either the copy constructor or the prefix increment + * operator throw. + * + */ + ParentType operator++(int) { + ParentType copy_of_me(static_cast(*this)); + ++(*this); + return copy_of_me; + } + + /** @brief Determines if two iterators are equivalent. + * + * This function works by calling are_equal. Hence the definition of + * equality used is that of the are_equal function. + * + * @param[in] rhs The iterator to compare to. + * @return true if the two iterators are equal + * @throws None. No throw guarantee + */ + bool operator==(const ParentType& rhs) const noexcept { + return are_equal(rhs); + } + + /** @brief Check to ensure two iterators are not identical. + * + * The definition of iterator equality is taken from operator==. This + * function + * simply negates the result. + * + * @param[in] rhs The iterator to compare against. + * @returns True if the current iterator is not identical to @p rhs. + * @throws None. No throw guarantee. + */ + bool operator!=(const ParentType& rhs) const noexcept { + return !((*this) == rhs); + } +}; + +/** @brief This class is designed to facilitate making your own bidirectional + * iterator class. + * + * When making your own iterator there's a lot of boiler plate involved. This + * class factors out as much boiler plate as possible for a bidirectional + * iterator, which is an iterator that allows you to access either the next + * element or the previous element. + * + * To use this class you'll need to implement in your derived class: + * - dereference + * - are_equal + * - increment + * - decrement + * + * @tparam ParentType The type of iterator that you are making. This class + * works off CRTP so it needs to know the most derived class. + * @tparam ValueType The type of the elements you are storing in your + * container. + * @tparam SizeType The type of an index into your container. Default: + std::size_t. + * @tparam DifferenceType The type of the difference between two SizeType + * instances. Default: long int. + */ +template +struct BidirectionalIteratorBase + : public InputIteratorBase { + using iterator_category = std::bidirectional_iterator_tag; + + /// Implement to provide decrement functionality + virtual ParentType& decrement() = 0; + + /** @brief Decrements the current iterator before returning the value. + * + * This operator relies on decrement for its functionality. + * + * @returns The current iterator after decrementing it. + * @throws exception if decrement throws. + */ + ParentType& operator--() { return decrement(); } + + /** @brief Decrements the current iterator after returning the value. + * + * Like the postfix increment operator this operator relies on the copy + * constructor and the prefix decrement operator. + * + * @returns A copy of the current iterator before decrementing it. + * @throws exception if either the copy constructor or prefix decrement + * operator throws. + */ + ParentType operator--(int) { + ParentType& up_me = static_cast(*this); + ParentType copy_of_me(up_me); + --(*this); + return copy_of_me; + } +}; + +/** @brief This class is designed to facilitate making your own random access + * iterator class. + * + * When making your own iterator there's a lot of boiler plate involved. This + * class factors out as much boiler plate as possible for a bidirectional + * iterator, which is an iterator that allows you to access either the next + * element or the previous element. + * + * To use this class you'll need to implement in your derived class: + * - dereference + * - increment + * - are_equal + * - decrement + * - advance + * - distance_to + * + * @tparam ParentType The type of iterator that you are making. This class + * works off CRTP so it needs to know the most derived class. + * @tparam ValueType The type of the elements you are storing in your + * container. + * @tparam SizeType The type of an index into your container. Default: + std::size_t. + * @tparam DifferenceType The type of the difference between two SizeType + * instances. Default: long int. + */ +template +struct RandomAccessIteratorBase + : public BidirectionalIteratorBase { + using iterator_category = std::random_access_iterator_tag; + + /// Implement to provide advance + virtual ParentType& advance(DifferenceType n) = 0; + + /// Implement to provide ordering + virtual DifferenceType distance_to(const ParentType& rhs) const = 0; + + /** @brief Provides random access to any element in the container relative + * to the element currently pointed to by this iterator. + * + * This function ultimately works by calling operator+ + * + * @param[in] n The desired number of iterations away. + * @return The element that is @p n iterations away + * @throws exception if operator + throws + */ + ValueType operator[](DifferenceType n) const { return *((*this) + n); } + + /** @brief Compares two iterators and returns true if the current iterator + * points to an element appearing earlier in the container + * + * This function ultimately calls distance_to and then uses the fact + * that a positive distance means that @p rhs points to an element + * further in the sequence. + * + * @param[in] rhs The iterator to compare to. + * @returns True if the current iterator points to an element indexed + * earlier then the element pointed to by @p rhs. + * @throws None. No throw guarantee. + */ + bool operator<(const ParentType& rhs) const noexcept { + return distance_to(rhs) > 0; + } + + /** @brief Compares two iterators and returns true if the current iterator + * points to an element appearing earlier in the container or if the two + * iterators point to the same element. + * + * This function ultimately works by checking the less than operator and + * the equality operator. Hence the definitions of less than and equal to + * are set by those functions. + * + * @param[in] rhs The iterator to compare to. + * @returns True if the current iterator points to an element indexed + * earlier than or the same as the element pointed to by @p rhs. + * @throws None. No throw guarantee. + */ + bool operator<=(const ParentType& rhs) const noexcept { + return (*this) < rhs || (*this) == rhs; + } + + /** @brief Compares two iterators and returns true if this iterator points + * to an element later in the container than the rhs iterator. + * + * This function ultimately checks if @p rhs is less than the current + * iterator and thus uses the same concept of order. + * + * @param[in] rhs The iterator to compare to. + * @returns True if the current element is indexed after that of @p rhs. + * @throws None. No throw guarantee. + */ + bool operator>(const ParentType& rhs) const noexcept { + return rhs < static_cast(*this); + } + + /** @brief Compares two iterators and returns true if they point to the + * same element or the element pointed to by this iterator occurs later in + * the sequence than that of the rhs iterator. + * + * This function ultimately checks if @p rhs is less than or equal to + * the current iterator and hence uses the same definitions of equality + * and order. + * + * @param[in] rhs The iterator to compare to. + * @returns true if the current iterator points to the same element or one + * later in the sequence than @p rhs. + * @throws None. No throw guarantee. + */ + bool operator>=(const ParentType& rhs) const noexcept { + return rhs <= static_cast(*this); + } + + /** @brief Advances the current iterator a specified number of iterations. + * + * This function ultimately relies on advance. + * + * @param[in] n The number of iterations to move forward by. If @p n is + * negative then the iterator will actually move backward by @p n. + * @returns The iterator advanced @p n elements. + * @throws exception if advance throws. + */ + ParentType& operator+=(DifferenceType n) { return advance(n); } + + /** @brief Creates a copy of the current iterator that points to the element + * a specified number of iterations away. + * + * This function ultimately relies on operator+= and the copy constructor. + * + * @param[in] n The number of iterations to move forward by. If @p n is + * negative then the iterator will actually move backward by @p n. + * @returns A copy of the current iterator pointing to the element @n + * iterations away. + * @throws exception if either the copy constructor or operator+= throw. + */ + ParentType operator+(DifferenceType n) const { + ParentType copy_of_me(static_cast(*this)); + copy_of_me += n; + return copy_of_me; + } + + /** @brief Moves the current iterator backwards by the specified number of + * iterations. + * + * This function ultimately negates its input and then calls operator+. + * + * @param[in] n The number of iterations to move backward by. If @p n is + * negative then the iterator will actually move forward by @p n. + * @returns The current iterator pointing to the element @p n iterations + * away. + * @throws exception if operator+ throws. + */ + ParentType& operator-=(DifferenceType n) { return (*this) += (-n); } + + /** @brief Creates a copy of the current iterator that points to the element + * a specified number of iterations away. + * + * This function ultimately relies on operator-= and the copy constructor. + * + * @param[in] n The number of iterations to move backward by. If @p n is + * negative then the iterator will actually move forward by @p n. + * @returns A copy of the current iterator pointing to the element @n + * iterations away. + * @throws exception if either the copy constructor or operator-= throw. + */ + ParentType operator-(DifferenceType n) const { + ParentType copy_of_me(static_cast(*this)); + copy_of_me -= n; + return copy_of_me; + } + + /** @brief Returns the distance between two iterators + * + * This ultimately works by calling distance_to. + * + * @param[in] rhs The iterator to compare to. + * @returns The distance between this and rhs. + * @throws exception if distance_to throws. + */ + DifferenceType operator-(const ParentType& rhs) const { + return distance_to(rhs); + } +}; + +/** + * Template meta-programming to select the correct base type given the iterator + * tag type. + */ +///@{ +template +struct Iterator2Base {}; + +template +struct Iterator2Base { + using type = InputIteratorBase; +}; + +template +struct Iterator2Base { + using type = BidirectionalIteratorBase; +}; + +template +struct Iterator2Base { + using type = RandomAccessIteratorBase; +}; +///@} + +} // namespace detail_ +} // namespace utilities diff --git a/utilities/type_traits/tuple_utilities.hpp b/utilities/type_traits/tuple_utilities.hpp new file mode 100644 index 0000000..0d0b191 --- /dev/null +++ b/utilities/type_traits/tuple_utilities.hpp @@ -0,0 +1,221 @@ +#pragma once +#include "utilities/type_traits/type_traits_extensions.hpp" +#include +#include + +/** @file TupleUtilities.hpp + * + * This file collects a series of useful utilities for doing template + * meta-programming using tuples. Their actual definitions are a bit messy so + * it is highly recommended you consult their documentation or the tests to see + * how to use them. For the most part the algorithms in this file strive to + * emulate the STL container routines with similar names. + * + * - tuple_transform: element-wise applies a unary/binary functor to a + * tuple/pair of tuples and collects the results in a new + * tuple + * - tuple_accumulate: applies a functor to each element of a tuple and + * accumulates the results in a single value + * - tuple_find_if: returns the first index of the tuple element for which a + * functor returns true + * + */ + +namespace utilities { +namespace detail_ { + +/** @brief Type that will tell us if we have iterated over all elements of a + * tuple. + * + * If @p I is the same as the number of elements in the tuple the resulting + * class will contain a bool member @p value set to true. Otherwise @p value + * will be set to false. + * + * @tparam I The iteration number. + * @tparam tuple_type The type of the tuple we are iterating over. + */ +template +using recursion_done = std::is_same< + std::integral_constant, + std::integral_constant>::value>>; + +/** @brief Type that will tell us if we are not done iterating over all + * elements of a tuple. + * + * If @p I is the same as the number of elements in the tuple the resulting + * class will contain a bool member @p value set to false. Otherwise @p value + * will be set to true. + * + * @note One could negate the result of recursion_done instead of using this + * class; however, doing so results in the same type and can't be used for + * SFINAE. + * + * @tparam I The iteration number. + * @tparam tuple_type The type of the tuple we are iterating over. + */ +template +using recursion_not_done = Negation>; + +/// Enables a function via SFINAE if recursion through a tuple has finished +template +using done_recursing = + std::enable_if::value, int>; + +/// Enables a function via SFINAE if recursion through a tuple is still going +template +using recursing = std::enable_if::value, int>; + +/// Actually implements the tuple_transform function +template +auto tuple_transform_impl(tuple_type&& tuple, functor_type&& functor, + std::index_sequence) { + return std::make_tuple( + functor.template run(std::get(std::forward(tuple)))...); +}; + +/// The guts behind actually combining tuples via a functor +template +auto tuple_transform_impl(lhs_type&& lhs, rhs_type&& rhs, + functor_type&& functor, std::index_sequence) { + return std::make_tuple( + functor.template run(std::get(std::forward(lhs)), + std::get(std::forward(rhs)))...); +}; + +/// End-point for reducing a tuple +template::type = 0> +return_type tuple_accumulate_impl(tuple_type&&, functor_type&&, + return_type val) { + return val; +}; + +/// The guts of the recursive process for reducing a tuple +template::type = 0> +return_type tuple_accumulate_impl(tuple_type&& tuple, functor_type&& functor, + return_type val) { + auto new_val = functor.template run( + val, std::get(std::forward(tuple))); + return tuple_accumulate_impl(std::forward(tuple), + std::forward(functor), + new_val); +}; + +template::type = 0> +std::size_t tuple_find_if_impl(tuple_type&& t, functor_type&& functor) { + return I; +} + +template::type = 0> +std::size_t tuple_find_if_impl(tuple_type&& t, functor_type&& functor) { + return functor.template run(std::get(t)) ? + I : + tuple_find_if_impl(std::forward(t), + std::forward(functor)); +} + +} // namespace detail_ + +/** @brief Given a tuple of objects this function will apply a functor to + * each of them and return the results as a tuple. + * + * @param[in] tuple The tuple to apply the functor to. + * @param[in] functor An instance of the functor to use. + * + * @tparam tuple_type The type of the std::tuple we are applying a functor to. + * @tparam functor_type The type of the functor we are applying to the tuple. + * The functor's operator() must be take a single argument, which will be one of + * the tuple's elements (hence for tuples with hetrogenous types, it must be + * templated on the input type). Hint: one can use auto as the return type to + * avoid having to do additional template meta-programming. + * + */ +template +auto tuple_transform(tuple_type&& tuple, functor_type&& functor) { + constexpr std::size_t nelems = + std::tuple_size>::value; + return detail_::tuple_transform_impl(std::forward(tuple), + std::forward(functor), + std::make_index_sequence()); +}; + +/** + * @brief Applies a binary operation two tuples returning the result. + * + * @tparam lhs_type The type of the tuple on the left of the operation. + * @tparam rhs_type The type of the tuple on the right of the operation. + * @tparam functor_type They type of the binary operation to apply + * @param lhs The tuple on the left. + * @param rhs The tuple on the right. + * @param functor The actual functor instance to apply. + * @return A tuple whose @f$i@f$-th element contains the result of applying + * @p functor to the @f$i@f$-th element of @p lhs and @p rhs. + * + */ +template +auto tuple_transform(lhs_type&& lhs, rhs_type&& rhs, functor_type&& functor) { + constexpr std::size_t size = std::tuple_size>::value; + static_assert(size == std::tuple_size>::value, + "LHS size != RHS size"); + return detail_::tuple_transform_impl( + std::forward(lhs), std::forward(rhs), + std::forward(functor), std::make_index_sequence()); +}; + +/** @brief Applies a reduction to a tuple + * + * @param tuple The tuple to reduce + * @param functor The functor instance to use to do the reduction + * @param val The initial value for the reduction. + * @return The value resulting from reducing all elements of the tuple + * + * @tparam tuple_type The type of the tuple we are reducing + * + * @tparam functor_type The type of the functor doing the reduction. Must + * define a member() with the signature: + * @code + * //Applies the functor to a tuple element combining it + * //param sum is the current reduction total + * //param element is the element to reduce into sum + * //return is the result of reducing sum and element + * return_type operator()(return_type sum, tuple_element element); + * @endcode + * + * @tparam return_type The type resulting from a reduction. + * + * @throws ??? Throws if any application of the functor to the tuple throws. + * Throw guarantee is same as that of functor. + */ +template +return_type tuple_accumulate(tuple_type&& tuple, functor_type&& functor, + return_type val) { + return detail_::tuple_accumulate_impl<0>( + std::forward(tuple), std::forward(functor), + val); +}; + +/** + * @brief Applies a functor to each element of a tuple and returns the index + * of the tuple element for which the functor first evaluates to true. + * @tparam tuple_type The type of the tuple. + * @tparam functor_type The type of a unary functor used to "select" an element + * of the tuple. + * @param t The tuple. + * @param functor The functor instance to apply to each element of the tuple. + * Should define a member function bool operator()(T&&) which will be + * called for the evaluation. + * @return The index for which the functor first evaluates to true or the size + * of the tuple if no entry evaluates to true. + */ +template +std::size_t tuple_find_if(tuple_type&& t, functor_type&& functor) { + return detail_::tuple_find_if_impl<0>(std::forward(t), + std::forward(functor)); +}; + +} // namespace utilities diff --git a/utilities/type_traits/type_traits_extensions.hpp b/utilities/type_traits/type_traits_extensions.hpp new file mode 100644 index 0000000..5d4c6bf --- /dev/null +++ b/utilities/type_traits/type_traits_extensions.hpp @@ -0,0 +1,399 @@ +#pragma once +#include +#include + +/** @file type_traitsExtensions.hpp + * + * This file contains functions that should be viewed as extensions of + * the STL's type_traits library. + * + * The main intent of this file is so people can ensure their classes adhere + * to the STL concepts. Heavy usage of them is likely to balloon compile time + * as they are template heavy. + * + * @note These checks should be taken as necessary, but not sufficient at this + * time. + * + * @note My capitalization syntax for the most part matches that of the STL. + * The reason being much of this is code that is generated by macros to + * look for standard features mandated by the STL. It's easier and more + * intuitive to adopt to their syntax. Only when there may be a + * conflict with newer versions of the STL do I revert back to CamelCase. + * + * Contents: + * - VoidType : Type that always defines a type "type" that is void + * - Conjunction : performs multiple checks returning true if all are true + * - has_value_type : Checks if type defines typedef "value_type" + * - has_reference : Checks if type defines typedef "reference" + * - has_const_reference : Checks if type defines typedef "const_reference" + * - has_pointer : Checks if type defines typedef "pointer" + * - has_const_pointer : Checks if type defines typedef "const_pointer" + * - has_iterator : Checks if type defines typedef "iterator" + * - has_const_iterator : Checks if type defines typedef "const_iterator" + * - has_difference_type : Checks if type defines typedef "difference_type" + * - has_size_type : Checks if type defines typedef "size_type" + * - has_iterator_category : Checks for typedef "iterator_category" + * - has_begin : Checks for member function "begin" + * - has_end : Checks for member function "end" + * - has_cbegin : Checks for member function "cbegin" + * - has_cend : Checks for member function "cend" + * - has_size : Checks for member function "size" + * - has_max_size : Checks for member function "max_size" + * - has_empty : Checks for member function "empty" + * - has_equal_to : Checks for operator== + * - has_not_equal_to : Checks for operator!= + * - has_less_than : Checks for operator< + * - has_greater_than : Checks for operator> + * - has_less_than_equal : Checks for operator<= + * - has_greater_than_equal : Checks for operator>= + * - has_increment_by : Checks for operator+= + * - has_decrement_by : Checks for operator-= + * - has_plus : Checks for operator+ + * - has_minus : Checks for operator- + * - has_dereference : Checks for operator*() + * - has_arrow : Checks for operator-> + * - has_prefix_increment : Checks for operator++() + * - has_postfix_increment : Checks for operator++(int) + * - has_prefix_decrement : Checks for operator--() + * - has_postfix_decrement : Checks for operator--(int) + * - is_indexable : Checks for operator[] + * - is_container : Checks if type satisfies concept of "container" + * - is_iterator : Checks if type satisfies concept of "iterator" + * - is_input_iterator : Checks if type satisfies concept of input iterator + * - is_forward_iterator : Checks if type satisfies concept of forward iterator + * - is_bidirectional_iterator : Checks if type satisfies concept of + * bidirectional iterator + * - is_random_access_iterator : Checks if type satisfies concept of random + * access iterator + * + */ + +/// Namespace for all functionality in the Utilities library +namespace utilities { + +/** @brief A type that is always void. + * + * Basically this class provides us a SFINAE vessel for checking if a type + * exists, without specializing the call to that type. + * + * @todo C++17 provides this type, switch when we can use C++17 + * + * @tparam T An arbitrary type + */ +template +struct VoidType { + using type = void; +}; + +/** @brief Sets the member value to the opposite of the input value + * + * @tparam T the class containing the value to negate + * @todo C++17 provides this type + */ +template +struct Negation { + constexpr static bool value = !T::value; +}; + +/** @brief Returns true if all the types are true too. + * + * @todo C++17 provides this class. + * + */ +template +struct Conjunction : std::true_type {}; +template +struct Conjunction : B1 {}; +template +struct Conjunction + : std::conditional_t, B1> {}; + +/** @brief This macro will define a class gas_xxx for determining if a + * type defines a typedef xxx. + * + * Although at first it may seem like we could make the requested type a + * template type parameter a simple example shows why this won't work: + * @code + * //Won't work, type_2_check_for is not a type declared in this scope + * bool answer=HasType::value; + * @endcode + * We could do something like: + * @code + * bool answer=HasType::value; + * @endcode + * but that simply won't compile if it doesn't have the type (in other words, + * it doesn't actually return false as intended). + * + * The only way I can see to do this is to declare a has_type_xxx for each type + * we want to check for. This isn't so bad because we really only need to + * check for a few types, assuming usual nomenclature. These classes really + * only differ in their names, and the type they try to call, hence a macro is + * needed to reduce the boiler-plate. + * + * @param[in] NAME name of the type to define a check for. + */ +#define HAS_TYPE(NAME) \ + template \ + struct has_type_##NAME : std::false_type {}; \ + template \ + struct has_type_##NAME::type> \ + : std::true_type {} + +HAS_TYPE(value_type); +HAS_TYPE(reference); +HAS_TYPE(const_reference); +HAS_TYPE(pointer); +HAS_TYPE(const_pointer); +HAS_TYPE(iterator); +HAS_TYPE(const_iterator); +HAS_TYPE(difference_type); +HAS_TYPE(size_type); +HAS_TYPE(iterator_category); + +#undef HAS_TYPE + +/** @brief This macro will define a class has_xxx for determining if a + * type has a function xxx. + * + * @note The function must not return void for this macro to work + * + * @param[in] type_name name of the member to define a check for. + */ +#define HAS_MEMBER(NAME) \ + template \ + struct has_##NAME : std::false_type {}; \ + template \ + struct has_##NAME< \ + T, typename VoidType().NAME())>::type> \ + : std::true_type {} + +HAS_MEMBER(begin); +HAS_MEMBER(end); +HAS_MEMBER(cbegin); +HAS_MEMBER(cend); +HAS_MEMBER(size); +HAS_MEMBER(max_size); +HAS_MEMBER(empty); + +#undef HAS_MEMBER + +/** @brief This macro will define a class has_xxx for determining if a + * type has an operator corresponding to description xxx. + * + * @param[in] NAME name of the member for function + * @param[in] symbol the symbol the check for + */ +#define HAS_OPERATOR(NAME, symbol) \ + template \ + struct has_##NAME : std::false_type {}; \ + template \ + struct has_##NAME() symbol std::declval())>::type> \ + : std::true_type {} + +HAS_OPERATOR(equal_to, ==); +HAS_OPERATOR(not_equal_to, !=); +HAS_OPERATOR(less_than, <); +HAS_OPERATOR(greater_than, >); +HAS_OPERATOR(less_than_equal, <=); +HAS_OPERATOR(greater_than_equal, >=); +HAS_OPERATOR(increment_by, +=); +HAS_OPERATOR(decrement_by, -=); +HAS_OPERATOR(plus, +); +HAS_OPERATOR(minus, -); + +/** @brief Checks if a type is dereference-able via the star operator + * + * @tparam T the type to check. + */ +template +struct has_dereference : std::false_type {}; +template +struct has_dereference())>::type> + : std::true_type {}; + +/** @brief Checks if a type has the arrow operator + * + * @tparam T the type to check. + */ +template +struct has_arrow : std::false_type {}; +template +struct has_arrow< + T, typename VoidType().operator->())>::type> + : std::true_type {}; + +/** @brief Checks if a type implements the prefix increment operator + * + * @tparam T The type to check + */ +template +struct has_prefix_increment : std::false_type {}; +template +struct has_prefix_increment< + T, typename VoidType())>::type> : std::true_type { +}; + +/** @brief Checks if a type implements the postfix increment operator + * + * @tparam T The type to check + */ +template +struct has_postfix_increment : std::false_type {}; +template +struct has_postfix_increment< + T, typename VoidType()++)>::type> : std::true_type { +}; + +/** @brief Checks if a type implements the prefix decrement operator + * + * @tparam T The type to check + */ +template +struct has_prefix_decrement : std::false_type {}; +template +struct has_prefix_decrement< + T, typename VoidType())>::type> : std::true_type { +}; + +/** @brief Checks if a type implements the postfix decrement operator + * + * @tparam T The type to check + */ +template +struct has_postfix_decrement : std::false_type {}; +template +struct has_postfix_decrement< + T, typename VoidType()--)>::type> : std::true_type { +}; + +/** @brief Checks if a type implements the index operator + * + * @tparam T The type to check + */ +template +struct is_indexable : std::false_type {}; +template +struct is_indexable()[0])>::type> + : std::true_type {}; + +#undef HAS_OPERATOR + +/** @brief This struct will contain a value true if the type satisfies the C++ + * concept of "Container" + * + * @warning This type will not check if the type is swappable. A type_trait, + * is_swappable exists in C++17 for this purpose. Implementing our + * own is a royal pain on account of how the STL actually does + * swapping... + * + * The full specificiation of what a "container" must have is available + * < a href="http://en.cppreference.com/w/cpp/concept/Container">here. + */ +template +struct is_container + : Conjunction, has_type_reference, + has_type_const_reference, has_type_iterator, + has_type_const_iterator, has_type_difference_type, + has_type_size_type, std::is_default_constructible, + std::is_copy_constructible, std::is_assignable, + std::is_destructible, has_begin, has_end, + has_cbegin, has_cend, has_equal_to, + has_not_equal_to, has_size, has_max_size, + has_empty> {}; + +/** @brief This struct will contain a value true if the type satisfies the C++ + * concept of "Iterator" + * + * @warning This type will not check if the type is swappable. A type_trait, + * is_swappable exists in C++17 for this purpose. Implementing our + * own is a royal pain on account of how the STL actually does + * swapping... + * + * The full specificiation of what an "iterator" must have is available + * < a href="http://en.cppreference.com/w/cpp/concept/iterator">here. + */ +template +struct is_iterator + : Conjunction, has_type_difference_type, + has_type_reference, has_type_pointer, + has_type_iterator_category, has_dereference, + has_prefix_increment, std::is_copy_constructible, + std::is_assignable, std::is_destructible> {}; + +/** @brief This struct will contain a value true if the type satisfies the C++ + * concept of "InputIterator" + * + * @warning This type will not check if the type is swappable. A type_trait, + * is_swappable exists in C++17 for this purpose. Implementing our + * own is a royal pain on account of how the STL actually does + * swapping... + * + * The full specificiation of what an "input iterator" must have is available + * < a href="http://en.cppreference.com/w/cpp/concept/InputIterator">here. + */ +template +struct is_input_iterator + : Conjunction, has_equal_to, has_not_equal_to, + has_postfix_increment, has_arrow> {}; + +/** @brief This struct will contain a value true if the type satisfies the C++ + * concept of "ForwardIterator" + * + * @note A forward iterator is just an input iterator with a multipass + * guarantee. We currently are not checking for this hence this struct + * is only a typedef for clarity. + * + * @warning This type will not check if the type is swappable. A type_trait, + * is_swappable exists in C++17 for this purpose. Implementing our + * own is a royal pain on account of how the STL actually does + * swapping... + * + * The full specificiation of what an "forward iterator" must have is available + * < a + * href="http://en.cppreference.com/w/cpp/concept/ForwardIterator">here. + */ +template +using is_forward_iterator = is_input_iterator; + +/** @brief This struct will contain a value true if the type satisfies the C++ + * concept of "BidirectionalIterator" + * + * @warning This type will not check if the type is swappable. A type_trait, + * is_swappable exists in C++17 for this purpose. Implementing our + * own is a royal pain on account of how the STL actually does + * swapping... + * + * The full specificiation of what an "bidirectional iterator" must have is + * available + * < a + * href="http://en.cppreference.com/w/cpp/concept/BidirectionalIterator">here. + */ +template +struct is_bidirectional_iterator + : Conjunction, has_prefix_decrement, + has_postfix_decrement> {}; + +/** @brief This struct will contain a value true if the type satisfies the C++ + * concept of "RandomAccessIterator" + * + * @warning This type will not check if the type is swappable. A type_trait, + * is_swappable exists in C++17 for this purpose. Implementing our + * own is a royal pain on account of how the STL actually does + * swapping... + * + * The full specificiation of what an "random access iterator" must have is + * available + * < a + * href="http://en.cppreference.com/w/cpp/concept/RandomAccessIterator">here. + */ +template +struct is_random_access_iterator + : Conjunction, has_increment_by, + has_decrement_by, has_plus, + has_minus, has_minus, is_indexable, + has_less_than, has_greater_than, + has_less_than_equal, has_greater_than_equal> {}; + +} // namespace utilities