Repository: wwkimball/yamlpath (language: python)

Issue:
Unexpected results of search
## Operating System

1. Name/Distribution: macOS 
2. Version: Monterey 12.6.1 (21G217)

## Version of Python and packages in use at the time of the issue.

1. [Distribution](https://wiki.python.org/moin/PythonDistributions): CPython from homebrew
2. Python Version: 3.11.0
3. Version of yamlpath installed: 3.6.9
4. Version of ruamel.yaml installed: 0.17.21

## Minimum sample of YAML (or compatible) data necessary to trigger the issue

```yaml
projects_and_groups:

  "*":
    merge_requests_approval_rules:
      standard:
        approvals_required: 1
        name: "All eligible users"

  "foo/bar":
    merge_requests_approval_rules:
      dev-uat:
        groups:
          - some_group
        users:
          - a_user
```

## Complete steps to reproduce the issue when triggered via:

YAML Path: `"**.merge_requests_approval_rules.*[.=users]"`

## Expected Outcome

1 result, the array at "projects_and_groups."foo/bar".merge_requests_approval_rules.dev-uat.users".

## Actual Outcome

Exception: `Cannot add PathSegmentTypes.MATCH_ALL subreference to dictionaries at 'None' in '**.merge_requests_approval_rules.*[.=users]'.`

## Screenshot(s), if Available



Discussion / hints:

Fix patch:
diff --git a/.codacy.yaml b/.codacy.yaml
index 3615d74c..281b438e 100644
--- a/.codacy.yaml
+++ b/.codacy.yaml
@@ -12,3 +12,4 @@ exclude_paths:
   - 'README.md'
   - 'yamlpath/func.py'  # Deprecated; the entire file is just relays
   - 'yamlpath/patches/**' # 3rd Party contributions
+  - 'yamlpath/enums/pathseperators.py'  # Legacy spelling compatibility
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 854e5b8c..5a5fcc2a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,10 +12,10 @@ on:
 jobs:
   build:
 
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04 # Use ubuntu-latest when https://github.com/actions/setup-python/issues/544 is fixed
     strategy:
       matrix:
-        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
+        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
 
     steps:
     - uses: actions/checkout@v2
diff --git a/.github/workflows/python-publish-to-prod-pypi.yml b/.github/workflows/python-publish-to-prod-pypi.yml
index dfa69766..41ab912d 100644
--- a/.github/workflows/python-publish-to-prod-pypi.yml
+++ b/.github/workflows/python-publish-to-prod-pypi.yml
@@ -7,7 +7,7 @@ on:
 jobs:
   publish:
     name: Publish to Production PyPI
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04 # Use ubuntu-latest when https://github.com/actions/setup-python/issues/544 is fixed
     environment: 'PyPI:  Production'
 
     steps:
diff --git a/.pylintrc b/.pylintrc
index ded117dc..07cd022d 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -264,8 +264,8 @@ max-module-lines=1000
 # separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
 # `trailing-comma` allows a space between comma and closing bracket: (a, ).
 # `empty-line` allows space-only lines.
-no-space-check=trailing-comma,
-               dict-separator
+; no-space-check=trailing-comma,
+;                dict-separator
 
 # Allow the body of a class to be on the same line as the declaration if body
 # contains single statement.
diff --git a/CHANGES b/CHANGES
index 9c6c6825..91f050b9 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,40 @@
+3.7.0
+Enhancements:
+* Support for Python 3.11 has been added.
+* A new Search Keyword has been added, [unique([NAME])].  This operates against
+  collections to return only values which have no duplicates within the
+  collection; i.e.:  [1, 2, 2, 3] has unique values, [1, 3].  The NAME argument
+  is required when operating against Hashes (maps/dicts) and Arrays-of-Hashes
+  (sequences/lists of maps/dicts) to identify which field/property to evaluate
+  for uniqueness.  This can be inverted to return only results which are
+  duplicated within the collection.
+* A new Search Keyword has been added, [distinct([NAME])].  This operates
+  against collections to return exactly one of every value within the
+  collection, discarding duplicates; i.e.:  [1, 2, 2, 3] has distinct values,
+  [1, 2, 3].  The NAME argument is required when operating against Hashes
+  (maps/dicts) and Arrays-of-Hashes (sequences/lists of maps/dicts) to identify
+  which field/property to evaluate for uniqueness. This cannot be inverted.
+* Added a new Collector Math Operator:  & (intersection)
+  As in set mathematics, this yields only elements of two collections which are
+  common to both collections.  Unlike set mathematics, collections allow
+  duplicate elements.  If you need to enforce distinctness of the intersected
+  results, use the [distinct([NAME])] Search Keyword against the collected
+  result, as in `((list1)&(list2))[distinct([NAME])]`.
+
+Bug Fixes:
+* A typographical error in yamlpath.enums has been corrected with backward-
+  compatible adapters in place to support both the correct and incorrect
+  spelling of PathSeparators (formerly PathSeperators).  If the PathSeperators
+  version appears in your own code, please update to the new spelling.  The
+  incorrectly spelled version of this enumeration is now deprecated and will be
+  removed in a future release.  Thanks go entirely to
+  https://github.com/AndydeCleyre for working so hard to submit this fix!
+* Processor.get_nodes would emit a nonobvious error message when mustexist is
+  left at its default value (False) and yaml_path contained ** or * segments.
+  Now, these segment types are excluded from generating "missing" nodes; only
+  nodes which exist can be matched by * and **.  Credit and my thanks go to
+  https://github.com/gdubicki for discovering and reporting this issue.
+
 3.6.9:
 Enhancements:
 * Partial support for updating bare Python dict and Python's native
@@ -454,7 +491,7 @@ API Changes:
 * YAMLPath instances now support nonmutating addition of individual segments
   via the + operator.  Whereas the append() method mutates the YAMLPath being
   acted upon, + creates a new YAMLPath that is the original plus the new
-  segment.  In both cases, the orignal YAMLPath's seperator is retained during
+  segment.  In both cases, the orignal YAMLPath's separator is retained during
   both operations.  As with .append(), new segments added via + must also be
   properly escaped -- typically via path.escape_path_section -- before being
   added.
@@ -695,9 +732,9 @@ Enhancements:
   tool, so a comprehensive treatise will be added to the project Wiki to
   explore its capabilities.  Along with those of its component classes, its
   unit tests also provide many examples of the same.
-* YAMLPath instances now support arbitrary changes to seperator.
+* YAMLPath instances now support arbitrary changes to separator.
 * YAMLPath instances now support equality testing (against the stored path),
-  immune to differences in seperator.
+  immune to differences in separator.
 * The get_yaml_data function now supports "-" as a source file.  This is
   interpreted as a read from STDIN.
 * Due to the change to the get_yaml_data function, the yaml-get reference
@@ -853,7 +890,7 @@ Enhancements:
 * Added a new yaml-paths command-line tool.  In short, it enables searching
   YAML/Compatible files, returning YAML Paths for any matches.  As an Alpha-
   grade tool, it is being released at version 0.0.1.  Feedback welcome!
-* All command-line tools which accept --pathsep now accept symbolic seperators
+* All command-line tools which accept --pathsep now accept symbolic separators
   rather than only names; so, --pathsep=/ is idental to --pathsep=fslash, etc.
   Minor changes were also made to all command-line tools to consolidate some
   repeat code.  Each has a version bump to reflect this minor refactoring
@@ -955,7 +992,7 @@ Enhancements:
 * The separator used for identifying Hash sub-keys can now be customized.  If
   you prefer your paths to look like "/hash/sub/key" rather than "hash.sub.key",
   you can now have it your way.  For now, only . and / are allowed.  The
-  seperator can be either strictly specified or automatically inferred by
+  separator can be either strictly specified or automatically inferred by
   whether the first character of a given YAML Path is /.  Command-line tools
   like yaml-get and yaml-set have a new --pathsep argument for this; the default
   is "auto" and can be set to "fslash" (/) or "dot" (.).
diff --git a/README.md b/README.md
index 994ffe76..3a301d96 100644
--- a/README.md
+++ b/README.md
@@ -134,7 +134,7 @@ With an example like this, YAML Path enables:
   `Some decrypted value, provided you have the appropriate EYAML keys`
 * pass-through selections against arrays-of-hashes: `/users/roles` =
   `["Writers"]\n["Power Users", "Editors"]` (each user's list of roles are a
-  seperate result)
+  separate result)
 * collection of disparate results: `(/users/name)` =
   `["User One", "User Two"]` (all names appear in a single result instead of
   one per line)
@@ -144,7 +144,7 @@ For a deeper exploration of YAML Path's capabilities, please visit the
 
 ## Supported YAML Path Segments
 
-A YAML Path *segment* is the text between seperators which identifies zero or
+A YAML Path *segment* is the text between separators which identifies zero or
 more parent or leaf nodes within the data structure.  For dot-notation, a path
 like `hash.key` identifies two segments:  `hash` (a parent node) and `key` (a
 leaf node).  The same path in forward-slash notation would be:  `/hash/key`.
