From 48767f7e0989a66fa3f3abfbb0c8d47b0bbcee30 Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Wed, 25 Mar 2020 15:45:29 -0700 Subject: [PATCH 01/10] Initial unordered-containers user guide content. [ci skip] --- .gitmodules | 3 + .readthedocs.yml | 11 + docs/Makefile | 20 + docs/_extensions/haddock-autolink | 1 + docs/_extensions/hs-theme.py | 2 + docs/_extensions/hs-theme.pyc | Bin 0 -> 382 bytes docs/_static/css/hs-theme.css | 11 + docs/_static/images/favicon-16x16.png | Bin 0 -> 884 bytes docs/_static/images/favicon-green-16x16.png | Bin 0 -> 789 bytes docs/_static/images/haskell-logo-black.svg | 17 + docs/_static/images/haskell-logo-green.svg | 17 + docs/_static/sitemap.xml | 39 ++ docs/_templates/layout.html | 7 + docs/conf.py | 101 ++++ docs/hash-map.rst | 578 ++++++++++++++++++++ docs/hash-set.rst | 386 +++++++++++++ docs/index.rst | 13 + docs/intro.rst | 86 +++ docs/make.bat | 36 ++ 19 files changed, 1328 insertions(+) create mode 100644 .gitmodules create mode 100644 .readthedocs.yml create mode 100644 docs/Makefile create mode 160000 docs/_extensions/haddock-autolink create mode 100644 docs/_extensions/hs-theme.py create mode 100644 docs/_extensions/hs-theme.pyc create mode 100644 docs/_static/css/hs-theme.css create mode 100644 docs/_static/images/favicon-16x16.png create mode 100644 docs/_static/images/favicon-green-16x16.png create mode 100644 docs/_static/images/haskell-logo-black.svg create mode 100644 docs/_static/images/haskell-logo-green.svg create mode 100644 docs/_static/sitemap.xml create mode 100644 docs/_templates/layout.html create mode 100644 docs/conf.py create mode 100644 docs/hash-map.rst create mode 100644 docs/hash-set.rst create mode 100644 docs/index.rst create mode 100644 docs/intro.rst create mode 100644 docs/make.bat diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..0933b785 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/_extensions/haddock-autolink"] + path = docs/_extensions/haddock-autolink + url = https://github.com/m-renaud/haddock-autolink diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..94d73749 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,11 @@ +version: 2 + +sphinx: + configuration: docs/conf.py + +submodules: + include: + - docs/_extensions/haddock-autolink + +python: + version: 2.7 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..ad7b0424 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = -a -E +SPHINXBUILD = sphinx-build +SPHINXPROJ = unordered-containers +SOURCEDIR = . +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/_extensions/haddock-autolink b/docs/_extensions/haddock-autolink new file mode 160000 index 00000000..51c39abb --- /dev/null +++ b/docs/_extensions/haddock-autolink @@ -0,0 +1 @@ +Subproject commit 51c39abba4ceacd5a171603e43453a6cfe7e2ac6 diff --git a/docs/_extensions/hs-theme.py b/docs/_extensions/hs-theme.py new file mode 100644 index 00000000..8201ff85 --- /dev/null +++ b/docs/_extensions/hs-theme.py @@ -0,0 +1,2 @@ +def setup(app): + app.add_stylesheet('css/hs-theme.css') diff --git a/docs/_extensions/hs-theme.pyc b/docs/_extensions/hs-theme.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a006264bde42a46835de47e2f8d0086c80fd2d41 GIT binary patch literal 382 zcmb_YI|>3Z5S=K32#Vff8_fko1PeP0?JSff8Hl=@EIV0Hw9}(n*n0tIRzVM7;Jx|J zOVWIHJNL(VW8iZX+bM&(Ocvk14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>AP)q@2~ zAPOM5AR=J0)JzLx6cAbIMgVOA3Ia)p`9MJz(=3SMT#L#&v#MG!f~W_I2UwK?MSw1Z zXouL9Y*7wT>1kC`WC79wQ&|ZyD$x?80Iayw73BUBKl^Hk6p)0v0p^_=v&#Sf|NHP} z-vow5NJ)?%C|r>NBO_z&f*!-Rh2>mvA11W^`xeLA_dapgbq(3m$D*IlmkN)M^Z(eC zwS;AI?Eb5M4XyLlYX9c0ShvqJTFsgxp0Vm@ns7CDW%|X9Ktmamyxm>GYq@U913By^ zp1!W^j~Kbx1Pqf_TmZ%+!!l17#}J9j$q5eZe|mhFntVK%oDL~1N_r$T>5KuR=1T#| zNXtrAv9J{li-I~fb!DxXWhHIx(%|fD?BX4*9un?<|9}98goI^pkMk6tCr^CO9&%`C zY&?BO$>ruHtt+>VUE|{rU|RT%uTOz-g_!!zfXa0731TxAuFG4P^-j*gIq_Om)ytNy z>f*z)&kjF+{rrjifz#~;R>cd}R0J5vcRjzs!g9l)hM!^iJq4$?&B_@-7ps=IMwFx^ zmZVxG7o`Fz1|tJQ3ta<4T|>hV12Zc_GbejJGDp}?H+U@Y(qnifE?Dx($#g2v O3I14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>` zDbG_@+O4Ft3xt$Am7(Ihl@xa?0%a9?6hN|Iq}`wmlvVCfR@|Zp5s~SWQErE7QRr4s zoUaH}px6V_2vXUuthfs#0z?YypyntpR8;B(8K}5dQDwIZ$X=kzJ<3XZK!VzHaZ@8o!=yLOtAirRS#^p<7=PmDb{I|Fvb!x!*k8(Wsp4$G}DU>jO zpTF${`L^CgDSMyW@4jRf{7K$$+tT#LM7G(JneETlXakL4O!9VjVd!9$^#F1>3p^r= zfpjDYGp?GuvJJ>!FY)wsWq-uTEp8^-FtP44P^iVz#W6(Ua%;c4&>;sN7FQ$5*;~|R z&pDWK_J4d^Sk6ZG?;BS~nJS%9nXWJ5?Rc}>Yif4sf``77cZw=y{dZk{dhb3*8wsub z87Yemqhm}aW>{^mXtbEAAL6lT3QNRV(}{~`uRWO8|6+l=szsZ{Tz$51bDgOV+)CKx ztZYj!&tL!G@5z5t_bpu{( zyEr+qAXP8FD1G)j8!4co@T!oAlAy$Lg@U5|w9K4Tg_6pGRE5lfl4J&kiaC!z@o*G| zX=t4CKYhmYX%GXmGPhnbx3IFX_hb=fVFi~4lfx;@%9}$JPT#n4;>ejJGDp}?H+U@Y a(qnifE?Dx($#g2v3I + Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + background + + + + Layer 1 + + + + + + + + diff --git a/docs/_static/images/haskell-logo-green.svg b/docs/_static/images/haskell-logo-green.svg new file mode 100644 index 00000000..906af037 --- /dev/null +++ b/docs/_static/images/haskell-logo-green.svg @@ -0,0 +1,17 @@ + + Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + background + + + + Layer 1 + + + + + + + + diff --git a/docs/_static/sitemap.xml b/docs/_static/sitemap.xml new file mode 100644 index 00000000..94d32a8a --- /dev/null +++ b/docs/_static/sitemap.xml @@ -0,0 +1,39 @@ + + + + + https://haskell-unordered-containers.readthedocs.io/en/latest/ + 2020-03-24T19:12:16+00:00 + 1.00 + + + https://haskell-unordered-containers.readthedocs.io/en/latest/intro.html + 2020-03-24T19:12:16+00:00 + 0.80 + + + https://haskell-unordered-containers.readthedocs.io/en/latest/hash-set.html + 2020-03-24T19:12:17+00:00 + 0.80 + + + https://haskell-unordered-containers.readthedocs.io/en/latest/hash-map.html + 2020-03-24T19:12:16+00:00 + 0.80 + + + https://haskell-unordered-containers.readthedocs.io/en/stable/ + 2020-03-24T18:24:14+00:00 + 0.80 + + + https://haskell-unordered-containers.readthedocs.io/en/latest/index.html + 2020-03-24T19:12:16+00:00 + 0.64 + + + diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 00000000..57f1b91a --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,7 @@ +{% extends "!layout.html" %} + +{%- block extrahead %} + + + +{% endblock %} diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..767ba8bc --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# + +from docutils.parsers.rst import roles +from docutils import nodes +import itertools +import string +import os +import sphinx_rtd_theme +import sys + + +# -- General configuration ------------------------------------------------ + +# Add the _extenions dir to the search path. +sys.path.insert(0, os.path.abspath('.') + '/_extensions') +sys.path.insert(0, os.path.abspath('.') + '/_extensions/haddock-autolink') + +extensions = ['sphinx.ext.intersphinx', + 'sphinx.ext.ifconfig', + 'haddock-autolink', + 'hs-theme'] + +templates_path = ['_templates'] + +source_suffix = '.rst' + +master_doc = 'index' + +# General information about the project. +project = u'unordered-containers' +copyright = u'2020, Matt Renaud' +author = u'Matt Renaud' + +# The short X.Y version. +version = u'0.2.10' +# The full version, including alpha/beta/rc tags. +release = u'0.2.10.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'friendly' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# 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_logo = '_static/images/haskell-logo-green.svg' +html_static_path = ['_static'] +html_context = { + 'source_url_prefix': "https://github.com/tibbe/unordered-containers/tree/master/docs/", + "display_github": True, + "github_host": "github.com", + "github_user": "tibbe", + "github_repo": 'unordered-containers', + "github_version": "master/", + "conf_py_path": "docs/", + "source_suffix": '.rst', +} + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} diff --git a/docs/hash-map.rst b/docs/hash-map.rst new file mode 100644 index 00000000..caee3077 --- /dev/null +++ b/docs/hash-map.rst @@ -0,0 +1,578 @@ +Hash Maps +========= + +.. highlight:: haskell + +Maps (sometimes referred to as dictionaries in other languages) allow you to +store associations between *unique keys* and *values*. There are two +implementations provided by the ``unordered-containers`` package: +:haddock:`/Data.HashMap.Strict` and :haddock:`/Data.HashMap.Lazy`. You almost +never want the lazy version so use ``Data.Map.Strict``, and if your keys are +``Int`` consider using ``Data.IntMap`` from the :haddock:`containers` package +which is `faster `_ for many +operations. + +:: + + data HashMap k v = ... + +.. IMPORTANT:: + ``HashMap`` relies on the key type ``k`` having instances of the ``Eq`` and + ``Hashable`` typeclasses for its internal representation. These are already + defined for builtin types, and if you are using your own data type you can + use the `deriving + `_ + mechanism. + +All of these implementations are *immutable* which means that any update +functions do not modify the map that you passed in, they creates a new map. In +order to keep the changes you need to assign it to a new variable. For example:: + + let m1 = HashMap.fromList [("a", 1), ("b", 2)] + let m2 = HashMap.delete "a" m1 + print m1 + > fromList [("a",1),("b",2)] + print m2 + > fromList [("b",2)] + + +Short Example +------------- + +The following GHCi session shows some of the basic hash map functionality:: + + import qualified Data.HashMap.Strict as HashMap + + let nums = HashMap.fromList [(1,"one"), (2,"two"), (3,"three")] + + -- Get the English word for the number 3 and 4. + HashMap.lookup 3 nums + > Just "three" + + HashMap.lookup 4 nums + > Nothing + + + -- Add (4, "four") to our original map. + let moreNums = HashMap.insert 4 "four" nums + + HashMap.member 4 moreNums + > True + + + -- Remove the entry for 1 from our original map. + let fewerNums = HashMap.delete 1 nums + + HashMap.toList fewerNums + > [(2,"two"),(3,"three")] + + + -- Create a new map and combine it with our original map. + -- fromList is right-biased: if a key is repeated the rightmost value is taken. + let newNums = HashMap.fromList [(3,"new three"), (4,"new four"), (4,"newer four")] + + -- union is left-biased: if a key occurs more than once the value from the + -- left map is taken. + HashMap.union newNums nums + > fromList [(1,"one"),(2,"two"),(3,"new three"),(4,"newer four")] + +.. TIP:: You can use the `OverloadedLists + `_ extension so + you don't need to write ``fromList [1, 2, 3]`` everywhere; instead you + can just write ``[1, 2, 3]`` and if the function is expecting a map it + will be converted automatically! The code here will continue to use + ``fromList`` for clarity though. + + +Importing HashMap +----------------- + +When using ``HashMap`` in a Haskell source file you should always use a +``qualified`` import because these modules export names that clash with the +standard Prelude (you can import the type constructor on its own though!). You +should also import ``Prelude`` and hide ``lookup`` because if you accidentally +leave off the ``HashMap.`` qualifier you'll get confusing type errors. You can +always import any specific identifiers you want unqualified. Most of the time, +that will include the type constructor (``HashMap``). + +:: + + import Prelude hiding (lookup) + + import Data.HashMap.Strict (HashMap) + import qualified Data.HashMap.Strict as HashMap + + +Common API Functions +-------------------- + +.. NOTE:: + A ``HashMap`` is printed as an association list preceeded by ``fromList``. For + example, it might look like ``fromList [(Key1,True),(Key2,False)]``. + + +Construction and Conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Create an empty map +""""""""""""""""""" + +:: + + HashMap.empty :: HashMap k v + HashMap.empty = ... + +:haddock_short:`/Data.HashMap.Strict#empty` creates a map without any entries. + +:: + + HashMap.empty + > fromList [] + +Create a map with one entry (singleton) +""""""""""""""""""""""""""""""""""""""" + +:: + + HashMap.singleton :: k -> v -> HashMap k v + HashMap.singleton key value = ... + +:haddock_short:`/Data.HashMap.Strict#singleton` creates a map with a single +``(key,value)`` entry in it. + +:: + + HashMap.singleton 1 "one" + > fromList [(1,"one")] + + HashMap.singleton "containers" ["base"] + > fromList [("containers",["base"])] + +Create a map from a list +"""""""""""""""""""""""" + +:: + + HashMap.fromList :: [(k, v)] -> HashMap k v + HashMap.fromList xs = ... + +:haddock_short:`/Data.HashMap.Strict#fromList` creates a map containing the entries +of the list ``xs`` where the keys comes from the first entries of the pairs and +the values from the second. If the same key appears more than once then the last +value is taken. + +:: + + HashMap.fromList [] + > fromList [] + + HashMap.fromList [(1,"uno"), (1,"one"), (2,"two"), (3,"three")] + > fromList [(1,"one"),(2,"two"),(3,"three")] + +There's another incredibly useful function for constructing a map from a list:: + + HashMap.fromListWith :: (a -> a -> a) -> [(k, a)] -> HashMap k a + HashMap.fromListWith f xs = ... + +:haddock_short:`/Data.HashMap.Strict#fromListWith` allows you to build a map from a +list ``xs`` with repeated keys, where ``f`` is used to "combine" (or "choose") +values with the same key. + +:: + + -- Build a map from a list, but only keep the largest value for each key. + HashMap.fromListWith max [("a", 2), ("a", 1), ("b", 2)] + > fromList [("a",2),("b",2)] + + -- Build a histogram from a list of elements. + HashMap.fromListWith (+) (map (\x -> (x, 1)) ["a", "a", "b", "c", "c", "c"]) + > fromList [("a",2),("b",1),("c",3)] + + -- Build a map from a list, combining the string values for the same key. + HashMap.fromListWith (++) [(1, "a"), (1, "b"), (2, "x"), (2, "y")] + > fromList [(1,"ba"),(2,"yx")] + + + +Create a list from a map +"""""""""""""""""""""""" + +:: + + HashMap.toList :: HashMap k v -> [(k, v)] + HashMap.toList m = ... + +.. NOTE:: + ``HashMap.toList`` is **not** the same as ``Foldable.toList``; the latter is + equivalent to ``elems``, although is rarely useful for maps. + +:haddock_short:`/Data.HashMap.Strict#toList` returns a list containing the (key, +value) pairs in the map ``m``, the order is unspecified. + +:: + + HashMap.toList (HashMap.fromList [(1,"one"), (2,"two"), (3,"three")]) + > [(1,"one"),(2,"two"),(3,"three")] + + HashMap.toList (HashMap.fromList [(1,"one"), (2,"two"), (-3,"negative three")]) + > [(1,"one"),(2,"two"),(-3,"negative three")] + + +Querying +^^^^^^^^ + +Lookup an entry in the map (lookup) +""""""""""""""""""""""""""""""""""" + +:: + + HashMap.lookup :: k -> HashMap k v -> Maybe v + HashMap.lookup key m = ... + +:haddock_short:`/Data.HashMap.Strict#lookup` the value corresponding to the given +``key``, returns ``Nothing`` if the key is not present. + +If you want to provide a default value if the key doesn't exist you can use: + +:: + + HashMap.lookupDefault :: v -> k -> HashMap k v -> v + HashMap.lookupDefault defaultVal key m = ... + +For example:: + + import Data.HashMap.Strict ((!?)) + + HashMap.lookup 1 HashMap.empty + > Nothing + + HashMap.lookup 1 (HashMap.fromList [(1,"one"),(2,"two"),(3,"three")]) + > Just "one" + + > (HashMap.fromList [(1,"one"),(2,"two"),(3,"three")]) !? 1 + > Just "one" + + HashMap.lookupDefault "?" k HashMap.empty + > "?" + + HashMap.lookupDefault "?" 1 (Map.fromList [(1,"one"), (2,"two"), (3,"three")]) + > "one" + +.. WARNING:: + **DO NOT** Use ``HashMap.!``. It is partial and throws a runtime error if + the key doesn't exist. + +Check if a map is empty +""""""""""""""""""""""" + +:: + + HashMap.null :: HashMap k v -> Bool + HashMap.null m = ... + +:haddock_short:`/Data.HashMap.Strict#null` returns ``True`` if the map ``m`` is +empty and ``False`` otherwise. + +:: + + HashMap.null HashMap.empty + > True + + HashMap.null (HashMap.fromList [(1,"one")]) + > False + +The number of entries in a map +"""""""""""""""""""""""""""""" + +:: + + HashMap.size :: HashMap k v -> Int + HashMap.size m = ... + +:haddock_short:`/Data.HashMap.Strict#size` returns the number of entries in the map +``m``. + +:: + + HashMap.size HashMap.empty + > 0 + + HashMap.size (HashMap.fromList [(1,"one"), (2,"two"), (3,"three")]) + > 3 + + +Modification +^^^^^^^^^^^^ + +Adding a new entry to a map +""""""""""""""""""""""""""" + +:: + + HashMap.insert :: k -> v -> HashMap k v -> HashMap k v + HashMap.insert key value m = ... + +:haddock_short:`/Data.HashMap.Strict#insert` adds the ``value`` into the map +``m`` with the given ``key``, replacing the existing value if the key already +exists. + +:: + + HashMap.insert 1 "one" HashMap.empty + > HashMap.fromList [(1,"one")] + + HashMap.insert 4 "four" (HashMap.fromList [(1,"one"), (2,"two"), (3,"three")]) + > fromList [(1,"one"),(2,"two"),(3,"three"),(4,"four")] + + HashMap.insert 1 "uno" (HashMap.fromList [(1,"one"), (2,"two"), (3,"three")]) + > fromList [(1,"uno"),(2,"two"),(3,"three")] + + +Removing an entry from a map +"""""""""""""""""""""""""""" + +:: + + HashMap.delete :: k -> HashMap k v -> HashMap k v + HashMap.delete key m = ... + +:haddock_short:`/Data.HashMap.Strict#delete` removes the entry with the +specified ``key`` from the map ``m``. If the key doesn't exist it leaves the +map unchanged. + +:: + + HashMap.delete 1 HashMap.empty + > HashMap.empty + + HashMap.delete 1 (HashMap.fromList [(1,"one"),(2,"two"),(3,"three")]) + > fromList [(2,"two"),(3,"three")] + +Filtering map entries +""""""""""""""""""""" + +:: + + HashMap.filterWithKey :: (k -> v -> Bool) -> HashMap k v -> HashMap k v + HashMap.filterWithKey predicate m = ... + +:haddock_short:`/Data.HashMap.Strict#filterWithKey` produces a map consisting of +all entries of ``m`` for which the ``predicate`` returns ``True``. + +:: + + let f key value = key == 2 || value == "one" + HashMap.filterWithKey f (HashMap.fromList [(1,"one"), (2,"two"), (3,"three")]) + > fromList [(1,"one"),(2,"two"] + + +Modifying a map entry +""""""""""""""""""""" + +:: + + HashMap.adjust :: (v -> v) -> k -> HashMap k v -> HashMap k v + HashMap.adjust f key m = ... + +:haddock_short:`/Data.HashMap.Strict#adjust` applies the value transformation +function ``f`` to the entry with given ``key``. If no entry for that key exists +then the map is left unchanged. + +:: + + HashMap.alter :: (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v + HashMap.alter f key m = ... + +Apply the value transformation function ``f`` to the entry with given ``key``, +if no entry for that key exists then the function is passed ``Nothing``. If the +function returns ``Nothing`` then the entry is deleted, if the function returns +``Just v2`` then the value for the ``key`` is updated to ``v2``. In other words, +alter can be used to insert, update, or delete a value. + +:: + + import Data.Maybe (isJust) + let addValueIfMissing mv = if isJust mv then mv else (Just 1) + HashMap.alter addValueIfMissing "key" (HashMap.fromList [("key", 0)]) + > fromList [("key",0)] + + let addValueIfMissing mv = if isJust mv then mv else (Just 1) + HashMap.alter addValueIfMissing "new_key" (HashMap.fromList [("key", 0)]) + > fromList [("key",0),("new_key",1)] + +The function ``doubleIfPositive`` below will need to be placed in a Haskell +source file. + +:: + + doubleIfPositive :: Maybe Int -> Maybe Int + doubleIfPositive mv = case mv of + -- Do nothing if the key doesn't exist. + Nothing -> Nothing + + -- If the key does exist, double the value if it is positive. + Just v -> if v > 0 then (Just v*2) else (Just v) + + -- In GHCi + HashMap.alter doubleIfPositive "a" (HashMap.fromList [("a", 1), ("b", -1)]) + > HashMap.fromList [("a",2), ("b",-1)] + + HashMap.alter doubleIfPositive "b" (HashMap.fromList [("a", 1), ("b", -1)]) + > HashMap.fromList [("a", 1), ("b",-1)] + +Modifying all map entries (mapping and traversing) +"""""""""""""""""""""""""""""""""""""""""""""""""" + +:: + + HashMap.map :: (a -> b) -> HashMap k a -> HashMap k v + HashMap.map f m = ... + + HashMap.mapWithKey :: (k -> a -> b) -> HashMap k a -> hashMap k b + HashMap.mapWithKey g m = ... + + +:haddock_short:`/Data.HashMap.Strict#map` creates a new map by applying the +transformation function ``f`` to each entries value. This is how `Functor +`_ is defined for maps. + +:haddock_short:`/Data.HashMap.Strict#mapWithKey` does the same as ``map`` but +gives you access to the key in the transformation function ``g``. + +:: + + HashMap.map (*10) (HashMap.fromList [("haskell", 45), ("idris", 15)]) + > fromList [("haskell",450),("idris",150)] + + -- Use the Functor instance for Map. + (*10) <$> HashMap.fromList [("haskell", 45), ("idris", 15)] + > fromList [("haskell",450),("idris",150)] + + let g key value = if key == "haskell" then (value * 1000) else value + HashMap.mapWithKey g (HashMap.fromList [("haskell", 45), ("idris", 15)]) + > fromList [("haskell",45000),("idris",15)] + + +You can also apply a function which performs *actions* (such as printing) to +each entry in the map. + +:: + + HashMap.traverseWithKey :: Applicative t => (k -> a -> t b) -> HashMap k a -> t (HashMap k b) + HashMap.traverseWithKey f m = ... + +:haddock_short:`/Data.HashMap.Strict#traverseWithKey` maps each element of the +map ``m`` to an *action* that produces a result of type ``b``. The actions are +performed and the values of the map are replaced with the results from the +function. You can think of this as a ``map`` with affects. + +:: + + -- | Ask the user how they want to schedule a bunch of tasks + -- that the boss has assigned certain priorities. + makeSchedule :: HashMap Task Priority -> IO (HashMap Task DateTime) + makeSchedule = traverseWithKey $ \task priority -> + do + putStrLn $ "The boss thinks " ++ show task ++ + " has priority " ++ show priority ++ + ". When do you want to do it?" + readLn + + + +Set-like Operations +^^^^^^^^^^^^^^^^^^^ + +.. _union: + +Union +""""" + +:: + + HashMap.unionWith :: (v -> v -> v) -> HashMap k v -> HashMap k v -> HashMap k v + HashMap.unionWith f l r = ... + +:haddock_short:`/Data.HashMap.Strict#union` returns a map containing all entries that +are keyed in either of the two maps. If the same key appears in both maps, the +value is determined by calling ``f`` passing in the left and right value (`set +union `_). + +:: + + + HashMap.unionWith (++) HashMap.empty (HashMap.fromList [(1,"x"),(2,"y")]) + > fromList [(1,"x"),(2,"y")] + + let f lv rv = lv + HashMap.unionWith f (HashMap.fromList [(1, "a")]) (HashMap.fromList [(1,"x"),(2,"y")]) + > fromList [(1,"a"),(2,"y")] + + HashMap.unionWith (++) (HashMap.fromList [(1, "a")]) (HashMap.fromList [(1,"x"),(2,"y")]) + > fromList [(1,"ax"),(2,"y")] + + +Intersection +"""""""""""" + +:: + + HashMap.intersectionWith :: (v -> v -> v) -> HashMap k v -> HashMap k v -> HashMap k v + HashMap.intersectionWith f l r = ... + +:haddock_short:`/Data.HashMap.Strict#intersection` returns a map containing all +entries that have a key in both maps ``l`` and ``r``. The value in the returned +map is determined by calling ``f`` on the values from the left and right map +(`set intersection `_). + +:: + + HashMap.intersectionWith (++) HashMap.empty (HashMap.fromList [(1,"x"), (2,"y")]) + > fromList [] + + HashMap.intersectionWith (++) (HashMap.fromList [(1, "a")]) (HashMap.fromList [(1,"x"),(2,"y")]) + > fromList [(1,"ax")] + + + +Difference +"""""""""" + +:: + + HashMap.difference :: HashMap k v -> HashMap k v -> HashMap k v + HashMap.difference l r = ... + +:haddock_short:`/Data.HashMap.Strict#difference` returns a map containing all +entries that have a key in the ``l`` map but not the ``r`` map (`set +difference/relative complement +`_). + +:: + + HashMap.difference (HashMap.fromList [(1,"one"), (2,"two"), (3,"three")]) HashMap.empty + > fromList [(1,"uno"),(2,"two"),(3,"three")] + + HashMap.difference (HashMap.fromList[(1,"one"), (2,"two")]) (HashMap.fromList [(1,"uno")]) + > fromList [(2,"two")] + + +Serialization +------------- + +TODO(m-renaud): Serialization docs. + +Performance +----------- + +The API docs are annotated with the Big-*O* complexities of each of the map +operations. For benchmarks see the `haskell-perf/dictionaries +`_ page. + + +Looking for more? +----------------- + +Didn't find what you're looking for? This tutorial only covered the most common +map functions, for a full list of functions see the +:haddock_short:`/Data.HashMap.Strict#HashMap` API documentation. diff --git a/docs/hash-set.rst b/docs/hash-set.rst new file mode 100644 index 00000000..bd69c50a --- /dev/null +++ b/docs/hash-set.rst @@ -0,0 +1,386 @@ +Hash Sets +========= + +.. highlight:: haskell + +Sets allow you to store *unique* elements, providing efficient insertion, +lookups, and deletions. If you are storing sets of ``Int`` s consider using +``Data.IntSet`` from the :haddock:`containers` package. You can find the +introductory documentation for `containers` at +https://haskell-containers.readthedocs.io. + +:: + + data HashSet element = ... + +.. IMPORTANT:: + ``HashSet`` relies on the `element` type having instances of the ``Eq`` and + ``Hashable`` typeclasses for its internal representation. These are already + defined for builtin types, and if you are using your own data type you can + use the `deriving + `_ + mechanism. + + +All of these implementations are *immutable* which means that any update +functions do not modify the set that you passed in, they creates a new set. In +order to keep the changes you need to assign it to a new variable. For example:: + + let s1 = HashSet.fromList ["a", "b"] + let s2 = HashSet.delete "a" s1 + print s1 + > fromList ["a","b"] + print s2 + > fromList ["b"] + + +Short Example +------------- + +The following GHCi session shows some of the basic set functionality:: + + import qualified Data.HashSet as HashSet + + let dataStructures = HashSet.fromList ["HashSet", "HashMap", "Graph"] + + -- Check if "HashMap" and "Trie" are in the set of data structures. + HashSet.member "HashMap" dataStructures + > True + + HashSet.member "Trie" dataStructures + > False + + + -- Add "Trie" to our original set of data structures. + let moreDataStructures = HashSet.insert "Trie" dataStructures + + HashSet.member "Trie" moreDataStructures + > True + + + -- Remove "Graph" from our original set of data structures. + let fewerDataStructures = HashSet.delete "Graph" dataStructures + + HashSet.toList fewerDataStructures + > ["HashSet", "HashMap"] + + + -- Create a new set and combine it with our original set. + let orderedDataStructures = HashSet.fromList ["Set", "Map"] + + HashSet.union dataStructures orderedDataStructures + > fromList ["Map", "HashSet", "Graph", "HashMap", "Set"] + + + +.. TIP:: You can use the `OverloadedLists + `_ extension so + you don't need to write ``fromList [1, 2, 3]`` everywhere. Instead you + can just write ``[1, 2, 3]`` and if the function is expecting a set it + will be converted automatically! The code here will continue to use + ``fromList`` for clarity though. + + +Importing HashSet +----------------- + +When using ``HashSet`` in a Haskell source file you should always use a +``qualified`` import because these modules export names that clash with the +standard Prelude. You can import the type constructor unqualified. + +:: + + import Data.HashSet (HashSet) + import qualified Data.HashSet as HashSet + + +Common API Functions +-------------------- + +.. TIP:: + All of these functions that work for ``HashSet`` will also work for + ``IntSet``, which has the element type ``a`` specialized to ``Int``. Anywhere + that you see ``HashSet Int`` you can replace it with ``IntSet``. This will + speed up most operations tremendously (see `Performance`_) with the exception + of ``size`` which is O(1) for ``HashSet`` and O(n) for ``IntSet``. + +.. NOTE:: + ``fromList [some,list,elements]`` is how a ``HashSet`` is printed. + + +Construction and Conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Create an empty set +""""""""""""""""""" + +:: + + HashSet.empty :: HashSet a + HashSet.empty = ... + +:haddock_short:`/Data.HashSet#empty` creates a set with zero elements. + +:: + + HashSet.empty + > fromList [] + +Create a set with one element (singleton) +""""""""""""""""""""""""""""""""""""""""" + +:: + + HashSet.singleton :: a -> HashSet a + HashSet.singleton x = ... + +:haddock_short:`/Data.HashSet#singleton` creates a set with a single element +``x`` in it. + +:: + + HashSet.singleton "containers" + > fromList ["containers"] + + HashSet.singleton 1 + > fromList [1] + +Create a set from a list +"""""""""""""""""""""""" + +:: + + HashSet.fromList :: [a] -> HashSet a + HashSet.fromList xs = ... + +:haddock_short:`/Data.HashSet#fromList` creates a set containing the elements of the +list ``xs``. Since sets don't contain duplicates, if there are repeated elements +in the list they will only appear once. + +:: + + HashSet.fromList ["base", "containers", "QuickCheck"] + > fromList [,"containers","base","QuickCheck"] + + HashSet.fromList [1, 1, 2, 3, 4, 4, 5, 1] + > fromList [1,2,3,4,5] + +Create a list from a set +"""""""""""""""""""""""" + +:: + + HashSet.toList :: HashSet a -> [a] + HashSet.toList s = ... + +:haddock_short:`/Data.HashSet#toList` returns a list containing the elements of +the set, the order is unspecified. + + +Querying +^^^^^^^^ + +Check if an element is in a set (member) +"""""""""""""""""""""""""""""""""""""""" + +:: + + HashSet.member :: a -> HashSet a -> Bool + HashSet.member x s = ... + +:haddock_short:`/Data.HashSet#member` returns ``True`` if the element ``x`` is +in the set ``s``, ``False`` otherwise. + +:: + + HashSet.member 0 HashSet.empty + > False + + HashSet.member 0 (HashSet.fromList [0, 2, 4, 6]) + > True + +Check if a set is empty +""""""""""""""""""""""" + +:: + + HashSet.null :: HashSet a -> Bool + HashSet.null s = ... + +:haddock_short:`/Data.HashSet#null` returns ``True`` if the set ``s`` is empty, +``False`` otherwise. + +:: + + HashSet.null HashSet.empty + > True + + HashSet.null (HashSet.fromList [0, 2, 4, 6]) + > False + + +The number of elements in a set +""""""""""""""""""""""""""""""" + +:: + + HashSet.size :: HashSet a -> Int + HashSet.size s = ... + +:haddock_short:`/Data.HashSet#size` returns the number of elements in the set +``s``. + +:: + + HashSet.size HashSet.empty + > 0 + + HashSet.size (HashSet.fromList [0, 2, 4, 6]) + > 4 + + +Modification +^^^^^^^^^^^^ + +Adding a new element to a set +""""""""""""""""""""""""""""" + +:: + + HashSet.insert :: a -> HashSet a -> HashSet a + HashSet.insert x s = ... + +:haddock_short:`/Data.HashSet#insert` places the element ``x`` into the set +``s``, replacing an existing equal element if it already exists. + +:: + + HashSet.insert 100 HashSet.empty + > fromList [100] + + HashSet.insert 0 (HashSet.fromList [0, 2, 4, 6]) + > fromList [0,2,4,6] + +Removing an element from a set +"""""""""""""""""""""""""""""" + +:: + + HashSet.delete :: a -> HashSet a -> HashSet a + HashSet.delete x s = ... + +:haddock_short:`/Data.HashSet#delete` the element ``x`` from the set ``s``. If +it’s not a member it leaves the set unchanged. + +:: + + HashSet.delete 0 (HashSet.fromList [0, 2, 4, 6]) + > fromList [2,4,6] + +Filtering elements from a set +""""""""""""""""""""""""""""" + +:: + + HashSet.filter :: (a -> Bool) -> HashSet a -> HashSet a + HashSet.filter predicate s = ... + +:haddock_short:`/Data.HashSet#filter` produces a set consisting of all elements +of ``s`` for which the ``predicate`` returns ``True``. + +:: + + HashSet.filter (==0) (HashSet.fromList [0, 2, 4, 6]) + > fromList [0] + + +Set Operations +^^^^^^^^^^^^^^ + +Union +""""" + +:: + + HashSet.union :: HashSet a -> HashSet a -> HashSet a + HashSet.union l r = ... + +:haddock_short:`/Data.HashSet#union` returns a set containing all elements that +are in either of the two sets ``l`` or ``r`` (`set union +`_). + +:: + + HashSet.union HashSet.empty (HashSet.fromList [0, 2, 4, 6]) + > fromList [0,2,4,6] + + HashSet.union (HashSet.fromList [1, 3, 5, 7]) (HashSet.fromList [0, 2, 4, 6]) + > fromList [0,1,2,3,4,5,6,7] + +Intersection +"""""""""""" + +:: + + HashSet.intersection :: HashSet a -> HashSet a -> HashSet a + HashSet.intersection l r = ... + +:haddock_short:`/Data.HashSet#intersection` returns a set the elements that are +in both sets ``l`` and ``r`` (`set intersection +`_). + +:: + + HashSet.intersection HashSet.empty (HashSet.fromList [0, 2, 4, 6]) + > fromList [] + + HashSet.intersection (HashSet.fromList [1, 3, 5, 7]) (HashSet.fromList [0, 2, 4, 6]) + > fromList [] + + HashSet.intersection (HashSet.singleton 0) (HashSet.fromList [0, 2, 4, 6]) + > fromList [0] + +Difference +"""""""""" + +:: + + HashSet.difference :: HashSet a -> HashSet a -> HashSet a + HashSet.difference l r = ... + +:haddock_short:`/Data.HashSet#difference` returns a set containing the elements +that are in the first set ``l`` but not the second set ``r`` (`set +difference/relative compliment +`_). + +:: + + HashSet.difference (HashSet.fromList [0, 2, 4, 6]) HashSet.empty + > fromList [0,2,4,6] + + HashSet.difference (HashSet.fromList [0, 2, 4, 6]) (HashSet.fromList [1, 3, 5, 7]) + > fromList [0,2,4,6] + + HashSet.difference (HashSet.fromList [0, 2, 4, 6]) (HashSet.singleton 0) + > fromList [2,4,6] + + +Serialization +------------- + +TODO(m-renaud): Write serialization section. + +Performance +----------- + +The API docs are annotated with the Big-*O* complexities of each of the set +operations. For benchmarks see the `haskell-perf/sets +`_ page. + + +Looking for more? +----------------- + +Didn't find what you're looking for? This tutorial only covered the most common +set functions, for a full list of functions see the +:haddock_short:`/Data.HashSet#HashSet` documentation. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..38124a59 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,13 @@ +``unordered-containers`` - Introduction and Tutorial +==================================================== + +This site contains an introduction and overview of the main features of the +``unordered-containers`` package. For full API documentation see the +:haddock:`unordered-containers` Haddocks. + +.. toctree:: + :maxdepth: 2 + + intro + hash-set + hash-map diff --git a/docs/intro.rst b/docs/intro.rst new file mode 100644 index 00000000..dee6ef6e --- /dev/null +++ b/docs/intro.rst @@ -0,0 +1,86 @@ +``unordered-containers`` Introduction +===================================== + +The ``unordered-containers`` package provides implementations of various +hash-based immutable data structures. + +Some of the data structures provided by this package have a very large API +surface (for better or worse). The docs here focus on the most common functions +which should be more than enough to get you started. Once you know the basics, +or if you're looking for a specific function, you can head over to the +:haddock:`unordered-containers` Haddocks to check out the full API +documentation! + +Provided Data Structures +------------------------ + +- :doc:`hash-set`: unordered, non-duplicated elements +- :doc:`hash-map`: unordered maps from keys to values (aka. dictionaries) + + +Related Packages +---------------- + +- :haddock:`containers` - ordered containers using trees instead of + hashing. + +- :haddock:`hashable` - types that can be converted to a hash value. + +- :haddock:`hashtables` - mutable hash tables in the ST monad. + + +Looking for more resources? +--------------------------- + +If you've worked your way through the documentation here and you're looking for +more examples or tutorials you should check out: + +- `haskell-lang.org's containers tutorial + `_, its focused on the ordered + ``containers`` library but provides some useful examples. +- `Learn You a Haskell "Modules" chapter `_ + +.. _installing: + +Installing and using the ``unordered-containers`` package +--------------------------------------------------------- + +Version Requirements +^^^^^^^^^^^^^^^^^^^^ + +All of the examples here should work for all recent versions of the package. + + +Importing modules +^^^^^^^^^^^^^^^^^ + +All of the modules in ``unordered-containers`` should be imported ``qualified`` +since they use names that conflict with the standard Prelude. + +:: + + import qualified Data.HashSet as HashSet + import qualified Data.HashMap.Strict as HashMap + + +In GHCi +^^^^^^^ + +Start the GHCi `REPL +`_ with +``ghci``, ``cabal repl``, or ``stack ghci``. Once the REPL is loaded, import the +modules you want using the ``import`` statements above and you're good to go! + + +In a `Cabal `_ or `Stack `_ project +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add ``unordered-containers`` to the ``build-depends:`` stanza for your library, +executable, or test-suite:: + + library + build-depends: + base >= 4.3 && < 5, + unordered-containers >= 0.2.7 && < 0.3 + +and ``import`` any modules you need in your Haskell source files. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..fe8cc655 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=unordered-containers + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd From c93a798f96125b86800f594cac5990dd08c3fa30 Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Fri, 29 May 2020 10:28:58 -0700 Subject: [PATCH 02/10] Address emilypi's review comments. - Change copyright/author to 'unordered-containers maintainers' - Change warning about HashMap.!? - Remove incomplete Serialization sections, todo in follow-up. --- docs/conf.py | 4 ++-- docs/hash-map.rst | 10 +++------- docs/hash-set.rst | 5 ----- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 767ba8bc..1c19ec89 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,8 +29,8 @@ # General information about the project. project = u'unordered-containers' -copyright = u'2020, Matt Renaud' -author = u'Matt Renaud' +copyright = u'2020, unordered-containers maintainers' +author = u'unordered-containers maintainers' # The short X.Y version. version = u'0.2.10' diff --git a/docs/hash-map.rst b/docs/hash-map.rst index caee3077..9b3040e5 100644 --- a/docs/hash-map.rst +++ b/docs/hash-map.rst @@ -259,8 +259,9 @@ For example:: > "one" .. WARNING:: - **DO NOT** Use ``HashMap.!``. It is partial and throws a runtime error if - the key doesn't exist. + ``HashMap.!`` is a partial function and throws a runtime error if + the key doesn't exist. **DO NOT** use it if you are expecting a total + function or cannot tolerate a runtime failure; prefer to use ``lookup``. Check if a map is empty """"""""""""""""""""""" @@ -557,11 +558,6 @@ difference/relative complement > fromList [(2,"two")] -Serialization -------------- - -TODO(m-renaud): Serialization docs. - Performance ----------- diff --git a/docs/hash-set.rst b/docs/hash-set.rst index bd69c50a..f6a6871a 100644 --- a/docs/hash-set.rst +++ b/docs/hash-set.rst @@ -365,11 +365,6 @@ difference/relative compliment > fromList [2,4,6] -Serialization -------------- - -TODO(m-renaud): Write serialization section. - Performance ----------- From 361fbf553c1900fff7200d816ccead66fcb97094 Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Fri, 29 May 2020 10:41:22 -0700 Subject: [PATCH 03/10] Fix incorrect HashMap.!? operator. --- docs/hash-map.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hash-map.rst b/docs/hash-map.rst index 9b3040e5..31431a5a 100644 --- a/docs/hash-map.rst +++ b/docs/hash-map.rst @@ -259,7 +259,7 @@ For example:: > "one" .. WARNING:: - ``HashMap.!`` is a partial function and throws a runtime error if + ``HashMap.!?`` is a partial function and throws a runtime error if the key doesn't exist. **DO NOT** use it if you are expecting a total function or cannot tolerate a runtime failure; prefer to use ``lookup``. From dc030aa8a94cf380d63f2857facdcf08a6112827 Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Fri, 29 May 2020 12:16:17 -0700 Subject: [PATCH 04/10] Undo incorrect operator rename. --- docs/hash-map.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hash-map.rst b/docs/hash-map.rst index 31431a5a..9b3040e5 100644 --- a/docs/hash-map.rst +++ b/docs/hash-map.rst @@ -259,7 +259,7 @@ For example:: > "one" .. WARNING:: - ``HashMap.!?`` is a partial function and throws a runtime error if + ``HashMap.!`` is a partial function and throws a runtime error if the key doesn't exist. **DO NOT** use it if you are expecting a total function or cannot tolerate a runtime failure; prefer to use ``lookup``. From fe4ffde665218212f3ddac77a2d08e0ccb3c9c03 Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Fri, 29 May 2020 12:21:05 -0700 Subject: [PATCH 05/10] Remove use of (missing) HashMap.!?. --- docs/hash-map.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/hash-map.rst b/docs/hash-map.rst index 9b3040e5..6c3cad44 100644 --- a/docs/hash-map.rst +++ b/docs/hash-map.rst @@ -241,17 +241,12 @@ If you want to provide a default value if the key doesn't exist you can use: For example:: - import Data.HashMap.Strict ((!?)) - HashMap.lookup 1 HashMap.empty > Nothing HashMap.lookup 1 (HashMap.fromList [(1,"one"),(2,"two"),(3,"three")]) > Just "one" - > (HashMap.fromList [(1,"one"),(2,"two"),(3,"three")]) !? 1 - > Just "one" - HashMap.lookupDefault "?" k HashMap.empty > "?" From 9e3ccdd7726848866ce8b1e2a68e99b473d21fc0 Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Sat, 30 May 2020 16:15:08 -0700 Subject: [PATCH 06/10] Add docs for newly added (since 0.2.11) HashMap.!?. --- docs/hash-map.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/hash-map.rst b/docs/hash-map.rst index 6c3cad44..e5721e10 100644 --- a/docs/hash-map.rst +++ b/docs/hash-map.rst @@ -229,8 +229,13 @@ Lookup an entry in the map (lookup) HashMap.lookup :: k -> HashMap k v -> Maybe v HashMap.lookup key m = ... + HashMap.!? :: HashMap k v -> k -> Maybe v + HashMap.!? m key = ... + :haddock_short:`/Data.HashMap.Strict#lookup` the value corresponding to the given -``key``, returns ``Nothing`` if the key is not present. +``key``, returns ``Nothing`` if the key is not present. The ``!?`` operator +(*since 0.2.11*) is a flipped version of ``lookup`` and can often be imported +unqualified. If you want to provide a default value if the key doesn't exist you can use: @@ -247,6 +252,9 @@ For example:: HashMap.lookup 1 (HashMap.fromList [(1,"one"),(2,"two"),(3,"three")]) > Just "one" + (HashMap.fromList [(1,"one"),(2,"two"),(3,"three")]) !? 1 + > Just "one" + HashMap.lookupDefault "?" k HashMap.empty > "?" From 522fc419d9a79128d040f0dda47938c71a7a48ed Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Sun, 31 May 2020 19:51:49 -0700 Subject: [PATCH 07/10] DO NOT SUBMIT: Some partial conversion to Tutorial module. --- Tutorial.hs | 80 +++++++++++++++++++++++++++++++++ Tutorial/HashSet.hs | 92 ++++++++++++++++++++++++++++++++++++++ unordered-containers.cabal | 2 + 3 files changed, 174 insertions(+) create mode 100644 Tutorial.hs create mode 100644 Tutorial/HashSet.hs diff --git a/Tutorial.hs b/Tutorial.hs new file mode 100644 index 00000000..ae3cfe46 --- /dev/null +++ b/Tutorial.hs @@ -0,0 +1,80 @@ +{-# OPTIONS_GHC -fno-warn-unused-imports #-} + +{-| + The @unordered-containers@ package provides implementations of various + hash-based immutable data structures. + + Some of the data structures provided by this package have a very large API + surface (for better or worse). The docs here focus on the most common functions + which should be more than enough to get you started. Once you know the basics, + or if you're looking for a specific function, you can head over to the + full API documentation! +-} + + +module Tutorial ( + -- * Provided Data Structures + -- $provideddatastructures + + -- * Related Packages + -- $relatedpackages + + -- * Looking for more resources? + -- $moreresources + + -- * Installing and using the @unordered-containers@ packages + -- $installing + + -- * HashSet and HashMap tutorial + -- $tutorials + + ) where + +{- $provideddatastructures +* 'Data.HashSet' - unordered, non-duplicated elements +* 'Data.HashMap' - unordered map from keys to values (aka. dictionaries) +-} + +{- $relatedpackages +* - ordered containers using trees instead of hashing. +* - types that can be converted to a hash value. +-} + +{- $moreresources +If you've worked your way through the documentation here and you're looking for +more examples or tutorials you should check out: + +* , its focused on the ordered + library but provides some useful examples. +* +-} + +{- $installing +__Version Requirements__ + +All of the examples here should work for all recent versions of the package. + +__Importing modules__ + +All of the modules in @unordered-containers@@ should be imported @qualified@ +since they use names that conflict with the standard Prelude. + +@ +import qualified Data.HashSet as HashSet +import qualified Data.HashMap.Strict as HashMap +@ + +__In GHCi__ + +Start the GHCi + with +@ghci@, @cabal repl@, or @stack ghci@. Once the REPL is loaded, import the +modules you want using the @import@ statements above and you're good to go! +-} + + +{- $tutorials + +See "Tutorial.HashSet" and "Tutorial.HashMap". + +-} diff --git a/Tutorial/HashSet.hs b/Tutorial/HashSet.hs new file mode 100644 index 00000000..9b8c8f76 --- /dev/null +++ b/Tutorial/HashSet.hs @@ -0,0 +1,92 @@ +{-# OPTIONS_GHC -fno-warn-unused-imports #-} + +{-| +Sets allow you to store *unique* elements, providing efficient insertion, +lookups, and deletions. If you are storing sets of @Int@ s consider using +'Data.IntSet' from the package. You can find the +introductory documentation for @containers@ at +. + +@ +data HashSet element = ... +@ + + +All of these implementations are *immutable* which means that any update +functions do not modify the set that you passed in, they creates a new set. In +order to keep the changes you need to assign it to a new variable. For example: + +@ +let s1 = HashSet.fromList ["a", "b"] +let s2 = HashSet.delete "a" s1 +print s1 +> fromList ["a","b"] +print s2 +> fromList ["b"] +@ + +__IMPORTANT:__ @HashSet@ relies on the @element@ type having instances of the @Eq@ and + @Hashable@ typeclasses for its internal representation. These are already + defined for builtin types, and if you are using your own data type you can + use the + + mechanism. + + +-} + +module Tutorial.HashSet ( + -- * Short Example + -- $shortexample + + ) where + + +{- $shortexample + +The following GHCi session shows some of the basic set functionality: + +@ +import qualified Data.HashSet as HashSet + +let dataStructures = HashSet.fromList ["HashSet", "HashMap", "Graph"] + +-- Check if "HashMap" and "Trie" are in the set of data structures. +HashSet.member "HashMap" dataStructures +> True + +HashSet.member "Trie" dataStructures +> False + + +-- Add "Trie" to our original set of data structures. +let moreDataStructures = HashSet.insert "Trie" dataStructures + +HashSet.member "Trie" moreDataStructures +> True + + +-- Remove "Graph" from our original set of data structures. +let fewerDataStructures = HashSet.delete "Graph" dataStructures + +HashSet.toList fewerDataStructures +> ["HashSet", "HashMap"] + + +-- Create a new set and combine it with our original set. +let orderedDataStructures = HashSet.fromList ["Set", "Map"] + +HashSet.union dataStructures orderedDataStructures +> fromList ["Map", "HashSet", "Graph", "HashMap", "Set"] +@ + + +__TIP__: You can use the `OverloadedLists + `_ extension so + you don't need to write ``fromList [1, 2, 3]`` everywhere. Instead you + can just write ``[1, 2, 3]`` and if the function is expecting a set it + will be converted automatically! The code here will continue to use + ``fromList`` for clarity though. + + +-} diff --git a/unordered-containers.cabal b/unordered-containers.cabal index b00be4ef..8c064d2f 100644 --- a/unordered-containers.cabal +++ b/unordered-containers.cabal @@ -40,6 +40,8 @@ library Data.HashMap.Lazy Data.HashMap.Strict Data.HashSet + Tutorial + Tutorial.HashSet other-modules: Data.HashMap.Array Data.HashMap.Base From a331c1c03378e9ce24f4c1b5ce4ab9806e4b5d42 Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Mon, 1 Jun 2020 13:13:27 -0700 Subject: [PATCH 08/10] Formatting fixes in tutorial modules. --- Tutorial.hs | 10 +++++----- Tutorial/HashSet.hs | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Tutorial.hs b/Tutorial.hs index ae3cfe46..8dd28edf 100644 --- a/Tutorial.hs +++ b/Tutorial.hs @@ -50,21 +50,21 @@ more examples or tutorials you should check out: -} {- $installing -__Version Requirements__ +==Version Requirements All of the examples here should work for all recent versions of the package. -__Importing modules__ +==Importing modules All of the modules in @unordered-containers@@ should be imported @qualified@ since they use names that conflict with the standard Prelude. @ -import qualified Data.HashSet as HashSet -import qualified Data.HashMap.Strict as HashMap +import qualified 'Data.HashSet' as HashSet +import qualified 'Data.HashMap.Strict' as HashMap @ -__In GHCi__ +==In GHCi Start the GHCi with diff --git a/Tutorial/HashSet.hs b/Tutorial/HashSet.hs index 9b8c8f76..232dab1e 100644 --- a/Tutorial/HashSet.hs +++ b/Tutorial/HashSet.hs @@ -8,7 +8,7 @@ introductory documentation for @containers@ at . @ -data HashSet element = ... +data 'HashSet' element = ... @ @@ -17,8 +17,8 @@ functions do not modify the set that you passed in, they creates a new set. In order to keep the changes you need to assign it to a new variable. For example: @ -let s1 = HashSet.fromList ["a", "b"] -let s2 = HashSet.delete "a" s1 +let s1 = 'HashSet.fromList' ["a", "b"] +let s2 = 'HashSet.delete' "a" s1 print s1 > fromList ["a","b"] print s2 @@ -41,42 +41,43 @@ module Tutorial.HashSet ( ) where +import qualified Data.HashSet as HashSet {- $shortexample The following GHCi session shows some of the basic set functionality: @ -import qualified Data.HashSet as HashSet +import qualified 'Data.HashSet' as HashSet -let dataStructures = HashSet.fromList ["HashSet", "HashMap", "Graph"] +let dataStructures = 'HashSet.fromList' ["HashSet", "HashMap", "Graph"] -- Check if "HashMap" and "Trie" are in the set of data structures. -HashSet.member "HashMap" dataStructures +'HashSet.member' "HashMap" dataStructures > True -HashSet.member "Trie" dataStructures +'HashSet.member' "Trie" dataStructures > False -- Add "Trie" to our original set of data structures. let moreDataStructures = HashSet.insert "Trie" dataStructures -HashSet.member "Trie" moreDataStructures +'HashSet.member' "Trie" moreDataStructures > True -- Remove "Graph" from our original set of data structures. let fewerDataStructures = HashSet.delete "Graph" dataStructures -HashSet.toList fewerDataStructures +'HashSet.toList' fewerDataStructures > ["HashSet", "HashMap"] -- Create a new set and combine it with our original set. let orderedDataStructures = HashSet.fromList ["Set", "Map"] -HashSet.union dataStructures orderedDataStructures +'HashSet.union' dataStructures orderedDataStructures > fromList ["Map", "HashSet", "Graph", "HashMap", "Set"] @ From 5e43fc15dcb07052816b0ac23337883ab6ed164e Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Tue, 2 Jun 2020 07:51:48 -0700 Subject: [PATCH 09/10] More tutorial module content for comparison. --- Tutorial.hs | 31 +++++--- Tutorial/HashSet.hs | 156 ++++++++++++++++++++++++++++++------- unordered-containers.cabal | 1 + 3 files changed, 151 insertions(+), 37 deletions(-) diff --git a/Tutorial.hs b/Tutorial.hs index 8dd28edf..c8ebe148 100644 --- a/Tutorial.hs +++ b/Tutorial.hs @@ -22,8 +22,16 @@ module Tutorial ( -- * Looking for more resources? -- $moreresources - -- * Installing and using the @unordered-containers@ packages - -- $installing + -- * Installing and using the @unordered-containers@ packages #installing# + + -- ** Version Requirements + -- $versionreqs + + -- ** Importing modules + -- $importingmodules + + -- ** In GHCi + -- $ghci -- * HashSet and HashMap tutorial -- $tutorials @@ -31,8 +39,8 @@ module Tutorial ( ) where {- $provideddatastructures -* 'Data.HashSet' - unordered, non-duplicated elements -* 'Data.HashMap' - unordered map from keys to values (aka. dictionaries) +* "Data.HashSet" - unordered, non-duplicated elements +* '"Data.HashMap" - unordered map from keys to values (aka. dictionaries) -} {- $relatedpackages @@ -49,23 +57,24 @@ more examples or tutorials you should check out: * -} -{- $installing -==Version Requirements - +{- $versionreqs All of the examples here should work for all recent versions of the package. +-} -==Importing modules +{- $importingmodules All of the modules in @unordered-containers@@ should be imported @qualified@ since they use names that conflict with the standard Prelude. @ -import qualified 'Data.HashSet' as HashSet -import qualified 'Data.HashMap.Strict' as HashMap +import qualified "Data.HashSet" as HashSet +import qualified "Data.HashMap.Strict" as HashMap @ -==In GHCi +-} + +{- $ghci Start the GHCi with @ghci@, @cabal repl@, or @stack ghci@. Once the REPL is loaded, import the diff --git a/Tutorial/HashSet.hs b/Tutorial/HashSet.hs index 232dab1e..833633fa 100644 --- a/Tutorial/HashSet.hs +++ b/Tutorial/HashSet.hs @@ -12,17 +12,19 @@ data 'HashSet' element = ... @ -All of these implementations are *immutable* which means that any update +All of these implementations are /immutable/ which means that any update functions do not modify the set that you passed in, they creates a new set. In order to keep the changes you need to assign it to a new variable. For example: @ -let s1 = 'HashSet.fromList' ["a", "b"] -let s2 = 'HashSet.delete' "a" s1 +import qualified Data.HashSet as HashSet + +let s1 = HashSet.'HashSet.fromList' ["a", "b"] +let s2 = HashSet.'HashSet.delete' "a" s1 print s1 -> fromList ["a","b"] +> HashSet.'HashSet.fromList' ["a","b"] print s2 -> fromList ["b"] +> HashSet.'HashSet.fromList' ["b"] @ __IMPORTANT:__ @HashSet@ relies on the @element@ type having instances of the @Eq@ and @@ -39,6 +41,23 @@ module Tutorial.HashSet ( -- * Short Example -- $shortexample + -- * Importing HashSet + -- $importing + + -- * Common API Functions + -- ** Construction and Conversion + + -- *** Create an empty set + -- $empty + + -- *** Create a set with one element (singleton) + -- $singleton + + -- *** Create a set from a list + -- $fromlist + + -- * Continue reading + -- $continuereading ) where import qualified Data.HashSet as HashSet @@ -48,46 +67,131 @@ import qualified Data.HashSet as HashSet The following GHCi session shows some of the basic set functionality: @ -import qualified 'Data.HashSet' as HashSet +import qualified Data.HashSet as HashSet -let dataStructures = 'HashSet.fromList' ["HashSet", "HashMap", "Graph"] +let dataStructures = HashSet.'HashSet.fromList' [\"HashSet\", \"HashMap\", \"Graph\"] --- Check if "HashMap" and "Trie" are in the set of data structures. -'HashSet.member' "HashMap" dataStructures +-- Check if HashMap and Trie are in the set of data structures. +HashSet.'HashSet.member' \"HashMap\" dataStructures > True -'HashSet.member' "Trie" dataStructures +HashSet.'HashSet.member' "Trie" dataStructures > False --- Add "Trie" to our original set of data structures. -let moreDataStructures = HashSet.insert "Trie" dataStructures +-- Add Trie to our original set of data structures. +let moreDataStructures = HashSet.'HashSet.insert' \"Trie\" dataStructures -'HashSet.member' "Trie" moreDataStructures +HashSet.'HashSet.member' \"Trie\" moreDataStructures > True --- Remove "Graph" from our original set of data structures. -let fewerDataStructures = HashSet.delete "Graph" dataStructures +-- Remove Graph from our original set of data structures. +let fewerDataStructures = HashSet.'HashSet.delete' \"Graph\" dataStructures -'HashSet.toList' fewerDataStructures -> ["HashSet", "HashMap"] +HashSet.'HashSet.toList' fewerDataStructures +> [\"HashSet\", \"HashMap\"] -- Create a new set and combine it with our original set. -let orderedDataStructures = HashSet.fromList ["Set", "Map"] +let orderedDataStructures = HashSet.'HashSet.fromList' [\"Set\", \"Map\"] + +HashSet.'HashSet.union' dataStructures orderedDataStructures +> fromList [\"Map\", \"HashSet\", \"Graph\", \"HashMap\", \"Set\"] +@ + + +__TIP__: You can use the + + extension so you don't need to write `fromList [1, 2, 3]` everywhere. + Instead you can just write `[1, 2, 3]` and if the function is expecting + a set it will be converted automatically! The code here will continue + to use `fromList` for clarity though. + + +-} + + +{- $importing + +When using HashSet in a Haskell source file you should always use a `qualified` +import because these modules export names that clash with the standard Prelude. +You can import the type constructor unqualified. + +@ +import Data.HashSet (HashSet) +import qualified Data.HashSet as HashSet +@ + +-} + +{- $commonapifunctions + -'HashSet.union' dataStructures orderedDataStructures -> fromList ["Map", "HashSet", "Graph", "HashMap", "Set"] +.. TIP:: + All of these functions that work for `HashSet` will also work for + `IntSet`, which has the element type `a` specialized to `Int`. Anywhere + that you see `HashSet Int` you can replace it with `IntSet`. This will + speed up most operations tremendously (see `Performance`_) with the exception + of `size` which is O(1) for `HashSet` and O(n) for `IntSet`. + +.. NOTE:: + `fromList [some,list,elements]` is how a `HashSet` is printed. + +-} + +{- $empty +@ +HashSet.empty :: HashSet a +HashSet.empty = ... +@ + +`HashSet.empty' creates a set with zero elements. + +@ +HashSet.empty +> fromList [] +@ + +-} + +{- $singleton +@ +HashSet.singleton :: a -> HashSet a +HashSet.singleton x = ... @ +'HashSet.singleton' creates a set with a single element +`x` in it. -__TIP__: You can use the `OverloadedLists - `_ extension so - you don't need to write ``fromList [1, 2, 3]`` everywhere. Instead you - can just write ``[1, 2, 3]`` and if the function is expecting a set it - will be converted automatically! The code here will continue to use - ``fromList`` for clarity though. +@ +HashSet.singleton "containers" +> fromList ["containers"] +HashSet.singleton 1 +> fromList [1] +@ +-} + +{- $fromlist +@ +HashSet.fromList :: [a] -> HashSet a +HashSet.fromList xs = ... +@ + +'HashSet.fromList' creates a set containing the elements of the +list `xs`. Since sets don't contain duplicates, if there are repeated elements +in the list they will only appear once. + +@ +HashSet.fromList ["base", "containers", "QuickCheck"] +> fromList [,"containers","base","QuickCheck"] + +HashSet.fromList [1, 1, 2, 3, 4, 4, 5, 1] +> fromList [1,2,3,4,5] +@ +-} +{- $continuereading +Continue the tutorial at "Tutorial.HashMap". -} diff --git a/unordered-containers.cabal b/unordered-containers.cabal index 8c064d2f..3092d6c6 100644 --- a/unordered-containers.cabal +++ b/unordered-containers.cabal @@ -41,6 +41,7 @@ library Data.HashMap.Strict Data.HashSet Tutorial + Tutorial.HashMap Tutorial.HashSet other-modules: Data.HashMap.Array From 858da6aea507e67ce2a4fecfad8e05f9eb9eaada Mon Sep 17 00:00:00 2001 From: Matt Renaud Date: Wed, 3 Jun 2020 17:58:55 -0700 Subject: [PATCH 10/10] Add Tutorial.HashMap module. --- Tutorial/HashMap.hs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Tutorial/HashMap.hs diff --git a/Tutorial/HashMap.hs b/Tutorial/HashMap.hs new file mode 100644 index 00000000..b195029a --- /dev/null +++ b/Tutorial/HashMap.hs @@ -0,0 +1,15 @@ +{-# OPTIONS_GHC -fno-warn-unused-imports #-} + +{-| +Sets allow you to store *unique* elements, providing efficient insertion, +lookups, and deletions. If you are storing sets of @Int@ s consider using +'Data.IntSet' from the package. You can find the +introductory documentation for @containers@ at +. + +@ +data HashMap k v = ... +@ +-} + +module Tutorial.HashMap () where