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/Tutorial.hs b/Tutorial.hs new file mode 100644 index 00000000..c8ebe148 --- /dev/null +++ b/Tutorial.hs @@ -0,0 +1,89 @@ +{-# 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# + + -- ** Version Requirements + -- $versionreqs + + -- ** Importing modules + -- $importingmodules + + -- ** In GHCi + -- $ghci + + -- * 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. +* +-} + +{- $versionreqs +All of the examples here should work for all recent versions of the package. +-} + +{- $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 +@ + +-} + + +{- $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/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 diff --git a/Tutorial/HashSet.hs b/Tutorial/HashSet.hs new file mode 100644 index 00000000..833633fa --- /dev/null +++ b/Tutorial/HashSet.hs @@ -0,0 +1,197 @@ +{-# 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: + +@ +import qualified Data.HashSet as HashSet + +let s1 = HashSet.'HashSet.fromList' ["a", "b"] +let s2 = HashSet.'HashSet.delete' "a" s1 +print s1 +> HashSet.'HashSet.fromList' ["a","b"] +print s2 +> HashSet.'HashSet.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 + + -- * 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 + +{- $shortexample + +The following GHCi session shows some of the basic set functionality: + +@ +import qualified Data.HashSet as HashSet + +let dataStructures = HashSet.'HashSet.fromList' [\"HashSet\", \"HashMap\", \"Graph\"] + +-- Check if HashMap and Trie are in the set of data structures. +HashSet.'HashSet.member' \"HashMap\" dataStructures +> True + +HashSet.'HashSet.member' "Trie" dataStructures +> False + + +-- Add Trie to our original set of data structures. +let moreDataStructures = HashSet.'HashSet.insert' \"Trie\" dataStructures + +HashSet.'HashSet.member' \"Trie\" moreDataStructures +> True + + +-- Remove Graph from our original set of data structures. +let fewerDataStructures = HashSet.'HashSet.delete' \"Graph\" dataStructures + +HashSet.'HashSet.toList' fewerDataStructures +> [\"HashSet\", \"HashMap\"] + + +-- Create a new set and combine it with our original set. +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 + + +.. 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. + +@ +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/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 00000000..a006264b Binary files /dev/null and b/docs/_extensions/hs-theme.pyc differ diff --git a/docs/_static/css/hs-theme.css b/docs/_static/css/hs-theme.css new file mode 100644 index 00000000..98088de0 --- /dev/null +++ b/docs/_static/css/hs-theme.css @@ -0,0 +1,11 @@ +.wy-side-nav-search { + background-color: #222 !important; +} + +.wy-nav-top { + background-color: #333 !important; +} + +.wy-nav-top i { + color: #282 !important; +} diff --git a/docs/_static/images/favicon-16x16.png b/docs/_static/images/favicon-16x16.png new file mode 100644 index 00000000..24009f5f Binary files /dev/null and b/docs/_static/images/favicon-16x16.png differ diff --git a/docs/_static/images/favicon-green-16x16.png b/docs/_static/images/favicon-green-16x16.png new file mode 100644 index 00000000..7c691aa8 Binary files /dev/null and b/docs/_static/images/favicon-green-16x16.png differ diff --git a/docs/_static/images/haskell-logo-black.svg b/docs/_static/images/haskell-logo-black.svg new file mode 100644 index 00000000..f65315f7 --- /dev/null +++ b/docs/_static/images/haskell-logo-black.svg @@ -0,0 +1,17 @@ + + 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..1c19ec89 --- /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, unordered-containers maintainers' +author = u'unordered-containers maintainers' + +# 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..e5721e10 --- /dev/null +++ b/docs/hash-map.rst @@ -0,0 +1,577 @@ +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 = ... + + 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. 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: + +:: + + HashMap.lookupDefault :: v -> k -> HashMap k v -> v + HashMap.lookupDefault defaultVal key m = ... + +For example:: + + 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:: + ``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 +""""""""""""""""""""""" + +:: + + 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")] + + +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..f6a6871a --- /dev/null +++ b/docs/hash-set.rst @@ -0,0 +1,381 @@ +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] + + +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 diff --git a/unordered-containers.cabal b/unordered-containers.cabal index b00be4ef..3092d6c6 100644 --- a/unordered-containers.cabal +++ b/unordered-containers.cabal @@ -40,6 +40,9 @@ library Data.HashMap.Lazy Data.HashMap.Strict Data.HashSet + Tutorial + Tutorial.HashMap + Tutorial.HashSet other-modules: Data.HashMap.Array Data.HashMap.Base