{"id":16014673,"url":"https://github.com/green-coder/transducer-exercises","last_synced_at":"2025-08-21T14:11:47.979Z","repository":{"id":145686948,"uuid":"136267732","full_name":"green-coder/transducer-exercises","owner":"green-coder","description":"Learn how to write your own transducers. A complement from my blog post serie \"Build Your Own Transducer and Impress Your Cat\".","archived":false,"fork":false,"pushed_at":"2019-01-04T09:16:36.000Z","size":10741,"stargazers_count":20,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-16T08:46:51.910Z","etag":null,"topics":["clojure","exercises","transducer"],"latest_commit_sha":null,"homepage":"https://vincent.404.taipei/clojure/build-your-own-transducer-part1/","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/green-coder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-06T03:37:56.000Z","updated_at":"2025-05-21T17:07:28.000Z","dependencies_parsed_at":"2023-06-04T18:15:15.529Z","dependency_job_id":null,"html_url":"https://github.com/green-coder/transducer-exercises","commit_stats":{"total_commits":7,"total_committers":1,"mean_commits":7.0,"dds":0.0,"last_synced_commit":"650078a6f615ab1886cf590b3cb4d78672b9fcaf"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/green-coder/transducer-exercises","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/green-coder%2Ftransducer-exercises","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/green-coder%2Ftransducer-exercises/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/green-coder%2Ftransducer-exercises/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/green-coder%2Ftransducer-exercises/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/green-coder","download_url":"https://codeload.github.com/green-coder/transducer-exercises/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/green-coder%2Ftransducer-exercises/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271493232,"owners_count":24769117,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["clojure","exercises","transducer"],"created_at":"2024-10-08T15:04:44.713Z","updated_at":"2025-08-21T14:11:47.953Z","avatar_url":"https://github.com/green-coder.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Transducer Exercises\n\nContains exercises on implementing custom transducers in Clojure.\n\nIt is recommended (but not required) to read [this serie of articles](https://vincent.404.taipei/clojure/build-your-own-transducer-part1/) first.\n\nThe solutions are linked at the end of each section.\n\n## Fair notice\n\nImplementing transducers is hard and you may feel confused.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/meme_kid_cry.gif\" alt=\"Cry Kid Meme\"\u003e\n\u003c/p\u003e\n\nThat's normal and to be expected, it happens also to me when I read my own code. Just don't give up.\n\n## No Operation transducer\n\nImplement a transducer that does nothing but passing the data from his input to his output. Functionally speaking, it is an identity transducer.\n\n```clojure\n(into [] identity (range 3))\n; =\u003e [0 1 2]\n```\n\n[Link to the solution](solution/identity.clj)\n\nUse it as your transducer template for the following.\n\n## Prepare for battle\n\n* Implement the `(debug in out)` transducer that helps to debug.\n\n```clojure\n; We use this function instead of `into` for debugging.\n; The reason is that this avoids using transient\n; structures which do not `print` nicely.\n(defn slow-into [to xf from]\n  (transduce xf conj to from))\n\n(slow-into [] (debug \"in\" \"out\") (range 3))\n;; Outputs:\n; in 0\n; out [0]\n; in 1\n; out [0 1]\n; in 2\n; out [0 1 2]\n```\n\n* Add the `(debug)`, `(debug indent)` and `(debug indent in out)` variants for convenience. They are all calling the `(debug in out)` transducer under the hood.\n\n```clojure\n(slow-into []\n           (comp (debug)\n                 (debug 2)\n                 (debug 4 \"\u003e\" \"\u003c\"))\n                 (debug \"      \u003e\" \"      \u003c\")) ; 6-spaces prefix\n           (range 3))\n;; Outputs:\n; \u003e 0\n;   \u003e 0\n;     \u003e 0\n;       \u003e 0\n;       \u003c [0]\n;     \u003c [0]\n;   \u003c [0]\n; \u003c [0]\n; \u003e 1\n;   \u003e 1\n;     \u003e 1\n;       \u003e 1\n;       \u003c [0 1]\n;     \u003c [0 1]\n;   \u003c [0 1]\n; \u003c [0 1]\n; \u003e 2\n;   \u003e 2\n;     \u003e 2\n;       \u003e 2\n;       \u003c [0 1 2]\n;     \u003c [0 1 2]\n;   \u003c [0 1 2]\n; \u003c [0 1 2]\n```\n\n[Link to a solution](solution/debug.clj)\n\nStrange patterns, it reminds me of ...\n\n**The PHP Hadouken !!!**\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/php_hadouken.jpg\" alt=\"PHP Hadouken\"\u003e\n\u003c/p\u003e\n\nWe are now ready to face real transducer implementations and confront an army of problems.\n\n## May I beg your pardon?\n\nYou heard me well, I want you to implement the following transducer.\n\n```clojure\n(def beg-data (list :may :i :beg :your :pardon :?))\n\n(into [] (beg 2) beg-data)\n; =\u003e [:may :may :i :i :beg :beg :your :your :pardon :pardon :? :?]\n```\n\n[Link to a partial solution](solution/beg-step1.clj)\n\nMake sure that you are handling the early termination as well.\n\n```clojure\n(into []\n      (comp (take 3)\n            (beg 2))\n      beg-data)\n; =\u003e [:may :may :i :i :beg :beg]\n```\n\nTest for both sides.\n\n```clojure\n(into []\n      (comp (beg 2)\n            (take 3))\n      beg-data)\n; =\u003e [:may :may :i]\n```\n\nTest with the debug transducer (expect problems and losing some hair).\n\n```clojure\n(slow-into []\n           (comp (debug 0)\n                 (beg 2)\n                 (debug 2)\n                 (take 3)\n                 (debug 4))\n           beg-data)\n; Output:\n; \u003e :may\n;   \u003e :may\n;     \u003e :may\n;     \u003c [:may]\n;   \u003c [:may]\n;   \u003e :may\n;     \u003e :may\n;     \u003c [:may :may]\n;   \u003c [:may :may]\n; \u003c [:may :may]\n; \u003e :i\n;   \u003e :i\n;     \u003e :i\n;     \u003c [:may :may :i]\n;   \u003c #reduced[{:status :ready, :val [:may :may :i]} 0x8cdcdd1]\n; \u003c #reduced[{:status :ready, :val [:may :may :i]} 0x8cdcdd1]\n\n; Result:\n; =\u003e [:may :may :i]\n```\n\nThe `beg` transducer should not continue sending data downstream after it receives a reduced result. Fix your implementation if needed.\n\n[Link to a complete solution](solution/beg-step2.clj)\n\n## All your data are belong to me\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/coco_transducer.jpg\" alt=\"Catducer\"\u003e\n\u003c/p\u003e\n\nImplement the `my-cat` transducer. For each step, you will need to adapt your implementation to the new requirements described by the test samples.\n\n* Step 1, shapeless cat\n\nIt functions similarly to `clojure.core/cat`. Don't handle early termination at the moment.\n\n```clojure\n(def cat-data [[1 2 :fish 3] [:heat 4] [5 :sleep 6] [7]])\n\n(into [] my-cat cat-data)\n; =\u003e [1 2 :fish 3 :heat 4 5 :sleep 6 7]\n```\n[Link to a solution, part 1](solution/my-cat-step1.clj)\n\n* Step 2, hungry cat\n\nAs you can see, the transducer is keeping for itself all the fishes and the heat.\n\n```clojure\n(into [] my-cat cat-data)\n; =\u003e [1 2 3 4 5 :sleep 6 7]\n```\n\n[Link to a solution, part 2](solution/my-cat-step2.clj)\n\n* Step 3, sleepy cat\n\nThat version of the transducer falls asleep an the `:sleep` keyword and do not process any subsequent data.\n\n```clojure\n(into [] my-cat cat-data)\n; =\u003e [1 2 3 4 5]\n```\n\n[Link to a solution, part 3](solution/my-cat-step3.clj)\n\n* Step 4, correct cat\n\nAt last, we want our transducer to respect the normal early termination (with `reduced?` tested on the downstream result) in a correct manner.\n\n```clojure\n(into [] (comp (take 2)\n               my-cat)\n      cat-data)\n; =\u003e [1 2 3 4]\n\n(into [] (comp my-cat\n               (take 2))\n      cat-data)\n; =\u003e [1 2]\n\n(slow-into [] (comp (debug 0)\n                    my-cat    ; try replacing it with `cat` and compare\n                    (debug 2)\n                    (take 2)\n                    (debug 4))\n           cat-data)\n;; Outputs:\n; \u003e [1 2 :fish 3]\n;   \u003e 1\n;     \u003e 1\n;     \u003c [1]\n;   \u003c [1]\n;   \u003e 2\n;     \u003e 2\n;     \u003c [1 2]\n;   \u003c #reduced[{:status :ready, :val [1 2]} 0x517a7e8e]\n; \u003c #reduced[{:status :ready, :val [1 2]} 0x517a7e8e]\n```\n\n[Link to a complete solution](solution/my-cat-step4.clj)\n\n* Provide an idiomatic equivalent to `my-cat`.\n\n[Link to the idiomatic solution](solution/my-cat-step5.clj)\n\n## A.D.D. transducer\n\n* Implement a transducer that daydream during a number of elements. While in the daydream state, it buffers its input. When it stops daydreaming, it processes all of its buffer as a batch, then daydreams again.\n\nThe `a-d-d` transducer never loses data.\n\n```clojure\n(into [] (a-d-d 3) (range 10))\n; =\u003e [0 1 2 3 4 5 6 7 8 9]\n\n; Try:\n(slow-into [] (comp (debug 0)\n                    (a-d-d 3)\n                    (debug 2))\n           (range 10))\n```\n\n[Link to a partial solution](solution/a-d-d-step1.clj)\n\n* Verify that it works well with early termination.\n\n```clojure\n(slow-into [] (comp (debug 0)\n                    (a-d-d 3)\n                    (debug 2)\n                    (take 5))\n           (range 10))\n; =\u003e [0 1 2 3 4]\n```\n\n[Link to a complete solution](solution/a-d-d-step2.clj)\n\n* Change `a-d-d` so that it works similarly to `clojure.core/partition-all`.\n\n```clojure\n(into [] (a-d-d 3) (range 10))\n; =\u003e [[0 1 2] [3 4 5] [6 7 8] [9]]\n```\n\n[Link to a partition-all '-ish' solution](solution/a-d-d-step3.clj)\n\n* Verify that it works well with early termination.\n\n```clojure\n(slow-into [] (comp (debug 0)\n                    (a-d-d 3)\n                    (debug 2)\n                    (take 2))\n           (range 10))\n; =\u003e [[0 1 2] [3 4 5]]\n```\n\n## A reducer inside a transducer\n\nImplement `serieduce` which provides a transducer which reduces incoming elements and emits all intermediary elements.\n\n```clojure\n(into [] (serieduce conj [1 2]) (range 3 6))\n; =\u003e [[1 2 3] [1 2 3 4] [1 2 3 4 5]]\n\n(into [] (serieduce +) (range 5))\n; =\u003e [0 1 3 6 10]\n```\n\n[Link to a partial solution](solution/serieduce-step1.clj)\n\n[Link to a complete solution](solution/serieduce-step2.clj)\n\n## Congratulations if you made it that far!\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"img/success_kid.png\" alt=\"Success Kid\"\u003e\n\u003c/p\u003e\n\nYou are one of a few.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgreen-coder%2Ftransducer-exercises","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgreen-coder%2Ftransducer-exercises","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgreen-coder%2Ftransducer-exercises/lists"}