{"id":13448559,"url":"https://github.com/ClickerMonkey/iteratez","last_synced_at":"2025-03-22T09:31:28.334Z","repository":{"id":34648546,"uuid":"181946648","full_name":"ClickerMonkey/iteratez","owner":"ClickerMonkey","description":"A powerful functional iterator, transformer, and mutator - like Underscore.js, except for everything","archived":false,"fork":false,"pushed_at":"2022-12-08T22:43:36.000Z","size":2237,"stargazers_count":14,"open_issues_count":16,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-06T23:32:31.045Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/ClickerMonkey.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":"2019-04-17T18:24:07.000Z","updated_at":"2019-10-01T08:31:33.000Z","dependencies_parsed_at":"2023-01-15T08:20:40.629Z","dependency_job_id":null,"html_url":"https://github.com/ClickerMonkey/iteratez","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickerMonkey%2Fiteratez","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickerMonkey%2Fiteratez/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickerMonkey%2Fiteratez/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickerMonkey%2Fiteratez/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ClickerMonkey","download_url":"https://codeload.github.com/ClickerMonkey/iteratez/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244937751,"owners_count":20535124,"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-31T05:01:49.063Z","updated_at":"2025-03-22T09:31:27.305Z","avatar_url":"https://github.com/ClickerMonkey.png","language":"TypeScript","readme":"# iteratez\n\nA powerful functional iterator, transformer, and mutator. \n\nOut of the box you can iterate over arrays, objects, trees, sets, maps, linked-lists, iterables - and you can provide iteration capabilites to your own code no matter how complex the type (dynamically calculated, etc).\n\nThe iterator is lazy, so you can chain \"views\" and iteration is not done until you perform \"operations\" or \"mutations\" to the underlying source.\n\n## Features\n\n- Array, object, tree, set, map, linked-list, and iterables out of the box.\n- Iteration is lazy, so iteration is only done when it absolutely needs to be.\n- Some [operations](#operations) can exit early and cease iteration saving time and resources.\n- When iterating, you can stop at any time.\n- If the underlying source supports it, [remove](#mutations) a value.\n- If the underlying source supports it, [replace](#mutations) a value.\n- You can chain [views](#views) which don't cause iteration until an [operation](#operations) or [mutation](#mutations) are called.\n- You can call [mutations](#mutations) to affect the underlying source.\n- You can call [operations](#operations) to iterate and produce a result.\n- You can create a [reusable function](#reusable-function) to perform operations repeatedly.\n- [Create your own iterator.](#custom-iterators)\n\nYou can see all of these features in the [examples](#examples) below.\n\n### Views\nReturns an iterator...\n- `where`: for a subset of the values.\n- `not`: for a subset of the values that don't pass test (opposite of where).\n- `transform`: that transforms the values to another type.\n- `reverse`: that iterates over the values in reverse order.\n- `exclude`: that excludes values found in another iterator.\n- `intersect`: that has common values in another iterator.\n- `sorted`: that is sorted based on some comparison.\n- `shuffle`: that is randomly ordered.\n- `unique`: that has only unique values.\n- `duplicates`: that has all the duplicate values.\n- `readonly`: that ignores mutations.\n- `keys`: only for the keys of the values (replace not supported).\n- `values`: only for the values (new key is index based).\n- `take`: that only iterates over the first X values.\n- `skip`: that skips the first X values.\n- `drop`: that drops off the last X values.\n- `append`: that is the original iterator + one or more iterators specified.\n- `prepend`: that is one or more iterators specified + the original iterator.\n- `gt`: that only has values greater than a value.\n- `gte`: that only has values greater than or equal to a value.\n- `lt`: that only has values less than a value.\n- `lte`: that only has values less than or equal to a value.\n- `fork`: that is this, but allows a function to perform fork operations\n- `split`: Splits the values into two iterators (pass/fail) based on a condition.\n- `unzip`: Splits the view into two iterates (keys/values).\n\n### Mutations\n- `delete`: Removes values in the view from the source.\n- `overwrite`: Replaces values in the view from the source with a constant replacement.\n- `update`: Replace values in the view from the source with a dynamic replacement.\n- `extract`: Removes values in the view from the source and returns a new iterator with the removed values.\n\n### Operations\n- `empty`: Determines if view contains zero values.\n- `has`: Determines if the view contains any values.\n- `contains`: Determines if the view contains a specific value.\n- `first`: Gets the first value in the view.\n- `last`: Gets the last value in the view.\n- `count`: Counts the number of values in the view.\n- `array`: Builds an array of the values in the view.\n- `set`: Builds a Set of the values in the view.\n- `object`: Builds an object of the values in the view.\n- `entries`: Builds an array of `[key, value]` in the view.\n- `map`: Builds a Map of the values and keys in the view.\n- `group`: Builds an object of value arrays grouped by a value derived from each value.\n- `reduce`: Reduces the values in the view down to a single value.\n- `min`: Returns the minimum value in the view.\n- `max`: Returns the maximum value in the view.\n- `iterate`: Invokes a function for each value in the view.\n- `copy`: Copies the values in the view and returns a new iterator.\n- `changes`: Notifies you when values are added, removed, or still present on an iterator since the last time called.\n\n### Comparison Logic\nThe following chainable functions define how values should be compared.\n\n- `numbers`: Set number comparison logic to the iterator.\n- `strings`: Set string comparison logic to the iterator.\n- `dates`: Set date comparison logic to the iterator.\n- `desc`: Reverses the comparison logic.\n- `withEquality`: Set a custom equality function.\n- `withComparator`: Set a custom comparison function.\n\n### Reset\nThe following function(s) allow you to change the source for iteration.\n\n- `reset`: Sets a new source to iterate.\n\n### Other Functions\nThe following static functions exist to help iterate simple sources:\n\n- `Iterate.array`: Iterates an array.\n- `Iterate.object`: Iterates the properties of an object, optionally just the properties explicitly set on the object.\n- `Iterate.tree`: Iterates trees.\n- `Iterate.linked`: Iterates linked-lists.\n- `Iterate.map`: Iterates Maps\n- `Iterate.set`: Iterates Sets\n- `Iterate.join`: Returns an iterator that iterates over one or more iterators.\n- `Iterate.zip`: Combines a key iterator and value iterator into one.\n- `Iterate.empty`: Iterates nothing.\n- `Iterate.entries`: Iterates an array of `[key, value]` entries.\n- `Iterate.iterable`: Iterates any collection that implements iterable.\n- `Iterate.hasEntries`: Iterates any object which has the `entries()` iterator.\n\n## Examples\nThe example is in Typescript, but iterator is available as `iz.Iterate` and the function whic dynamically returns an iterator is `iz.iterate` or simply `iz` in JS\n\n```typescript\nimport iz, { Iterate } from 'iteratez';\n\n// Creating an iterator\nlet source = iz([1, 5, 7, 9, 10]);\nlet source = iz({\n  name: 'ClickerMonkey',\n  age: 30\n});\nlet source = iz('string'); // each character\nlet source = iz(...source); // anything iterable\nlet source = iz([['key', 'value'], ['key', 'value']]); // [key, value] tuples \nlet source = iz(new Map()); // Map\u003cK, V\u003e\nlet source = iz(new Set()); // Set\u003cT\u003e\nlet source = iz(ReadonlyArray | ReadonlyMap | ReadonlySet | Int8Array | ...) // something that has entries() function\nlet source = Iterate.tree( ... )(head);\nlet source = Iterate.linked( ... )(head);\nlet source = yourSource.yourIteratorGenerator();\n\n// ============ ITERATION ============ \n\n// Stop\nsource.each((value, key, iter) =\u003e {\n  if (someCondition(value)) {\n    iter.stop(42)\n  }\n}).withResult((result) =\u003e {\n  // result = 42\n  // function only called with previous iteration stopped with a value\n});\n\n// Remove\n// - if the source is a sequential collection, it's removed from the sequence (array, object, etc)\n// - if the source is a tree, it removes it from the tree including it's children\n// - otherwise, up to the custom source\nsource.each((value, key, iter) =\u003e {\n  if (someCondition(value)) {\n    iter.remove();\n  }\n});\n\n// Replace\nsource.each((value, key, iter) =\u003e {\n  if (someCondition(value)) {\n    iter.replace(replacement);\n  }\n});\n\n// ============ Operations ============ \n// These are at the end of a chain of views\n\nlet empty = source.empty(); // boolean\nlet has = source.has(); // boolean\nlet contains = source.contains(2); // boolean\nlet first = source.first(); // T\nlet last = source.last(); // T\nlet count = source.count(); // number\nlet array = source.array(); // T[]\nlet array = source.array(dest); // T[]\nlet set = source.set(); // Set\u003cT\u003e\nlet object = source.object(value =\u003e value.id); // { [value.id]: value }\nlet object = source.object(value =\u003e value.id, dest);\nlet entries = source.entries(): // Array\u003c[K, T]\u003e\nlet map = source.map(); // Map\u003cT, K\u003e\nlet group = source.group(value =\u003e value.age); // { [age]: T[] }\nlet reduced = source.reduce(R, (T, R) =\u003e R); // R\nlet min = source.min(); // T\nlet max = source.max(); // T\nlet copy = source.copy(): // Iterate\u003cT\u003e\nlet that = source.changes(onAdd, onRemove, onPresent); // this\n\n// ============ Mutations ============ \n// These are at the end of a chain of views and they\n// take the values in the current iterator and affects the\n// underlying source.\n\nsource.delete(); // removes all values in iterator\nsource.where(x =\u003e x.id).delete(); // remove values without an ID\n\nsource.extract(); // does a delete and returns a new iterator with the removed values\n\nsource.overwrite(42); // replaces all values in iterator\nsource.where(x =\u003e x \u003e 34).overwrite(12); // replace all numbers over 34 with 12\n\nsource.update(x =\u003e x * 2); // multiply all numbers by 2\n\n// ============ Views ============ \n// These are chainable, at the end if you call an operation it performs\n// it only on the values in the iterator at that point. If you call\n// a mutation then it changes the underlying source but only on the\n// values in the view.\n\nsource.where(x =\u003e x.age \u003e 0); // values that past test\nsource.not(x =\u003e x.age \u003e 0); // values that don't pass test\nsource.transform(x =\u003e x.name); // values transformed to a new type\nsource.reverse(); // values in reverse\nsource.exclude(anotherSource); // not shared values\nsource.intersect(anotherSource); // shared values\nsource.sorted(comparator?); // sorted by a comparator\nsource.shuffle(times?); // randomly orders\nsource.unique(equality?); // unique values only\nsource.duplicates(onlyOnce?); // duplicate values only\nsource.readonly(); // all subsequent mutations are ignored\nsource.keys(); // just the keys (index based), delete mutation works\nsource.values(); // just the values (index based)\nsource.take(10); // first 10 values\nsource.skip(5); // after first 5 values\nsource.drop(3); // ignore last 3\nsource.append(anotherSource); // union of two\nsource.prepend(anotherSource); // union in reverse order\nsource.gt(value, comparator?); // all values greater than value\nsource.gte(value, comparator?); // all values greater/equal to value\nsource.lt(value, comparator?); // all values less than value\nsource.lte(value, comparator?); // all values less/equal to value\nsource.fork(f =\u003e f.where(x =\u003e !!x.male).delete()); // fork operation\nsource.split(x =\u003e x.male); // { pass, fail }\nsource.split(x =\u003e x.male, (pass, fail) =\u003e {}): // two iterators\nsource.unzip(); // { keys, values }\nsource.unzip((keys, values) =\u003e {}); // two iterators\n\n// ============ Logic ============ \n\n// comparator is used for max/min/sorted/gt/gte/lt/lte\n// also will set withEquality if not specified\nsource.withComparator((a, b) =\u003e number); \n\n// equality check used for contains/exclude/intersect/unique/duplicates\nsource.withEquality((a, b) =\u003e boolean);\n\n// Pre-defined logic\nsource.numbers(ascending?, nullsFirst?); // number logic\nsource.strings(sensitive?, ascending?, nullsFirst?); // string logic\nsource.dates(equalityTimespan?, utc?, ascending?, nullsFirst?); // date logic\nsource.desc(); // reverse comparison logic\n\n// ============ Reset ============ \nsource.reset(['a', 'new', 'source', 'to', 'iterate']);\n\n// ============ Examples ============ \n// Views ending with an operation or mutation.\n\nsource.duplicates().has(); // has duplicates?\nsource.duplicates().delete(); // remove duplicates\nsource.where(x =\u003e x.age \u003c 18).extract(); // remove \u003c 18yo\nsource.sorted().skip(5).take(10).array(); // sort, get 5-\u003e15 as array\n\n// Map to a new iterator, but support replacement\nsource.transform\u003cstring\u003e(\n  // transforms values to new type\n  value =\u003e value.name, \n  // if replace is called un a subsequent iteration, how do we take the transformed value and apply it back to the original value?         \n  (replaceWith, current, value) =\u003e {\n    value.name = replaceWith;\n  }\n).each((name, key, iter) =\u003e {\n  // Make all names uppercase in the most obtuse way possible\n  iter.replace(name.toUpperCase());\n});\n\n// Iterate with a callback\nsource.each((value, key, iter) =\u003e {\n  // iter.remove();\n  // iter.stop(withResult);\n  // iter.replace(newValue);\n});\n\n\n// ============ Linked List ============ \n// You can have any structure you wish\ninterface Node\u003cT\u003e {\n  value: T\n  next?: Node\u003cT\u003e\n}\n\nconst linkedIterator = Iterate.linked\u003cNode\u003cstring\u003e, string\u003e(\n  // get value from a node\n  (node) =\u003e node.value,\n  // get next node\n  (node) =\u003e node.next,\n  // remove the node (optional)\n  (node, prev) =\u003e prev.next = node.next,\n  // replace value (optional)\n  (node, value) =\u003e node.value = value,\n  // if you want a key different than the value, specify this\n  (node) =\u003e node.value.length\n);\n\nconst head: Node\u003cstring\u003e = ...;\n\n// for all nodes which have a value that passes the regex...\n// - sort them by value (asending), remove the top 5\n// - convert the unremoved unsorted nodes into an array\nlinkedIterator(head)\n  .where(x =\u003e /regex/.test(x))\n  .fork(f =\u003e f.strings().sorted().take(5).delete())\n  .array();\n\n\n// ============ Tree ============ \ninterface Node\u003cT\u003e {\n  value: T\n  children?: Node\u003cT\u003e[]\n}\n\n// You create an iterator ready for nodes.\nconst treeIterator = Iterate.tree\u003cNode\u003cstring\u003e, string\u003e(\n  // get a value from a node\n  (node) =\u003e node.value,\n  // get children from a node (can return an array, another iterator, undefined, or null)\n  (node) =\u003e node.children,\n  // if replace is called, apply the value (this is optional)\n  (node, value) =\u003e node.value = value\n);\n\nconst head: Node\u003cstring\u003e = ...\n\n// Iterate depth-first and convert to an array\nconst depthFirstList = treeIterator(head).array();\n\n// Iterate breadth-first and convert to an array\nconst breadthFirstList = treeIterator(head, false).array();\n\n```\n\n## Reusable Function\nYou can define a function which takes an iterator and performs any \nnumber of operations. You can optionally have it return a result.\n\n```typescript\n// Iterate.func\u003cT, R, A, K, S\u003e\n// - T = value type\n// - R = function return type\n// - A = array of parameter types\n// - K = desired key type (restricts the source that can be passed to the function)\n// - S = desired source (type passed to function must match this type)\n\n// A function without a result. If a word contains an a, uppercase the word\nconst fn = Iterate.func\u003cstring\u003e(\n  source =\u003e source\n    .where(x =\u003e x.indexOf('a') !== -1)\n    .update(x =\u003e x.toUpperCase())\n);\n\n// Any iterable source that has strings as values can be passed to function\nconst a = ['apple', 'bit', 'cat'];\nfn(a);\n// a = [APPLE, bit, CAT]\n\n// A function with a result. If a word contains an a, uppercase it. Return the \n// number of changed words. The counting has to happen before the update\n// since the update would make no values pass the where condition.\nconst fn = Iterate.func\u003cstring, number\u003e(\n  (source, setResult) =\u003e source\n    .where(x =\u003e x.indexOf('a') !== -1)\n    .count(setResult)\n    .update(x =\u003e x.toUpperCase())\n);\n\nconst a = ['apple', 'bit', 'cat'];\nconst b = fn(a); // 2\n// a = [APPLE, bit, CAT]\n\n// A function can have special paramters passed to it.\n// Given an array of people, I want a subset of that array given\n// a limit and offset.\nconst getPage = Iterate.func\u003cPerson, Person[], [number, number]\u003e(\n  (source, setResult, offset, limit) =\u003e source\n    .skip(offset)\n    .take(limit)\n    .array(setResult)\n);\n\nconst persons: Person[] = ...;\nconst page = getPage(persons, 5, 10);\n// page = at most 10 people starting at index 5\n```\n\n## Custom Iterators\nYou can add your own iterators to pick up your own types. If you are not using \nTypeScript you can ignore and remove the types.\n\n```typescript\n// Lets assume we have a type which is a [Date, number] tuple\n// which represents a range of dates.\n\ntype DateRange = [Date, number];\n\n// First we create an iterator given the Range\n// The generic arguments for Iterate\u003cT, K, S\u003e represent:\n// - T = the value being iterated\n// - K = the key type\n// - S = the source type being iterated\nfunction getDateIterator ([start, max]: DateRange)\n{\n  return new Iterate\u003cDate, number, DateRange\u003e(iter =\u003e \n  {\n    // This function is called when an operation or mutation is called on iter\n    // You should iterate over your values and respond to the action requested\n    // In this example, if iterateNode returns false, stop all iteration\n    const curr = new Date(start.getTime());\n\n    for (let key = 0; key \u003c max; key++)\n    {\n      // Dates are not immutable, don't want the iterator to mess this up.\n      const value = new Date(curr.getTime());\n\n      switch (iter.act(value, key)) \n      {\n        // stop all iteration\n        case IterateAction.STOP:\n          return;\n\n        // remove this value, and subsequentally all children from tree\n        case IterateAction.REMOVE:\n          // doesn't apply here, this is a dynamic set\n          break;\n\n        // replace the value\n        case IterateAction.REPLACE:\n          // doesn't apply here, this is a dynamic set\n          break;\n      }\n\n      // the next date in the sequence\n      curr.setDate(curr.getDate() + 1);\n    }\n  });\n}\n\n// Now that we have an iterator generator, if we wanted to we could \n// autmatically have the library detect my custom type and call my custom \n// iterator.\nimport { Generators } from 'iteratez';\n\n// We need to detect our custom type. s is DateRange is just syntactical sugar\nfunction isDateRange(s: any): s is DateRange {\n  return Array.isArray(s) \n    \u0026\u0026 s.length === 2 \n    \u0026\u0026 s[0] instanceof Date \n    \u0026\u0026 typeof s[1] === 'number'\n  ;\n}\n\n// Add a generator detection to the beginning of the list\n// It must return false if it's not a valid source.\nGenerators.unshift(s =\u003e isDateRange(s) ? getDateIterator(s) : false);\n\n// Now when we do this...\nconst dateIterator = iz([new Date(), 10]);\n\n// We have our iterator and can do anything we want with it.\nconst dates = dateIterator.array();\n\n// BONUS!\n// If we want the iz function to return a type Iterator (like Iterate\u003cDate, number, DateRange\u003e)\n// we can add our own declaration file like this:\n\n// \u003ctypes/iteratez/index.d.ts\u003e\nimport { Iterate } from 'iteratez';\n\ndeclare module 'iteratez'\n{\n  // add the function overload\n  export function iterate (range: DateRange): Iterate\u003cDate, number, DateRange\u003e\n}\n// \u003c/types/iteratez/index.d.ts\u003e\n\n// then instead of this everywhere:\nimport { iz } from 'iteratez';\n\nconst dateIterator = iz([new Date(), 3]); // Iterate\u003cDate, number, DateRange\u003e magic!\n```\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FClickerMonkey%2Fiteratez","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FClickerMonkey%2Fiteratez","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FClickerMonkey%2Fiteratez/lists"}