{"id":13400245,"url":"https://github.com/joboccara/pipes","last_synced_at":"2025-03-14T06:31:36.739Z","repository":{"id":41445858,"uuid":"82485377","full_name":"joboccara/pipes","owner":"joboccara","description":"Pipelines for expressive code on collections in C++","archived":false,"fork":false,"pushed_at":"2022-01-01T01:14:06.000Z","size":1044,"stargazers_count":792,"open_issues_count":21,"forks_count":78,"subscribers_count":35,"default_branch":"master","last_synced_at":"2024-07-31T19:23:47.528Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C++","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/joboccara.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}},"created_at":"2017-02-19T19:55:17.000Z","updated_at":"2024-07-31T09:30:14.000Z","dependencies_parsed_at":"2022-08-01T00:17:54.735Z","dependency_job_id":null,"html_url":"https://github.com/joboccara/pipes","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joboccara%2Fpipes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joboccara%2Fpipes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joboccara%2Fpipes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joboccara%2Fpipes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joboccara","download_url":"https://codeload.github.com/joboccara/pipes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243537637,"owners_count":20307098,"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","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":[],"created_at":"2024-07-30T19:00:49.886Z","updated_at":"2025-03-14T06:31:36.429Z","avatar_url":"https://github.com/joboccara.png","language":"C++","funding_links":["https://www.patreon.com/join/fluentcpp?"],"categories":["GPU computing","Miscellaneous","C++","Recently Updated"],"sub_categories":["[Oct 31, 2024](/content/2024/10/31/README.md)"],"readme":"\u003cp\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/cpp_pipes.png\"/\u003e\u003c/p\u003e\n\n[![Build Status](https://travis-ci.org/joboccara/pipes.svg?branch=master)](https://travis-ci.org/joboccara/pipes)\n![GitHub](https://img.shields.io/github/license/joboccara/pipes)\n\n\u003ca href=\"https://www.patreon.com/join/fluentcpp?\"\u003e\u003cimg alt=\"become a patron\" src=\"https://c5.patreon.com/external/logo/become_a_patron_button.png\" height=\"35px\"\u003e\u003c/a\u003e\n\nPipes are small components for writing expressive code when working on collections. Pipes chain together into a pipeline that receives data from a source, operates on that data, and send the results to a destination.\n\nThis is a header-only library, implemented in C++14.\n\nThe library is under development and subject to change. Contributions are welcome. You can also [log an issue](https://github.com/joboccara/pipes/issues) if you have a wish for enhancement or if you spot a bug.\n\n# Contents\n\n* [A First Example](#a-first-example)\n* [A Second Example](#a-second-example)\n* [Doesn't it look like ranges?](#doesnt-it-look-like-ranges)\n* [Operating on several collections](#operating-on-several-collections)\n* [End pipes](#end-pipes)\n* [Easy integration with STL algorithms](#easy-integration-with-stl-algorithms)\n* [Streams support](#streams-support)\n* [List of available pipes](#list-of-available-pipes)\n\n# A First Example\n\nHere is a simple example of a pipeline made of two pipes: `transform` and `filter`:\n\n```cpp\nauto const source = std::vector\u003cint\u003e{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\nauto destination = std::vector\u003cint\u003e{};\n\nsource \u003e\u003e= pipes::filter([](int i){ return i % 2 == 0; })\n       \u003e\u003e= pipes::transform([](int i){ return i * 2; })\n       \u003e\u003e= pipes::push_back(destination);\n\n// destination contains {0, 4, 8, 12, 16};\n```\n### What's going on here:\n* Each elements of  `source` is sent to `filter`.\n* Every time `filter` receives a piece of data, it sends its to the next pipe (here, `transform`) only if that piece of data satisfies `filter`'s' predicate.\n* `transform` then applies its function on the data its gets and sends the result to the next pipe (here, `pipes::push_back`).\n* `pipes::push_back` `push_back`s the data it receives to its `vector` (here, `destination`).\n\n# A Second Example\n\nHere is a more elaborate example with a pipeline that branches out in several directions:\n\n```cpp\nA \u003e\u003e= pipes::transform(f)\n  \u003e\u003e= pipes::filter(p)\n  \u003e\u003e= pipes::unzip(pipes::push_back(B),\n                   pipes::fork(pipes::push_back(C),\n                               pipes::filter(q) \u003e\u003e= pipes::push_back(D),\n                               pipes::filter(r) \u003e\u003e= pipes::push_back(E));\n```\n\nHere, `unzip` takes the `std::pair`s or `std::tuple`s it receives and breaks them down into individual elements. It sends each element to the pipes it takes (here `pipes::push_back` and `fork`).\n\n`fork` takes any number of pipes and sends the data it receives to each of them.\n\nSince data circulates through pipes, real life pipes and plumbing provide a nice analogy (which gave its names to the library). For example, the above pipeline can be graphically represented like this:\n \n \u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/pipeline.png\"/\u003e\u003c/p\u003e\n\n# Doesn't it look like ranges?\n\nPipes sort of look like [ranges](https://github.com/ericniebler/range-v3) adaptors from afar, but those two libraries have very different designs.\n\nRange views are about adapting ranges with view layers, and reading through those layers in lazy mode. Ranges are \"pull based\", in that components ask for the next value.\nPipes are about sending pieces of data as they come along in a collection through a pipeline, and let them land in a destination. Pipes are \"push based\", in that components wait for the next value.  \n\nRanges and pipes have overlapping components such as `transform` and `filter`. But pipes do things like ranges can't do, such as `pipes::mux`, `pipes::fork` and `pipes:unzip`, and ranges do things that pipes can't do, like infinite ranges.\n\nIt is possible to use ranges and pipes in the same expression though:\n\n```cpp\nranges::view::zip(dadChromosome, momChromosome)\n    \u003e\u003e= pipes::transform(crossover) // crossover takes and returns a tuple of 2 elements\n    \u003e\u003e= pipes::unzip(pipes::push_back(gameteChromosome1),\n                     pipes::push_back(gameteChromosome2));\n```\n\n# Operating on several collections\n\nThe pipes library allows to manipulate several collections at the same time, with the `pipes::mux` helper.\nNote that contrary to `range::view::zip`, `pipes::mux` doesn't require to use tuples:\n\n```cpp\nauto const input1 = std::vector\u003cint\u003e{1, 2, 3, 4, 5};\nauto const input2 = std::vector\u003cint\u003e{10, 20, 30, 40, 50};\n\nauto results = std::vector\u003cint\u003e{};\n\npipes::mux(input1, input2) \u003e\u003e= pipes::filter   ([](int a, int b){ return a + b \u003c 40; })\n                           \u003e\u003e= pipes::transform([](int a, int b) { return a * b; })\n                           \u003e\u003e= pipes::push_back(results);\n\n// results contains {10, 40, 90}\n```\n\n## Operating on all the possible combinations between several collections\n\n`pipes::cartesian_product` takes any number of collections, and generates all the possible combinations between the elements of those collections. It sends each combination successively to the next pipe after it.\n\nLike `pipes::mux`, `pipes::cartesian_product` doesn't use tuples but sends the values directly to the next pipe:\n\n```cpp\nauto const inputs1 = std::vector\u003cint\u003e{1, 2, 3};\nauto const inputs2 = std::vector\u003cstd::string\u003e{\"up\", \"down\"};\n\nauto results = std::vector\u003cstd::string\u003e{};\n\npipes::cartesian_product(inputs1, inputs2)\n    \u003e\u003e= pipes::transform([](int i, std::string const\u0026 s){ return std::to_string(i) + '-' + s; })\n    \u003e\u003e= pipes::push_back(results);\n\n// results contains {\"1-up\", \"1-down\", \"2-up\", \"2-down\", \"3-up\", \"3-down\"}\n```\n\n## Operating on adjacent elements of a collection\n\n`pipes::adjacent` allows to send adjacent pairs of element from a range to a pipeline:\n\n```cpp\nauto const input = std::vector\u003cint\u003e{1, 2, 4, 7, 11, 16};\n\nauto results = std::vector\u003cint\u003e{};\n\npipes::adjacent(input)\n    \u003e\u003e= pipes::transform([](int a, int b){ return b - a; })\n    \u003e\u003e= pipes::push_back(results);\n\n// result contains {1, 2, 3, 4, 5};\n```\n\n## Operating on all combinations of elements of one collection\n\n`pipes::combinations` sends each possible couple of different elements of a range to a pipeline:\n\n```cpp\nauto const inputs = std::vector\u003cint\u003e{ 1, 2, 3, 4, 5 };\n\nauto results = std::vector\u003cstd::pair\u003cint, int\u003e\u003e{};\n\n pipes::combinations(inputs)\n     \u003e\u003e= pipes::transform([](int i, int j){ return std::make_pair(i, j); })\n     \u003e\u003e= pipes::push_back(results);\n     \n /*\n results contains:\n {\n    { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 },\n              { 2, 3 }, { 2, 4 }, { 2, 5 },\n                        { 3, 4 }, { 3, 5 },\n                                  { 4, 5 }\n }\n /*\n```\n\n# End pipes\n\nThis library also provides end pipes, which are components that send data to a collection in an elaborate way. For example, the `map_aggregate`  pipe receives `std::pair\u003cKey, Value\u003e`s and adds them to a map with the following rule:\n* if its key is not already in the map, insert the incoming pair in the map,\n* otherwise, aggregate the value of the incoming pair with the existing one in the map.\n\nExample:\n\n```cpp\nstd::map\u003cint, std::string\u003e entries = { {1, \"a\"}, {2, \"b\"}, {3, \"c\"}, {4, \"d\"} };\nstd::map\u003cint, std::string\u003e entries2 = { {2, \"b\"}, {3, \"c\"}, {4, \"d\"}, {5, \"e\"} };\nstd::map\u003cint, std::string\u003e results;\n\n// results is empty\n\nentries \u003e\u003e= pipes::map_aggregator(results, concatenateStrings);\n\n// the elements of entries have been inserted into results\n\nentries2 \u003e\u003e= pipes::map_aggregator(results, concatenateStrings);\n\n// the new elements of entries2 have been inserter into results, the existing ones have been concatenated with the new values \n// results contains { {1, \"a\"}, {2, \"bb\"}, {3, \"cc\"}, {4, \"dd\"}, {5, \"e\"} }\n```\n\nAll components are located in the namespace `pipes`.\n\n# Easy integration with STL algorithms\n\nAll pipes can be used as output iterators of STL algorithms:\n\n```cpp\nstd::set_difference(begin(setA), end(setA),\n                    begin(setB), end(setB),\n                    transform(f) \u003e\u003e= filter(p) \u003e\u003e= map_aggregator(results, addValues));\n```\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/pipes-STL-algos.png\"/\u003e\u003c/p\u003e\n\n# Streams support\n\nThe contents of an input stream can be sent to a pipe by using `read_in_stream`.\nThe end pipe `to_out_stream` sends data to an output stream.\n\nThe following example reads strings from the standard input, transforms them to upper case, and sends them to the standard output:\n\n```cpp\nstd::cin \u003e\u003e= pipes::read_in_stream\u003cstd::string\u003e{}\n         \u003e\u003e= pipes::transform(toUpper)\n         \u003e\u003e= pipes::to_out_stream(std::cout);\n```\n\n# List of available pipes\n\n* [General pipes](#general-pipes)\n    * [`dev_null`](#dev_null)\n    * [`drop`](#drop)\n    * [`drop_while`](#drop_while)\n    * [`filter`](#filter)\n    * [`fork`](#fork)\n    * [`partition`](#partition)\n    * [`read_in_stream`](#read_in_stream)\n    * [`switch`](#switch)\n    * [`stride`](#stride)\n    * [`take`](#take)\n    * [`tee`](#tee)\n    * [`transform`](#transform)\n    * [`unzip`](#unzip)\n* [End pipes](#end-pipes-1)\n    * [`for_each`](#for_each)\n    * [`map_aggregator`](#map_aggregator)\n    * [`override`](#override)\n    * [`push_back`](#push_back)\n    * [`set_aggregator`](#set_aggregator)\n    * [`insert`](#insert)\n    * [`to_out_stream`](#to_out_stream)\n\n## General pipes\n\n### `dev_null`\n\n`dev_null` is a pipe that doesn't do anything with the value it receives. It is useful for selecting only some data coming out of an algorithm that has several outputs.\nAn example of such algorithm is [`set_segregate`](https://github.com/joboccara/sets):\n\n```cpp\nstd::set\u003cint\u003e setA = {1, 2, 3, 4, 5};\nstd::set\u003cint\u003e setB = {3, 4, 5, 6, 7};\n\nstd::vector\u003cint\u003e inAOnly;\nstd::vector\u003cint\u003e inBoth;\n\nsets::set_seggregate(setA, setB,\n                     pipes::push_back(inAOnly),\n                     pipes::push_back(inBoth),\n                     dev_null{});\n\n// inAOnly contains {1, 2}\n// inBoth contains {3, 4, 5}\n```\n\n### `drop`\n\n`drop` is a pipe that ignores the first N incoming values, and sends on the values after them to the next pipe:\n\n```cpp\nauto const input = std::vector\u003cint\u003e{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\nauto result = std::vector\u003cint\u003e{};\n\ninput \u003e\u003e= pipes::drop(5)\n      \u003e\u003e= pipes::push_back(result);\n\n// result contains { 6, 7, 8, 9, 10 }\n```\n\n### `drop_while`\n\n`drop` is a pipe that ignores the incoming values until they stop satisfying a predicate, and sends on the values after them to the next pipe:\n\n```cpp\nauto const input = std::vector\u003cint\u003e{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\nauto result = std::vector\u003cint\u003e{};\n\ninput \u003e\u003e= pipes::drop_while([](int i){ return i != 6; })\n      \u003e\u003e= pipes::push_back(result);\n\n// result contains { 6, 7, 8, 9, 10 }\n```\n\n### `filter`\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/filter_pipe.png\"/\u003e\u003c/p\u003e\n\n`filter` is a pipe that takes a predicate `p` and, when it receives a value `x`, sends the result on to the next pipe iif `p(x)` is `true`.\n\n```cpp\nstd::vector\u003cint\u003e input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\nstd::vector\u003cint\u003e results;\n\ninput \u003e\u003e= pipes::filter([](int i){ return i % 2 == 0; })\n      \u003e\u003e= pipes::push_back(results);\n\n// results contains {2, 4, 6, 8, 10}\n```\n\n### `fork`\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/fork_pipe.png\"/\u003e\u003c/p\u003e\n\n`fork` is a pipe that takes any number of pipes, and sends a copy of the values it receives to each of those pipes.\n\n```cpp\nstd::vector\u003cint\u003e input = {1, 2, 3, 4, 5};\nstd::vector\u003cint\u003e results1;\nstd::vector\u003cint\u003e results2;\nstd::vector\u003cint\u003e results3;\n\ninput \u003e\u003e= pipes::fork(pipes::push_back(results1),\n                       pipes::push_back(results2),\n                       pipes::push_back(results3));\n\n// results1 contains {1, 2, 3, 4, 5}\n// results2 contains {1, 2, 3, 4, 5}\n// results3 contains {1, 2, 3, 4, 5}\n```\n\n### `join`\n\nThe `join` pipe receives collection and sends each element of each of those collections to the next pipe:\n\n```cpp\nauto const input = std::vector\u003cstd::vector\u003cint\u003e\u003e{ {1, 2}, {3, 4}, {5, 6} };\nauto results = std::vector\u003cint\u003e{};\n\ninput \u003e\u003e= pipes::join \u003e\u003e= pipes::push_back(results);\n\n// results contain {1, 2, 3, 4, 5, 6}\n```\n\n### `partition`\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/partition_pipe.png\"/\u003e\u003c/p\u003e\n\n`partition` is a pipe that takes a predicate `p` and two other pipes. When it receives a value `x`, sends the result on to the first pipe iif `p(x)` is `true`, and to the second pipe if `p(x)` is `false`.\n\n```cpp\nstd::vector\u003cint\u003e input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\nstd::vector\u003cint\u003e evens;\nstd::vector\u003cint\u003e odds;\n\ninput \u003e\u003e= pipes::partition([](int n){ return n % 2 == 0; },\n                           pipes::push_back(evens),\n                           pipes::push_back(odds));\n\n// evens contains {2, 4, 6, 8, 10}\n// odds contains {1, 3, 5, 7, 9}\n```\n\n### `read_in_stream`\n\n`read_in_stream` is a template pipe that reads from an input stream. The template parameter indicates what type of data to request from the stream:\n\n```cpp\nauto const input = std::string{\"1.1 2.2 3.3\"};\n\nstd::istringstream(input) \u003e\u003e= pipes::read_in_stream\u003cdouble\u003e{}\n                          \u003e\u003e= pipes::transform([](double d){ return d * 10; })\n                          \u003e\u003e= pipes::push_back(results);\n\n// results contain {11, 22, 33};\n```\n\n### `switch`\n\n`switch_` is a pipe that takes several `case_` branches. Each branch contains a predicate and a pipe. When it receives a value, it tries it successively on the predicates of each branch, and sends the value on to the pipe of the first branch where the predicate returns `true`.\nThe `default_` branch is equivalent to one that takes a predicate that returns always `true`. Having a `default_` branch is not mandatory.\n\n```cpp\nstd::vector\u003cint\u003e numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\nstd::vector\u003cint\u003e multiplesOf4;\nstd::vector\u003cint\u003e multiplesOf3;\nstd::vector\u003cint\u003e rest;\n\nnumbers \u003e\u003e= pipes::switch_(pipes::case_([](int n){ return n % 4 == 0; }) \u003e\u003e= pipes::push_back(multiplesOf4),\n                           pipes::case_([](int n){ return n % 3 == 0; }) \u003e\u003e= pipes::push_back(multiplesOf3),\n                           pipes::default_ \u003e\u003e= pipes::push_back(rest) ));\n\n// multiplesOf4 contains {4, 8};\n// multiplesOf3 contains {3, 6, 9};\n// rest contains {1, 2, 5, 7, 10};\n```\n\n### `stride`\n\n`stride` is a pipe that sends every `N`\u003csup\u003eth\u003c/sup\u003e element starting from the first one. Hence `N-1` elements after every `N`\u003csup\u003eth\u003c/sup\u003e element are ignored\n\n```cpp\nauto const input = std::vector\u003cint\u003e{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\nauto result = std::vector\u003cint\u003e{};\n\ninput \u003e\u003e= pipes::stride(3)\n      \u003e\u003e= pipes::push_back(result);\n      \n// result contains {1, 4, 7, 10}\n```\n\n### `take`\n\n`take` takes a number `N` and sends to the next pipe the first `N` element that it receives. The elements after it are ignored:\n\n```cpp\nauto const input = std::vector\u003cint\u003e{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\nauto result = std::vector\u003cint\u003e{};\n\ninput \u003e\u003e= pipes::take(6)\n      \u003e\u003e= pipes::push_back(result);\n      \n// result contains {1, 2, 3, 4, 5, 6}\n```\n\n### `take_while`\n\n`take_while` takes a predicate and sends to the next pipe the first values it receives. It stops when one of them doesn't satisfy the predicate:\n\n```cpp\nauto const input = std::vector\u003cint\u003e{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\nauto result = std::vector\u003cint\u003e{};\n\ninput \u003e\u003e= pipes::take_while([](int i){ return i != 7; })\n      \u003e\u003e= pipes::push_back(result);\n\n// result contains {1, 2, 3, 4, 5, 6}\n```\n\n### `tee`\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/tee_pipe.png\"/\u003e\u003c/p\u003e\n\n`tee` is a pipe that takes one other pipe, and sends a copy of the values it receives to each of these pipes before sending them on to the next pipe.\nLike the `tee` command on UNIX, this pipe is useful to take a peek at intermediary results.\n\n```cpp\nauto const inputs = std::vector\u003cint\u003e{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\nauto intermediaryResults = std::vector\u003cint\u003e{};\nauto results = std::vector\u003cint\u003e{};\n\ninputs\n    \u003e\u003e= pipes::transform([](int i) { return i * 2; })\n    \u003e\u003e= pipes::tee(pipes::push_back(intermediaryResults))\n    \u003e\u003e= pipes::filter([](int i){ return i \u003e= 12; })\n    \u003e\u003e= pipes::push_back(results);\n\n// intermediaryResults contains {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}\n// results contains {12, 14, 16, 18, 20}\n```\n\n### `transform`\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/transform_pipe.png\"/\u003e\u003c/p\u003e\n\n`transform` is a pipe that takes a function `f` and, when it receives a value, applies `f` on it and sends the result on to the next pipe.\n\n```cpp\nstd::vector\u003cint\u003e input = {1, 2, 3, 4, 5};\nstd::vector\u003cint\u003e results;\n\ninput \u003e\u003e= pipes::transform([](int i) { return i*2; })\n      \u003e\u003e= pipes::push_back(results);\n\n// results contains {2, 4, 6, 8, 10}\n```\n\n### `unzip`\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/joboccara/pipes/blob/readme/docs/unzip_pipe.png\"/\u003e\u003c/p\u003e\n\n`unzip` is a pipe that takes N other pipes. When it receives a `std::pair` or `std::tuple` of size N (for `std::pair` N is 2), it sends each of its components to the corresponding output pipe:\n\n```cpp\nstd::map\u003cint, std::string\u003e entries = { {1, \"one\"}, {2, \"two\"}, {3, \"three\"}, {4, \"four\"}, {5, \"five\"} };\nstd::vector\u003cint\u003e keys;\nstd::vector\u003cstd::string\u003e values;\n\nentries \u003e\u003e= pipes::unzip(pipes::push_back(keys),\n                         pipes::push_back(values)));\n\n// keys contains {1, 2, 3, 4, 5};\n// values contains {\"one\", \"two\", \"three\", \"four\", \"five\"};\n```\n\n## End pipes\n\n### `for_each`\n\n`for_each` takes a function (or function object) that sends to the data it receives to that function. One of its usages is to give legacy code that does not use STL containers access to STL algorithms:\n\n```cpp\nstd::vector\u003cint\u003e input = {1, 2, 3, 4, 5, 6, 7 ,8, 9, 10};\n\nvoid legacyInsert(int number, DarkLegacyStructure const\u0026 thing); // this function inserts into the old non-STL container\n\nDarkLegacyStructure legacyStructure = // ...\n\nstd::copy(begin(input), end(input), for_each([\u0026legacyStructure](int number){ legacyInsert(number, legacyStructure); });\n```\n\nRead the [full story](https://www.fluentcpp.com/2017/11/24/how-to-use-the-stl-in-legacy-code/) about making legacy code compatible with the STL.\n\nNote that `for_each` goes along with a helper function object, `do_`, that allows to perfom several actions sequentially on the output of the algorithm:\n\n```cpp\nstd::copy(begin(input), end(input), pipes::for_each(pipes::do_([\u0026](int i){ results1.push_back(i*2);}).\n                                                           then_([\u0026](int i){ results2.push_back(i+1);}).\n                                                           then_([\u0026](int i){ results3.push_back(-i);})));\n\n```\n\n### `insert`\n\nIn the majority of cases where it is used in algoritms, `std::inserter` forces its user to provide a position. It makes sense for un-sorted containers such as `std::vector`, but for sorted containers such as `std::set` we end up choosing begin or end by default, which doesn't make sense:\n\n```cpp\nstd::vector\u003cint\u003e v = {1, 3, -4, 2, 7, 10, 8};\nstd::set\u003cint\u003e results;\nstd::copy(begin(v), end(v), std::inserter(results, end(results)));\n```\n\n`insert` removes this constraint by making the position optional. If no hint is passed, the containers is left to determine the correct position to insert:\n\n```cpp\nstd::vector\u003cint\u003e v = {1, 3, -4, 2, 7, 10, 8};\nstd::set\u003cint\u003e results;\nstd::copy(begin(v), end(v), insert(results));\n\n//results contains { -4, 1, 2, 3, 7, 8, 10 }\n```\nRead the [full story](https://www.fluentcpp.com/2017/03/17/smart-iterators-for-inserting-into-sorted-container/) about `insert`.\n\n### `map_aggregator`\n\n`map_aggregator` provides the possibility to embark an aggregator function in the inserter iterator, so that new elements whose **key is already present in the map** can be merged with the existent (e.g. have their values added together).\n\n```cpp\nstd::vector\u003cstd::pair\u003cint, std::string\u003e\u003e entries = { {1, \"a\"}, {2, \"b\"}, {3, \"c\"}, {4, \"d\"} };\nstd::vector\u003cstd::pair\u003cint, std::string\u003e\u003e entries2 = { {2, \"b\"}, {3, \"c\"}, {4, \"d\"}, {5, \"e\"} };\nstd::map\u003cint, std::string\u003e results;\n\nstd::copy(entries.begin(), entries.end(), map_aggregator(results, concatenateStrings));\nstd::copy(entries2.begin(), entries2.end(), map_aggregator(results, concatenateStrings));\n\n// results contains { {1, \"a\"}, {2, \"bb\"}, {3, \"cc\"}, {4, \"dd\"}, {5, \"e\"} }\n```\n\n`set_aggreagator` provides a similar functionality for aggregating elements into sets.\n\nRead the [full story](https://www.fluentcpp.com/2017/03/21/smart-iterator-aggregating-new-elements-existing-ones-map-set/) about `map_aggregator` and `set_aggregator`.\n\n### `override`\n\n`override` is the pipe equivalent to calling `begin` on an existing collection. The data that `override` receives overrides the first element of the container, then the next, and so on:\n\n```cpp\nstd::vector\u003cint\u003e input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\nstd::vector\u003cint\u003e results = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n\ninput \u003e\u003e= pipes::filter([](int i){ return i % 2 == 0; })\n      \u003e\u003e= pipes::override(results);\n\n// results contains {2, 4, 6, 8, 10, 0, 0, 0, 0, 0};\n```\n\n`override` can also write on a specifc data member instead of erasing the complete structure in the outputs:\n\n```cpp\nstruct P\n{\n    int x = 0;\n    int y = 0;\n};\n\nauto const xs = std::vector\u003cint\u003e{1, 2, 3, 4, 5};\nauto results = std::vector\u003cP\u003e(5);\n\nxs \u003e\u003e= pipes::override(results, \u0026P::x);\n\n// results now contains { {1,0}, {2,0}, {3,0}, {4,0}, {5,0} }\n```\n\n`override` can also send data to a specific setter function of the outputs:\n\n```cpp\nstruct P\n{\n    int x = 0;\n    int y = 0;\n    \n    void setX(int aX){ x = aX; }\n};\n\nauto const xs = std::vector\u003cint\u003e{1, 2, 3, 4, 5};\nauto results = std::vector\u003cP\u003e(5);\n\nxs \u003e\u003e= pipes::override(results, \u0026P::setX);\n\n// results now contains { {1,0}, {2,0}, {3,0}, {4,0}, {5,0} }\n```\n\n### `push_back`\n\n`push_back` is a pipe that is equivalent to `std::back_inserter`. It takes a collection that has a `push_back` member function, such as a `std::vector`, and `push_back`s the values it receives into that collection.\n\n### `set_aggregator`\n\nLike `map_aggregator`, but inserting/aggregating into `std::set`s. Since `std::set` values are `const`, this pipe erases the element and re-inserts the aggregated value into the `std::set`.\n\n```cpp\nstruct Value\n{\n    int i;\n    std::string s;\n};\n\nbool operator==(Value const\u0026 value1, Value const\u0026 value2)\n{\n    return value1.i == value2.i \u0026\u0026 value1.s == value2.s;\n}\n\nbool operator\u003c(Value const\u0026 value1, Value const\u0026 value2)\n{\n    if (value1.i \u003c value2.i) return true;\n    if (value2.i \u003c value1.i) return false;\n    return value1.s \u003c value2.s;\n}\n\nValue concatenateValues(Value const\u0026 value1, Value const\u0026 value2)\n{\n    if (value1.i != value2.i) throw std::runtime_error(\"Incompatible values\");\n    return { value1.i, value1.s + value2.s };\n}\n\nint main()\n{\n    std::vector\u003cValue\u003e entries = { Value{1, \"a\"}, Value{2, \"b\"}, Value{3, \"c\"}, Value{4, \"d\"} };\n    std::vector\u003cValue\u003e entries2 = { Value{2, \"b\"}, Value{3, \"c\"}, Value{4, \"d\"}, Value{5, \"e\"} };\n    std::set\u003cValue\u003e results;\n\n    std::copy(entries.begin(), entries.end(), pipes::set_aggregator(results, concatenateValues));\n    std::copy(entries2.begin(), entries2.end(), pipes::set_aggregator(results, concatenateValues));\n\n    // results contain { Value{1, \"a\"}, Value{2, \"bb\"}, Value{3, \"cc\"}, Value{4, \"dd\"}, Value{5, \"e\"} }\n}\n```\n\n### `to_out_stream`\n\n`to_out_stream` takes an output stream and sends incoming to it:\n\n```cpp\nauto const input = std::vector\u003cstd::string\u003e{\"word1\", \"word2\", \"word3\"};\n\ninput \u003e\u003e= pipes::transform(toUpper)\n      \u003e\u003e= pipes::to_out_stream(std::cout);\n\n// sends \"WORD1WORD2WORD3\" to the standard output\n```\n\n\u003ca href=\"https://www.patreon.com/join/fluentcpp?\"\u003e\u003cimg alt=\"become a patron\" src=\"https://c5.patreon.com/external/logo/become_a_patron_button.png\" height=\"35px\"\u003e\u003c/a\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoboccara%2Fpipes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoboccara%2Fpipes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoboccara%2Fpipes/lists"}