{"id":38518845,"url":"https://github.com/mvcisback/dfa","last_synced_at":"2026-01-17T06:36:25.293Z","repository":{"id":37974642,"uuid":"181256437","full_name":"mvcisback/dfa","owner":"mvcisback","description":"A simple python implementation of a DFA. ","archived":false,"fork":false,"pushed_at":"2024-05-10T02:17:40.000Z","size":233,"stargazers_count":23,"open_issues_count":2,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-10T18:10:43.599Z","etag":null,"topics":["dfa","immutable","moore-machine"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mvcisback.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-04-14T03:36:57.000Z","updated_at":"2025-09-08T08:30:29.000Z","dependencies_parsed_at":"2024-06-21T15:57:27.234Z","dependency_job_id":"906897fc-52b9-4545-8e54-6c9e27eb1298","html_url":"https://github.com/mvcisback/dfa","commit_stats":{"total_commits":126,"total_committers":3,"mean_commits":42.0,"dds":0.04761904761904767,"last_synced_commit":"26d64160e0d8d07dc820bc02aa0b0290c08dea27"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mvcisback/dfa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvcisback%2Fdfa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvcisback%2Fdfa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvcisback%2Fdfa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvcisback%2Fdfa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mvcisback","download_url":"https://codeload.github.com/mvcisback/dfa/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvcisback%2Fdfa/sbom","scorecard":{"id":669275,"data":{"date":"2025-08-11","repo":{"name":"github.com/mvcisback/dfa","commit":"4c8a4cb380838bce7d3fbb081ff1c24b711dfe81"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-21T19:22:46.645Z","repository_id":37974642,"created_at":"2025-08-21T19:22:46.645Z","updated_at":"2025-08-21T19:22:46.645Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28502593,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T04:31:57.058Z","status":"ssl_error","status_checked_at":"2026-01-17T04:31:45.816Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dfa","immutable","moore-machine"],"created_at":"2026-01-17T06:36:24.588Z","updated_at":"2026-01-17T06:36:25.253Z","avatar_url":"https://github.com/mvcisback.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DFA\n\nA simple python implementation of a DFA. \n\n[![Build Status](https://cloud.drone.io/api/badges/mvcisback/dfa/status.svg)](https://cloud.drone.io/mvcisback/dfa)\n[![PyPI version](https://badge.fury.io/py/dfa.svg)](https://badge.fury.io/py/dfa)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n\u003c!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-generate-toc again --\u003e\n**Table of Contents**\n\n- [Installation](#installation)\n- [Usage](#usage)\n    - [Membership Queries](#membership-queries)\n    - [Transitions and Traces](#transitions-and-traces)\n    - [Non-boolean output alphabets](#non-boolean-output-alphabets)\n    - [Moore Machines](#moore-machines)\n    - [DFA \u003c-\u003e Dictionary](#dfa---dictionary)\n    - [Computing Reachable States](#computing-reachable-states)\n    - [Sampling Paths](#sampling-paths)\n    - [Running interactively (Co-Routine API)](#running-interactively-co-routine-api)\n    - [Visualizing DFAs](#visualizing-dfas)\n\n\u003c!-- markdown-toc end --\u003e\n\n\n**Features:**\n\n1. State can be any Hashable object.\n2. Alphabet can be any finite sequence of Hashable objects.\n3. Designed to be immutable and hashable (assuming components are\n   immutable and hashable).\n4. Design choice to allow transition map and accepting set to be\n   given as functions rather than an explicit `dict` or `set`.\n\n# Installation\n\nIf you just need to use `dfa`, you can just run:\n\n`$ pip install dfa`\n\nFor developers, note that this project uses the\n[poetry](https://poetry.eustace.io/) python package/dependency\nmanagement tool. Please familarize yourself with it and then\nrun:\n\n`$ poetry install`\n\n# Usage\n\nThe `dfa` api is centered around the `DFA` object. \n\nBy default, the `DFA` object models a `Deterministic Finite Acceptor`,\ne.g., a recognizer of a Regular Language. \n\n**Example Usage:**\n```python\nfrom dfa import DFA\n\ndfa1 = DFA(\n    start=0,\n    inputs={0, 1},\n    label=lambda s: (s % 4) == 3,\n    transition=lambda s, c: (s + c) % 4,\n)\n\ndfa2 = DFA(\n    start=\"left\",\n    inputs={\"move right\", \"move left\"},\n    label=lambda s: s == \"left\",\n    transition=lambda s, c: \"left\" if c == \"move left\" else \"right\",\n)\n```\n\n## Membership Queries\n\n```python\nassert dfa1.label([1, 1, 1, 1])\nassert not dfa1.label([1, 0])\n\nassert dfa2.label([\"move right\"]*100 + [\"move left\"])\nassert not dfa2.label([\"move left\", \"move right\"])\n```\n\n## Transitions and Traces\n\n```python\nassert dfa1.transition([1, 1, 1]) == 3\nassert list(dfa1.trace([1, 1, 1])) == [0, 1, 2, 3]\n```\n\n## Non-boolean output alphabets\n\nSometimes, it is useful to model an automata which can label a word\nusing a non-Boolean alphabet. For example, `{True, False, UNSURE}`.\n\nThe `DFA` object supports this by specifying the output alphabet.\n\n```python\nUNSURE = None\n\ndef my_labeler(s):\n    if s % 4 == 2:\n       return None\n    return (s % 4) == 3\n\n\ndfa3 = DFA(\n    start=0,\n    inputs={0, 1},\n    label=my_labeler,\n    transition=lambda s, c: (s + c) % 4,\n    outputs={True, False, UNSURE},\n)\n```\n\n**Note:** If `outputs` is set to `None`, then no checks are done that\nthe outputs are within the output alphabet.\n\n```python\ndfa3 = DFA(\n    start=0,\n    inputs={0, 1},\n    label=my_labeler,\n    transition=lambda s, c: (s + c) % 4,\n    outputs=None,\n)\n```\n\n## Moore Machines\n\nFinally, by reinterpreting the structure of the `DFA` object, one can\nmodel a Moore Machine. For example, in 3 state counter, `dfa1`, the\nMoore Machine can output the current count.\n\n```python\nassert dfa1.transduce(()) == ()\nassert dfa1.transduce((1,)) == (False,)\nassert dfa1.transduce((1, 1, 1, 1)) == (False, False, False, True)\n```\n\n## Language Queries\n\nUtility functions are available for testing if a language:\n\n1. Is empty: `utils.find_word`\n2. Is equivilent to another language: `utils.find_equiv_counterexample`\n3. Is a subset of a another language: `utils.find_subset_counterexample`\n\nThese operate by returning `None` if the property holds, i.e.,\n`lang(dfa1) = ∅, lang(dfa1) ≡ lang(dfa2), lang(dfa1) ⊆ lang(dfa2)`, and\nreturning a counterexample `Word` otherwise.\n\n## DFA \u003c-\u003e Dictionary\n\nNote that `dfa` provides helper functions for going from a dictionary\nbased representation of a deterministic transition system to a `DFA`\nobject and back.\n\n```python\nfrom dfa import dfa2dict, dict2dfa\n\n# DFA encoded a nested dictionaries with the following\n# signature.\n#     \u003cstate\u003e: (\u003clabel\u003e, {\u003caction\u003e: \u003cnext state\u003e})\n\ndfa_dict = {\n    0: (False, {0: 0, 1: 1}),\n    1: (False, {0: 1, 1: 2}),\n    2: (False, {0: 2, 1: 3}), \n    3: (True, {0: 3, 1: 0})\n}\n\n# Dictionary -\u003e DFA\ndfa = dict2dfa(dfa_dict, start=0)\n\n# DFA -\u003e Dictionary\ndfa_dict2, start = dfa2dict(dfa)\n\nassert (dfa_dict, 0) == (dfa_dict2, start)\n```\n\n## Computing Reachable States\n\n```python\n# Perform a depth first traversal to collect all reachable states.\nassert dfa1.states() == {0, 1, 2, 3}\n```\n\n## Finding Words and Access strings\n\nTo generate accepting strings (words) in a DFA (breadth first using string length) one can use the `dfa.utils.words` function:\n\n```python\nfrom dfa.utils.import dfa2dict, words, find_words\n\ndfa_dict = {\n    0: (False, {0: 0, 1: 1}),\n    1: (False, {0: 1, 1: 2}),\n    2: (False, {0: 2, 1: 3}),\n    3: (True, {0: 3, 1: 0})\n}\nlang = dict2dfa(dfa_dict, start=0)\n\nxs = set(fn.take(5, words(lang)))\nassert len(xs) == 5\nassert all(lang.label(x) for x in xs)\n```\n\nTo get a single word, a helper function is provided in `dfa.utils.find_word` which returns `None` if the language of the DFA is empty:\n\n```python\n# ... Same as above ...\n\nx = find_word(lang)\nassert x is not None\nassert lang.label(x)\n```\n\n\nOften times, it is useful to sample a path between two states, say `a`\nand `b`. `dfa` supports this using `dfa.utils.paths`. This function\nreturns a generator of words, `w`, such that `dfa.transition(w,\nstart=b) == a`. For example:\n\n\n```python\nfrom dfa.utils import paths\n\naccess_strings = paths(\n    dfa1, \n    start=0,\n    end=1,  # Optional. If not specified returns all paths\n            # starting at `start`.\n    max_length=7,  #  Defaults to float('inf')\n    randomize=True,  #  Randomize the order. Shorter paths still found first.\n)\n\nfor word in access_strings:\n    assert dfa1.transition(word, start=0) == 1\n```\n\n## DFA minimization\n\nDFAs can be minimized using the `minimize` method.\n\n```python\nmy_dfa = my_dfa.minimize()\n```\n\n## DFA advancement (progression)\n\nOne can create the DFA starting at the state indexed by a given word by using\nthe `advance` method. \n\n```python\nmy_dfa = my_dfa.advance(word)\n```\n\n\n## Running interactively (Co-Routine API)\n\n`dfa` supports interactively stepping through a `DFA` object via\nco-routines. This is particularly useful when using DFA in a control\nloop. For example, the following code counts how many `1`'s it takes\nto advance `dfa1`'s state back to the start state.\n\n```python\n\nmachine = dfa1.run()\n\nnext(machine)\nstate = None\n\ncount = 0\nwhile state != dfa1.start:\n    count += 1\n    state = machine.send(1)\n```\n\n## Visualizing DFAs\n\n`dfa` optionally supports visualizing DFAs using graphviz. To use this\nfunctionality be sure to install `dfa` using with the `draw` option:\n\n```python\npip install dfa[draw]\n```\n\nor \n\n```python\npoetry install -E draw\n```\n\nThen one can simply use `dfa.draw.write_dot` to write a `.dot` file\nrepresenting the DFA. This `.dot` file can be rendered using any\ngraphviz supporting tool.\n\n```python\nfrom dfa.draw import write_dot\n\nwrite_dot(dfa1, \"path/to/dfa1.dot\")\n```\n\nUsing the `dot` command in linux results in the following rendering of `dfa1`.\n\n`$ dot -Tsvg path/to/dfa1.dot \u003e dfa1.svg`\n\n\u003cfigure\u003e\n  \u003cimg src=\"assets/dfa1.svg\" alt=\"visualization of dfa1\" width=500px\u003e\n  \u003cfigcaption\u003e\n    Visualization of dfa1 using graphviz.\n  \u003c/figcaption\u003e\n\u003c/figure\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmvcisback%2Fdfa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmvcisback%2Fdfa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmvcisback%2Fdfa/lists"}