@@ -235,6 +235,8 @@ YAML Path understands these segment types:
   keywords are
   [deeply explored on the Wiki](https://github.com/wwkimball/yamlpath/wiki/Search-Keywords)
   and include:
+  * `[distinct(NAME)]`: Match exactly one of every value within collections,
+    discarding duplicates; i.e.:  [1, 2, 2, 3] has distinct values, [1, 2, 3]
   * `[has_child(NAME)]`: Match nodes having a named child key
   * `[max([NAME])]`: Match nodes having the maximum value
   * `[min([NAME])]`: Match nodes having the minimum value
@@ -242,10 +244,12 @@ YAML Path understands these segment types:
     children
   * `[parent([STEPS])]`, Step up 1-N levels in the document from the present
     node
+  * `[unique(NAME)]`: Match only values which have no duplicates within
+    collections; i.e.:  [1, 2, 2, 3] has unique values, [1, 3]
 * Collectors:  Placing any portion of the YAML Path within parenthesis defines a
-  virtual list collector, like `(YAML Path)`; concatenation and exclusion
-  operators are supported -- `+` and `-`, respectively -- along with nesting,
-  like `(...)-((...)+(...))`
+  virtual list collector, like `(YAML Path)`; concatenation, exclusion, and
+  intersection operators are supported -- `+`, `-`, and `&`, respectively --
+  along with nesting, like `(...)-((...)+(...))&(...)`
 * Complex combinations:
   `some::deep.hierarchy[with!=""].'any.valid'[.=~/(yaml|json)/][data%structure].or.complexity[4].2`
   or `/some::deep/hierarchy[with!=""]/any*.*valid[.=~/(yaml|json)/][data%structure]/or/compl*xity[4]/2/**`
@@ -267,7 +271,7 @@ ephemeral or longer-lasting virtual Python environments.
 ### Requirements
 
 This project requires [Python](https://www.python.org/) 3.  It is rigorously
-tested against Pythons 3.6 through 3.9.  Most operating systems and
+tested against Pythons 3.6 through 3.11.  Most operating systems and
 distributions have access to Python 3 even if only Python 2 -- or no Python, at
 all -- came pre-installed.  It is generally safe to have more than one version
 of Python on your system at the same time, especially when using
@@ -331,7 +335,7 @@ pip3.7 install --upgrade setuptools
 
 When you cannot or will not update `pip` or *setuptools*, just pre-install
 *ruamel.yaml* and *python-dateutil* before yamlpath.  Each must be installed
-seperately and in order, like this (you **cannot** combine these installations
+separately and in order, like this (you **cannot** combine these installations
 into a single command):
 
 ```shell
@@ -473,7 +477,7 @@ optional arguments:
                         that differences exist -- when they do -- with an
                         exit-state of 1
   -t ['.', '/', 'auto', 'dot', 'fslash'], --pathsep ['.', '/', 'auto', 'dot', 'fslash']
-                        indicate which YAML Path seperator to use when
+                        indicate which YAML Path separator to use when
                         rendering results; default=dot
   -d, --debug           output debugging details
   -v, --verbose         increase output verbosity
@@ -520,7 +524,7 @@ optional arguments:
   -h, --help            show this help message and exit
   -V, --version         show program's version number and exit
   -t ['.', '/', 'auto', 'dot', 'fslash'], --pathsep ['.', '/', 'auto', 'dot', 'fslash']
-                        indicate which YAML Path seperator to use when
+                        indicate which YAML Path separator to use when
                         rendering results; default=dot
   -S, --nostdin         Do not implicitly read from STDIN, even when YAML_FILE
                         is not set and the session is non-TTY
@@ -666,7 +670,7 @@ optional arguments:
                         child leaf nodes (see "reference handling options" for
                         restrictions)
   -t ['.', '/', 'auto', 'dot', 'fslash'], --pathsep ['.', '/', 'auto', 'dot', 'fslash']
-                        indicate which YAML Path seperator to use when
+                        indicate which YAML Path separator to use when
                         rendering results; default=dot
   -a, --refnames        also search the names of &anchor and *alias references
   -S, --nostdin         Do not implicitly read from STDIN, even when there are
@@ -780,7 +784,7 @@ optional arguments:
   -b, --backup          save a backup YAML_FILE with an extra .bak file-
                         extension
   -t ['.', '/', 'auto', 'dot', 'fslash'], --pathsep ['.', '/', 'auto', 'dot', 'fslash']
-                        indicate which YAML Path seperator to use when rendering
+                        indicate which YAML Path separator to use when rendering
                         results; default=dot
   -M CHARS, --random-from CHARS
                         characters from which to build a value for --random;
diff --git a/setup.py b/setup.py
index acda304e..ca9bf6cd 100644
--- a/setup.py
+++ b/setup.py
@@ -20,6 +20,7 @@
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
         "Operating System :: OS Independent",
         "Environment :: Console",
         "Topic :: Utilities",
diff --git a/yamlpath/__init__.py b/yamlpath/__init__.py
index 9867cf46..8df9db58 100644
--- a/yamlpath/__init__.py
+++ b/yamlpath/__init__.py
@@ -1,6 +1,6 @@
 """Core YAML Path classes."""
 # Establish the version number common to all components
-__version__ = "3.6.9"
+__version__ = "3.7.0"
 
 from yamlpath.yamlpath import YAMLPath
 from yamlpath.processor import Processor
diff --git a/yamlpath/commands/yaml_diff.py b/yamlpath/commands/yaml_diff.py
index 9d37b149..54f93dfd 100644
--- a/yamlpath/commands/yaml_diff.py
+++ b/yamlpath/commands/yaml_diff.py
@@ -10,7 +10,7 @@
 
 from yamlpath import __version__ as YAMLPATH_VERSION
 from yamlpath.common import Parsers
-from yamlpath.enums import PathSeperators
+from yamlpath.enums import PathSeparators
 from yamlpath.differ.enums import AoHDiffOpts, ArrayDiffOpts
 from yamlpath.wrappers import ConsolePrinter
 from yamlpath.differ import DifferConfig, Differ
@@ -106,10 +106,10 @@ def processcli():
     parser.add_argument(
         "-t", "--pathsep",
         default="dot",
-        choices=PathSeperators,
-        metavar=PathSeperators.get_choices(),
-        type=PathSeperators.from_str,
-        help="indicate which YAML Path seperator to use when\nrendering"
+        choices=PathSeparators,
+        metavar=PathSeparators.get_choices(),
+        type=PathSeparators.from_str,
+        help="indicate which YAML Path separator to use when\nrendering"
              " results; default=dot")
 
     eyaml_group = parser.add_argument_group(
diff --git a/yamlpath/commands/yaml_get.py b/yamlpath/commands/yaml_get.py
index 617c8222..237fd6d8 100644
--- a/yamlpath/commands/yaml_get.py
+++ b/yamlpath/commands/yaml_get.py
@@ -25,7 +25,7 @@
 from yamlpath import YAMLPath
 from yamlpath.exceptions import YAMLPathException
 from yamlpath.eyaml.exceptions import EYAMLCommandException
-from yamlpath.enums import PathSeperators
+from yamlpath.enums import PathSeparators
 from yamlpath.wrappers import NodeCoords
 from yamlpath.eyaml import EYAMLProcessor
 
@@ -61,10 +61,10 @@ def processcli():
     parser.add_argument(
         "-t", "--pathsep",
         default="dot",
-        choices=PathSeperators,
-        metavar=PathSeperators.get_choices(),
-        type=PathSeperators.from_str,
-        help="indicate which YAML Path seperator to use when rendering\
+        choices=PathSeparators,
+        metavar=PathSeparators.get_choices(),
+        type=PathSeparators.from_str,
+        help="indicate which YAML Path separator to use when rendering\
               results; default=dot")
 
     parser.add_argument(
diff --git a/yamlpath/commands/yaml_merge.py b/yamlpath/commands/yaml_merge.py
index a7c812ef..29a904c8 100644
--- a/yamlpath/commands/yaml_merge.py
+++ b/yamlpath/commands/yaml_merge.py
@@ -455,13 +455,13 @@ def merge_docs(
 
     return return_state
 
-def main():
+def main() -> None:
     """Perform the work specified via CLI arguments and exit.
 
     Main code.
     """
-    args = processcli()
-    log = ConsolePrinter(args)
+    args: argparse.Namespace = processcli()
+    log: ConsolePrinter = ConsolePrinter(args)
     validateargs(args, log)
 
     # For the remainder of processing, overwrite overwrites output
@@ -469,12 +469,12 @@ def main():
         args.output = args.overwrite
 
     # Merge all input files
-    yaml_editor = Parsers.get_yaml_editor()
-    merge_config = MergerConfig(log, args)
-    exit_state = 0
-    consumed_stdin = False
+    yaml_editor: YAML = Parsers.get_yaml_editor()
+    merge_config: MergerConfig = MergerConfig(log, args)
+    exit_state: int = 0
+    consumed_stdin: bool = False
     mergers: List[Merger] = []
-    merge_count = 0
+    merge_count: int = 0
     for yaml_file in args.yaml_files:
         if yaml_file.strip() == '-':
             consumed_stdin = True
diff --git a/yamlpath/commands/yaml_paths.py b/yamlpath/commands/yaml_paths.py
index 68593b9d..6df93e18 100644
--- a/yamlpath/commands/yaml_paths.py
+++ b/yamlpath/commands/yaml_paths.py
@@ -22,7 +22,7 @@
 from yamlpath.enums import (
     AnchorMatches,
     IncludeAliases,
-    PathSeperators,
+    PathSeparators,
     PathSearchMethods
 )
 from yamlpath.path import SearchTerms
@@ -103,10 +103,10 @@ def processcli():
     parser.add_argument(
         "-t", "--pathsep",
         default="dot",
-        choices=PathSeperators,
-        metavar=PathSeperators.get_choices(),
-        type=PathSeperators.from_str,
-        help="indicate which YAML Path seperator to use when rendering\
+        choices=PathSeparators,
+        metavar=PathSeparators.get_choices(),
+        type=PathSeparators.from_str,
+        help="indicate which YAML Path separator to use when rendering\
               results; default=dot")
 
     keyname_group_ex = parser.add_argument_group("key name searching options")
@@ -266,7 +266,7 @@ def validateargs(args, log):
 
 # pylint: disable=locally-disabled,too-many-arguments,too-many-locals,too-many-branches
 def yield_children(logger: ConsolePrinter, data: Any,
-                   terms: SearchTerms, pathsep: PathSeperators,
+                   terms: SearchTerms, pathsep: PathSeparators,
                    build_path: str, seen_anchors: List[str],
                    **kwargs: bool) -> Generator[YAMLPath, None, None]:
     """
@@ -286,7 +286,7 @@ def yield_children(logger: ConsolePrinter, data: Any,
                               AnchorMatches.ALIAS_EXCLUDED]
 
     if isinstance(data, CommentedSeq):
-        if not build_path and pathsep is PathSeperators.FSLASH:
+        if not build_path and pathsep is PathSeparators.FSLASH:
             build_path = str(pathsep)
         build_path += "["
 
@@ -326,7 +326,7 @@ def yield_children(logger: ConsolePrinter, data: Any,
     elif isinstance(data, CommentedMap):
         if build_path:
             build_path += str(pathsep)
-        elif pathsep is PathSeperators.FSLASH:
+        elif pathsep is PathSeparators.FSLASH:
             build_path = str(pathsep)
 
         pool = data.non_merged_items()
@@ -366,14 +366,14 @@ def yield_children(logger: ConsolePrinter, data: Any,
                 yield YAMLPath(tmp_path)
 
     else:
-        if not build_path and pathsep is PathSeperators.FSLASH:
+        if not build_path and pathsep is PathSeparators.FSLASH:
             build_path = str(pathsep)
         yield YAMLPath(build_path)
 
 # pylint: disable=locally-disabled,too-many-arguments,too-many-locals,too-many-branches,too-many-statements
 def search_for_paths(logger: ConsolePrinter, processor: EYAMLProcessor,
                      data: Any, terms: SearchTerms,
-                     pathsep: PathSeperators = PathSeperators.DOT,
+                     pathsep: PathSeparators = PathSeparators.DOT,
                      build_path: str = "",
                      seen_anchors: Optional[List[str]] = None,
                      **kwargs) -> Generator[YAMLPath, None, None]:
@@ -402,7 +402,7 @@ def search_for_paths(logger: ConsolePrinter, processor: EYAMLProcessor,
 
     if isinstance(data, CommentedSeq):
         # Build the path
-        if not build_path and pathsep is PathSeperators.FSLASH:
+        if not build_path and pathsep is PathSeparators.FSLASH:
             build_path = strsep
         build_path += "["
 
@@ -490,7 +490,7 @@ def search_for_paths(logger: ConsolePrinter, processor: EYAMLProcessor,
     elif isinstance(data, CommentedMap):
         if build_path:
             build_path += strsep
-        elif pathsep is PathSeperators.FSLASH:
+        elif pathsep is PathSeparators.FSLASH:
             build_path = strsep
 
         pool = data.non_merged_items()
@@ -651,7 +651,7 @@ def search_for_paths(logger: ConsolePrinter, processor: EYAMLProcessor,
     elif isinstance(data, CommentedSet):
         if build_path:
             build_path += strsep
-        elif pathsep is PathSeperators.FSLASH:
+        elif pathsep is PathSeparators.FSLASH:
             build_path = strsep
 
         for key in data:
@@ -758,7 +758,7 @@ def print_results(
         resline += buffers[0]
         if print_yaml_path:
             if args.noescape:
-                use_flash = args.pathsep is PathSeperators.FSLASH
+                use_flash = args.pathsep is PathSeparators.FSLASH
                 seglines = []
                 join_mark = "/" if use_flash else "."
                 path_prefix = "/" if use_flash else ""
@@ -784,8 +784,9 @@ def print_results(
         print(resline)
 
 def process_yaml_file(
-    args, yaml, log, yaml_file, processor, search_values, search_keys,
-    include_key_aliases, include_value_aliases, file_tally = 0
+    args: Any, yaml: Any, log: ConsolePrinter, yaml_file: str,
+    processor: EYAMLProcessor, search_values: bool, search_keys: bool,
+    include_key_aliases: bool, include_value_aliases: bool, file_tally: int = 0
 ):
     """Process a (potentially multi-doc) YAML file."""
     # Try to open the file
@@ -807,7 +808,7 @@ def process_yaml_file(
         processor.data = yaml_data
         all_anchors: Dict[str, Any] = {}
         Anchors.scan_for_anchors(yaml_data, all_anchors)
-        yaml_paths = []
+        yaml_paths: List[Tuple[str, YAMLPath]] = []
         for expression in args.search:
             exterm = get_search_term(log, expression)
             log.debug(("yaml_paths::process_yaml_file:"
diff --git a/yamlpath/commands/yaml_set.py b/yamlpath/commands/yaml_set.py
index b3cd4d44..d2c4b8c4 100644
--- a/yamlpath/commands/yaml_set.py
+++ b/yamlpath/commands/yaml_set.py
@@ -24,7 +24,7 @@
 from yamlpath.common import Nodes, Parsers
 from yamlpath import YAMLPath
 from yamlpath.exceptions import YAMLPathException
-from yamlpath.enums import YAMLValueFormats, PathSeperators
+from yamlpath.enums import YAMLValueFormats, PathSeparators
 from yamlpath.eyaml.exceptions import EYAMLCommandException
 from yamlpath.eyaml.enums import EYAMLOutputFormats
 from yamlpath.eyaml import EYAMLProcessor
@@ -119,10 +119,10 @@ def processcli():
     parser.add_argument(
         "-t", "--pathsep",
         default="dot",
-        choices=PathSeperators,
-        metavar=PathSeperators.get_choices(),
-        type=PathSeperators.from_str,
-        help="indicate which YAML Path seperator to use when rendering\
+        choices=PathSeparators,
+        metavar=PathSeparators.get_choices(),
+        type=PathSeparators.from_str,
+        help="indicate which YAML Path separator to use when rendering\
               results; default=dot")
 
     parser.add_argument(
diff --git a/yamlpath/common/anchors.py b/yamlpath/common/anchors.py
index 1710456b..2c113a58 100644
--- a/yamlpath/common/anchors.py
+++ b/yamlpath/common/anchors.py
@@ -144,7 +144,7 @@ def replace_anchor(data: Any, old_node: Any, repl_node: Any) -> None:
     @staticmethod
     def generate_unique_anchor_name(
         document: Any, node_coord: NodeCoords,
-        known_anchors: Dict[str, Any] = None
+        known_anchors: Optional[Dict[str, Any]] = None
     ) -> str:
         """
         Generate a unique Anchor name to a given node.
diff --git a/yamlpath/common/keywordsearches.py b/yamlpath/common/keywordsearches.py
index 57a56cb8..1960cd2e 100644
--- a/yamlpath/common/keywordsearches.py
+++ b/yamlpath/common/keywordsearches.py
@@ -1,12 +1,13 @@
+#pylint: disable=too-many-lines
 """
 Implement KeywordSearches.
 
 This is a static library of generally-useful code for searching data based on
 pre-defined keywords (in the programming language sense).
 
-Copyright 2020 William W. Kimball, Jr. MBA MSIS
+Copyright 2020, 2022 William W. Kimball, Jr. MBA MSIS
 """
-from typing import Any, Dict, Generator, List
+from typing import Any, Dict, Generator, List, Optional
 
 from ruamel.yaml.comments import CommentedMap
 
@@ -44,7 +45,10 @@ def search_matches(
         parameters: List[str] = terms.parameters
         nc_matches: Generator[NodeCoords, None, None]
 
-        if keyword is PathSearchKeywords.HAS_CHILD:
+        if keyword is PathSearchKeywords.DISTINCT:
+            nc_matches = KeywordSearches.distinct(
+                haystack, invert, parameters, yaml_path, **kwargs)
+        elif keyword is PathSearchKeywords.HAS_CHILD:
             nc_matches = KeywordSearches.has_child(
                 haystack, invert, parameters, yaml_path, **kwargs)
         elif keyword is PathSearchKeywords.NAME:
@@ -59,6 +63,9 @@ def search_matches(
         elif keyword is PathSearchKeywords.PARENT:
             nc_matches = KeywordSearches.parent(
                 haystack, invert, parameters, yaml_path, **kwargs)
+        elif keyword is PathSearchKeywords.UNIQUE:
+            nc_matches = KeywordSearches.unique(
+                haystack, invert, parameters, yaml_path, **kwargs)
         else:
             raise YAMLPathException(
                 "Unsupported search keyword {} in".format(keyword),
@@ -480,7 +487,7 @@ def max(
             for key, val in data.items():
                 next_path = (
                     translated_path + YAMLPath.escape_path_section(
-                        key, translated_path.seperator))
+                        key, translated_path.separator))
                 next_ancestry = ancestry + [(data, key)]
                 if isinstance(val, dict):
                     if val is not None and scan_node in val:
@@ -688,7 +695,7 @@ def min(
                 next_ancestry = ancestry + [(data, key)]
                 next_path = (
                     translated_path + YAMLPath.escape_path_section(
-                        key, translated_path.seperator))
+                        key, translated_path.separator))
                 if isinstance(val, dict):
                     if val is not None and scan_node in val:
                         eval_val = val[scan_node]
@@ -874,3 +881,297 @@ def parent(
             yield NodeCoords(
                 data, parent, parentref, translated_path, ancestry,
                 relay_segment)
+
+
+    @staticmethod
+    # pylint: disable=locally-disabled,too-many-locals,too-many-branches,too-many-statements
+    def distinct(
+        data: Any, invert: bool, parameters: List[str], yaml_path: YAMLPath,
+        **kwargs: Any
+    ) -> Generator[NodeCoords, None, None]:
+        """
+        Find distinct values.
+
+        Parameters:
+        1. data (Any) The data to evaluate
+        2. invert (bool) Invert the evaluation
+        3. parameters (List[str]) Parsed parameters
+        4. yaml_path (YAMLPath) YAML Path begetting this operation
+
+        Keyword Arguments:
+        * parent (ruamel.yaml node) The parent node from which this query
+          originates
+        * parentref (Any) The Index or Key of data within parent
+        * relay_segment (PathSegment) YAML Path segment presently under
+          evaluation
+        * translated_path (YAMLPath) YAML Path indicating precisely which node
+          is being evaluated
+        * ancestry (List[AncestryEntry]) Stack of ancestors preceding the
+          present node under evaluation
+
+        Returns:  (Generator[NodeCoords, None, None]) each result as it is
+            generated
+        """
+        parent: Any = kwargs.pop("parent", None)
+        parentref: Any = kwargs.pop("parentref", None)
+        translated_path: YAMLPath = kwargs.pop("translated_path", YAMLPath(""))
+        ancestry: List[AncestryEntry] = kwargs.pop("ancestry", [])
+        relay_segment: PathSegment = kwargs.pop("relay_segment", None)
+
+        if invert:
+            raise YAMLPathException(
+                "Inversion is meaningless to"
+                f" {PathSearchKeywords.DISTINCT}([NAME])", str(yaml_path))
+
+        # There may be 0 or 1 parameters
+        max_params: int = 1
+        param_count: int = len(parameters)
+        if param_count > max_params:
+            raise YAMLPathException(
+                "Invalid parameter count to"
+                f" {PathSearchKeywords.DISTINCT}([NAME]); up to {max_params}"
+                f" permitted, got {param_count} in YAML Path", str(yaml_path))
+
+        scan_node: Optional[str] = parameters[0] if param_count > 0 else None
+        unwrapped_data: Any = NodeCoords.unwrap_node_coords(data)
+        seen_values: Dict[Any, List[NodeCoords]] = {}
+        if Nodes.node_is_aoh(
+            unwrapped_data, accept_nulls=True
+        ):
+            # A named child node is mandatory
+            if scan_node is None:
+                raise YAMLPathException(
+                    f"The {PathSearchKeywords.DISTINCT}([NAME]) Search Keyword"
+                    " requires a key name to scan when evaluating an"
+                    " Array-of-Hashes (sequence/list of maps/dicts) in"
+                    " YAML Path", str(yaml_path))
+
+            for idx, raw_ele in enumerate(data):
+                next_path = translated_path + f"[{idx}]"
+                next_ancestry = ancestry + [(data, idx)]
+                wrapped_ele = (raw_ele
+                    if isinstance(raw_ele, NodeCoords) else NodeCoords(
+                        raw_ele, data, idx, next_path, next_ancestry,
+                        relay_segment))
+                eval_ele = (NodeCoords.unwrap_node_coords(raw_ele)
+                    if isinstance(raw_ele, NodeCoords) else raw_ele)
+                if eval_ele is not None and scan_node in eval_ele:
+                    eval_val = eval_ele[scan_node]
+                    if eval_val in seen_values:
+                        seen_values[eval_val].append(wrapped_ele)
+                    else:
+                        seen_values[eval_val] = [wrapped_ele]
+
+        elif isinstance(data, dict):
+            # A named child node is mandatory
+            if scan_node is None:
+                raise YAMLPathException(
+                    f"The {PathSearchKeywords.DISTINCT}([NAME]) Search Keyword"
+                    " requires a key name to scan when evaluating Hash"
+                    " (map/dict) children in YAML Path", str(yaml_path))
+
+            for key, val in data.items():
+                next_path = (
+                    translated_path + YAMLPath.escape_path_section(
+                        key, translated_path.separator))
+                next_ancestry = ancestry + [(data, key)]
+                if isinstance(val, dict):
+                    if val is not None and scan_node in val:
+                        wrapped_ele = NodeCoords(
+                            val, data, key, next_path, next_ancestry,
+                            relay_segment)
+                        eval_val = val[scan_node]
+                        if eval_val in seen_values:
+                            seen_values[eval_val].append(wrapped_ele)
+                        else:
+                            seen_values[eval_val] = [wrapped_ele]
+
+                elif scan_node in data:
+                    # The user probably meant to operate against the parent
+                    raise YAMLPathException(
+                        f"The {PathSearchKeywords.DISTINCT}([NAME]) Search"
+                        " Keyword operates against collections of data which"
+                        " share a common attribute yet there is only a single"
+                        " node to consider.  Did you mean to evaluate the"
+                        " parent of the selected node?  Please review your"
+                        " YAML Path", str(yaml_path))
+
+        elif isinstance(data, list):
+            # A named child node is useless
+            if scan_node is not None:
+                raise YAMLPathException(
+                    f"The {PathSearchKeywords.DISTINCT}([NAME]) Search Keyword"
+                    " cannot utilize a key name when comparing Array"
+                    " (sequence/list) elements to one another in YAML Path",
+                    str(yaml_path))
+
+            for idx, ele in enumerate(data):
+                next_path = translated_path + f"[{idx}]"
+                next_ancestry = ancestry + [(data, idx)]
+                eval_val = (NodeCoords.unwrap_node_coords(ele)
+                    if isinstance(ele, NodeCoords) else ele)
+                wrapped_ele = (ele
+                    if isinstance(ele, NodeCoords) else NodeCoords(
+                        ele, data, idx, next_path, next_ancestry,
+                        relay_segment))
+                if eval_val in seen_values:
+                    seen_values[eval_val].append(wrapped_ele)
+                else:
+                    seen_values[eval_val] = [wrapped_ele]
+
+        else:
+            # Non-complex data is always unique
+            seen_values[data] = [NodeCoords(
+                data, parent, parentref, translated_path, ancestry,
+                relay_segment)]
+
+        # Yield the first of every match
+        for nodes in seen_values.values():
+            yield nodes[0]
+
+
+    @staticmethod
+    # pylint: disable=locally-disabled,too-many-locals,too-many-branches,too-many-statements
+    def unique(
+        data: Any, invert: bool, parameters: List[str], yaml_path: YAMLPath,
+        **kwargs: Any
+    ) -> Generator[NodeCoords, None, None]:
+        """
+        Find unique values.
+
+        Parameters:
+        1. data (Any) The data to evaluate
+        2. invert (bool) Invert the evaluation
+        3. parameters (List[str]) Parsed parameters
+        4. yaml_path (YAMLPath) YAML Path begetting this operation
+
+        Keyword Arguments:
+        * parent (ruamel.yaml node) The parent node from which this query
+          originates
+        * parentref (Any) The Index or Key of data within parent
+        * relay_segment (PathSegment) YAML Path segment presently under
+          evaluation
+        * translated_path (YAMLPath) YAML Path indicating precisely which node
+          is being evaluated
+        * ancestry (List[AncestryEntry]) Stack of ancestors preceding the
+          present node under evaluation
+
+        Returns:  (Generator[NodeCoords, None, None]) each result as it is
+            generated
+        """
+        parent: Any = kwargs.pop("parent", None)
+        parentref: Any = kwargs.pop("parentref", None)
+        translated_path: YAMLPath = kwargs.pop("translated_path", YAMLPath(""))
+        ancestry: List[AncestryEntry] = kwargs.pop("ancestry", [])
+        relay_segment: PathSegment = kwargs.pop("relay_segment", None)
+
+        # There may be 0 or 1 parameters
+        max_params: int = 1
+        param_count: int = len(parameters)
+        if param_count > max_params:
+            raise YAMLPathException(
+                "Invalid parameter count to"
+                f" {PathSearchKeywords.UNIQUE}([NAME]); up to {max_params}"
+                f" permitted, got {param_count} in YAML Path", str(yaml_path))
+
+        scan_node: Optional[str] = parameters[0] if param_count > 0 else None
+        unwrapped_data: Any = NodeCoords.unwrap_node_coords(data)
+        seen_values: Dict[Any, List[NodeCoords]] = {}
+        if Nodes.node_is_aoh(
+            unwrapped_data, accept_nulls=True
+        ):
+            # A named child node is mandatory
+            if scan_node is None:
+                raise YAMLPathException(
+                    f"The {PathSearchKeywords.UNIQUE}([NAME]) Search Keyword"
+                    " requires a key name to scan when evaluating an"
+                    " Array-of-Hashes (sequence/list of maps/dicts) in"
+                    " YAML Path", str(yaml_path))
+
+            for idx, raw_ele in enumerate(data):
+                next_path = translated_path + f"[{idx}]"
+                next_ancestry = ancestry + [(data, idx)]
+                wrapped_ele = (raw_ele
+                    if isinstance(raw_ele, NodeCoords) else NodeCoords(
+                        raw_ele, data, idx, next_path, next_ancestry,
+                        relay_segment))
+                eval_ele = (NodeCoords.unwrap_node_coords(raw_ele)
+                    if isinstance(raw_ele, NodeCoords) else raw_ele)
+                if eval_ele is not None and scan_node in eval_ele:
+                    eval_val = eval_ele[scan_node]
+                    if eval_val in seen_values:
+                        seen_values[eval_val].append(wrapped_ele)
+                    else:
+                        seen_values[eval_val] = [wrapped_ele]
+
+        elif isinstance(data, dict):
+            # A named child node is mandatory
+            if scan_node is None:
+                raise YAMLPathException(
+                    f"The {PathSearchKeywords.UNIQUE}([NAME]) Search Keyword"
+                    " requires a key name to scan when evaluating Hash"
+                    " (map/dict) children in YAML Path", str(yaml_path))
+
+            for key, val in data.items():
+                next_path = (
+                    translated_path + YAMLPath.escape_path_section(
+                        key, translated_path.separator))
+                next_ancestry = ancestry + [(data, key)]
+                if isinstance(val, dict):
+                    if val is not None and scan_node in val:
+                        wrapped_ele = NodeCoords(
+                            val, data, key, next_path, next_ancestry,
+                            relay_segment)
+                        eval_val = val[scan_node]
+                        if eval_val in seen_values:
+                            seen_values[eval_val].append(wrapped_ele)
+                        else:
+                            seen_values[eval_val] = [wrapped_ele]
+
+                elif scan_node in data:
+                    # The user probably meant to operate against the parent
+                    raise YAMLPathException(
+                        f"The {PathSearchKeywords.UNIQUE}([NAME]) Search"
+                        " Keyword operates against collections of data which"
+                        " share a common attribute yet there is only a single"
+                        " node to consider.  Did you mean to evaluate the"
+                        " parent of the selected node?  Please review your"
+                        " YAML Path", str(yaml_path))
+
+        elif isinstance(data, list):
+            # A named child node is useless
+            if scan_node is not None:
+                raise YAMLPathException(
+                    f"The {PathSearchKeywords.UNIQUE}([NAME]) Search Keyword"
+                    " cannot utilize a key name when comparing Array"
+                    " (sequence/list) elements to one another in YAML Path",
+                    str(yaml_path))
+
+            for idx, ele in enumerate(data):
+                next_path = translated_path + f"[{idx}]"
+                next_ancestry = ancestry + [(data, idx)]
+                eval_val = (NodeCoords.unwrap_node_coords(ele)
+                    if isinstance(ele, NodeCoords) else ele)
+                wrapped_ele = (ele
+                    if isinstance(ele, NodeCoords) else NodeCoords(
+                        ele, data, idx, next_path, next_ancestry,
+                        relay_segment))
+                if eval_val in seen_values:
+                    seen_values[eval_val].append(wrapped_ele)
+                else:
+                    seen_values[eval_val] = [wrapped_ele]
+
+        else:
+            # Non-complex data is always unique
+            seen_values[data] = [NodeCoords(
+                data, parent, parentref, translated_path, ancestry,
+                relay_segment)]
+
+        # Yield the non/unique matches
+        if invert:
+            yield_nodes = [v for v in seen_values.values() if 1 < len(v)]
+        else:
+            yield_nodes = [v for v in seen_values.values() if 1 == len(v)]
+
+        for node_coord in [i for v in yield_nodes for i in v]:
+            yield node_coord
diff --git a/yamlpath/common/nodes.py b/yamlpath/common/nodes.py
index 1586369a..ec268fd5 100644
--- a/yamlpath/common/nodes.py
+++ b/yamlpath/common/nodes.py
@@ -6,7 +6,7 @@
 import re
 from datetime import datetime, date, timedelta, timezone
 from ast import literal_eval
-from typing import Any
+from typing import Any, Optional
 
 from dateutil import parser
 
@@ -253,7 +253,9 @@ def make_new_node(
         return new_node
 
     @staticmethod
-    def make_date_node(value: date, anchor: str = None) -> AnchoredDate:
+    def make_date_node(
+        value: date, anchor: Optional[str] = None
+    ) -> AnchoredDate:
         r"""
         Create a new AnchoredDate data node from a bare date.
 
@@ -283,7 +285,7 @@ def make_date_node(value: date, anchor: str = None) -> AnchoredDate:
 
     @staticmethod
     def make_timestamp_node(
-        value: datetime, t_separator: str, anchor: str = None
+        value: datetime, t_separator: str, anchor: Optional[str] = None
     ) -> AnchoredTimeStamp:
         r"""
         Create a new AnchoredTimeStamp data node from a bare datetime.
@@ -332,7 +334,7 @@ def make_timestamp_node(
         return new_node
 
     @staticmethod
-    def make_float_node(value: float, anchor: str = None):
+    def make_float_node(value: float, anchor: Optional[str] = None):
         """
         Create a new ScalarFloat data node from a bare float.
 
@@ -469,7 +471,7 @@ def build_next_node(
 
     @staticmethod
     def append_list_element(
-        data: Any, value: Any = None, anchor: str = None
+        data: Any, value: Any = None, anchor: Optional[str] = None
     ) -> Any:
         """
         Append a new element to an ruamel.yaml List.
diff --git a/yamlpath/differ/diffentry.py b/yamlpath/differ/diffentry.py
index 383c6265..04dbfcde 100644
--- a/yamlpath/differ/diffentry.py
+++ b/yamlpath/differ/diffentry.py
@@ -9,7 +9,7 @@
 from ruamel.yaml.comments import CommentedBase, TaggedScalar
 
 from yamlpath.common import Parsers
-from yamlpath.enums import PathSeperators
+from yamlpath.enums import PathSeparators
 from yamlpath import YAMLPath
 from .enums.diffactions import DiffActions
 
@@ -122,16 +122,16 @@ def index(self) -> str:
         return self._index
 
     @property
-    def pathsep(self) -> PathSeperators:
-        """Seperator used to delimit reported YAML Paths (accessor)."""
-        return self._path.seperator
+    def pathsep(self) -> PathSeparators:
+        """Separator used to delimit reported YAML Paths (accessor)."""
+        return self._path.separator
 
     @pathsep.setter
-    def pathsep(self, value: PathSeperators) -> None:
-        """Seperator used to delimit reported YAML Paths (mutator)."""
+    def pathsep(self, value: PathSeparators) -> None:
+        """Separator used to delimit reported YAML Paths (mutator)."""
         # No unnecessary changes
         if value is not self.pathsep:
-            self._path.seperator = value
+            self._path.separator = value
 
     @property
     def verbose(self) -> bool:
diff --git a/yamlpath/differ/differ.py b/yamlpath/differ/differ.py
index 03c82988..e501aea1 100644
--- a/yamlpath/differ/differ.py
+++ b/yamlpath/differ/differ.py
@@ -90,7 +90,7 @@ def _purge_document(self, path: YAMLPath, data: Any) -> None:
             for key, val in data.items():
                 lhs_iteration += 1
                 next_path = (path +
-                    YAMLPath.escape_path_section(key, path.seperator))
+                    YAMLPath.escape_path_section(key, path.separator))
                 self._diffs.append(
                     DiffEntry(
                         DiffActions.DELETE, next_path, val, None,
@@ -105,7 +105,7 @@ def _purge_document(self, path: YAMLPath, data: Any) -> None:
         elif isinstance(data, CommentedSet):
             for idx, ele in enumerate(data):
                 next_path = (path +
-                    YAMLPath.escape_path_section(ele, path.seperator))
+                    YAMLPath.escape_path_section(ele, path.separator))
                 self._diffs.append(
                     DiffEntry(
                         DiffActions.DELETE, next_path, ele, None,
@@ -131,7 +131,7 @@ def _add_everything(self, path: YAMLPath, data: Any) -> None:
             for key, val in data.items():
                 rhs_iteration += 1
                 next_path = (path +
-                    YAMLPath.escape_path_section(key, path.seperator))
+                    YAMLPath.escape_path_section(key, path.separator))
                 self._diffs.append(
                     DiffEntry(
                         DiffActions.ADD, next_path, None, val,
@@ -146,7 +146,7 @@ def _add_everything(self, path: YAMLPath, data: Any) -> None:
         elif isinstance(data, CommentedSet):
             for idx, ele in enumerate(data):
                 next_path = (path +
-                    YAMLPath.escape_path_section(ele, path.seperator))
+                    YAMLPath.escape_path_section(ele, path.separator))
                 self._diffs.append(
                     DiffEntry(
                         DiffActions.ADD, next_path, None, ele,
@@ -258,7 +258,7 @@ def _diff_dicts(
             if key in lhs and key in rhs
         ]:
             next_path = (path +
-                YAMLPath.escape_path_section(key, path.seperator))
+                YAMLPath.escape_path_section(key, path.separator))
             self._diff_between(
                 next_path, lhs[key], val,
                 lhs_parent=lhs, lhs_iteration=lhs_key_indicies[key],
@@ -268,7 +268,7 @@ def _diff_dicts(
         # Look for deleted keys
         for key in lhs_keys - rhs_keys:
             next_path = (path +
-                YAMLPath.escape_path_section(key, path.seperator))
+                YAMLPath.escape_path_section(key, path.separator))
             self._diffs.append(
                 DiffEntry(
                     DiffActions.DELETE, next_path, lhs[key], None,
@@ -279,7 +279,7 @@ def _diff_dicts(
         # Look for new keys
         for key in rhs_keys - lhs_keys:
             next_path = (path +
-                YAMLPath.escape_path_section(key, path.seperator))
+                YAMLPath.escape_path_section(key, path.separator))
             self._diffs.append(
                 DiffEntry(
                     DiffActions.ADD, next_path, None, rhs[key],
@@ -620,7 +620,7 @@ def _diff_sets(
             if key in lhs and key in rhs
         ]:
             next_path = (path +
-                YAMLPath.escape_path_section(key, path.seperator))
+                YAMLPath.escape_path_section(key, path.separator))
             lhs_ele = None
             rhs_ele = None
             for ele in lhs:
@@ -640,7 +640,7 @@ def _diff_sets(
         # Look for deleted keys
         for key in lhs_keys - rhs_keys:
             next_path = (path +
-                YAMLPath.escape_path_section(key, path.seperator))
+                YAMLPath.escape_path_section(key, path.separator))
             self._diffs.append(
                 DiffEntry(
                     DiffActions.DELETE, next_path, key, None,
@@ -651,7 +651,7 @@ def _diff_sets(
         # Look for new keys
         for key in rhs_keys - lhs_keys:
             next_path = (path +
-                YAMLPath.escape_path_section(key, path.seperator))
+                YAMLPath.escape_path_section(key, path.separator))
             self._diffs.append(
                 DiffEntry(
                     DiffActions.ADD, next_path, None, key,
diff --git a/yamlpath/enums/__init__.py b/yamlpath/enums/__init__.py
index 2050c804..00f60bc7 100644
--- a/yamlpath/enums/__init__.py
+++ b/yamlpath/enums/__init__.py
@@ -5,5 +5,8 @@
 from .pathsearchkeywords import PathSearchKeywords
 from .pathsearchmethods import PathSearchMethods
 from .pathsegmenttypes import PathSegmentTypes
-from .pathseperators import PathSeperators
+from .pathseparators import PathSeparators
 from .yamlvalueformats import YAMLValueFormats
+
+# Legacy spelling compatibility:
+from .pathseperators import PathSeperators
diff --git a/yamlpath/enums/collectoroperators.py b/yamlpath/enums/collectoroperators.py
index fb05186c..1dfc1f46 100644
--- a/yamlpath/enums/collectoroperators.py
+++ b/yamlpath/enums/collectoroperators.py
@@ -1,7 +1,7 @@
 """
 Implements the CollectorOperators enumeration.
 
-Copyright 2019, 2020 William W. Kimball, Jr. MBA MSIS
+Copyright 2019, 2020, 2021 William W. Kimball, Jr. MBA MSIS
 """
 from enum import Enum, auto
 from typing import List
@@ -27,11 +27,15 @@ class CollectorOperators(Enum):
     `SUBTRACTION`
         The Collector's results are removed from its immediate predecessor
         Collector's results.  Only exact matches are removed.
+
+    `INTERSECTION`
+        Only those elements which are common to both Collectors are returned.
     """
 
     ADDITION = auto()
     NONE = auto()
     SUBTRACTION = auto()
+    INTERSECTION = auto()
 
     def __str__(self) -> str:
         """Get a String representation of an employed value of this enum."""
@@ -40,6 +44,8 @@ def __str__(self) -> str:
             operator = '+'
         elif self is CollectorOperators.SUBTRACTION:
             operator = '-'
+        elif self is CollectorOperators.INTERSECTION:
+            operator = '&'
         return operator
 
     @staticmethod
@@ -77,6 +83,8 @@ def from_operator(operator: str) -> "CollectorOperators":
             check = "ADDITION"
         elif check == '-':
             check = "SUBTRACTION"
+        elif check == '&':
+            check = "INTERSECTION"
 
         if check in CollectorOperators.get_names():
             return CollectorOperators[check]
diff --git a/yamlpath/enums/pathsearchkeywords.py b/yamlpath/enums/pathsearchkeywords.py
index de9af170..1b17434b 100644
--- a/yamlpath/enums/pathsearchkeywords.py
+++ b/yamlpath/enums/pathsearchkeywords.py
@@ -1,7 +1,7 @@
 """
 Implements the PathSearchKeywords enumeration.
 
-Copyright 2021 William W. Kimball, Jr. MBA MSIS
+Copyright 2021, 2022 William W. Kimball, Jr. MBA MSIS
 """
 from enum import Enum, auto
 from typing import List
@@ -13,6 +13,10 @@ class PathSearchKeywords(Enum):
 
     These include:
 
+    `DISTINCT`
+        Match exactly one of every value within collections, discarding
+        duplicates.
+
     `HAS_CHILD`
         Matches when the node has a direct child with a given name.
 
@@ -35,18 +39,25 @@ class PathSearchKeywords(Enum):
 
     `PARENT`
         Access the parent(s) of the present node.
+
+    `UNIQUE`
+        Match only values which have no duplicates within collections.
     """
 
+    DISTINCT = auto()
     HAS_CHILD = auto()
     NAME = auto()
     MAX = auto()
     MIN = auto()
     PARENT = auto()
+    UNIQUE = auto()
 
     def __str__(self) -> str:
         """Get a String representation of an employed value of this enum."""
         keyword = ''
-        if self is PathSearchKeywords.HAS_CHILD:
+        if self is PathSearchKeywords.DISTINCT:
+            keyword = 'distinct'
+        elif self is PathSearchKeywords.HAS_CHILD:
             keyword = 'has_child'
         elif self is PathSearchKeywords.NAME:
             keyword = 'name'
@@ -56,6 +67,8 @@ def __str__(self) -> str:
             keyword = 'min'
         elif self is PathSearchKeywords.PARENT:
             keyword = 'parent'
+        elif self is PathSearchKeywords.UNIQUE:
+            keyword = 'unique'
 
         return keyword
 
diff --git a/yamlpath/enums/pathseparators.py b/yamlpath/enums/pathseparators.py
new file mode 100644
index 00000000..975de788
--- /dev/null
+++ b/yamlpath/enums/pathseparators.py
@@ -0,0 +1,140 @@
+"""
+Implements the PathSeparators enumeration.
+
+Copyright 2019, 2020 William W. Kimball, Jr. MBA MSIS
+"""
+from enum import Enum, auto
+from typing import List
+
+
+class PathSeparators(Enum):
+    """
+    Supported YAML Path segment separators.
+
+    Separators include:
+
+    `AUTO`
+        The separator must be manually dictated or automatically inferred from
+        the YAML Path being evaluated.
+
+    `DOT`
+        YAML Path segments are separated via dots (.).
+
+    `FSLASH`
+        YAML Path segments are separated via forward-slashes (/).
+    """
+
+    AUTO = auto()
+    DOT = auto()
+    FSLASH = auto()
+
+    def __str__(self) -> str:
+        """Get a String representation of this employed enum's value."""
+        separator = '.'
+        if self is PathSeparators.FSLASH:
+            separator = '/'
+        return separator
+
+    @staticmethod
+    def get_names() -> List[str]:
+        """
+        Get all upper-cased entry names for this enumeration.
+
+        Parameters:  N/A
+
+        Returns:  (List[str]) Upper-case names from this enumeration
+
+        Raises:  N/A
+        """
+        return [entry.name.upper() for entry in PathSeparators]
+
+    @staticmethod
+    def get_choices() -> List[str]:
+        """
+        Get all entry names with symbolic representations for this enumeration.
+
+        All returned entries are lower-cased.
+
+        Parameters:  N/A
+
+        Returns:  (List[str]) Lower-case names and symbols from this
+            enumeration
+
+        Raises:  N/A
+        """
+        names = [l.lower() for l in PathSeparators.get_names()]
+        symbols = [str(e) for e in PathSeparators]
+        choices = list(set(names + symbols))
+        choices.sort()
+        return choices
+
+    @staticmethod
+    def from_str(name: str) -> "PathSeparators":
+        """
+        Convert a string value to a value of this enumeration, if valid.
+
+        Parameters:
+            1. name (str) The name to convert
+
+        Returns:  (PathSeparators) the converted enumeration value
+
+        Raises:
+            - `NameError` when name doesn't match any enumeration values
+        """
+        if isinstance(name, PathSeparators):
+            return name
+
+        check: str = str(name).upper()
+        if check == '.':
+            check = "DOT"
+        elif check == '/':
+            check = "FSLASH"
+
+        if check in PathSeparators.get_names():
+            return PathSeparators[check]
+        raise NameError("PathSeparators has no such item, {}.".format(check))
+
+    @staticmethod
+    def infer_separator(yaml_path: str) -> "PathSeparators":
+        """
+        Infer the separator used within a sample YAML Path.
+
+        Will attempt to return the best PathSeparators match.  Always returns
+        `PathSeparators.AUTO` when the sample is empty.
+
+        Parameters:
+            1. yaml_path (str) The sample YAML Path to evaluate
+
+        Returns: (PathSeparators) the inferred PathSeparators value
+
+        Raises:  N/A
+        """
+        separator: PathSeparators = PathSeparators.AUTO
+
+        if yaml_path:
+            if yaml_path[0] == '/':
+                separator = PathSeparators.FSLASH
+            else:
+                separator = PathSeparators.DOT
+
+        return separator
+
+    @staticmethod
+    def infer_seperator(yaml_path: str) -> "PathSeparators":
+        """
+        Infer the separator used within a sample YAML Path.
+
+        Will attempt to return the best PathSeparators match.  Always returns
+        `PathSeparators.AUTO` when the sample is empty.
+
+        This is provided for compatibility with older versions,
+        before the spelling was updated to "separator."
+
+        Parameters:
+            1. yaml_path (str) The sample YAML Path to evaluate
+
+        Returns: (PathSeparators) the inferred PathSeparators value
+
+        Raises:  N/A
+        """
+        return PathSeparators.infer_separator(yaml_path)
diff --git a/yamlpath/enums/pathseperators.py b/yamlpath/enums/pathseperators.py
index 18411355..ec158a57 100644
--- a/yamlpath/enums/pathseperators.py
+++ b/yamlpath/enums/pathseperators.py
@@ -1,120 +1,10 @@
+#pylint: disable=unused-import
 """
-Implements the PathSeperators enumeration.
+Implements the PathSeparators enumeration.
+
+This is provided for compatibility with older versions,
+before the spelling was updated to "separator."
 
 Copyright 2019, 2020 William W. Kimball, Jr. MBA MSIS
 """
-from enum import Enum, auto
-from typing import List
-
-
-class PathSeperators(Enum):
-    """
-    Supported YAML Path segment seperators.
-
-    Seperators include:
-
-    `AUTO`
-        The seperator must be manually dictated or automatically inferred from
-        the YAML Path being evaluated.
-
-    `DOT`
-        YAML Path segments are seperated via dots (.).
-
-    `FSLASH`
-        YAML Path segments are seperated via forward-slashes (/).
-    """
-
-    AUTO = auto()
-    DOT = auto()
-    FSLASH = auto()
-
-    def __str__(self) -> str:
-        """Get a String representation of this employed enum's value."""
-        seperator = '.'
-        if self is PathSeperators.FSLASH:
-            seperator = '/'
-        return seperator
-
-    @staticmethod
-    def get_names() -> List[str]:
-        """
-        Get all upper-cased entry names for this enumeration.
-
-        Parameters:  N/A
-
-        Returns:  (List[str]) Upper-case names from this enumeration
-
-        Raises:  N/A
-        """
-        return [entry.name.upper() for entry in PathSeperators]
-
-    @staticmethod
-    def get_choices() -> List[str]:
-        """
-        Get all entry names with symbolic representations for this enumeration.
-
-        All returned entries are lower-cased.
-
-        Parameters:  N/A
-
-        Returns:  (List[str]) Lower-case names and symbols from this
-            enumeration
-
-        Raises:  N/A
-        """
-        names = [l.lower() for l in PathSeperators.get_names()]
-        symbols = [str(e) for e in PathSeperators]
-        choices = list(set(names + symbols))
-        choices.sort()
-        return choices
-
-    @staticmethod
-    def from_str(name: str) -> "PathSeperators":
-        """
-        Convert a string value to a value of this enumeration, if valid.
-
-        Parameters:
-            1. name (str) The name to convert
-
-        Returns:  (PathSeperators) the converted enumeration value
-
-        Raises:
-            - `NameError` when name doesn't match any enumeration values
-        """
-        if isinstance(name, PathSeperators):
-            return name
-
-        check: str = str(name).upper()
-        if check == '.':
-            check = "DOT"
-        elif check == '/':
-            check = "FSLASH"
-
-        if check in PathSeperators.get_names():
-            return PathSeperators[check]
-        raise NameError("PathSeperators has no such item, {}.".format(check))
-
-    @staticmethod
-    def infer_seperator(yaml_path: str) -> "PathSeperators":
-        """
-        Infer the seperator used within a sample YAML Path.
-
-        Will attempt to return the best PathSeperators match.  Always returns
-        `PathSeperators.AUTO` when the sample is empty.
-
-        Parameters:
-            1. yaml_path (str) The sample YAML Path to evaluate
-
-        Returns: (PathSeperators) the inferred PathSeperators value
-
-        Raises:  N/A
-        """
-        seperator: PathSeperators = PathSeperators.AUTO
-
-        if yaml_path:
-            if yaml_path[0] == '/':
-                seperator = PathSeperators.FSLASH
-            else:
-                seperator = PathSeperators.DOT
-
-        return seperator
+from .pathseparators import PathSeparators as PathSeperators
diff --git a/yamlpath/eyaml/eyamlprocessor.py b/yamlpath/eyaml/eyamlprocessor.py
index 917dbb62..bd0c407d 100644
--- a/yamlpath/eyaml/eyamlprocessor.py
+++ b/yamlpath/eyaml/eyamlprocessor.py
@@ -14,7 +14,7 @@
 from yamlpath import YAMLPath
 from yamlpath.common import Anchors
 from yamlpath.eyaml.enums import EYAMLOutputFormats
-from yamlpath.enums import YAMLValueFormats, PathSeperators
+from yamlpath.enums import YAMLValueFormats, PathSeparators
 from yamlpath.eyaml.exceptions import EYAMLCommandException
 from yamlpath.wrappers import ConsolePrinter, NodeCoords
 from yamlpath import Processor
@@ -75,7 +75,7 @@ def _find_eyaml_paths(
                 node_anchor = Anchors.get_node_anchor(ele)
                 if node_anchor is not None:
                     escaped_section = YAMLPath.escape_path_section(
-                        node_anchor, PathSeperators.DOT)
+                        node_anchor, PathSeparators.DOT)
                     tmp_path_segment = f"[&{escaped_section}]"
                 else:
                     tmp_path_segment = f"[{idx}]"
@@ -90,7 +90,7 @@ def _find_eyaml_paths(
         elif isinstance(data, CommentedMap):
             for key, val in data.non_merged_items():
                 tmp_path = build_path + YAMLPath.escape_path_section(
-                    key, PathSeperators.DOT)
+                    key, PathSeparators.DOT)
                 if self.is_eyaml_value(val):
                     yield tmp_path
                 else:
diff --git a/yamlpath/merger/merger.py b/yamlpath/merger/merger.py
index 2d51d242..ac735acb 100644
--- a/yamlpath/merger/merger.py
+++ b/yamlpath/merger/merger.py
@@ -148,7 +148,7 @@ def _merge_dicts(
         buffer_pos = 0
         for key, val in rhs.non_merged_items():
             path_next = (path +
-                YAMLPath.escape_path_section(key, path.seperator))
+                YAMLPath.escape_path_section(key, path.separator))
             if key in lhs:
                 # Write the buffer if populated
                 for b_key, b_val in buffer:
@@ -476,7 +476,7 @@ def _merge_sets(
         tagless_lhs = Nodes.tagless_elements(list(lhs))
         for ele in rhs:
             path_next = (path +
-                YAMLPath.escape_path_section(ele, path.seperator))
+                YAMLPath.escape_path_section(ele, path.separator))
             self.logger.debug(
                 "Processing set element {} at {}.".format(ele, path_next),
                 prefix="Merger::_merge_sets:  ", data=ele)
diff --git a/yamlpath/processor.py b/yamlpath/processor.py
index fff59c39..f6457a68 100644
--- a/yamlpath/processor.py
+++ b/yamlpath/processor.py
@@ -2,7 +2,7 @@
 """
 YAML Path processor based on ruamel.yaml.
 
-Copyright 2018, 2019, 2020, 2021 William W. Kimball, Jr. MBA MSIS
+Copyright 2018, 2019, 2020, 2021, 2022 William W. Kimball, Jr. MBA MSIS
 """
 from collections import OrderedDict
 from typing import Any, Dict, Generator, List, Union
@@ -26,7 +26,7 @@
     PathSegmentTypes,
     PathSearchKeywords,
     CollectorOperators,
-    PathSeperators,
+    PathSeparators,
 )
 
 
@@ -64,9 +64,9 @@ def get_nodes(
         * default_value (Any) The value to set at yaml_path should
           it not already exist in data and mustexist is False;
           default=None
-        * pathsep (PathSeperators) Forced YAML Path segment seperator; set
+        * pathsep (PathSeparators) Forced YAML Path segment separator; set
           only when automatic inference fails;
-          default = PathSeperators.AUTO
+          default = PathSeparators.AUTO
 
         Returns:  (Generator) The requested YAML nodes as they are matched
 
@@ -75,7 +75,7 @@ def get_nodes(
         """
         mustexist: bool = kwargs.pop("mustexist", False)
         default_value: Any = kwargs.pop("default_value", None)
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
 
         if self.data is None:
             self.logger.debug(
@@ -85,8 +85,8 @@ def get_nodes(
 
         if isinstance(yaml_path, str):
             yaml_path = YAMLPath(yaml_path, pathsep)
-        elif pathsep is not PathSeperators.AUTO:
-            yaml_path.seperator = pathsep
+        elif pathsep is not PathSeparators.AUTO:
+            yaml_path.separator = pathsep
 
         self.logger.debug(
             "Processing YAML Path:",
@@ -135,9 +135,9 @@ def set_value(
         * value_format (YAMLValueFormats) The demarcation or visual
           representation to use when writing the data;
           default=YAMLValueFormats.DEFAULT
-        * pathsep (PathSeperators) Forced YAML Path segment seperator; set
+        * pathsep (PathSeparators) Forced YAML Path segment separator; set
           only when automatic inference fails;
-          default = PathSeperators.AUTO
+          default = PathSeparators.AUTO
         * tag (str) Custom data-type tag to assign
 
         Returns:  N/A
@@ -154,13 +154,13 @@ def set_value(
         mustexist: bool = kwargs.pop("mustexist", False)
         value_format: YAMLValueFormats = kwargs.pop("value_format",
                                                     YAMLValueFormats.DEFAULT)
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
         tag: str = kwargs.pop("tag", None)
 
         if isinstance(yaml_path, str):
             yaml_path = YAMLPath(yaml_path, pathsep)
-        elif pathsep is not PathSeperators.AUTO:
-            yaml_path.seperator = pathsep
+        elif pathsep is not PathSeparators.AUTO:
+            yaml_path.separator = pathsep
 
         if mustexist:
             self.logger.debug(
@@ -316,13 +316,13 @@ def _get_anchor_node(
             - `YAMLPathException` when YAML Path is invalid or a supplied
                anchor_name is illegal
         """
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
         anchor_name: str = kwargs.pop("anchor_name", "")
 
         if isinstance(anchor_path, str):
             anchor_path = YAMLPath(anchor_path, pathsep)
-        elif pathsep is not PathSeperators.AUTO:
-            anchor_path.seperator = pathsep
+        elif pathsep is not PathSeparators.AUTO:
+            anchor_path.separator = pathsep
 
         anchor_node_coordinates: List[NodeCoords] = []
         for node_coords in self._get_required_nodes(self.data, anchor_path):
@@ -370,7 +370,7 @@ def ymk_nodes(
         anchor_path: Union[YAMLPath, str], **kwargs: Any
     ) -> None:
         """Add a YAML Merge Key to YAML Path specified nodes."""
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
         anchor_name: str = kwargs.pop("anchor_name", "")
 
         if self.data is None:
@@ -382,8 +382,8 @@ def ymk_nodes(
 
         if isinstance(yaml_path, str):
             yaml_path = YAMLPath(yaml_path, pathsep)
-        elif pathsep is not PathSeperators.AUTO:
-            yaml_path.seperator = pathsep
+        elif pathsep is not PathSeparators.AUTO:
+            yaml_path.separator = pathsep
 
         anchor_node = self._get_anchor_node(
             anchor_path, pathsep=pathsep, anchor_name=anchor_name)
@@ -404,7 +404,7 @@ def ymk_gathered_nodes(
         **kwargs: Any
     ) -> None:
         """Add a YAML Merge Key to pre-gathered nodes."""
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
         anchor_name: str = kwargs.pop("anchor_name", "")
 
         if self.data is None:
@@ -467,9 +467,9 @@ def alias_nodes(
            Aliases for more than one Anchor.
 
         Keyword Arguments:
-        * pathsep (PathSeperators) Forced YAML Path segment seperator; set
+        * pathsep (PathSeparators) Forced YAML Path segment separator; set
           only when automatic inference fails;
-          default = PathSeperators.AUTO
+          default = PathSeparators.AUTO
         * anchor_name (str) Override the Alias name to any non-empty name you
           set; attempts to re-use an existing Anchor name will result in a
           YAMLPathException.
@@ -479,7 +479,7 @@ def alias_nodes(
         Raises:
             - `YAMLPathException` when YAML Path is invalid
         """
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
         anchor_name: str = kwargs.pop("anchor_name", "")
 
         if self.data is None:
@@ -490,8 +490,8 @@ def alias_nodes(
 
         if isinstance(yaml_path, str):
             yaml_path = YAMLPath(yaml_path, pathsep)
-        elif pathsep is not PathSeperators.AUTO:
-            yaml_path.seperator = pathsep
+        elif pathsep is not PathSeparators.AUTO:
+            yaml_path.separator = pathsep
 
         anchor_node = self._get_anchor_node(
             anchor_path, pathsep=pathsep, anchor_name=anchor_name)
@@ -518,16 +518,16 @@ def alias_gathered_nodes(
         2. anchor_path (Union[YAMLPath, str]) YAML Path to the source Anchor
 
         Keyword Arguments:
-        * pathsep (PathSeperators) Forced YAML Path segment seperator; set
+        * pathsep (PathSeparators) Forced YAML Path segment separator; set
           only when automatic inference fails;
-          default = PathSeperators.AUTO
+          default = PathSeparators.AUTO
         * anchor_name (str) Override automatic anchor name; use this, instead
 
         Returns:  N/A
 
         Raises:  N/A
         """
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
         anchor_name: str = kwargs.pop("anchor_name", "")
 
         if self.data is None:
@@ -574,16 +574,16 @@ def tag_nodes(
         2. tag (str) The tag to assign
 
         Keyword Arguments:
-        * pathsep (PathSeperators) Forced YAML Path segment seperator; set
+        * pathsep (PathSeparators) Forced YAML Path segment separator; set
           only when automatic inference fails;
-          default = PathSeperators.AUTO
+          default = PathSeparators.AUTO
 
         Returns:  N/A
 
         Raises:
         - `YAMLPathException` when YAML Path is invalid
         """
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
 
         if self.data is None:
             self.logger.debug(
@@ -593,8 +593,8 @@ def tag_nodes(
 
         if isinstance(yaml_path, str):
             yaml_path = YAMLPath(yaml_path, pathsep)
-        elif pathsep is not PathSeperators.AUTO:
-            yaml_path.seperator = pathsep
+        elif pathsep is not PathSeparators.AUTO:
+            yaml_path.separator = pathsep
 
         gathered_nodes: List[NodeCoords] = []
         for node_coords in self._get_required_nodes(self.data, yaml_path):
@@ -644,16 +644,16 @@ def delete_nodes(
         1. yaml_path (Union[YAMLPath, str]) The YAML Path to evaluate
 
         Keyword Arguments:
-        * pathsep (PathSeperators) Forced YAML Path segment seperator; set
+        * pathsep (PathSeparators) Forced YAML Path segment separator; set
           only when automatic inference fails;
-          default = PathSeperators.AUTO
+          default = PathSeparators.AUTO
 
         Returns:  (Generator) Affected NodeCoords before they are deleted
 
         Raises:
             - `YAMLPathException` when YAML Path is invalid
         """
-        pathsep: PathSeperators = kwargs.pop("pathsep", PathSeperators.AUTO)
+        pathsep: PathSeparators = kwargs.pop("pathsep", PathSeparators.AUTO)
 
         if self.data is None:
             self.logger.debug(
@@ -663,8 +663,8 @@ def delete_nodes(
 
         if isinstance(yaml_path, str):
             yaml_path = YAMLPath(yaml_path, pathsep)
-        elif pathsep is not PathSeperators.AUTO:
-            yaml_path.seperator = pathsep
+        elif pathsep is not PathSeparators.AUTO:
+            yaml_path.separator = pathsep
 
         # Nodes must be processed in reverse order while deleting them to avoid
         # corrupting list element indecies, thereby deleting the wrong nodes.
@@ -931,7 +931,7 @@ def _get_nodes_by_key(
         if isinstance(data, dict):
             next_translated_path = (translated_path +
                 YAMLPath.escape_path_section(
-                    str_stripped, translated_path.seperator))
+                    str_stripped, translated_path.separator))
             next_ancestry = ancestry + [(data, stripped_attrs)]
             if stripped_attrs in data:
                 self.logger.debug(
@@ -1000,7 +1000,7 @@ def _get_nodes_by_key(
                         ).format(str_stripped))
                     next_translated_path = (translated_path +
                         YAMLPath.escape_path_section(
-                            ele_val, translated_path.seperator))
+                            ele_val, translated_path.separator))
                     next_ancestry = ancestry + [(data, ele)]
                     yield NodeCoords(
                         ele, data, stripped_attrs,
@@ -1086,7 +1086,7 @@ def _get_nodes_by_index(
                         yield NodeCoords(
                             val, data, key,
                             translated_path + YAMLPath.escape_path_section(
-                                key, translated_path.seperator),
+                                key, translated_path.separator),
                             ancestry + [(data, key)], pathseg)
 
             elif isinstance(data, (CommentedSet, set)):
@@ -1095,7 +1095,7 @@ def _get_nodes_by_index(
                         yield NodeCoords(
                             ele, data, ele,
                             translated_path + YAMLPath.escape_path_section(
-                                ele, translated_path.seperator),
+                                ele, translated_path.separator),
                             ancestry + [(data, ele)], pathseg)
         else:
             try:
@@ -1155,7 +1155,7 @@ def _get_nodes_by_anchor(
         (_, stripped_attrs) = pathseg
         next_translated_path = translated_path + "[&{}]".format(
             YAMLPath.escape_path_section(
-                str(stripped_attrs), translated_path.seperator))
+                str(stripped_attrs), translated_path.separator))
 
         self.logger.debug(
             "Processor::_get_nodes_by_anchor:  Seeking ANCHOR node at {}."
@@ -1365,7 +1365,7 @@ def _get_nodes_by_search(
                         yield NodeCoords(
                             val, data, key,
                             translated_path + YAMLPath.escape_path_section(
-                                key, translated_path.seperator),
+                                key, translated_path.separator),
                             ancestry + [(data, key)], pathseg)
 
             elif attr in data:
@@ -1385,7 +1385,7 @@ def _get_nodes_by_search(
                     yield NodeCoords(
                         value, data, attr,
                         translated_path + YAMLPath.escape_path_section(
-                            attr, translated_path.seperator),
+                            attr, translated_path.separator),
                         ancestry + [(data, attr)], pathseg)
 
             else:
@@ -1439,7 +1439,7 @@ def _get_nodes_by_search(
                     yield NodeCoords(
                         ele, data, ele,
                         translated_path + YAMLPath.escape_path_section(
-                            ele, translated_path.seperator),
+                            ele, translated_path.separator),
                         ancestry + [(data, ele)], pathseg)
 
         else:
@@ -1502,7 +1502,7 @@ def _collector_addition(
         return updated_coords
 
     def _collector_subtraction(
-        self, data: Any, peek_path: YAMLPath, collected_ncs: List[NodeCoords],
+        self, data: Any, peek_path: YAMLPath, lhs_ncs: List[NodeCoords],
         **kwargs
     ) -> List[NodeCoords]:
         """List nodes matching the given path of a Subtraction Collector.
@@ -1557,14 +1557,14 @@ def get_del_nodes(
             prefix="Processor::_collector_subtraction:  REMOVAL NODES->",
             data={
                 "REMOVING": rem_data,
-                "FROM": collected_ncs,
+                "FROM": lhs_ncs,
             })
 
         # If LHS in RHS, delete it
         rem_dels = []
         rem_idx = 0
         updated_coords: List[NodeCoords] = []
-        for lhs in collected_ncs:
+        for lhs in lhs_ncs:
             unwrapped_lhs = lhs.unwrapped_node
             deepest_lhs = lhs.deepest_node_coord
             append_node = True
@@ -1603,6 +1603,49 @@ def get_del_nodes(
         )
         return updated_coords
 
+    def _collector_intersection(
+        self, data: Any, peek_path: YAMLPath, lhs_ncs: List[NodeCoords],
+        **kwargs
+    ) -> List[NodeCoords]:
+        """
+        Calculate the intersection between two collections.
+
+        Helper for _get_nodes_by_collector.
+        """
+        def deeply_unwrap_nodes(
+            del_nodes: List[Any], node_coord: NodeCoords
+        ) -> None:
+            unwrapped_node = NodeCoords.unwrap_node_coords(node_coord)
+            if isinstance(unwrapped_node, (list, set)):
+                for ele in unwrapped_node:
+                    del_nodes.append(ele)
+            else:
+                del_nodes.append(unwrapped_node)
+
+
+        parent: Any = kwargs.pop("parent", None)
+        parentref: Any = kwargs.pop("parentref", None)
+        translated_path: YAMLPath = kwargs.pop("translated_path", YAMLPath(""))
+        ancestry: List[AncestryEntry] = kwargs.pop("ancestry", [])
+        relay_segment: PathSegment = kwargs.pop("relay_segment")
+        rhs_unwrapped_data: List[Any] = []
+        for node_coord in self._get_required_nodes(
+            data, peek_path, 0, parent=parent, parentref=parentref,
+            translated_path=translated_path, ancestry=ancestry,
+            relay_segment=relay_segment
+        ):
+            self.logger.debug(
+                "Extracting node(s) for intersection from collected result:"
+                , prefix="Processor::_collector_intersection:  "
+                , data=node_coord)
+            deeply_unwrap_nodes(rhs_unwrapped_data, node_coord)
+
+        updated_coords = [
+            nc for nc in lhs_ncs
+            if NodeCoords.unwrap_node_coords(nc) in rhs_unwrapped_data]
+
+        return updated_coords
+
     def _get_nodes_by_collector(
         self, data: Any, yaml_path: YAMLPath, segment_index: int,
         terms: CollectorTerms, **kwargs: Any
@@ -1634,7 +1677,7 @@ def _get_nodes_by_collector(
 
         Raises:  N/A
         """
-        if not terms.operation is CollectorOperators.NONE:
+        if terms.operation is not CollectorOperators.NONE:
             self.logger.debug((
                 "Processor::_get_nodes_by_collector:  Bailing out -- yielding"
                 " the input data -- because the operation is {}"
@@ -1689,8 +1732,8 @@ def _get_nodes_by_collector(
                         node_coord.path, node_coord.ancestry, pathseg))
             node_coords = flat_nodes
 
-        # As long as each next segment is an ADDITION or SUBTRACTION
-        # COLLECTOR, keep combining the results.
+        # As long as each next segment is an ADDITION, SUBTRACTION, or
+        # INTERSECTION COLLECTOR, keep combining the results.
         while next_segment_idx < len(segments):
             peekseg: PathSegment = segments[next_segment_idx]
             (peek_type, peek_attrs) = peekseg
@@ -1711,6 +1754,13 @@ def _get_nodes_by_collector(
                         parent=parent, parentref=parentref,
                         translated_path=translated_path, ancestry=ancestry,
                         relay_segment=peekseg)
+                elif peek_attrs.operation == CollectorOperators.INTERSECTION:
+                    node_coords = self._collector_intersection(
+                        data, peek_path, node_coords,
+                        parent=parent, parentref=parentref,
+                        translated_path=translated_path, ancestry=ancestry,
+                        relay_segment=peekseg,
+                        predecessor_segment=pathseg)
                 else:
                     raise YAMLPathException(
                         "Adjoining Collectors without an operator has no"
@@ -1783,7 +1833,7 @@ def _get_nodes_by_traversal(
                 for key, val in data.items():
                     next_translated_path = (
                         translated_path + YAMLPath.escape_path_section(
-                            key, translated_path.seperator))
+                            key, translated_path.separator))
                     next_ancestry = ancestry + [(data, key)]
                     for node_coord in self._get_nodes_by_traversal(
                         val, yaml_path, segment_index,
@@ -1814,7 +1864,7 @@ def _get_nodes_by_traversal(
                 for ele in data:
                     next_translated_path = (
                         translated_path + YAMLPath.escape_path_section(
-                            ele, translated_path.seperator))
+                            ele, translated_path.separator))
                     self.logger.debug(
                         "Yielding unfiltered Set value:",
                         prefix="Processor::_get_nodes_by_traversal:  ",
@@ -1869,7 +1919,7 @@ def _get_nodes_by_traversal(
                         .format(key, parentref))
                     next_translated_path = (
                         translated_path + YAMLPath.escape_path_section(
-                            key, translated_path.seperator))
+                            key, translated_path.separator))
                     next_ancestry = ancestry + [(data, key)]
                     for node_coord in self._get_nodes_by_traversal(
                         val, yaml_path, segment_index,
@@ -1947,7 +1997,7 @@ def _get_nodes_by_match_all_unfiltered(
             for key, val in data.items():
                 next_translated_path = (
                     translated_path + YAMLPath.escape_path_section(
-                        key, translated_path.seperator))
+                        key, translated_path.separator))
                 next_ancestry = ancestry + [(data, key)]
                 self.logger.debug(
                     f"Yielding dict value at key, {key} from data:",
@@ -1971,7 +2021,7 @@ def _get_nodes_by_match_all_unfiltered(
             for ele in data:
                 next_translated_path = (
                     translated_path + YAMLPath.escape_path_section(
-                        ele, translated_path.seperator))
+                        ele, translated_path.separator))
                 self.logger.debug(
                     "Yielding set element:",
                     prefix=dbg_prefix, data=ele)
@@ -2031,7 +2081,7 @@ def _get_nodes_by_match_all_filtered(
             for key, val in data.items():
                 next_translated_path = (
                     translated_path + YAMLPath.escape_path_section(
-                        key, translated_path.seperator))
+                        key, translated_path.separator))
                 next_ancestry = ancestry + [(data, key)]
                 for filtered_nc in self._get_nodes_by_path_segment(
                     val, yaml_path, next_segment_idx, parent=data,
@@ -2362,10 +2412,14 @@ def _get_optional_nodes(
                 ):
                     yield node_coord
 
+            # Do not add arbitrary nodes that are based on searched or
+            # wildcards.  Such an outcome is unpredictable.
             if (
                     matched_nodes < 1
                     and segment_type is not PathSegmentTypes.SEARCH
                     and segment_type is not PathSegmentTypes.KEYWORD_SEARCH
+                    and segment_type is not PathSegmentTypes.MATCH_ALL
+                    and segment_type is not PathSegmentTypes.TRAVERSE
             ):
                 # Add the missing element
                 self.logger.debug(
@@ -2461,7 +2515,7 @@ def _get_optional_nodes(
                         next_translated_path = (
                             translated_path + YAMLPath.escape_path_section(
                                 str(stripped_attrs),
-                                translated_path.seperator))
+                                translated_path.separator))
                         next_ancestry = ancestry + [(data, stripped_attrs)]
                         for node_coord in self._get_optional_nodes(
                                 data[stripped_attrs], yaml_path, value,
diff --git a/yamlpath/yamlpath.py b/yamlpath/yamlpath.py
index 132e8e1d..99593bc1 100644
--- a/yamlpath/yamlpath.py
+++ b/yamlpath/yamlpath.py
@@ -1,3 +1,4 @@
+#pylint: disable=too-many-lines
 """
 Implement YAML Path.
 
@@ -12,7 +13,7 @@
     PathSegmentTypes,
     PathSearchKeywords,
     PathSearchMethods,
-    PathSeperators,
+    PathSeparators,
     CollectorOperators,
 )
 from yamlpath.path import SearchKeywordTerms, SearchTerms, CollectorTerms
@@ -24,7 +25,7 @@ class YAMLPath:
 
     This will keep track of:
       * the original, unparsed, and unmodified YAML Path;
-      * its segment seperator (inferred or manually specified);
+      * its segment separator (inferred or manually specified);
       * the unescaped, parsed representation of the YAML Path; and
       * the escaped, parsed representation of the YAML Path.
 
@@ -33,21 +34,21 @@ class YAMLPath:
     """
 
     def __init__(self, yaml_path: Union["YAMLPath", str, None] = "",
-                 pathsep: PathSeperators = PathSeperators.AUTO) -> None:
+                 pathsep: PathSeparators = PathSeparators.AUTO) -> None:
         """
         Instantiate this class into an object.
 
         Parameters:
         1. yaml_path (Union["YAMLPath", str, None]) The YAML Path to parse or
            copy
-        2. pathsep (PathSeperators) Forced YAML Path segment separator; set
+        2. pathsep (PathSeparators) Forced YAML Path segment separator; set
            only when automatic inference fails
 
         Returns:  N/A
 
         Raises:  N/A
         """
-        self._seperator: PathSeperators = pathsep
+        self._separator: PathSeparators = pathsep
         self._original: str = ""
         self._unescaped: deque = deque()
         self._escaped: deque = deque()
@@ -64,13 +65,13 @@ def __str__(self) -> str:
             return self._stringified
 
         self._stringified = YAMLPath._stringify_yamlpath_segments(
-            self.unescaped, self.seperator)
+            self.unescaped, self.separator)
         return self._stringified
 
     def __repr__(self) -> str:
         """Generate an eval()-safe representation of this object."""
         return ("{}('{}', '{}')".format(self.__class__.__name__,
-                                        self.original, self.seperator))
+                                        self.original, self.separator))
 
     def __len__(self) -> int:
         """Indicate how many segments comprise this YAML Path."""
@@ -80,7 +81,7 @@ def __eq__(self, other: object) -> bool:
         """
         Indicate equivalence of two YAMLPaths.
 
-        The path seperator is ignored for this comparison.  This is deliberate
+        The path separator is ignored for this comparison.  This is deliberate
         and allows "some.path[1]" == "/some/path[1]" because both forms of the
         same path yield exactly the same data.
 
@@ -93,11 +94,11 @@ def __eq__(self, other: object) -> bool:
             return False
 
         equiv_this = YAMLPath(self)
-        equiv_this.seperator = PathSeperators.FSLASH
+        equiv_this.separator = PathSeparators.FSLASH
         cmp_this = str(equiv_this)
 
         equiv_that = YAMLPath(other)
-        equiv_that.seperator = PathSeperators.FSLASH
+        equiv_that.separator = PathSeparators.FSLASH
         cmp_that = str(equiv_that)
 
         return cmp_this == cmp_that
@@ -117,19 +118,19 @@ def append(self, segment: str) -> "YAMLPath":
 
         Parameters:
         1. segment (str) The new -- pre-escaped -- segment to append to this
-           YAML Path.  Do NOT include any seperator with this value; it will be
+           YAML Path.  Do NOT include any separator with this value; it will be
            added for you.
 
         Returns:  (YAMLPath) The adjusted YAMLPath
         """
-        seperator = (
-            PathSeperators.FSLASH
-            if self.seperator is PathSeperators.AUTO
-            else self.seperator)
+        separator = (
+            PathSeparators.FSLASH
+            if self.separator is PathSeparators.AUTO
+            else self.separator)
         if len(self._original) < 1:
             self.original = segment
         else:
-            self.original += "{}{}".format(seperator, segment)
+            self.original += "{}{}".format(separator, segment)
         return self
 
     def pop(self) -> PathSegment:
@@ -150,8 +151,8 @@ def pop(self) -> PathSegment:
         popped_segment: PathSegment = segments.pop()
         popped_queue.append(popped_segment)
         removable_segment = YAMLPath._stringify_yamlpath_segments(
-            popped_queue, self.seperator)
-        prefixed_segment = "{}{}".format(self.seperator, removable_segment)
+            popped_queue, self.separator)
+        prefixed_segment = "{}{}".format(self.separator, removable_segment)
         path_now = self.original
 
         if path_now.endswith(prefixed_segment):
@@ -159,7 +160,7 @@ def pop(self) -> PathSegment:
         elif path_now.endswith(removable_segment):
             self.original = path_now[0:len(path_now) - len(removable_segment)]
         elif (
-            self.seperator == PathSeperators.FSLASH
+            self.separator == PathSeparators.FSLASH
             and path_now.endswith(removable_segment[1:])
         ):
             self.original = path_now[
@@ -204,49 +205,85 @@ def original(self, value: str) -> None:
             str_val = ""
 
         self._original = str_val
-        self._seperator = PathSeperators.AUTO
+        self._separator = PathSeparators.AUTO
         self._unescaped = deque()
         self._escaped = deque()
         self._stringified = ""
 
     @property
-    def seperator(self) -> PathSeperators:
+    def separator(self) -> PathSeparators:
         """
-        Get the seperator used to demarcate YAML Path segments.
+        Get the separator used to demarcate YAML Path segments.
 
         Parameters:  N/A
 
-        Returns:  (PathSeperators) The segment demarcation symbol
+        Returns:  (PathSeparators) The segment demarcation symbol
 
         Raises:  N/A
         """
-        if self._seperator is PathSeperators.AUTO:
-            self._seperator = PathSeperators.infer_seperator(self._original)
+        if self._separator is PathSeparators.AUTO:
+            self._separator = PathSeparators.infer_separator(self._original)
 
-        return self._seperator
+        return self._separator
 
-    @seperator.setter
-    def seperator(self, value: PathSeperators) -> None:
+    @separator.setter
+    def separator(self, value: PathSeparators) -> None:
         """
-        Set the seperator used to demarcate YAML Path segments.
+        Set the separator used to demarcate YAML Path segments.
 
         This only affects __str__ and only when the new value differs from the
-        seperator already inferred from the original YAML Path.
+        separator already inferred from the original YAML Path.
 
         Parameters:
-        1. value (PathSeperators) The segment demarcation symbol
+        1. value (PathSeparators) The segment demarcation symbol
 
         Returns:  N/A
 
         Raises:  N/A
         """
-        old_value: PathSeperators = self._seperator
+        old_value: PathSeparators = self._separator
 
         # This changes only the stringified representation
         if not value == old_value:
             self._stringified = YAMLPath._stringify_yamlpath_segments(
                 self.unescaped, value)
-            self._seperator = value
+            self._separator = value
+
+    @property
+    def seperator(self) -> PathSeparators:
+        """
+        Get the separator used to demarcate YAML Path segments.
+
+        This is provided for compatibility with older versions,
+        before the spelling was updated to "separator."
+
+        Parameters:  N/A
+
+        Returns:  (PathSeparators) The segment demarcation symbol
+
+        Raises:  N/A
+        """
+        return self.separator
+
+    @seperator.setter
+    def seperator(self, value: PathSeparators) -> None:
+        """
+        Set the separator used to demarcate YAML Path segments.
+
+        This only affects __str__ and only when the new value differs from the
+        separator already inferred from the original YAML Path.
+
+        This is provided for compatibility with older versions,
+        before the spelling was updated to "separator."
+
+        Parameters:
+        1. value (PathSeparators) The segment demarcation symbol
+
+        Returns:  N/A
+
+        Raises:  N/A
+        """
+        self.separator = value
 
     @property
     def escaped(self) -> Deque[PathSegment]:
@@ -319,7 +356,7 @@ def _parse_path(self,
         search_keyword: Optional[PathSearchKeywords] = None
         seeking_regex_delim: bool = False
         capturing_regex: bool = False
-        pathsep: str = str(self.seperator)
+        pathsep: str = str(self.separator)
         collector_level: int = 0
         collector_operator: CollectorOperators = CollectorOperators.NONE
         seeking_collector_operator: bool = False
@@ -331,7 +368,7 @@ def _parse_path(self,
 
         # Infer the first possible position for a top-level Anchor mark
         first_anchor_pos = 0
-        if self.seperator is PathSeperators.FSLASH and len(yaml_path) > 1:
+        if self.separator is PathSeparators.FSLASH and len(yaml_path) > 1:
             first_anchor_pos = 1
         seeking_anchor_mark = yaml_path[first_anchor_pos] == "&"
 
@@ -390,13 +427,15 @@ def _parse_path(self,
                 segment_type = PathSegmentTypes.ANCHOR
                 continue
 
-            elif seeking_collector_operator and char in ['+', '-']:
+            elif seeking_collector_operator and char in ['+', '-', '&']:
                 seeking_collector_operator = False
                 next_char_must_be = '('
                 if char == '+':
                     collector_operator = CollectorOperators.ADDITION
                 elif char == '-':
                     collector_operator = CollectorOperators.SUBTRACTION
+                elif char == '&':
+                    collector_operator = CollectorOperators.INTERSECTION
                 continue
 
             elif next_char_must_be and char != next_char_must_be:
@@ -851,15 +890,15 @@ def _expand_splats(
 
     @staticmethod
     def _stringify_yamlpath_segments(
-        segments: Deque[PathSegment], seperator: PathSeperators
+        segments: Deque[PathSegment], separator: PathSeparators
     ) -> str:
         """Stringify segments of a YAMLPath."""
-        pathsep: str = str(seperator)
+        pathsep: str = str(separator)
         add_sep: bool = False
         ppath: str = ""
 
-        # FSLASH seperator requires a path starting with a /
-        if seperator is PathSeperators.FSLASH:
+        # FSLASH separator requires a path starting with a /
+        if separator is PathSeparators.FSLASH:
             ppath = pathsep
 
         for (segment_type, segment_attrs) in segments:
@@ -915,11 +954,11 @@ def strip_path_prefix(path: "YAMLPath", prefix: "YAMLPath") -> "YAMLPath":
         if prefix is None:
             return path
 
-        prefix.seperator = PathSeperators.FSLASH
+        prefix.separator = PathSeparators.FSLASH
         if str(prefix) == "/":
             return path
 
-        path.seperator = PathSeperators.FSLASH
+        path.separator = PathSeparators.FSLASH
         prefix_str = str(prefix)
         path_str = str(path)
         if path_str.startswith(prefix_str):
@@ -953,7 +992,7 @@ def ensure_escaped(value: str, *symbols: str) -> str:
         return escaped
 
     @staticmethod
-    def escape_path_section(section: str, pathsep: PathSeperators) -> str:
+    def escape_path_section(section: str, pathsep: PathSeparators) -> str:
         """
         Escape all special symbols present within a YAML Path segment.
 
@@ -963,7 +1002,7 @@ def escape_path_section(section: str, pathsep: PathSeperators) -> str:
 
         Parameters:
         1. section (str) The portion of a YAML Path segment to escape
-        2. pathsep (PathSeperators) The YAML Path segment seperator symbol to
+        2. pathsep (PathSeparators) The YAML Path segment separator symbol to
            also escape, when present
 
         Returns:  (str) `section` with all special symbols escaped


Test patch:
diff --git a/.github/workflows/python-publish-to-test-pypi.yml b/.github/workflows/python-publish-to-test-pypi.yml
index 8305e582..608435ec 100644
--- a/.github/workflows/python-publish-to-test-pypi.yml
+++ b/.github/workflows/python-publish-to-test-pypi.yml
@@ -13,10 +13,10 @@ on:
 jobs:
   validate:
     name: Code Quality Assessment
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04 # Use ubuntu-latest when https://github.com/actions/setup-python/issues/544 is fixed
     strategy:
       matrix:
-        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
+        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
 
     steps:
     - uses: actions/checkout@v2
diff --git a/tests/test_commands_yaml_paths.py b/tests/test_commands_yaml_paths.py
index c83b68a9..d56798a9 100644
--- a/tests/test_commands_yaml_paths.py
+++ b/tests/test_commands_yaml_paths.py
@@ -2,7 +2,7 @@
 
 from tests.conftest import create_temp_yaml_file
 
-from yamlpath.enums import PathSeperators
+from yamlpath.enums import PathSeparators
 
 class Test_yaml_paths():
     """Tests the yaml-paths command-line interface."""
@@ -888,7 +888,7 @@ def test_expand_sequence_parents(self, script_runner, tmp_path_factory):
         ]) + "\n" == result.stdout
 
     def test_yield_seq_children_direct(self, tmp_path_factory, quiet_logger):
-        from yamlpath.enums import PathSeperators, PathSearchMethods
+        from yamlpath.enums import PathSeparators, PathSearchMethods
         from yamlpath.path import SearchTerms
         from yamlpath.func import get_yaml_data, get_yaml_editor
         from yamlpath.commands.yaml_paths import yield_children
@@ -908,7 +908,7 @@ def test_yield_seq_children_direct(self, tmp_path_factory, quiet_logger):
         for assertion, path in zip_longest(assertions, yield_children(
             quiet_logger, yaml_data,
             SearchTerms(False, PathSearchMethods.EQUALS, "*", "value"),
-            PathSeperators.FSLASH, "", seen_anchors, search_anchors=True,
+            PathSeparators.FSLASH, "", seen_anchors, search_anchors=True,
             include_aliases=False
         )):
             assert assertion == str(path)
@@ -918,7 +918,7 @@ def test_yield_seq_children_direct(self, tmp_path_factory, quiet_logger):
         (True, ["/aliases[&aValue]", "/hash/key1", "/hash/key2", "/hash/key3"]),
     ])
     def test_yield_map_children_direct(self, tmp_path_factory, quiet_logger, include_aliases, assertions):
-        from yamlpath.enums import PathSeperators, PathSearchMethods
+        from yamlpath.enums import PathSeparators, PathSearchMethods
         from yamlpath.path import SearchTerms
         from yamlpath.func import get_yaml_data, get_yaml_editor
         from yamlpath.commands.yaml_paths import yield_children
@@ -941,13 +941,13 @@ def test_yield_map_children_direct(self, tmp_path_factory, quiet_logger, include
         for assertion, path in zip_longest(assertions, yield_children(
             quiet_logger, yaml_data,
             SearchTerms(False, PathSearchMethods.EQUALS, "*", "anchor"),
-            PathSeperators.FSLASH, "", seen_anchors, search_anchors=True,
+            PathSeparators.FSLASH, "", seen_anchors, search_anchors=True,
             include_value_aliases=include_aliases
         )):
             assert assertion == str(path)
 
     def test_yield_raw_children_direct(self, tmp_path_factory, quiet_logger):
-        from yamlpath.enums import PathSeperators, PathSearchMethods
+        from yamlpath.enums import PathSeparators, PathSearchMethods
         from yamlpath.path import SearchTerms
         from yamlpath.func import get_yaml_data, get_yaml_editor
         from yamlpath.commands.yaml_paths import yield_children
@@ -964,7 +964,7 @@ def test_yield_raw_children_direct(self, tmp_path_factory, quiet_logger):
         for assertion, path in zip_longest(assertions, yield_children(
             quiet_logger, yaml_data,
             SearchTerms(False, PathSearchMethods.STARTS_WITH, "*", "some"),
-            PathSeperators.FSLASH, "", seen_anchors, search_anchors=False,
+            PathSeparators.FSLASH, "", seen_anchors, search_anchors=False,
             include_key_aliases=False, include_value_aliases=False
         )):
             assert assertion == str(path)
@@ -1041,7 +1041,7 @@ def test_too_many_stdins(self, script_runner):
         assert "Only one YAML_FILE may be the - pseudo-file" in result.stderr
 
     @pytest.mark.parametrize("pathsep,output", [
-        (PathSeperators.AUTO, [
+        (PathSeparators.AUTO, [
             'foo.x: 12',
             'foo.y: hello world',
             "foo.ip_range['initial']: 1.2.3.4",
@@ -1049,7 +1049,7 @@ def test_too_many_stdins(self, script_runner):
             "foo.array['first']: Cluster1",
             'array2[]: bar',
         ]),
-        (PathSeperators.DOT, [
+        (PathSeparators.DOT, [
             'foo.x: 12',
             'foo.y: hello world',
             "foo.ip_range['initial']: 1.2.3.4",
@@ -1057,7 +1057,7 @@ def test_too_many_stdins(self, script_runner):
             "foo.array['first']: Cluster1",
             'array2[]: bar',
         ]),
-        (PathSeperators.FSLASH, [
+        (PathSeparators.FSLASH, [
             '/foo/x: 12',
             '/foo/y: hello world',
             "/foo/ip_range['initial']: 1.2.3.4",
diff --git a/tests/test_common_keywordsearches.py b/tests/test_common_keywordsearches.py
index 34e9bea8..526abcf4 100644
--- a/tests/test_common_keywordsearches.py
+++ b/tests/test_common_keywordsearches.py
@@ -219,3 +219,121 @@ def test_parent_invalid_step_count(self):
                 YAMLPath("/")
             ))
         assert -1 < str(ex.value).find("higher than the document root")
+
+
+    ###
+    # distinct
+    ###
+    def test_distinct_invalid_param_count(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.distinct(
+                {},
+                False,
+                ["1", "2"],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("Invalid parameter count to ")
+
+    def test_distinct_invalid_inversion(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.distinct(
+                {},
+                True,
+                [],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("Inversion is meaningless to ")
+
+    def test_distinct_missing_aoh_param(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.distinct(
+                [{'a': 1},{'a': 2}],
+                False,
+                [],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("when evaluating an Array-of-Hashes")
+
+    def test_distinct_missing_hash_param(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.distinct(
+                {'a': {'b': 1}, 'c': {'d': 2}},
+                False,
+                [],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("when evaluating Hash (map/dict) children")
+
+    def test_distinct_invalid_array_param(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.distinct(
+                [1, 2, 3],
+                False,
+                ['3'],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("when comparing Array (sequence/list) elements to one another")
+
+    def test_distinct_incorrect_node(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.distinct(
+                {'b': 2},
+                False,
+                ['b'],
+                YAMLPath("/*[max(b)]")
+            ))
+        assert -1 < str(ex.value).find("operates against collections of data")
+
+
+    ###
+    # unique
+    ###
+    def test_unique_invalid_param_count(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.unique(
+                {},
+                False,
+                ["1", "2"],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("Invalid parameter count to ")
+
+    def test_unique_missing_aoh_param(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.unique(
+                [{'a': 1},{'a': 2}],
+                False,
+                [],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("when evaluating an Array-of-Hashes")
+
+    def test_unique_missing_hash_param(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.unique(
+                {'a': {'b': 1}, 'c': {'d': 2}},
+                False,
+                [],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("when evaluating Hash (map/dict) children")
+
+    def test_unique_invalid_array_param(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.unique(
+                [1, 2, 3],
+                False,
+                ['3'],
+                YAMLPath("/")
+            ))
+        assert -1 < str(ex.value).find("when comparing Array (sequence/list) elements to one another")
+
+    def test_unique_incorrect_node(self):
+        with pytest.raises(YAMLPathException) as ex:
+            nodes = list(KeywordSearches.unique(
+                {'b': 2},
+                False,
+                ['b'],
+                YAMLPath("/*[max(b)]")
+            ))
+        assert -1 < str(ex.value).find("operates against collections of data")
diff --git a/tests/test_enums_collectoroperators.py b/tests/test_enums_collectoroperators.py
index 2c472e5d..bb212c3c 100644
--- a/tests/test_enums_collectoroperators.py
+++ b/tests/test_enums_collectoroperators.py
@@ -10,12 +10,14 @@ def test_get_names(self):
 			"ADDITION",
 			"NONE",
 			"SUBTRACTION",
+			"INTERSECTION",
 		]
 
 	@pytest.mark.parametrize("input,output", [
 		(CollectorOperators.ADDITION, "+"),
 		(CollectorOperators.NONE, ""),
 		(CollectorOperators.SUBTRACTION, "-"),
+		(CollectorOperators.INTERSECTION, "&"),
 	])
 	def test_str(self, input, output):
 		assert output == str(input)
@@ -23,11 +25,14 @@ def test_str(self, input, output):
 	@pytest.mark.parametrize("input,output", [
 		("+", CollectorOperators.ADDITION),
 		("-", CollectorOperators.SUBTRACTION),
+		("&", CollectorOperators.INTERSECTION),
 		("ADDITION", CollectorOperators.ADDITION),
 		("NONE", CollectorOperators.NONE),
 		("SUBTRACTION", CollectorOperators.SUBTRACTION),
+		("INTERSECTION", CollectorOperators.INTERSECTION),
 		(CollectorOperators.ADDITION, CollectorOperators.ADDITION),
 		(CollectorOperators.SUBTRACTION, CollectorOperators.SUBTRACTION),
+		(CollectorOperators.INTERSECTION, CollectorOperators.INTERSECTION),
 		(CollectorOperators.NONE, CollectorOperators.NONE),
 	])
 	def test_from_operator(self, input, output):
diff --git a/tests/test_enums_pathseparators.py b/tests/test_enums_pathseparators.py
new file mode 100644
index 00000000..fc2e7957
--- /dev/null
+++ b/tests/test_enums_pathseparators.py
@@ -0,0 +1,53 @@
+import pytest
+
+from yamlpath.enums import PathSeparators
+
+# Legacy spelling compatibility:
+from yamlpath.enums import PathSeperators
+
+
+class Test_enums_PathSeparators():
+	"""Tests for the PathSeparators enumeration."""
+	@pytest.mark.parametrize("pathsep_module", [PathSeparators, PathSeperators])
+	def test_get_names(self, pathsep_module):
+		assert pathsep_module.get_names() == [
+			"AUTO",
+			"DOT",
+			"FSLASH",
+		]
+
+	@pytest.mark.parametrize("input,output", [
+		(PathSeparators.AUTO, '.'),
+		(PathSeparators.DOT, '.'),
+		(PathSeparators.FSLASH, '/'),
+	])
+	def test_str(self, input, output):
+		assert output == str(input)
+
+	@pytest.mark.parametrize("input,output", [
+		(".", PathSeparators.DOT),
+		("/", PathSeparators.FSLASH),
+		("DOT", PathSeparators.DOT),
+		("FSLASH", PathSeparators.FSLASH),
+		(PathSeparators.DOT, PathSeparators.DOT),
+		(PathSeparators.FSLASH, PathSeparators.FSLASH),
+	])
+	@pytest.mark.parametrize("pathsep_module", [PathSeparators, PathSeperators])
+	def test_from_str(self, input, output, pathsep_module):
+		assert output == pathsep_module.from_str(input)
+
+	@pytest.mark.parametrize("pathsep_module", [PathSeparators, PathSeperators])
+	def test_from_str_nameerror(self, pathsep_module):
+		with pytest.raises(NameError):
+			pathsep_module.from_str("NO SUCH NAME")
+
+	@pytest.mark.parametrize("input,output", [
+		("abc", PathSeparators.DOT),
+		("abc.123", PathSeparators.DOT),
+		("/abc", PathSeparators.FSLASH),
+		("/abc/123", PathSeparators.FSLASH),
+	])
+	@pytest.mark.parametrize("pathsep_module", [PathSeparators, PathSeperators])
+	@pytest.mark.parametrize("func_name", ["infer_separator", "infer_seperator"])
+	def test_infer_separator(self, input, output, pathsep_module, func_name):
+		assert output == getattr(pathsep_module, func_name)(input)
diff --git a/tests/test_enums_pathseperators.py b/tests/test_enums_pathseperators.py
deleted file mode 100644
index 8dfc227d..00000000
--- a/tests/test_enums_pathseperators.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import pytest
-
-from yamlpath.enums import PathSeperators
-
-
-class Test_enums_PathSeperators():
-	"""Tests for the PathSeperators enumeration."""
-	def test_get_names(self):
-		assert PathSeperators.get_names() == [
-			"AUTO",
-			"DOT",
-			"FSLASH",
-		]
-
-	@pytest.mark.parametrize("input,output", [
-		(PathSeperators.AUTO, '.'),
-		(PathSeperators.DOT, '.'),
-		(PathSeperators.FSLASH, '/'),
-	])
-	def test_str(self, input, output):
-		assert output == str(input)
-
-	@pytest.mark.parametrize("input,output", [
-		(".", PathSeperators.DOT),
-		("/", PathSeperators.FSLASH),
-		("DOT", PathSeperators.DOT),
-		("FSLASH", PathSeperators.FSLASH),
-		(PathSeperators.DOT, PathSeperators.DOT),
-		(PathSeperators.FSLASH, PathSeperators.FSLASH),
-	])
-	def test_from_str(self, input, output):
-		assert output == PathSeperators.from_str(input)
-
-	def test_from_str_nameerror(self):
-		with pytest.raises(NameError):
-			PathSeperators.from_str("NO SUCH NAME")
-
-	@pytest.mark.parametrize("input,output", [
-		("abc", PathSeperators.DOT),
-		("abc.123", PathSeperators.DOT),
-		("/abc", PathSeperators.FSLASH),
-		("/abc/123", PathSeperators.FSLASH),
-	])
-	def test_infer_seperator(self, input, output):
-		assert output == PathSeperators.infer_seperator(input)
diff --git a/tests/test_func.py b/tests/test_func.py
index 1fb1c3c7..d43eadaf 100644
--- a/tests/test_func.py
+++ b/tests/test_func.py
@@ -362,6 +362,14 @@ def test_ensure_escaped(self):
     # escape_path_section
     ###
     def test_escape_path_section(self):
+        from yamlpath.enums.pathseparators import PathSeparators
+        assert r"a\\b\.c\(\)\[\]\^\$\%\ \'\"" == escape_path_section("a\\b.c()[]^$% '\"", PathSeparators.DOT)
+
+    def test_escape_path_section_legacy_spelling(self):
+        """
+        This is provided for compatibility with older versions,
+        before the spelling was updated to "separator."
+        """
         from yamlpath.enums.pathseperators import PathSeperators
         assert r"a\\b\.c\(\)\[\]\^\$\%\ \'\"" == escape_path_section("a\\b.c()[]^$% '\"", PathSeperators.DOT)
 
diff --git a/tests/test_processor.py b/tests/test_processor.py
index 00f3db13..716d9f9f 100644
--- a/tests/test_processor.py
+++ b/tests/test_processor.py
@@ -13,8 +13,9 @@
 from yamlpath.func import unwrap_node_coords
 from yamlpath.exceptions import YAMLPathException
 from yamlpath.enums import (
-    PathSeperators,
+    PathSeparators,
     PathSegmentTypes,
+    PathSearchMethods,
     YAMLValueFormats,
 )
 from yamlpath.path import SearchTerms
@@ -386,7 +387,7 @@ def test_enforce_pathsep(self, quiet_logger):
         yaml = YAML()
         processor = Processor(quiet_logger, yaml.load(yamldata))
         yamlpath = YAMLPath("aliases[&aliasAnchorOne]")
-        for node in processor.get_nodes(yamlpath, pathsep=PathSeperators.FSLASH):
+        for node in processor.get_nodes(yamlpath, pathsep=PathSeparators.FSLASH):
             assert unwrap_node_coords(node) == "Anchored Scalar Value"
 
     @pytest.mark.parametrize("yamlpath,mustexist", [
@@ -452,16 +453,16 @@ def test_set_value_in_none_data(self, capsys, quiet_logger):
         assert -1 == capsys.readouterr().out.find("abc")
 
     @pytest.mark.parametrize("yamlpath,value,tally,mustexist,vformat,pathsep", [
-        ("aliases[&testAnchor]", "Updated Value", 1, True, YAMLValueFormats.DEFAULT, PathSeperators.AUTO),
-        (YAMLPath("top_scalar"), "New top-level value", 1, False, YAMLValueFormats.DEFAULT, PathSeperators.DOT),
-        ("/top_array/2", 42, 1, False, YAMLValueFormats.INT, PathSeperators.FSLASH),
-        ("/top_hash/positive_float", 0.009, 1, True, YAMLValueFormats.FLOAT, PathSeperators.FSLASH),
-        ("/top_hash/negative_float", -0.009, 1, True, YAMLValueFormats.FLOAT, PathSeperators.FSLASH),
-        ("/top_hash/positive_float", -2.71828, 1, True, YAMLValueFormats.FLOAT, PathSeperators.FSLASH),
-        ("/top_hash/negative_float", 5283.4, 1, True, YAMLValueFormats.FLOAT, PathSeperators.FSLASH),
-        ("/null_value", "No longer null", 1, True, YAMLValueFormats.DEFAULT, PathSeperators.FSLASH),
-        ("(top_array[0])+(top_hash.negative_float)+(/null_value)", "REPLACEMENT", 3, True, YAMLValueFormats.DEFAULT, PathSeperators.FSLASH),
-        ("(((top_array[0])+(top_hash.negative_float))+(/null_value))", "REPLACEMENT", 3, False, YAMLValueFormats.DEFAULT, PathSeperators.FSLASH),
+        ("aliases[&testAnchor]", "Updated Value", 1, True, YAMLValueFormats.DEFAULT, PathSeparators.AUTO),
+        (YAMLPath("top_scalar"), "New top-level value", 1, False, YAMLValueFormats.DEFAULT, PathSeparators.DOT),
+        ("/top_array/2", 42, 1, False, YAMLValueFormats.INT, PathSeparators.FSLASH),
+        ("/top_hash/positive_float", 0.009, 1, True, YAMLValueFormats.FLOAT, PathSeparators.FSLASH),
+        ("/top_hash/negative_float", -0.009, 1, True, YAMLValueFormats.FLOAT, PathSeparators.FSLASH),
+        ("/top_hash/positive_float", -2.71828, 1, True, YAMLValueFormats.FLOAT, PathSeparators.FSLASH),
+        ("/top_hash/negative_float", 5283.4, 1, True, YAMLValueFormats.FLOAT, PathSeparators.FSLASH),
+        ("/null_value", "No longer null", 1, True, YAMLValueFormats.DEFAULT, PathSeparators.FSLASH),
+        ("(top_array[0])+(top_hash.negative_float)+(/null_value)", "REPLACEMENT", 3, True, YAMLValueFormats.DEFAULT, PathSeparators.FSLASH),
+        ("(((top_array[0])+(top_hash.negative_float))+(/null_value))", "REPLACEMENT", 3, False, YAMLValueFormats.DEFAULT, PathSeparators.FSLASH),
     ])
     def test_set_value(self, quiet_logger, yamlpath, value, tally, mustexist, vformat, pathsep):
         yamldata = """---
@@ -502,7 +503,7 @@ def test_set_value(self, quiet_logger, yamlpath, value, tally, mustexist, vforma
           1,
           True,
           YAMLValueFormats.DATE,
-          PathSeperators.FSLASH,
+          PathSeparators.FSLASH,
         ),
         ("datetimes.date",
           '2022-08-02',
@@ -510,7 +511,7 @@ def test_set_value(self, quiet_logger, yamlpath, value, tally, mustexist, vforma
           1,
           True,
           YAMLValueFormats.DATE,
-          PathSeperators.DOT,
+          PathSeparators.DOT,
         ),
         ("datetimes.timestamp",
           datetime(2022, 8, 2, 13, 22, 31),
@@ -518,7 +519,7 @@ def test_set_value(self, quiet_logger, yamlpath, value, tally, mustexist, vforma
           1,
           True,
           YAMLValueFormats.TIMESTAMP,
-          PathSeperators.DOT,
+          PathSeparators.DOT,
         ),
         ("/datetimes/timestamp",
           '2022-08-02T13:22:31',
@@ -526,7 +527,7 @@ def test_set_value(self, quiet_logger, yamlpath, value, tally, mustexist, vforma
           1,
           True,
           YAMLValueFormats.TIMESTAMP,
-          PathSeperators.FSLASH,
+          PathSeparators.FSLASH,
         ),
         ("aliases[&date]",
           '2022-08-02',
@@ -534,7 +535,7 @@ def test_set_value(self, quiet_logger, yamlpath, value, tally, mustexist, vforma
           1,
           True,
           YAMLValueFormats.DATE,
-          PathSeperators.DOT,
+          PathSeparators.DOT,
         ),
         ("aliases[&timestamp]",
           datetime(2022, 8, 2, 13, 22, 31),
@@ -542,7 +543,7 @@ def test_set_value(self, quiet_logger, yamlpath, value, tally, mustexist, vforma
           1,
           True,
           YAMLValueFormats.TIMESTAMP,
-          PathSeperators.DOT,
+          PathSeparators.DOT,
         ),
     ])
     def test_set_datetimes(self, quiet_logger, yamlpath, value, compare, tally, mustexist, vformat, pathsep):
@@ -798,10 +799,10 @@ def test_no_attrs_to_scalars_errors(self, quiet_logger):
         assert -1 < str(ex.value).find("Cannot add")
 
     @pytest.mark.parametrize("yamlpath,value,tally,mustexist,vformat,pathsep", [
-        ("/anchorKeys[&keyOne]", "Set self-destruct", 1, True, YAMLValueFormats.DEFAULT, PathSeperators.AUTO),
-        ("/hash[&keyTwo]", "Confirm", 1, True, YAMLValueFormats.DEFAULT, PathSeperators.AUTO),
-        ("/anchorKeys[&recursiveAnchorKey]", "Recurse more", 1, True, YAMLValueFormats.DEFAULT, PathSeperators.AUTO),
-        ("/hash[&recursiveAnchorKey]", "Recurse even more", 1, True, YAMLValueFormats.DEFAULT, PathSeperators.AUTO),
+        ("/anchorKeys[&keyOne]", "Set self-destruct", 1, True, YAMLValueFormats.DEFAULT, PathSeparators.AUTO),
+        ("/hash[&keyTwo]", "Confirm", 1, True, YAMLValueFormats.DEFAULT, PathSeparators.AUTO),
+        ("/anchorKeys[&recursiveAnchorKey]", "Recurse more", 1, True, YAMLValueFormats.DEFAULT, PathSeparators.AUTO),
+        ("/hash[&recursiveAnchorKey]", "Recurse even more", 1, True, YAMLValueFormats.DEFAULT, PathSeparators.AUTO),
     ])
     def test_key_anchor_changes(self, quiet_logger, yamlpath, value, tally, mustexist, vformat, pathsep):
         yamldata = """---
@@ -1020,6 +1021,16 @@ def test_scalar_collectors(self, quiet_logger, yamlpath, results):
         ("(hash)-(hoh.two.*)", [[{"key1": "value1"}]]),
         ("(aoa)-(hoa.two)", [[["value1", "value2", "value3"], ["value3"]]]),
         ("(aoh)-(aoh[max(key1)])", [[{"key2": "value2", "key3": "value3"}, {"key3": "value3"}]]),
+        ("(hash.*)&(array[1])", [["value2"]]),
+        ("(hash.*)&(array)", [["value1", "value2", "value3"]]),
+        ("(array)&(hash.*)", [["value1", "value2", "value3"]]),
+        ("(list1)&(list2)", [[2, 3]]),
+        ("(list2)&(list3)", [[3]]),
+        ("(list1)&(list3)", [[3]]),
+        ("(list1)&(list2)&(list3)", [[3]]),
+        ("(listA)&(listB)", [["B"]]),
+        ("(listB)&(listC)", [["C", "C"]]),
+        ("(listA)&(listC)", [["A", "A"]]),
     ])
     def test_collector_math(self, quiet_logger, yamlpath, results):
         yamldata = """---
@@ -1031,7 +1042,7 @@ def test_collector_math(self, quiet_logger, yamlpath, results):
 array:
   - value1
   - value2
-  - vlaue3
+  - value3
 
 hoh:
   one:
@@ -1070,6 +1081,31 @@ def test_collector_math(self, quiet_logger, yamlpath, results):
     - value3
   three:
     - value3
+
+# Intersections
+list1:
+  - 1
+  - 2
+  - 3
+list2:
+  - 2
+  - 3
+  - 4
+list3:
+  - 3
+  - 5
+
+listA:
+  - A
+  - A
+  - B
+listB:
+  - B
+  - C
+  - C
+listC:
+  - A
+  - C
 """
         yaml = YAML()
         processor = Processor(quiet_logger, yaml.load(yamldata))
@@ -1108,11 +1144,11 @@ def test_get_every_data_type(self, quiet_logger):
             assert unwrap_node_coords(node) == results[match_index]
             match_index += 1
 
-    @pytest.mark.parametrize("delete_yamlpath,pathseperator,old_deleted_nodes,new_flat_data", [
-        (YAMLPath("/**[&alias_number]"), PathSeperators.FSLASH, [1, 1, 1], [1,1,True,1,1,True,1,1,True,1,"ABC",123,"BCD",987,"CDE","8B8"]),
-        ("records[1]", PathSeperators.AUTO, ["ABC",123,"BCD",987], [1,1,1,True,1,1,1,True,1,1,1,True,1,1,"CDE","8B8"]),
+    @pytest.mark.parametrize("delete_yamlpath,pathseparator,old_deleted_nodes,new_flat_data", [
+        (YAMLPath("/**[&alias_number]"), PathSeparators.FSLASH, [1, 1, 1], [1,1,True,1,1,True,1,1,True,1,"ABC",123,"BCD",987,"CDE","8B8"]),
+        ("records[1]", PathSeparators.AUTO, ["ABC",123,"BCD",987], [1,1,1,True,1,1,1,True,1,1,1,True,1,1,"CDE","8B8"]),
     ])
-    def test_delete_nodes(self, quiet_logger, delete_yamlpath, pathseperator, old_deleted_nodes, new_flat_data):
+    def test_delete_nodes(self, quiet_logger, delete_yamlpath, pathseparator, old_deleted_nodes, new_flat_data):
         yamldata = """---
 aliases:
   - &alias_number 1
@@ -1146,7 +1182,7 @@ def test_delete_nodes(self, quiet_logger, delete_yamlpath, pathseperator, old_de
 
         # The return set must be received lest no nodes will be deleted
         deleted_nodes = []
-        for nc in processor.delete_nodes(delete_yamlpath, pathsep=pathseperator):
+        for nc in processor.delete_nodes(delete_yamlpath, pathsep=pathseparator):
             deleted_nodes.append(nc)
 
         for (test_value, verify_node_coord) in zip(old_deleted_nodes, deleted_nodes):
@@ -1217,11 +1253,11 @@ def test_null_docs_have_nothing_to_tag(self, capsys):
         console = capsys.readouterr()
         assert "Refusing to tag nodes from a null document" in console.out
 
-    @pytest.mark.parametrize("alias_path,anchor_path,anchor_name,pathseperator", [
-        (YAMLPath("/a_hash/a_key"), YAMLPath("/some_key"), "", PathSeperators.FSLASH),
-        ("a_hash.a_key", "some_key", "", PathSeperators.AUTO),
+    @pytest.mark.parametrize("alias_path,anchor_path,anchor_name,pathseparator", [
+        (YAMLPath("/a_hash/a_key"), YAMLPath("/some_key"), "", PathSeparators.FSLASH),
+        ("a_hash.a_key", "some_key", "", PathSeparators.AUTO),
     ])
-    def test_anchor_nodes(self, quiet_logger, alias_path, anchor_path, anchor_name, pathseperator):
+    def test_anchor_nodes(self, quiet_logger, alias_path, anchor_path, anchor_name, pathseparator):
         anchor_value = "This is the Anchored value!"
         yamlin = """---
 some_key: {}
@@ -1235,7 +1271,7 @@ def test_anchor_nodes(self, quiet_logger, alias_path, anchor_path, anchor_name,
 
         processor.alias_nodes(
             alias_path, anchor_path,
-            pathsep=pathseperator, anchor_name=anchor_name)
+            pathsep=pathseparator, anchor_name=anchor_name)
 
         match_count = 0
         for node in processor.get_nodes(
@@ -1245,29 +1281,29 @@ def test_anchor_nodes(self, quiet_logger, alias_path, anchor_path, anchor_name,
             assert unwrap_node_coords(node) == anchor_value
         assert match_count == 1
 
-    @pytest.mark.parametrize("change_path,ymk_path,anchor_name,pathseperator,validations", [
-        ("target", "source", "", PathSeperators.AUTO, [
+    @pytest.mark.parametrize("change_path,ymk_path,anchor_name,pathseparator,validations", [
+        ("target", "source", "", PathSeparators.AUTO, [
             ("target.target_key", ["other"]),
             ("target.source_key", ["value"]),
             ("target[&source].source_key", ["value"]),
             ("target.override_this", ["overridden"]),
             ("target[&source].override_this", ["original"]),
         ]),
-        (YAMLPath("target"), YAMLPath("source"), "", PathSeperators.DOT, [
+        (YAMLPath("target"), YAMLPath("source"), "", PathSeparators.DOT, [
             ("target.target_key", ["other"]),
             ("target.source_key", ["value"]),
             ("target[&source].source_key", ["value"]),
             ("target.override_this", ["overridden"]),
             ("target[&source].override_this", ["original"]),
         ]),
-        ("/target", "/source", "", PathSeperators.FSLASH, [
+        ("/target", "/source", "", PathSeparators.FSLASH, [
             ("/target/target_key", ["other"]),
             ("/target/source_key", ["value"]),
             ("/target/&source/source_key", ["value"]),
             ("/target/override_this", ["overridden"]),
             ("/target/&source/override_this", ["original"]),
         ]),
-        ("target", "source", "custom_name", PathSeperators.DOT, [
+        ("target", "source", "custom_name", PathSeparators.DOT, [
             ("target.target_key", ["other"]),
             ("target.source_key", ["value"]),
             ("target[&custom_name].source_key", ["value"]),
@@ -1275,7 +1311,7 @@ def test_anchor_nodes(self, quiet_logger, alias_path, anchor_path, anchor_name,
             ("target[&custom_name].override_this", ["original"]),
         ]),
     ])
-    def test_ymk_nodes(self, quiet_logger, change_path, ymk_path, anchor_name, pathseperator, validations):
+    def test_ymk_nodes(self, quiet_logger, change_path, ymk_path, anchor_name, pathseparator, validations):
         yamlin = """---
 source:
   source_key: value
@@ -1292,7 +1328,7 @@ def test_ymk_nodes(self, quiet_logger, change_path, ymk_path, anchor_name, paths
 
         processor.ymk_nodes(
             change_path, ymk_path,
-            pathsep=pathseperator, anchor_name=anchor_name)
+            pathsep=pathseparator, anchor_name=anchor_name)
 
         for (valid_path, valid_values) in validations:
             match_count = 0
@@ -1301,11 +1337,11 @@ def test_ymk_nodes(self, quiet_logger, change_path, ymk_path, anchor_name, paths
                 match_count += 1
             assert len(valid_values) == match_count
 
-    @pytest.mark.parametrize("yaml_path,tag,pathseperator", [
-        (YAMLPath("/key"), "!taggidy", PathSeperators.FSLASH),
-        ("key", "taggidy", PathSeperators.AUTO),
+    @pytest.mark.parametrize("yaml_path,tag,pathseparator", [
+        (YAMLPath("/key"), "!taggidy", PathSeparators.FSLASH),
+        ("key", "taggidy", PathSeparators.AUTO),
     ])
-    def test_tag_nodes(self, quiet_logger, yaml_path, tag, pathseperator):
+    def test_tag_nodes(self, quiet_logger, yaml_path, tag, pathseparator):
         yamlin = """---
 key: value
 """
@@ -1314,7 +1350,7 @@ def test_tag_nodes(self, quiet_logger, yaml_path, tag, pathseperator):
         data = yaml.load(yamlin)
         processor = Processor(quiet_logger, data)
 
-        processor.tag_nodes(yaml_path, tag, pathsep=pathseperator)
+        processor.tag_nodes(yaml_path, tag, pathsep=pathseparator)
 
         if tag and not tag[0] == "!":
             tag = "!{}".format(tag)
@@ -1489,6 +1525,81 @@ def test_yaml_merge_key_queries(self, quiet_logger, yamlpath, results):
             matchidx += 1
         assert len(results) == matchidx
 
+    @pytest.mark.parametrize("yamlpath,results,yp_error", [
+        ("list[distinct()]", [1, 2, 3, 4], None),
+        ("list[unique()]", [1, 2, 4], None),
+        ("list[!unique()]", [3, 3], None),
+        ("(list[!unique()])[distinct()]", [3], None),
+
+        ("map[distinct(id)].id", [1, 2, 3, 4], None),
+        ("map[unique(id)].id", [1, 2, 4], None),
+        ("map[!unique(id)].id", [3, 3], None),
+        ("(map[!unique(id)].id)[distinct()]", [3], None),
+
+        ("aoh[distinct(id)].id", [1, 2, 3, 4], None),
+        ("aoh[unique(id)].id", [1, 2, 4], None),
+        ("aoh[!unique(id)].id", [3, 3], None),
+        ("(aoh[!unique(id)].id)[distinct()]", [3], None),
+
+        ("non_conformant[distinct()]", ["value"], None),
+        ("non_conformant[unique()]", ["value"], None),
+        ("non_conformant[!unique()]", [], "Required YAML Path does not match any nodes"),
+    ])
+    def test_unique_vs_distinct(self, quiet_logger, yamlpath, results, yp_error):
+        yamldata = """---
+# Array
+list:
+  - 1
+  - 2
+  - 3
+  - 3
+  - 4
+
+# Hash
+map:
+  entity1:
+    id: 1
+  entity2:
+    id: 2
+  entity3:
+    id: 3
+  entity4:
+    id: 3
+  entity5:
+    id: 4
+
+# Array of Hashes
+aoh:
+  - name: entity1
+    id: 1
+  - name: entity2
+    id: 2
+  - name: entity3
+    id: 3
+  - name: entity4
+    id: 3
+  - name: entity5
+    id: 4
+
+non_conformant: value
+"""
+        yaml = YAML()
+        processor = Processor(quiet_logger, yaml.load(yamldata))
+        matchidx = 0
+        try:
+            for node in processor.get_nodes(yamlpath, mustexist=True):
+                assert unwrap_node_coords(node) == results[matchidx]
+                matchidx += 1
+        except YAMLPathException as ex:
+            if yp_error is not None:
+                assert yp_error in str(ex)
+            else:
+                # Unexpected error
+                assert False
+
+        assert len(results) == matchidx
+
+
     @pytest.mark.parametrize("yamlpath,results", [
         (r"temperature[. =~ /\d{3}/]", [110, 100, 114]),
     ])
diff --git a/tests/test_yamlpath.py b/tests/test_yamlpath.py
index 9763efc8..51e8a6d2 100644
--- a/tests/test_yamlpath.py
+++ b/tests/test_yamlpath.py
@@ -1,42 +1,42 @@
 import pytest
 
 from yamlpath.exceptions import YAMLPathException
-from yamlpath.enums import PathSegmentTypes, PathSeperators
+from yamlpath.enums import PathSegmentTypes, PathSeparators
 from yamlpath import YAMLPath
 
 class Test_YAMLPath():
     """Tests for the Path class."""
 
     @pytest.mark.parametrize("yamlpath,pathsep,output", [
-        (YAMLPath(""), PathSeperators.AUTO, ""),
-        (YAMLPath("abc"), PathSeperators.AUTO, "abc"),
-        ("abc", PathSeperators.AUTO, "abc"),
-        ("/abc", PathSeperators.AUTO, "/abc"),
-        ("abc.bcd", PathSeperators.AUTO, "abc.bcd"),
-        ("/abc[1]", PathSeperators.AUTO, "/abc[1]"),
-        ("&abc", PathSeperators.AUTO, "&abc"),
-        ("/abc[&def]", PathSeperators.AUTO, "/abc[&def]"),
-        ("abc[!def^ghi]", PathSeperators.AUTO, "abc[def!^ghi]"),
-        ("/(abc)[0]", PathSeperators.AUTO, "/(abc)[0]"),
-        ("abc[def =~ /ghi/]", PathSeperators.AUTO, "abc[def=~/ghi/]"),
-        ("/(abc)+(def)-(ghi)", PathSeperators.AUTO, "/(abc)+(def)-(ghi)"),
-        (r"abc.'def.g\"h\"i'", PathSeperators.AUTO, r"abc.def\.g\"h\"i"),
-        ("/abc[def>=1]", PathSeperators.AUTO, "/abc[def>=1]"),
-        ("abc[def<=10]", PathSeperators.AUTO, "abc[def<=10]"),
-        ("/abc[def==1]", PathSeperators.AUTO, "/abc[def=1]"),
-        ("abc[def$ghi]", PathSeperators.AUTO, "abc[def$ghi]"),
-        ("/abc[def%1]", PathSeperators.AUTO, "/abc[def%1]"),
-        ("abc[def%'ghi']", PathSeperators.AUTO, "abc[def%ghi]"),
-        ("abc*", PathSeperators.AUTO, "[.^abc]"),
-        ("*def", PathSeperators.AUTO, "[.$def]"),
-        ("a*f", PathSeperators.AUTO, "[.=~/^a.*f$/]"),
-        ("a*f*z", PathSeperators.AUTO, "[.=~/^a.*f.*z$/]"),
-        ("a*f*z*", PathSeperators.AUTO, "[.=~/^a.*f.*z.*$/]"),
-        ("*", PathSeperators.AUTO, "*"),
-        ("*.*", PathSeperators.AUTO, "*.*"),
-        ("**", PathSeperators.AUTO, "**"),
-        ("/**/def", PathSeperators.AUTO, "/**/def"),
-        ("abc.**.def", PathSeperators.AUTO, "abc.**.def"),
+        (YAMLPath(""), PathSeparators.AUTO, ""),
+        (YAMLPath("abc"), PathSeparators.AUTO, "abc"),
+        ("abc", PathSeparators.AUTO, "abc"),
+        ("/abc", PathSeparators.AUTO, "/abc"),
+        ("abc.bcd", PathSeparators.AUTO, "abc.bcd"),
+        ("/abc[1]", PathSeparators.AUTO, "/abc[1]"),
+        ("&abc", PathSeparators.AUTO, "&abc"),
+        ("/abc[&def]", PathSeparators.AUTO, "/abc[&def]"),
+        ("abc[!def^ghi]", PathSeparators.AUTO, "abc[def!^ghi]"),
+        ("/(abc)[0]", PathSeparators.AUTO, "/(abc)[0]"),
+        ("abc[def =~ /ghi/]", PathSeparators.AUTO, "abc[def=~/ghi/]"),
+        ("/(abc)+(def)-(ghi)", PathSeparators.AUTO, "/(abc)+(def)-(ghi)"),
+        (r"abc.'def.g\"h\"i'", PathSeparators.AUTO, r"abc.def\.g\"h\"i"),
+        ("/abc[def>=1]", PathSeparators.AUTO, "/abc[def>=1]"),
+        ("abc[def<=10]", PathSeparators.AUTO, "abc[def<=10]"),
+        ("/abc[def==1]", PathSeparators.AUTO, "/abc[def=1]"),
+        ("abc[def$ghi]", PathSeparators.AUTO, "abc[def$ghi]"),
+        ("/abc[def%1]", PathSeparators.AUTO, "/abc[def%1]"),
+        ("abc[def%'ghi']", PathSeparators.AUTO, "abc[def%ghi]"),
+        ("abc*", PathSeparators.AUTO, "[.^abc]"),
+        ("*def", PathSeparators.AUTO, "[.$def]"),
+        ("a*f", PathSeparators.AUTO, "[.=~/^a.*f$/]"),
+        ("a*f*z", PathSeparators.AUTO, "[.=~/^a.*f.*z$/]"),
+        ("a*f*z*", PathSeparators.AUTO, "[.=~/^a.*f.*z.*$/]"),
+        ("*", PathSeparators.AUTO, "*"),
+        ("*.*", PathSeparators.AUTO, "*.*"),
+        ("**", PathSeparators.AUTO, "**"),
+        ("/**/def", PathSeparators.AUTO, "/**/def"),
+        ("abc.**.def", PathSeparators.AUTO, "abc.**.def"),
     ])
     def test_str(self, yamlpath, pathsep, output):
         # Test twice to include cache hits
@@ -97,13 +97,26 @@ def test_ne(self, lhs, rhs, isne):
         else:
             assert not lhs != rhs
 
-    def test_seperator_change(self):
+    @pytest.mark.parametrize("sep_property", ["separator", "seperator"])
+    def test_separator_change(self, sep_property):
+        """`sep_property` is parametrized to ensure backward compatibility."""
         dotted = "abc.def"
         slashed = "/abc/def"
         testpath = YAMLPath(dotted)
-        testpath.seperator = PathSeperators.FSLASH
+        setattr(testpath, sep_property, PathSeparators.FSLASH)
         assert slashed == str(testpath) != dotted
 
+    @pytest.mark.parametrize("yamlpath, pathsep", [
+        ("abc.def", PathSeparators.DOT),
+        ("/abc/def", PathSeparators.FSLASH),
+    ])
+    def test_legacy_separator_access(self, yamlpath, pathsep):
+        """
+        This checks compatibility with older versions,
+        before the spelling was updated to "separator."
+        """
+        assert YAMLPath(yamlpath).seperator == pathsep
+
     def test_escaped(self):
         testpath = YAMLPath(r"abc.def\.ghi")
         assert list(testpath.escaped) == [
@@ -119,7 +132,7 @@ def test_unescaped(self):
         ]
 
     def test_append(self):
-        yp = YAMLPath("", PathSeperators.DOT)
+        yp = YAMLPath("", PathSeparators.DOT)
         yp.append("abc")
         yp.append("def")
         yp.append("ghi").append("jkl").append("mno")