{"id":13463137,"url":"https://github.com/hamstergem/hamster","last_synced_at":"2025-05-14T03:08:45.469Z","repository":{"id":699066,"uuid":"344359","full_name":"hamstergem/hamster","owner":"hamstergem","description":"Efficient, Immutable, Thread-Safe Collection classes for Ruby","archived":false,"fork":false,"pushed_at":"2021-11-30T17:59:47.000Z","size":2896,"stargazers_count":1882,"open_issues_count":16,"forks_count":92,"subscribers_count":31,"default_branch":"core","last_synced_at":"2025-05-11T18:37:47.246Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/hamstergem.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2009-10-21T04:10:49.000Z","updated_at":"2025-05-06T16:09:45.000Z","dependencies_parsed_at":"2022-07-14T18:30:50.221Z","dependency_job_id":null,"html_url":"https://github.com/hamstergem/hamster","commit_stats":null,"previous_names":["harukizaemon/hamster"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamstergem%2Fhamster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamstergem%2Fhamster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamstergem%2Fhamster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamstergem%2Fhamster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hamstergem","download_url":"https://codeload.github.com/hamstergem/hamster/tar.gz/refs/heads/core","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253868483,"owners_count":21976450,"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-31T13:00:46.775Z","updated_at":"2025-05-14T03:08:45.442Z","avatar_url":"https://github.com/hamstergem.png","language":"Ruby","readme":"Hamster\n=======\n\n  - [![Quality](http://img.shields.io/codeclimate/github/hamstergem/hamster.svg?style=flat-square)](https://codeclimate.com/github/hamstergem/hamster)\n  - [![Coverage](http://img.shields.io/codeclimate/coverage/github/hamstergem/hamster.svg?style=flat-square)](https://codeclimate.com/github/hamstergem/hamster)\n  - [![Build](http://img.shields.io/travis-ci/hamstergem/hamster.svg?style=flat-square)](https://travis-ci.org/hamstergem/hamster)\n  - [![Dependencies](http://img.shields.io/gemnasium/hamstergem/hamster.svg?style=flat-square)](https://gemnasium.com/hamstergem/hamster)\n  - [![Downloads](http://img.shields.io/gem/dtv/hamster.svg?style=flat-square)](https://rubygems.org/gems/hamster)\n  - [![Tags](http://img.shields.io/github/tag/hamstergem/hamster.svg?style=flat-square)](http://github.com/hamstergem/hamster/tags)\n  - [![Releases](http://img.shields.io/github/release/hamstergem/hamster.svg?style=flat-square)](http://github.com/hamstergem/hamster/releases)\n  - [![Issues](http://img.shields.io/github/issues/hamstergem/hamster.svg?style=flat-square)](http://github.com/hamstergem/hamster/issues)\n  - [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](http://opensource.org/licenses/MIT)\n  - [![Version](http://img.shields.io/gem/v/hamster.svg?style=flat-square)](https://rubygems.org/gems/hamster)\n  - [![Discuss](http://img.shields.io/badge/discuss-join%20gitter-brightgreen.svg?style=flat-square)](https://gitter.im/hamstergem/hamster)\n\nEfficient, immutable, and thread-safe collection classes for Ruby.\n\nHamster provides 6 [Persistent Data\nStructures][PDS]: [`Hash`][HASH-DOC], [`Vector`][VECTOR-DOC], [`Set`][SET-DOC], [`SortedSet`][SORTED-SET-DOC], [`List`][LIST-DOC], and [`Deque`][DEQUE-DOC] (which works as an immutable queue or stack).\n\nHamster collections are **immutable**. Whenever you modify a Hamster\ncollection, the original is preserved and a modified copy is returned. This\nmakes them inherently thread-safe and shareable. At the same time, they remain\nCPU and memory-efficient by sharing between copies. \n\nWhile Hamster collections are immutable, you can still mutate objects stored\nin them. We recommend that  you don't do this, unless you are sure you know \nwhat you are doing. Hamster collections are thread-safe and can be freely \nshared between threads, but you are responsible for making sure that the \nobjects stored in them are used in a thread-safe manner.\n\nHamster collections are almost always closed under a given operation. That is,\nwhereas Ruby's collection methods always return arrays, Hamster collections\nwill return an instance of the same class wherever possible.\n\nWhere possible, Hamster collections offer an interface compatible with Ruby's\nbuilt-in `Hash`, `Array`, and `Enumerable`, to ease code migration. Also, Hamster methods accept regular Ruby collections as arguments, so code which uses `Hamster` can easily interoperate with your other Ruby code.\n\nAnd lastly, Hamster lists are lazy, making it possible to (among other things)\nprocess \"infinitely large\" lists.\n\n[PDS]: http://en.wikipedia.org/wiki/Persistent_data_structure\n[HASH-DOC]: http://rubydoc.info/github/hamstergem/hamster/master/Hamster/Hash\n[SET-DOC]: http://rubydoc.info/github/hamstergem/hamster/master/Hamster/Set\n[VECTOR-DOC]: http://rubydoc.info/github/hamstergem/hamster/master/Hamster/Vector\n[LIST-DOC]: http://rubydoc.info/github/hamstergem/hamster/master/Hamster/List\n[SORTED-SET-DOC]: http://rubydoc.info/github/hamstergem/hamster/master/Hamster/SortedSet\n[DEQUE-DOC]: http://rubydoc.info/github/hamstergem/hamster/master/Hamster/Deque\n\n\nUsing\n=====\n\nTo make the collection classes available in your code:\n\n``` ruby\nrequire \"hamster\"\n```\n\nOr if you prefer to only pull in certain collection types:\n\n``` ruby\nrequire \"hamster/hash\"\nrequire \"hamster/vector\"\nrequire \"hamster/set\"\nrequire \"hamster/sorted_set\"\nrequire \"hamster/list\"\nrequire \"hamster/deque\"\n```\n\n\u003ch2\u003eHash \u003cspan style=\"font-size:0.7em\"\u003e(\u003ca href=\"http://rubydoc.info/github/hamstergem/hamster/master/Hamster/Hash\"\u003eAPI Documentation\u003c/a\u003e)\u003c/span\u003e\u003c/h2\u003e\n\nConstructing a Hamster `Hash` is almost as simple as a regular one:\n\n``` ruby\nperson = Hamster::Hash[name: \"Simon\", gender: :male]\n# =\u003e Hamster::Hash[:name =\u003e \"Simon\", :gender =\u003e :male]\n```\n\nAccessing the contents will be familiar to you:\n\n``` ruby\nperson[:name]                       # =\u003e \"Simon\"\nperson.get(:gender)                 # =\u003e :male\n```\n\nUpdating the contents is a little different than you are used to:\n\n``` ruby\nfriend = person.put(:name, \"James\") # =\u003e Hamster::Hash[:name =\u003e \"James\", :gender =\u003e :male]\nperson                              # =\u003e Hamster::Hash[:name =\u003e \"Simon\", :gender =\u003e :male]\nfriend[:name]                       # =\u003e \"James\"\nperson[:name]                       # =\u003e \"Simon\"\n```\n\nAs you can see, updating the hash returned a copy leaving\nthe original intact. Similarly, deleting a key returns\nyet another copy:\n\n``` ruby\nmale = person.delete(:name)         # =\u003e Hamster::Hash[:gender =\u003e :male]\nperson                              # =\u003e Hamster::Hash[:name =\u003e \"Simon\", :gender =\u003e :male]\nmale.key?(:name)                    # =\u003e false\nperson.key?(:name)                  # =\u003e true\n```\n\nSince it is immutable, Hamster's `Hash` doesn't provide an assignment\n(`Hash#[]=`) method. However, `Hash#put` can accept a block which\ntransforms the value associated with a given key:\n\n``` ruby\ncounters = Hamster::Hash[evens: 0, odds: 0]  # =\u003e Hamster::Hash[:evens =\u003e 0, :odds =\u003e 0]\ncounters.put(:odds) { |value| value + 1 }    # =\u003e Hamster::Hash[:odds =\u003e 1, :evens =\u003e 0]\n```\n\nOr more succinctly:\n\n``` ruby\ncounters.put(:odds, \u0026:next)         # =\u003e {:odds =\u003e 1, :evens =\u003e 0}\n```\n\nThis is just the beginning; see the [API documentation][HASH-DOC] for details on all `Hash` methods.\n\n\n\u003ch2\u003eVector \u003cspan style=\"font-size:0.7em\"\u003e(\u003ca href=\"http://rubydoc.info/github/hamstergem/hamster/master/Hamster/Vector\"\u003eAPI Documentation\u003c/a\u003e)\u003c/span\u003e\u003c/h2\u003e\n\nA `Vector` is an integer-indexed collection much like an immutable `Array`. Examples:\n\n``` ruby\nvector = Hamster::Vector[1, 2, 3, 4] # =\u003e Hamster::Vector[1, 2, 3, 4]\nvector[0]                            # =\u003e 1\nvector[-1]                           # =\u003e 4\nvector.put(1, :a)                    # =\u003e Hamster::Vector[1, :a, 3, 4]\nvector.add(:b)                       # =\u003e Hamster::Vector[1, 2, 3, 4, :b]\nvector.insert(2, :a, :b)             # =\u003e Hamster::Vector[1, 2, :a, :b, 3, 4]\nvector.delete_at(0)                  # =\u003e Hamster::Vector[2, 3, 4]\n```\n\nOther `Array`-like methods like `#select`, `#map`, `#shuffle`, `#uniq`, `#reverse`,\n`#rotate`, `#flatten`, `#sort`, `#sort_by`, `#take`, `#drop`, `#take_while`,\n`#drop_while`, `#fill`, `#product`, and `#transpose` are also supported. See the\n[API documentation][VECTOR-DOC] for details on all `Vector` methods.\n\n\n\u003ch2\u003eSet \u003cspan style=\"font-size:0.7em\"\u003e(\u003ca href=\"http://rubydoc.info/github/hamstergem/hamster/master/Hamster/Set\"\u003eAPI Documentation\u003c/a\u003e)\u003c/span\u003e\u003c/h2\u003e\n\nA `Set` is an unordered collection of values with no duplicates. It is much like the Ruby standard library's `Set`, but immutable. Examples:\n\n``` ruby\nset = Hamster::Set[:red, :blue, :yellow] # =\u003e Hamster::Set[:red, :blue, :yellow]\nset.include? :red                        # =\u003e true\nset.add :green                           # =\u003e Hamster::Set[:red, :blue, :yellow, :green]\nset.delete :blue                         # =\u003e Hamster::Set[:red, :yellow]\nset.superset? Hamster::Set[:red, :blue]  # =\u003e true\nset.union([:red, :blue, :pink])          # =\u003e Hamster::Set[:red, :blue, :yellow, :pink]\nset.intersection([:red, :blue, :pink])   # =\u003e Hamster::Set[:red, :blue]\n```\n\nLike most Hamster methods, the set-theoretic methods `#union`, `#intersection`, `#difference`, and `#exclusion` (aliased as `#|`, `#\u0026`, `#-`, and `#^`) all work with regular Ruby collections, or indeed any `Enumerable` object. So just like all the other Hamster collections, `Hamster::Set` can easily be used in combination with \"ordinary\" Ruby code.\n\nSee the [API documentation][SET-DOC] for details on all `Set` methods.\n\n\n\u003ch2\u003eSortedSet \u003cspan style=\"font-size:0.7em\"\u003e(\u003ca href=\"http://rubydoc.info/github/hamstergem/hamster/master/Hamster/SortedSet\"\u003eAPI Documentation\u003c/a\u003e)\u003c/span\u003e\u003c/h2\u003e\n\nA `SortedSet` is like a `Set`, but ordered. You can do everything with it that you can\ndo with a `Set`. Additionally, you can get the `#first` and `#last` item, or retrieve\nan item using an integral index:\n\n``` ruby\nset = Hamster::SortedSet['toast', 'jam', 'bacon'] # =\u003e Hamster::SortedSet[\"bacon\", \"jam\", \"toast\"]\nset.first                                         # =\u003e \"bacon\"\nset.last                                          # =\u003e \"toast\"\nset[1]                                            # =\u003e \"jam\"\n```\n\nYou can also specify the sort order using a block:\n\n``` ruby\nHamster::SortedSet.new(['toast', 'jam', 'bacon']) { |a,b| b \u003c=\u003e a }\nHamster::SortedSet.new(['toast', 'jam', 'bacon']) { |str| str.chars.last }\n```\n\nSee the [API documentation][SORTED-SET-DOC] for details on all `SortedSet` methods.\n\n\n\u003ch2\u003eList \u003cspan style=\"font-size:0.7em\"\u003e(\u003ca href=\"http://rubydoc.info/github/hamstergem/hamster/master/Hamster/List\"\u003eAPI Documentation\u003c/a\u003e)\u003c/span\u003e\u003c/h2\u003e\n\nHamster `List`s have a *head* (the value at the front of the list),\nand a *tail* (a list of the remaining items):\n\n``` ruby\nlist = Hamster::List[1, 2, 3]\nlist.head                    # =\u003e 1\nlist.tail                    # =\u003e Hamster::List[2, 3]\n```\n\nAdd to a list with `List#add`:\n\n``` ruby\noriginal = Hamster::List[1, 2, 3]\ncopy = original.add(0)      # =\u003e Hamster::List[0, 1, 2, 3]\n```\n\nNotice how modifying a list actually returns a new list.\nThat's because Hamster `List`s are immutable.\n\n### Laziness\n\n`List` is lazy where possible. It tries to defer processing items until\nabsolutely necessary. For example, the following code will only call\n`Prime.prime?` as many times as necessary to generate the first 3\nprime numbers between 10,000 and 1,000,000:\n\n``` ruby\nrequire 'prime'\n\nHamster.interval(10_000, 1_000_000).select do |number|\n  Prime.prime?(number)\nend.take(3)\n  # =\u003e 0.0009s\n```\n\nCompare that to the conventional equivalent which needs to\ncalculate all possible values in the range before taking the\nfirst three:\n\n``` ruby\n(10000..1000000).select do |number|\n  Prime.prime?(number)\nend.take(3)\n  # =\u003e 10s\n```\n\n### Construction\n\nBesides `Hamster::List[]` there are other ways to construct lists:\n\n  - `Hamster.interval(from, to)` creates a lazy list\n    equivalent to a list containing all the values between\n    `from` and `to` without actually creating a list that big.\n\n  - `Hamster.stream { ... }` allows you to creates infinite\n    lists. Each time a new value is required, the supplied\n    block is called. To generate a list of integers you\n    could do:\n\n    ``` ruby\n    count = 0\n    Hamster.stream { count += 1 }\n    ```\n\n  - `Hamster.repeat(x)` creates an infinite list with `x` the\n    value for every element.\n\n  - `Hamster.replicate(n, x)` creates a list of size `n` with\n    `x` the value for every element.\n\n  - `Hamster.iterate(x) { |x| ... }` creates an infinite\n    list where the first item is calculated by applying the\n    block on the initial argument, the second item by applying\n    the function on the previous result and so on. For\n    example, a simpler way to generate a list of integers\n    would be:\n\n    ``` ruby\n    Hamster.iterate(1) { |i| i + 1 }\n    ```\n\n    or even more succinctly:\n\n    ``` ruby\n    Hamster.iterate(1, \u0026:next)\n    ```\n\n  - `Hamster::List.empty` returns an empty list, which you can\n    build up using repeated calls to `#add` or other `List` methods.\n\n### Core Extensions\n\n`Enumerable#to_list` will convert any existing `Enumerable` to a list, so you can\nslowly transition from built-in collection classes to Hamster.\n\n`IO#to_list` enables lazy processing of huge files. For example, imagine the\nfollowing code to process a 100MB file:\n\n``` ruby\nrequire 'hamster/core_ext'\n\nFile.open(\"my_100_mb_file.txt\") do |file|\n  lines = []\n  file.each_line do |line|\n    break if lines.size == 10\n    lines \u003c\u003c line.chomp.downcase.reverse\n  end\nend\n```\n\nCompare to the following more functional version:\n\n``` ruby\nFile.open(\"my_100_mb_file.txt\") do |file|\n  file.map(\u0026:chomp).map(\u0026:downcase).map(\u0026:reverse).take(10)\nend\n```\n\nUnfortunately, though the second example reads nicely it\ntakes many seconds to run (compared with milliseconds\nfor the first) even though we're only interested in the first\nten lines. Using `#to_list` we can get the running time back comparable to the\nimperative version.\n\n``` ruby\nFile.open(\"my_100_mb_file.txt\") do |file|\n  file.to_list.map(\u0026:chomp).map(\u0026:downcase).map(\u0026:reverse).take(10)\nend\n```\n\nThis is possible because `IO#to_list` creates a lazy list whereby each line is\nonly ever read and processed as needed, in effect converting it to the first\nexample.\n\nSee the API documentation for details on all [`List`][LIST-DOC] methods.\n\n\n\u003ch2\u003eDeque \u003cspan style=\"font-size:0.7em\"\u003e(\u003ca href=\"http://rubydoc.info/github/hamstergem/hamster/master/Hamster/Deque\"\u003eAPI Documentation\u003c/a\u003e)\u003c/span\u003e\u003c/h2\u003e\n\nA `Deque` (or \"double-ended queue\") is an ordered collection, which allows you to push and pop items from both front and back. This makes it perfect as an immutable stack *or* queue. Examples:\n\n``` ruby\ndeque = Hamster::Deque[1, 2, 3] # =\u003e Hamster::Deque[1, 2, 3]\ndeque.first                     # 1\ndeque.last                      # 3\ndeque.pop                       # =\u003e Hamster::Deque[1, 2]\ndeque.push(:a)                  # =\u003e Hamster::Deque[1, 2, 3, :a]\ndeque.shift                     # =\u003e Hamster::Deque[2, 3]\ndeque.unshift(:a)               # =\u003e Hamster::Deque[:a, 1, 2, 3]\n```\n\nOf course, you can do the same thing with a `Vector`, but a `Deque` is more efficient. See the API documentation for details on all [`Deque`][DEQUE-DOC] methods.\n\n\n\u003ch2\u003eTransformations\u003c/h2\u003e\n\nHamster arrays, hashes, and nested structures of arrays and hashes may be transformed with the `update_in` method.\n\n``` ruby\nc = Hamster.from({\n  people: [{name: 'Chris', city: 'Lagos'}, {name: 'Pat', city: 'Madrid'}],\n  places: [{name: 'Lagos', population: 1}, {name: 'Madrid', population: 1}]})\nc2 = c.update_in(:people, 1, :city) { |old_city| 'Lagos' }\nc3 = c2.update_in(:places, 1, :population) { |old_population| old_population - 1 }\nc4 = c3.update_in(:places, 0, :population) { |old_population| old_population + 1 }\nHamster.to_ruby(c4)\n# =\u003e {:places=\u003e[{:population=\u003e2, :name=\u003e\"Lagos\"}, {:population=\u003e0, :name=\u003e\"Madrid\"}], :people=\u003e[{:name=\u003e\"Chris\", :city=\u003e\"Lagos\"}, {:name=\u003e\"Pat\", :city=\u003e\"Lagos\"}]}\n```\n\nNaturally, `update_in` never mutates your collections.\n\nSee `Hamster::Hash#update_in`, `Hamster::Vector#update_in`, and `Hamster::Associable#update_in` for details.\n\n\nInstalling\n==========\n\nAdd this line to your application's Gemfile:\n\n    gem \"hamster\"\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install hamster\n\n\nContributing\n============\n\n  1. Read the [Code of Conduct](/CONDUCT.md)\n  2. Fork it\n  3. Create your feature branch (`git checkout -b my-new-feature`)\n  4. Commit your changes (`git commit -am \"Add some feature\"`)\n  5. Push to the branch (`git push origin my-new-feature`)\n  6. Create new Pull Request\n\n\nOther Reading\n=============\n\n- The structure which is used for Hamster's `Hash` and `Set`: [Hash Array Mapped Tries][HAMT]\n- An interesting perspective on why immutability itself is inherently a good thing: Matthias Felleisen's [Function Objects presentation][FO].\n- The Hamster [FAQ](FAQ.md)\n- [Changelog](CHANGELOG.md)\n\n[HAMT]: http://lampwww.epfl.ch/papers/idealhashtrees.pdf\n[FO]: http://www.ccs.neu.edu/home/matthias/Presentations/ecoop2004.pdf\n\n\nLicensing\n=========\n\nCopyright (c) 2009-2015 Simon Harris\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","funding_links":[],"categories":["Developer Tools","Ruby","Core Extensions"],"sub_categories":["Ruby Core Extensions"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhamstergem%2Fhamster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhamstergem%2Fhamster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhamstergem%2Fhamster/lists"}