immutable-ruby 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/immutable.rb +9 -0
- data/lib/immutable/core_ext.rb +2 -0
- data/lib/immutable/core_ext/enumerable.rb +11 -0
- data/lib/immutable/core_ext/io.rb +21 -0
- data/lib/immutable/deque.rb +254 -0
- data/lib/immutable/enumerable.rb +152 -0
- data/lib/immutable/hash.rb +841 -0
- data/lib/immutable/list.rb +1595 -0
- data/lib/immutable/nested.rb +75 -0
- data/lib/immutable/set.rb +583 -0
- data/lib/immutable/sorted_set.rb +1464 -0
- data/lib/immutable/trie.rb +338 -0
- data/lib/immutable/undefined.rb +5 -0
- data/lib/immutable/vector.rb +1539 -0
- data/lib/immutable/version.rb +5 -0
- data/spec/fixtures/io_spec.txt +3 -0
- data/spec/lib/immutable/core_ext/array_spec.rb +13 -0
- data/spec/lib/immutable/core_ext/enumerable_spec.rb +29 -0
- data/spec/lib/immutable/core_ext/io_spec.rb +28 -0
- data/spec/lib/immutable/deque/clear_spec.rb +33 -0
- data/spec/lib/immutable/deque/construction_spec.rb +29 -0
- data/spec/lib/immutable/deque/copying_spec.rb +19 -0
- data/spec/lib/immutable/deque/dequeue_spec.rb +34 -0
- data/spec/lib/immutable/deque/empty_spec.rb +39 -0
- data/spec/lib/immutable/deque/enqueue_spec.rb +27 -0
- data/spec/lib/immutable/deque/first_spec.rb +17 -0
- data/spec/lib/immutable/deque/inspect_spec.rb +23 -0
- data/spec/lib/immutable/deque/last_spec.rb +17 -0
- data/spec/lib/immutable/deque/marshal_spec.rb +33 -0
- data/spec/lib/immutable/deque/new_spec.rb +43 -0
- data/spec/lib/immutable/deque/pop_spec.rb +36 -0
- data/spec/lib/immutable/deque/pretty_print_spec.rb +23 -0
- data/spec/lib/immutable/deque/push_spec.rb +36 -0
- data/spec/lib/immutable/deque/random_modification_spec.rb +33 -0
- data/spec/lib/immutable/deque/shift_spec.rb +29 -0
- data/spec/lib/immutable/deque/size_spec.rb +19 -0
- data/spec/lib/immutable/deque/to_a_spec.rb +26 -0
- data/spec/lib/immutable/deque/to_ary_spec.rb +35 -0
- data/spec/lib/immutable/deque/to_list_spec.rb +24 -0
- data/spec/lib/immutable/deque/unshift_spec.rb +30 -0
- data/spec/lib/immutable/hash/all_spec.rb +53 -0
- data/spec/lib/immutable/hash/any_spec.rb +53 -0
- data/spec/lib/immutable/hash/assoc_spec.rb +51 -0
- data/spec/lib/immutable/hash/clear_spec.rb +42 -0
- data/spec/lib/immutable/hash/construction_spec.rb +38 -0
- data/spec/lib/immutable/hash/copying_spec.rb +13 -0
- data/spec/lib/immutable/hash/default_proc_spec.rb +72 -0
- data/spec/lib/immutable/hash/delete_spec.rb +39 -0
- data/spec/lib/immutable/hash/each_spec.rb +77 -0
- data/spec/lib/immutable/hash/each_with_index_spec.rb +29 -0
- data/spec/lib/immutable/hash/empty_spec.rb +43 -0
- data/spec/lib/immutable/hash/eql_spec.rb +69 -0
- data/spec/lib/immutable/hash/except_spec.rb +42 -0
- data/spec/lib/immutable/hash/fetch_spec.rb +57 -0
- data/spec/lib/immutable/hash/find_spec.rb +43 -0
- data/spec/lib/immutable/hash/flat_map_spec.rb +35 -0
- data/spec/lib/immutable/hash/flatten_spec.rb +98 -0
- data/spec/lib/immutable/hash/get_spec.rb +79 -0
- data/spec/lib/immutable/hash/has_key_spec.rb +31 -0
- data/spec/lib/immutable/hash/has_value_spec.rb +27 -0
- data/spec/lib/immutable/hash/hash_spec.rb +29 -0
- data/spec/lib/immutable/hash/inspect_spec.rb +30 -0
- data/spec/lib/immutable/hash/invert_spec.rb +30 -0
- data/spec/lib/immutable/hash/key_spec.rb +27 -0
- data/spec/lib/immutable/hash/keys_spec.rb +15 -0
- data/spec/lib/immutable/hash/map_spec.rb +45 -0
- data/spec/lib/immutable/hash/marshal_spec.rb +28 -0
- data/spec/lib/immutable/hash/merge_spec.rb +82 -0
- data/spec/lib/immutable/hash/min_max_spec.rb +45 -0
- data/spec/lib/immutable/hash/new_spec.rb +70 -0
- data/spec/lib/immutable/hash/none_spec.rb +48 -0
- data/spec/lib/immutable/hash/partition_spec.rb +35 -0
- data/spec/lib/immutable/hash/pretty_print_spec.rb +34 -0
- data/spec/lib/immutable/hash/put_spec.rb +102 -0
- data/spec/lib/immutable/hash/reduce_spec.rb +35 -0
- data/spec/lib/immutable/hash/reject_spec.rb +61 -0
- data/spec/lib/immutable/hash/reverse_each_spec.rb +27 -0
- data/spec/lib/immutable/hash/sample_spec.rb +13 -0
- data/spec/lib/immutable/hash/select_spec.rb +57 -0
- data/spec/lib/immutable/hash/size_spec.rb +51 -0
- data/spec/lib/immutable/hash/slice_spec.rb +44 -0
- data/spec/lib/immutable/hash/sort_spec.rb +26 -0
- data/spec/lib/immutable/hash/store_spec.rb +75 -0
- data/spec/lib/immutable/hash/take_spec.rb +35 -0
- data/spec/lib/immutable/hash/to_a_spec.rb +13 -0
- data/spec/lib/immutable/hash/to_hash_spec.rb +21 -0
- data/spec/lib/immutable/hash/update_in_spec.rb +79 -0
- data/spec/lib/immutable/hash/values_at_spec.rb +13 -0
- data/spec/lib/immutable/hash/values_spec.rb +23 -0
- data/spec/lib/immutable/list/add_spec.rb +25 -0
- data/spec/lib/immutable/list/all_spec.rb +57 -0
- data/spec/lib/immutable/list/any_spec.rb +49 -0
- data/spec/lib/immutable/list/append_spec.rb +38 -0
- data/spec/lib/immutable/list/at_spec.rb +29 -0
- data/spec/lib/immutable/list/break_spec.rb +69 -0
- data/spec/lib/immutable/list/cadr_spec.rb +38 -0
- data/spec/lib/immutable/list/chunk_spec.rb +28 -0
- data/spec/lib/immutable/list/clear_spec.rb +24 -0
- data/spec/lib/immutable/list/combination_spec.rb +33 -0
- data/spec/lib/immutable/list/compact_spec.rb +34 -0
- data/spec/lib/immutable/list/compare_spec.rb +30 -0
- data/spec/lib/immutable/list/cons_spec.rb +25 -0
- data/spec/lib/immutable/list/construction_spec.rb +110 -0
- data/spec/lib/immutable/list/copying_spec.rb +19 -0
- data/spec/lib/immutable/list/count_spec.rb +36 -0
- data/spec/lib/immutable/list/cycle_spec.rb +28 -0
- data/spec/lib/immutable/list/delete_at_spec.rb +18 -0
- data/spec/lib/immutable/list/delete_spec.rb +16 -0
- data/spec/lib/immutable/list/drop_spec.rb +30 -0
- data/spec/lib/immutable/list/drop_while_spec.rb +38 -0
- data/spec/lib/immutable/list/each_slice_spec.rb +51 -0
- data/spec/lib/immutable/list/each_spec.rb +40 -0
- data/spec/lib/immutable/list/each_with_index_spec.rb +28 -0
- data/spec/lib/immutable/list/empty_spec.rb +23 -0
- data/spec/lib/immutable/list/eql_spec.rb +61 -0
- data/spec/lib/immutable/list/fill_spec.rb +49 -0
- data/spec/lib/immutable/list/find_all_spec.rb +70 -0
- data/spec/lib/immutable/list/find_index_spec.rb +35 -0
- data/spec/lib/immutable/list/find_spec.rb +42 -0
- data/spec/lib/immutable/list/flat_map_spec.rb +51 -0
- data/spec/lib/immutable/list/flatten_spec.rb +30 -0
- data/spec/lib/immutable/list/grep_spec.rb +46 -0
- data/spec/lib/immutable/list/group_by_spec.rb +41 -0
- data/spec/lib/immutable/list/hash_spec.rb +21 -0
- data/spec/lib/immutable/list/head_spec.rb +19 -0
- data/spec/lib/immutable/list/include_spec.rb +35 -0
- data/spec/lib/immutable/list/index_spec.rb +33 -0
- data/spec/lib/immutable/list/indices_spec.rb +61 -0
- data/spec/lib/immutable/list/init_spec.rb +28 -0
- data/spec/lib/immutable/list/inits_spec.rb +28 -0
- data/spec/lib/immutable/list/insert_spec.rb +46 -0
- data/spec/lib/immutable/list/inspect_spec.rb +29 -0
- data/spec/lib/immutable/list/intersperse_spec.rb +28 -0
- data/spec/lib/immutable/list/join_spec.rb +63 -0
- data/spec/lib/immutable/list/last_spec.rb +23 -0
- data/spec/lib/immutable/list/ltlt_spec.rb +19 -0
- data/spec/lib/immutable/list/map_spec.rb +45 -0
- data/spec/lib/immutable/list/maximum_spec.rb +39 -0
- data/spec/lib/immutable/list/merge_by_spec.rb +51 -0
- data/spec/lib/immutable/list/merge_spec.rb +59 -0
- data/spec/lib/immutable/list/minimum_spec.rb +39 -0
- data/spec/lib/immutable/list/multithreading_spec.rb +47 -0
- data/spec/lib/immutable/list/none_spec.rb +47 -0
- data/spec/lib/immutable/list/one_spec.rb +49 -0
- data/spec/lib/immutable/list/partition_spec.rb +115 -0
- data/spec/lib/immutable/list/permutation_spec.rb +55 -0
- data/spec/lib/immutable/list/pop_spec.rb +25 -0
- data/spec/lib/immutable/list/product_spec.rb +23 -0
- data/spec/lib/immutable/list/reduce_spec.rb +53 -0
- data/spec/lib/immutable/list/reject_spec.rb +45 -0
- data/spec/lib/immutable/list/reverse_spec.rb +34 -0
- data/spec/lib/immutable/list/rotate_spec.rb +36 -0
- data/spec/lib/immutable/list/sample_spec.rb +13 -0
- data/spec/lib/immutable/list/select_spec.rb +70 -0
- data/spec/lib/immutable/list/size_spec.rb +25 -0
- data/spec/lib/immutable/list/slice_spec.rb +229 -0
- data/spec/lib/immutable/list/sorting_spec.rb +46 -0
- data/spec/lib/immutable/list/span_spec.rb +76 -0
- data/spec/lib/immutable/list/split_at_spec.rb +43 -0
- data/spec/lib/immutable/list/subsequences_spec.rb +23 -0
- data/spec/lib/immutable/list/sum_spec.rb +23 -0
- data/spec/lib/immutable/list/tail_spec.rb +30 -0
- data/spec/lib/immutable/list/tails_spec.rb +28 -0
- data/spec/lib/immutable/list/take_spec.rb +30 -0
- data/spec/lib/immutable/list/take_while_spec.rb +46 -0
- data/spec/lib/immutable/list/to_a_spec.rb +39 -0
- data/spec/lib/immutable/list/to_ary_spec.rb +41 -0
- data/spec/lib/immutable/list/to_list_spec.rb +19 -0
- data/spec/lib/immutable/list/to_set_spec.rb +17 -0
- data/spec/lib/immutable/list/transpose_spec.rb +19 -0
- data/spec/lib/immutable/list/union_spec.rb +31 -0
- data/spec/lib/immutable/list/uniq_spec.rb +35 -0
- data/spec/lib/immutable/list/zip_spec.rb +23 -0
- data/spec/lib/immutable/nested/construction_spec.rb +95 -0
- data/spec/lib/immutable/set/add_spec.rb +75 -0
- data/spec/lib/immutable/set/all_spec.rb +51 -0
- data/spec/lib/immutable/set/any_spec.rb +51 -0
- data/spec/lib/immutable/set/clear_spec.rb +33 -0
- data/spec/lib/immutable/set/compact_spec.rb +30 -0
- data/spec/lib/immutable/set/construction_spec.rb +18 -0
- data/spec/lib/immutable/set/copying_spec.rb +13 -0
- data/spec/lib/immutable/set/count_spec.rb +36 -0
- data/spec/lib/immutable/set/delete_spec.rb +71 -0
- data/spec/lib/immutable/set/difference_spec.rb +49 -0
- data/spec/lib/immutable/set/disjoint_spec.rb +25 -0
- data/spec/lib/immutable/set/each_spec.rb +45 -0
- data/spec/lib/immutable/set/empty_spec.rb +44 -0
- data/spec/lib/immutable/set/eqeq_spec.rb +103 -0
- data/spec/lib/immutable/set/eql_spec.rb +109 -0
- data/spec/lib/immutable/set/exclusion_spec.rb +47 -0
- data/spec/lib/immutable/set/find_spec.rb +35 -0
- data/spec/lib/immutable/set/first_spec.rb +28 -0
- data/spec/lib/immutable/set/flatten_spec.rb +46 -0
- data/spec/lib/immutable/set/grep_spec.rb +57 -0
- data/spec/lib/immutable/set/group_by_spec.rb +59 -0
- data/spec/lib/immutable/set/hash_spec.rb +22 -0
- data/spec/lib/immutable/set/include_spec.rb +60 -0
- data/spec/lib/immutable/set/inspect_spec.rb +47 -0
- data/spec/lib/immutable/set/intersect_spec.rb +25 -0
- data/spec/lib/immutable/set/intersection_spec.rb +52 -0
- data/spec/lib/immutable/set/join_spec.rb +64 -0
- data/spec/lib/immutable/set/map_spec.rb +59 -0
- data/spec/lib/immutable/set/marshal_spec.rb +28 -0
- data/spec/lib/immutable/set/maximum_spec.rb +36 -0
- data/spec/lib/immutable/set/minimum_spec.rb +36 -0
- data/spec/lib/immutable/set/new_spec.rb +53 -0
- data/spec/lib/immutable/set/none_spec.rb +47 -0
- data/spec/lib/immutable/set/one_spec.rb +47 -0
- data/spec/lib/immutable/set/partition_spec.rb +52 -0
- data/spec/lib/immutable/set/product_spec.rb +23 -0
- data/spec/lib/immutable/set/reduce_spec.rb +55 -0
- data/spec/lib/immutable/set/reject_spec.rb +50 -0
- data/spec/lib/immutable/set/reverse_each_spec.rb +38 -0
- data/spec/lib/immutable/set/sample_spec.rb +13 -0
- data/spec/lib/immutable/set/select_spec.rb +73 -0
- data/spec/lib/immutable/set/size_spec.rb +17 -0
- data/spec/lib/immutable/set/sorting_spec.rb +59 -0
- data/spec/lib/immutable/set/subset_spec.rb +51 -0
- data/spec/lib/immutable/set/sum_spec.rb +23 -0
- data/spec/lib/immutable/set/superset_spec.rb +51 -0
- data/spec/lib/immutable/set/to_a_spec.rb +30 -0
- data/spec/lib/immutable/set/to_list_spec.rb +35 -0
- data/spec/lib/immutable/set/to_set_spec.rb +19 -0
- data/spec/lib/immutable/set/union_spec.rb +63 -0
- data/spec/lib/immutable/sorted_set/above_spec.rb +51 -0
- data/spec/lib/immutable/sorted_set/add_spec.rb +62 -0
- data/spec/lib/immutable/sorted_set/at_spec.rb +24 -0
- data/spec/lib/immutable/sorted_set/below_spec.rb +51 -0
- data/spec/lib/immutable/sorted_set/between_spec.rb +51 -0
- data/spec/lib/immutable/sorted_set/clear_spec.rb +43 -0
- data/spec/lib/immutable/sorted_set/copying_spec.rb +20 -0
- data/spec/lib/immutable/sorted_set/delete_at_spec.rb +18 -0
- data/spec/lib/immutable/sorted_set/delete_spec.rb +89 -0
- data/spec/lib/immutable/sorted_set/difference_spec.rb +22 -0
- data/spec/lib/immutable/sorted_set/disjoint_spec.rb +25 -0
- data/spec/lib/immutable/sorted_set/drop_spec.rb +55 -0
- data/spec/lib/immutable/sorted_set/drop_while_spec.rb +34 -0
- data/spec/lib/immutable/sorted_set/each_spec.rb +28 -0
- data/spec/lib/immutable/sorted_set/empty_spec.rb +34 -0
- data/spec/lib/immutable/sorted_set/eql_spec.rb +120 -0
- data/spec/lib/immutable/sorted_set/exclusion_spec.rb +22 -0
- data/spec/lib/immutable/sorted_set/fetch_spec.rb +64 -0
- data/spec/lib/immutable/sorted_set/find_index_spec.rb +40 -0
- data/spec/lib/immutable/sorted_set/first_spec.rb +18 -0
- data/spec/lib/immutable/sorted_set/from_spec.rb +51 -0
- data/spec/lib/immutable/sorted_set/group_by_spec.rb +57 -0
- data/spec/lib/immutable/sorted_set/include_spec.rb +23 -0
- data/spec/lib/immutable/sorted_set/inspect_spec.rb +37 -0
- data/spec/lib/immutable/sorted_set/intersect_spec.rb +25 -0
- data/spec/lib/immutable/sorted_set/intersection_spec.rb +28 -0
- data/spec/lib/immutable/sorted_set/last_spec.rb +36 -0
- data/spec/lib/immutable/sorted_set/map_spec.rb +43 -0
- data/spec/lib/immutable/sorted_set/marshal_spec.rb +36 -0
- data/spec/lib/immutable/sorted_set/maximum_spec.rb +36 -0
- data/spec/lib/immutable/sorted_set/minimum_spec.rb +19 -0
- data/spec/lib/immutable/sorted_set/new_spec.rb +71 -0
- data/spec/lib/immutable/sorted_set/reverse_each_spec.rb +28 -0
- data/spec/lib/immutable/sorted_set/sample_spec.rb +13 -0
- data/spec/lib/immutable/sorted_set/select_spec.rb +61 -0
- data/spec/lib/immutable/sorted_set/size_spec.rb +17 -0
- data/spec/lib/immutable/sorted_set/slice_spec.rb +256 -0
- data/spec/lib/immutable/sorted_set/sorting_spec.rb +44 -0
- data/spec/lib/immutable/sorted_set/subset_spec.rb +47 -0
- data/spec/lib/immutable/sorted_set/superset_spec.rb +47 -0
- data/spec/lib/immutable/sorted_set/take_spec.rb +54 -0
- data/spec/lib/immutable/sorted_set/take_while_spec.rb +33 -0
- data/spec/lib/immutable/sorted_set/to_set_spec.rb +17 -0
- data/spec/lib/immutable/sorted_set/union_spec.rb +27 -0
- data/spec/lib/immutable/sorted_set/up_to_spec.rb +52 -0
- data/spec/lib/immutable/sorted_set/values_at_spec.rb +33 -0
- data/spec/lib/immutable/vector/add_spec.rb +67 -0
- data/spec/lib/immutable/vector/any_spec.rb +69 -0
- data/spec/lib/immutable/vector/assoc_spec.rb +45 -0
- data/spec/lib/immutable/vector/bsearch_spec.rb +65 -0
- data/spec/lib/immutable/vector/clear_spec.rb +33 -0
- data/spec/lib/immutable/vector/combination_spec.rb +81 -0
- data/spec/lib/immutable/vector/compact_spec.rb +29 -0
- data/spec/lib/immutable/vector/compare_spec.rb +31 -0
- data/spec/lib/immutable/vector/concat_spec.rb +34 -0
- data/spec/lib/immutable/vector/copying_spec.rb +20 -0
- data/spec/lib/immutable/vector/count_spec.rb +17 -0
- data/spec/lib/immutable/vector/delete_at_spec.rb +53 -0
- data/spec/lib/immutable/vector/delete_spec.rb +30 -0
- data/spec/lib/immutable/vector/drop_spec.rb +41 -0
- data/spec/lib/immutable/vector/drop_while_spec.rb +54 -0
- data/spec/lib/immutable/vector/each_index_spec.rb +40 -0
- data/spec/lib/immutable/vector/each_spec.rb +44 -0
- data/spec/lib/immutable/vector/each_with_index_spec.rb +39 -0
- data/spec/lib/immutable/vector/empty_spec.rb +41 -0
- data/spec/lib/immutable/vector/eql_spec.rb +76 -0
- data/spec/lib/immutable/vector/fetch_spec.rb +64 -0
- data/spec/lib/immutable/vector/fill_spec.rb +88 -0
- data/spec/lib/immutable/vector/first_spec.rb +18 -0
- data/spec/lib/immutable/vector/flat_map_spec.rb +50 -0
- data/spec/lib/immutable/vector/flatten_spec.rb +58 -0
- data/spec/lib/immutable/vector/get_spec.rb +74 -0
- data/spec/lib/immutable/vector/group_by_spec.rb +57 -0
- data/spec/lib/immutable/vector/include_spec.rb +30 -0
- data/spec/lib/immutable/vector/insert_spec.rb +68 -0
- data/spec/lib/immutable/vector/inspect_spec.rb +49 -0
- data/spec/lib/immutable/vector/join_spec.rb +58 -0
- data/spec/lib/immutable/vector/last_spec.rb +45 -0
- data/spec/lib/immutable/vector/length_spec.rb +45 -0
- data/spec/lib/immutable/vector/ltlt_spec.rb +65 -0
- data/spec/lib/immutable/vector/map_spec.rb +51 -0
- data/spec/lib/immutable/vector/marshal_spec.rb +31 -0
- data/spec/lib/immutable/vector/maximum_spec.rb +33 -0
- data/spec/lib/immutable/vector/minimum_spec.rb +33 -0
- data/spec/lib/immutable/vector/multiply_spec.rb +47 -0
- data/spec/lib/immutable/vector/new_spec.rb +50 -0
- data/spec/lib/immutable/vector/partition_spec.rb +52 -0
- data/spec/lib/immutable/vector/permutation_spec.rb +91 -0
- data/spec/lib/immutable/vector/pop_spec.rb +26 -0
- data/spec/lib/immutable/vector/product_spec.rb +70 -0
- data/spec/lib/immutable/vector/reduce_spec.rb +55 -0
- data/spec/lib/immutable/vector/reject_spec.rb +43 -0
- data/spec/lib/immutable/vector/repeated_combination_spec.rb +77 -0
- data/spec/lib/immutable/vector/repeated_permutation_spec.rb +93 -0
- data/spec/lib/immutable/vector/reverse_each_spec.rb +31 -0
- data/spec/lib/immutable/vector/reverse_spec.rb +21 -0
- data/spec/lib/immutable/vector/rindex_spec.rb +36 -0
- data/spec/lib/immutable/vector/rotate_spec.rb +73 -0
- data/spec/lib/immutable/vector/sample_spec.rb +13 -0
- data/spec/lib/immutable/vector/select_spec.rb +63 -0
- data/spec/lib/immutable/vector/set_spec.rb +174 -0
- data/spec/lib/immutable/vector/shift_spec.rb +27 -0
- data/spec/lib/immutable/vector/shuffle_spec.rb +43 -0
- data/spec/lib/immutable/vector/slice_spec.rb +240 -0
- data/spec/lib/immutable/vector/sorting_spec.rb +56 -0
- data/spec/lib/immutable/vector/sum_spec.rb +17 -0
- data/spec/lib/immutable/vector/take_spec.rb +42 -0
- data/spec/lib/immutable/vector/take_while_spec.rb +34 -0
- data/spec/lib/immutable/vector/to_a_spec.rb +41 -0
- data/spec/lib/immutable/vector/to_ary_spec.rb +34 -0
- data/spec/lib/immutable/vector/to_list_spec.rb +30 -0
- data/spec/lib/immutable/vector/to_set_spec.rb +21 -0
- data/spec/lib/immutable/vector/transpose_spec.rb +48 -0
- data/spec/lib/immutable/vector/uniq_spec.rb +76 -0
- data/spec/lib/immutable/vector/unshift_spec.rb +28 -0
- data/spec/lib/immutable/vector/update_in_spec.rb +82 -0
- data/spec/lib/immutable/vector/values_at_spec.rb +33 -0
- data/spec/lib/immutable/vector/zip_spec.rb +57 -0
- data/spec/lib/load_spec.rb +42 -0
- data/spec/spec_helper.rb +92 -0
- metadata +830 -0
@@ -0,0 +1,1595 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "set"
|
3
|
+
require "concurrent/atomics"
|
4
|
+
|
5
|
+
require "immutable/undefined"
|
6
|
+
require "immutable/enumerable"
|
7
|
+
require "immutable/hash"
|
8
|
+
require "immutable/set"
|
9
|
+
|
10
|
+
module Immutable
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# Create a lazy, infinite list.
|
14
|
+
#
|
15
|
+
# The given block is called as necessary to return successive elements of the list.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# Immutable.stream { :hello }.take(3)
|
19
|
+
# # => Immutable::List[:hello, :hello, :hello]
|
20
|
+
#
|
21
|
+
# @return [List]
|
22
|
+
def stream(&block)
|
23
|
+
return EmptyList unless block_given?
|
24
|
+
LazyList.new { Cons.new(yield, stream(&block)) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Construct a list of consecutive integers.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# Immutable.interval(5,9)
|
31
|
+
# # => Immutable::List[5, 6, 7, 8, 9]
|
32
|
+
#
|
33
|
+
# @param from [Integer] Start value, inclusive
|
34
|
+
# @param to [Integer] End value, inclusive
|
35
|
+
# @return [List]
|
36
|
+
def interval(from, to)
|
37
|
+
return EmptyList if from > to
|
38
|
+
interval_exclusive(from, to.next)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create an infinite list repeating the same item indefinitely
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# Immutable.repeat(:chunky).take(4)
|
45
|
+
# => Immutable::List[:chunky, :chunky, :chunky, :chunky]
|
46
|
+
#
|
47
|
+
# @return [List]
|
48
|
+
def repeat(item)
|
49
|
+
LazyList.new { Cons.new(item, repeat(item)) }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create a list that contains a given item a fixed number of times
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# Immutable.replicate(3, :hamster)
|
56
|
+
# #=> Immutable::List[:hamster, :hamster, :hamster]
|
57
|
+
#
|
58
|
+
# @return [List]
|
59
|
+
def replicate(number, item)
|
60
|
+
repeat(item).take(number)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Create an infinite list where each item is derived from the previous one,
|
64
|
+
# using the provided block
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# Immutable.iterate(0) { |i| i.next }.take(5)
|
68
|
+
# # => Immutable::List[0, 1, 2, 3, 4]
|
69
|
+
#
|
70
|
+
# @param [Object] item Starting value
|
71
|
+
# @yieldparam [Object] previous The previous value
|
72
|
+
# @yieldreturn [Object] The next value
|
73
|
+
# @return [List]
|
74
|
+
def iterate(item, &block)
|
75
|
+
LazyList.new { Cons.new(item, iterate(yield(item), &block)) }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Turn an `Enumerator` into a `Immutable::List`. The result is a lazy
|
79
|
+
# collection where the values are memoized as they are generated.
|
80
|
+
#
|
81
|
+
# If your code uses multiple threads, you need to make sure that the returned
|
82
|
+
# lazy collection is realized on a single thread only. Otherwise, a `FiberError`
|
83
|
+
# will be raised. After the collection is realized, it can be used from other
|
84
|
+
# threads as well.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# def rg; loop { yield rand(100) }; end
|
88
|
+
# Immutable.enumerate(to_enum(:rg)).take(10)
|
89
|
+
#
|
90
|
+
# @param enum [Enumerator] The object to iterate over
|
91
|
+
# @return [List]
|
92
|
+
def enumerate(enum)
|
93
|
+
LazyList.new do
|
94
|
+
begin
|
95
|
+
Cons.new(enum.next, enumerate(enum))
|
96
|
+
rescue StopIteration
|
97
|
+
EmptyList
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def interval_exclusive(from, to)
|
105
|
+
return EmptyList if from == to
|
106
|
+
LazyList.new { Cons.new(from, interval_exclusive(from.next, to)) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# A `List` can be constructed with {List.[] List[]}, or {Enumerable#to_list}.
|
111
|
+
# It consists of a *head* (the first element) and a *tail* (which itself is also
|
112
|
+
# a `List`, containing all the remaining elements).
|
113
|
+
#
|
114
|
+
# This is a singly linked list. Prepending to the list with {List#add} runs
|
115
|
+
# in constant time. Traversing the list from front to back is efficient,
|
116
|
+
# however, indexed access runs in linear time because the list needs to be
|
117
|
+
# traversed to find the element.
|
118
|
+
#
|
119
|
+
module List
|
120
|
+
include Immutable::Enumerable
|
121
|
+
|
122
|
+
# @private
|
123
|
+
CADR = /^c([ad]+)r$/
|
124
|
+
|
125
|
+
# Create a new `List` populated with the given items.
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# list = Immutable::List[:a, :b, :c]
|
129
|
+
# # => Immutable::List[:a, :b, :c]
|
130
|
+
#
|
131
|
+
# @return [List]
|
132
|
+
def self.[](*items)
|
133
|
+
from_enum(items)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Return an empty `List`.
|
137
|
+
#
|
138
|
+
# @return [List]
|
139
|
+
def self.empty
|
140
|
+
EmptyList
|
141
|
+
end
|
142
|
+
|
143
|
+
# This method exists distinct from `.[]` since it is ~30% faster
|
144
|
+
# than splatting the argument.
|
145
|
+
#
|
146
|
+
# Marking as private only because it was introduced for an internal
|
147
|
+
# refactoring. It could potentially be made public with a good name.
|
148
|
+
#
|
149
|
+
# @private
|
150
|
+
def self.from_enum(items)
|
151
|
+
# use destructive operations to build up a new list, like Common Lisp's NCONC
|
152
|
+
# this is a very fast way to build up a linked list
|
153
|
+
list = tail = Cons.allocate
|
154
|
+
items.each do |item|
|
155
|
+
new_node = Cons.allocate
|
156
|
+
new_node.instance_variable_set(:@head, item)
|
157
|
+
tail.instance_variable_set(:@tail, new_node)
|
158
|
+
tail = new_node
|
159
|
+
end
|
160
|
+
tail.instance_variable_set(:@tail, EmptyList)
|
161
|
+
list.tail
|
162
|
+
end
|
163
|
+
|
164
|
+
# Return the number of items in this `List`.
|
165
|
+
# @return [Integer]
|
166
|
+
def size
|
167
|
+
result, list = 0, self
|
168
|
+
until list.empty?
|
169
|
+
if list.cached_size?
|
170
|
+
return result + list.size
|
171
|
+
else
|
172
|
+
result += 1
|
173
|
+
end
|
174
|
+
list = list.tail
|
175
|
+
end
|
176
|
+
result
|
177
|
+
end
|
178
|
+
alias :length :size
|
179
|
+
|
180
|
+
# Create a new `List` with `item` added at the front. This is a constant
|
181
|
+
# time operation.
|
182
|
+
#
|
183
|
+
# @example
|
184
|
+
# Immutable::List[:b, :c].add(:a)
|
185
|
+
# # => Immutable::List[:a, :b, :c]
|
186
|
+
#
|
187
|
+
# @param item [Object] The item to add
|
188
|
+
# @return [List]
|
189
|
+
def add(item)
|
190
|
+
Cons.new(item, self)
|
191
|
+
end
|
192
|
+
alias :cons :add
|
193
|
+
|
194
|
+
# Create a new `List` with `item` added at the end. This is much less efficient
|
195
|
+
# than adding items at the front.
|
196
|
+
#
|
197
|
+
# @example
|
198
|
+
# Immutable::List[:a, :b] << :c
|
199
|
+
# # => Immutable::List[:a, :b, :c]
|
200
|
+
#
|
201
|
+
# @param item [Object] The item to add
|
202
|
+
# @return [List]
|
203
|
+
def <<(item)
|
204
|
+
append(List[item])
|
205
|
+
end
|
206
|
+
|
207
|
+
# Call the given block once for each item in the list, passing each
|
208
|
+
# item from first to last successively to the block. If no block is given,
|
209
|
+
# returns an `Enumerator`.
|
210
|
+
#
|
211
|
+
# @return [self]
|
212
|
+
# @yield [item]
|
213
|
+
def each
|
214
|
+
return to_enum unless block_given?
|
215
|
+
list = self
|
216
|
+
until list.empty?
|
217
|
+
yield(list.head)
|
218
|
+
list = list.tail
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Return a `List` in which each element is derived from the corresponding
|
223
|
+
# element in this `List`, transformed through the given block. If no block
|
224
|
+
# is given, returns an `Enumerator`.
|
225
|
+
#
|
226
|
+
# @example
|
227
|
+
# Immutable::List[3, 2, 1].map { |e| e * e } # => Immutable::List[9, 4, 1]
|
228
|
+
#
|
229
|
+
# @return [List, Enumerator]
|
230
|
+
# @yield [item]
|
231
|
+
def map(&block)
|
232
|
+
return enum_for(:map) unless block_given?
|
233
|
+
LazyList.new do
|
234
|
+
next self if empty?
|
235
|
+
Cons.new(yield(head), tail.map(&block))
|
236
|
+
end
|
237
|
+
end
|
238
|
+
alias :collect :map
|
239
|
+
|
240
|
+
# Return a `List` which is realized by transforming each item into a `List`,
|
241
|
+
# and flattening the resulting lists.
|
242
|
+
#
|
243
|
+
# @example
|
244
|
+
# Immutable::List[1, 2, 3].flat_map { |x| Immutable::List[x, 100] }
|
245
|
+
# # => Immutable::List[1, 100, 2, 100, 3, 100]
|
246
|
+
#
|
247
|
+
# @return [List]
|
248
|
+
def flat_map(&block)
|
249
|
+
return enum_for(:flat_map) unless block_given?
|
250
|
+
LazyList.new do
|
251
|
+
next self if empty?
|
252
|
+
head_list = List.from_enum(yield(head))
|
253
|
+
next tail.flat_map(&block) if head_list.empty?
|
254
|
+
Cons.new(head_list.first, head_list.drop(1).append(tail.flat_map(&block)))
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Return a `List` which contains all the items for which the given block
|
259
|
+
# returns true.
|
260
|
+
#
|
261
|
+
# @example
|
262
|
+
# Immutable::List["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 }
|
263
|
+
# # => Immutable::List["Bird", "Elephant"]
|
264
|
+
#
|
265
|
+
# @return [List]
|
266
|
+
# @yield [item] Once for each item.
|
267
|
+
def select(&block)
|
268
|
+
return enum_for(:select) unless block_given?
|
269
|
+
LazyList.new do
|
270
|
+
list = self
|
271
|
+
while true
|
272
|
+
break list if list.empty?
|
273
|
+
break Cons.new(list.head, list.tail.select(&block)) if yield(list.head)
|
274
|
+
list = list.tail
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
alias :find_all :select
|
279
|
+
alias :keep_if :select
|
280
|
+
|
281
|
+
# Return a `List` which contains all elements up to, but not including, the
|
282
|
+
# first element for which the block returns `nil` or `false`.
|
283
|
+
#
|
284
|
+
# @example
|
285
|
+
# Immutable::List[1, 3, 5, 7, 6, 4, 2].take_while { |e| e < 5 }
|
286
|
+
# # => Immutable::List[1, 3]
|
287
|
+
#
|
288
|
+
# @return [List, Enumerator]
|
289
|
+
# @yield [item]
|
290
|
+
def take_while(&block)
|
291
|
+
return enum_for(:take_while) unless block_given?
|
292
|
+
LazyList.new do
|
293
|
+
next self if empty?
|
294
|
+
next Cons.new(head, tail.take_while(&block)) if yield(head)
|
295
|
+
EmptyList
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# Return a `List` which contains all elements starting from the
|
300
|
+
# first element for which the block returns `nil` or `false`.
|
301
|
+
#
|
302
|
+
# @example
|
303
|
+
# Immutable::List[1, 3, 5, 7, 6, 4, 2].drop_while { |e| e < 5 }
|
304
|
+
# # => Immutable::List[5, 7, 6, 4, 2]
|
305
|
+
#
|
306
|
+
# @return [List, Enumerator]
|
307
|
+
# @yield [item]
|
308
|
+
def drop_while(&block)
|
309
|
+
return enum_for(:drop_while) unless block_given?
|
310
|
+
LazyList.new do
|
311
|
+
list = self
|
312
|
+
list = list.tail while !list.empty? && yield(list.head)
|
313
|
+
list
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Return a `List` containing the first `number` items from this `List`.
|
318
|
+
#
|
319
|
+
# @example
|
320
|
+
# Immutable::List[1, 3, 5, 7, 6, 4, 2].take(3)
|
321
|
+
# # => Immutable::List[1, 3, 5]
|
322
|
+
#
|
323
|
+
# @param number [Integer] The number of items to retain
|
324
|
+
# @return [List]
|
325
|
+
def take(number)
|
326
|
+
LazyList.new do
|
327
|
+
next self if empty?
|
328
|
+
next Cons.new(head, tail.take(number - 1)) if number > 0
|
329
|
+
EmptyList
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Return a `List` containing all but the last item from this `List`.
|
334
|
+
#
|
335
|
+
# @example
|
336
|
+
# Immutable::List["A", "B", "C"].pop # => Immutable::List["A", "B"]
|
337
|
+
#
|
338
|
+
# @return [List]
|
339
|
+
def pop
|
340
|
+
LazyList.new do
|
341
|
+
next self if empty?
|
342
|
+
new_size = size - 1
|
343
|
+
next Cons.new(head, tail.take(new_size - 1)) if new_size >= 1
|
344
|
+
EmptyList
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Return a `List` containing all items after the first `number` items from
|
349
|
+
# this `List`.
|
350
|
+
#
|
351
|
+
# @example
|
352
|
+
# Immutable::List[1, 3, 5, 7, 6, 4, 2].drop(3)
|
353
|
+
# # => Immutable::List[7, 6, 4, 2]
|
354
|
+
#
|
355
|
+
# @param number [Integer] The number of items to skip over
|
356
|
+
# @return [List]
|
357
|
+
def drop(number)
|
358
|
+
LazyList.new do
|
359
|
+
list = self
|
360
|
+
while !list.empty? && number > 0
|
361
|
+
number -= 1
|
362
|
+
list = list.tail
|
363
|
+
end
|
364
|
+
list
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Return a `List` with all items from this `List`, followed by all items from
|
369
|
+
# `other`.
|
370
|
+
#
|
371
|
+
# @example
|
372
|
+
# Immutable::List[1, 2, 3].append(Immutable::List[4, 5])
|
373
|
+
# # => Immutable::List[1, 2, 3, 4, 5]
|
374
|
+
#
|
375
|
+
# @param other [List] The list to add onto the end of this one
|
376
|
+
# @return [List]
|
377
|
+
def append(other)
|
378
|
+
LazyList.new do
|
379
|
+
next other if empty?
|
380
|
+
Cons.new(head, tail.append(other))
|
381
|
+
end
|
382
|
+
end
|
383
|
+
alias :concat :append
|
384
|
+
alias :+ :append
|
385
|
+
|
386
|
+
# Return a `List` with the same items, but in reverse order.
|
387
|
+
#
|
388
|
+
# @example
|
389
|
+
# Immutable::List["A", "B", "C"].reverse # => Immutable::List["C", "B", "A"]
|
390
|
+
#
|
391
|
+
# @return [List]
|
392
|
+
def reverse
|
393
|
+
LazyList.new { reduce(EmptyList) { |list, item| list.cons(item) }}
|
394
|
+
end
|
395
|
+
|
396
|
+
# Combine two lists by "zipping" them together. The corresponding elements
|
397
|
+
# from this `List` and each of `others` (that is, the elements with the
|
398
|
+
# same indices) will be gathered into lists.
|
399
|
+
#
|
400
|
+
# If `others` contains fewer elements than this list, `nil` will be used
|
401
|
+
# for padding.
|
402
|
+
#
|
403
|
+
# @example
|
404
|
+
# Immutable::List["A", "B", "C"].zip(Immutable::List[1, 2, 3])
|
405
|
+
# # => Immutable::List[Immutable::List["A", 1], Immutable::List["B", 2], Immutable::List["C", 3]]
|
406
|
+
#
|
407
|
+
# @param others [List] The list to zip together with this one
|
408
|
+
# @return [List]
|
409
|
+
def zip(others)
|
410
|
+
LazyList.new do
|
411
|
+
next self if empty? && others.empty?
|
412
|
+
Cons.new(Cons.new(head, Cons.new(others.head)), tail.zip(others.tail))
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Gather the first element of each nested list into a new `List`, then the second
|
417
|
+
# element of each nested list, then the third, and so on. In other words, if each
|
418
|
+
# nested list is a "row", return a `List` of "columns" instead.
|
419
|
+
#
|
420
|
+
# Although the returned list is lazy, each returned nested list (each "column")
|
421
|
+
# is strict. So while each nested list in the input can be infinite, the parent
|
422
|
+
# `List` must not be, or trying to realize the first element in the output will
|
423
|
+
# cause an infinite loop.
|
424
|
+
#
|
425
|
+
# @example
|
426
|
+
# # First let's create some infinite lists
|
427
|
+
# list1 = Immutable.iterate(1, &:next)
|
428
|
+
# list2 = Immutable.iterate(2) { |n| n * 2 }
|
429
|
+
# list3 = Immutable.iterate(3) { |n| n * 3 }
|
430
|
+
#
|
431
|
+
# # Now we transpose our 3 infinite "rows" into an infinite series of 3-element "columns"
|
432
|
+
# Immutable::List[list1, list2, list3].transpose.take(4)
|
433
|
+
# # => Immutable::List[
|
434
|
+
# # Immutable::List[1, 2, 3],
|
435
|
+
# # Immutable::List[2, 4, 9],
|
436
|
+
# # Immutable::List[3, 8, 27],
|
437
|
+
# # Immutable::List[4, 16, 81]]
|
438
|
+
#
|
439
|
+
# @return [List]
|
440
|
+
def transpose
|
441
|
+
return EmptyList if empty?
|
442
|
+
LazyList.new do
|
443
|
+
next EmptyList if any? { |list| list.empty? }
|
444
|
+
heads, tails = EmptyList, EmptyList
|
445
|
+
reverse_each { |list| heads, tails = heads.cons(list.head), tails.cons(list.tail) }
|
446
|
+
Cons.new(heads, tails.transpose)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
# Concatenate an infinite series of copies of this `List` together into a
|
451
|
+
# new `List`. Or, if empty, just return an empty list.
|
452
|
+
#
|
453
|
+
# @example
|
454
|
+
# Immutable::List[1, 2, 3].cycle.take(10)
|
455
|
+
# # => Immutable::List[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
|
456
|
+
#
|
457
|
+
# @return [List]
|
458
|
+
def cycle
|
459
|
+
LazyList.new do
|
460
|
+
next self if empty?
|
461
|
+
Cons.new(head, tail.append(cycle))
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
# Return a new `List` with the same elements, but rotated so that the one at
|
466
|
+
# index `count` is the first element of the new list. If `count` is positive,
|
467
|
+
# the elements will be shifted left, and those shifted past the lowest position
|
468
|
+
# will be moved to the end. If `count` is negative, the elements will be shifted
|
469
|
+
# right, and those shifted past the last position will be moved to the beginning.
|
470
|
+
#
|
471
|
+
# @example
|
472
|
+
# l = Immutable::List["A", "B", "C", "D", "E", "F"]
|
473
|
+
# l.rotate(2) # => Immutable::List["C", "D", "E", "F", "A", "B"]
|
474
|
+
# l.rotate(-1) # => Immutable::List["F", "A", "B", "C", "D", "E"]
|
475
|
+
#
|
476
|
+
# @param count [Integer] The number of positions to shift items by
|
477
|
+
# @return [Vector]
|
478
|
+
# @raise [TypeError] if count is not an integer.
|
479
|
+
def rotate(count = 1)
|
480
|
+
raise TypeError, "expected Integer" if not count.is_a?(Integer)
|
481
|
+
return self if empty? || (count % size) == 0
|
482
|
+
count = (count >= 0) ? count % size : (size - (~count % size) - 1)
|
483
|
+
drop(count).append(take(count))
|
484
|
+
end
|
485
|
+
|
486
|
+
# Return two `List`s, one of the first `number` items, and another with the
|
487
|
+
# remaining.
|
488
|
+
#
|
489
|
+
# @example
|
490
|
+
# Immutable::List["a", "b", "c", "d"].split_at(2)
|
491
|
+
# # => [Immutable::List["a", "b"], Immutable::List["c", "d"]]
|
492
|
+
#
|
493
|
+
# @param number [Integer] The index at which to split this list
|
494
|
+
# @return [Array]
|
495
|
+
def split_at(number)
|
496
|
+
[take(number), drop(number)].freeze
|
497
|
+
end
|
498
|
+
|
499
|
+
# Return two `List`s, one up to (but not including) the first item for which the
|
500
|
+
# block returns `nil` or `false`, and another of all the remaining items.
|
501
|
+
#
|
502
|
+
# @example
|
503
|
+
# Immutable::List[4, 3, 5, 2, 1].span { |x| x > 2 }
|
504
|
+
# # => [Immutable::List[4, 3, 5], Immutable::List[2, 1]]
|
505
|
+
#
|
506
|
+
# @return [Array]
|
507
|
+
# @yield [item]
|
508
|
+
def span(&block)
|
509
|
+
return [self, EmptyList].freeze unless block_given?
|
510
|
+
splitter = Splitter.new(self, block)
|
511
|
+
mutex = Mutex.new
|
512
|
+
[Splitter::Left.new(splitter, splitter.left, mutex),
|
513
|
+
Splitter::Right.new(splitter, mutex)].freeze
|
514
|
+
end
|
515
|
+
|
516
|
+
# Return two `List`s, one up to (but not including) the first item for which the
|
517
|
+
# block returns true, and another of all the remaining items.
|
518
|
+
#
|
519
|
+
# @example
|
520
|
+
# Immutable::List[1, 3, 4, 2, 5].break { |x| x > 3 }
|
521
|
+
# # => [Immutable::List[1, 3], Immutable::List[4, 2, 5]]
|
522
|
+
#
|
523
|
+
# @return [Array]
|
524
|
+
# @yield [item]
|
525
|
+
def break(&block)
|
526
|
+
return span unless block_given?
|
527
|
+
span { |item| !yield(item) }
|
528
|
+
end
|
529
|
+
|
530
|
+
# Return an empty `List`. If used on a subclass, returns an empty instance
|
531
|
+
# of that class.
|
532
|
+
#
|
533
|
+
# @return [List]
|
534
|
+
def clear
|
535
|
+
EmptyList
|
536
|
+
end
|
537
|
+
|
538
|
+
# Return a new `List` with the same items, but sorted.
|
539
|
+
#
|
540
|
+
# @overload sort
|
541
|
+
# Compare elements with their natural sort key (`#<=>`).
|
542
|
+
#
|
543
|
+
# @example
|
544
|
+
# Immutable::List["Elephant", "Dog", "Lion"].sort
|
545
|
+
# # => Immutable::List["Dog", "Elephant", "Lion"]
|
546
|
+
#
|
547
|
+
# @overload sort
|
548
|
+
# Uses the block as a comparator to determine sorted order.
|
549
|
+
#
|
550
|
+
# @yield [a, b] Any number of times with different pairs of elements.
|
551
|
+
# @yieldreturn [Integer] Negative if the first element should be sorted
|
552
|
+
# lower, positive if the latter element, or 0 if
|
553
|
+
# equal.
|
554
|
+
# @example
|
555
|
+
# Immutable::List["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size }
|
556
|
+
# # => Immutable::List["Dog", "Lion", "Elephant"]
|
557
|
+
#
|
558
|
+
# @return [List]
|
559
|
+
def sort(&comparator)
|
560
|
+
LazyList.new { List.from_enum(super(&comparator)) }
|
561
|
+
end
|
562
|
+
|
563
|
+
# Return a new `List` with the same items, but sorted. The sort order is
|
564
|
+
# determined by mapping the items through the given block to obtain sort
|
565
|
+
# keys, and then sorting the keys according to their natural sort order
|
566
|
+
# (`#<=>`).
|
567
|
+
#
|
568
|
+
# @yield [element] Once for each element.
|
569
|
+
# @yieldreturn a sort key object for the yielded element.
|
570
|
+
# @example
|
571
|
+
# Immutable::List["Elephant", "Dog", "Lion"].sort_by { |e| e.size }
|
572
|
+
# # => Immutable::List["Dog", "Lion", "Elephant"]
|
573
|
+
#
|
574
|
+
# @return [List]
|
575
|
+
def sort_by(&transformer)
|
576
|
+
return sort unless block_given?
|
577
|
+
LazyList.new { List.from_enum(super(&transformer)) }
|
578
|
+
end
|
579
|
+
|
580
|
+
# Return a new `List` with `sep` inserted between each of the existing elements.
|
581
|
+
#
|
582
|
+
# @example
|
583
|
+
# Immutable::List["one", "two", "three"].intersperse(" ")
|
584
|
+
# # => Immutable::List["one", " ", "two", " ", "three"]
|
585
|
+
#
|
586
|
+
# @return [List]
|
587
|
+
def intersperse(sep)
|
588
|
+
LazyList.new do
|
589
|
+
next self if tail.empty?
|
590
|
+
Cons.new(head, Cons.new(sep, tail.intersperse(sep)))
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
# Return a `List` with the same items, but all duplicates removed.
|
595
|
+
# Use `#hash` and `#eql?` to determine which items are duplicates.
|
596
|
+
#
|
597
|
+
# @example
|
598
|
+
# Immutable::List[:a, :b, :a, :c, :b].uniq # => Immutable::List[:a, :b, :c]
|
599
|
+
# Immutable::List["a", "A", "b"].uniq(&:upcase) # => Immutable::List["a", "b"]
|
600
|
+
#
|
601
|
+
# @return [List]
|
602
|
+
def uniq(&block)
|
603
|
+
_uniq(::Set.new, &block)
|
604
|
+
end
|
605
|
+
|
606
|
+
# @private
|
607
|
+
# Separate from `uniq` so as not to expose `items` in the public API.
|
608
|
+
def _uniq(items, &block)
|
609
|
+
if block_given?
|
610
|
+
LazyList.new do
|
611
|
+
next self if empty?
|
612
|
+
if items.add?(block.call(head))
|
613
|
+
Cons.new(head, tail._uniq(items, &block))
|
614
|
+
else
|
615
|
+
tail._uniq(items, &block)
|
616
|
+
end
|
617
|
+
end
|
618
|
+
else
|
619
|
+
LazyList.new do
|
620
|
+
next self if empty?
|
621
|
+
next tail._uniq(items) if items.include?(head)
|
622
|
+
Cons.new(head, tail._uniq(items.add(head)))
|
623
|
+
end
|
624
|
+
end
|
625
|
+
end
|
626
|
+
protected :_uniq
|
627
|
+
|
628
|
+
# Return a `List` with all the elements from both this list and `other`,
|
629
|
+
# with all duplicates removed.
|
630
|
+
#
|
631
|
+
# @example
|
632
|
+
# Immutable::List[1, 2].union(Immutable::List[2, 3]) # => Immutable::List[1, 2, 3]
|
633
|
+
#
|
634
|
+
# @param other [List] The list to merge with
|
635
|
+
# @return [List]
|
636
|
+
def union(other, items = ::Set.new)
|
637
|
+
LazyList.new do
|
638
|
+
next other._uniq(items) if empty?
|
639
|
+
next tail.union(other, items) if items.include?(head)
|
640
|
+
Cons.new(head, tail.union(other, items.add(head)))
|
641
|
+
end
|
642
|
+
end
|
643
|
+
alias :| :union
|
644
|
+
|
645
|
+
# Return a `List` with all elements except the last one.
|
646
|
+
#
|
647
|
+
# @example
|
648
|
+
# Immutable::List["a", "b", "c"].init # => Immutable::List["a", "b"]
|
649
|
+
#
|
650
|
+
# @return [List]
|
651
|
+
def init
|
652
|
+
return EmptyList if tail.empty?
|
653
|
+
LazyList.new { Cons.new(head, tail.init) }
|
654
|
+
end
|
655
|
+
|
656
|
+
# Return the last item in this list.
|
657
|
+
# @return [Object]
|
658
|
+
def last
|
659
|
+
list = self
|
660
|
+
list = list.tail until list.tail.empty?
|
661
|
+
list.head
|
662
|
+
end
|
663
|
+
|
664
|
+
# Return a `List` of all suffixes of this list.
|
665
|
+
#
|
666
|
+
# @example
|
667
|
+
# Immutable::List[1,2,3].tails
|
668
|
+
# # => Immutable::List[
|
669
|
+
# # Immutable::List[1, 2, 3],
|
670
|
+
# # Immutable::List[2, 3],
|
671
|
+
# # Immutable::List[3]]
|
672
|
+
#
|
673
|
+
# @return [List]
|
674
|
+
def tails
|
675
|
+
LazyList.new do
|
676
|
+
next self if empty?
|
677
|
+
Cons.new(self, tail.tails)
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
# Return a `List` of all prefixes of this list.
|
682
|
+
#
|
683
|
+
# @example
|
684
|
+
# Immutable::List[1,2,3].inits
|
685
|
+
# # => Immutable::List[
|
686
|
+
# # Immutable::List[1],
|
687
|
+
# # Immutable::List[1, 2],
|
688
|
+
# # Immutable::List[1, 2, 3]]
|
689
|
+
#
|
690
|
+
# @return [List]
|
691
|
+
def inits
|
692
|
+
LazyList.new do
|
693
|
+
next self if empty?
|
694
|
+
Cons.new(List[head], tail.inits.map { |list| list.cons(head) })
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
# Return a `List` of all combinations of length `n` of items from this `List`.
|
699
|
+
#
|
700
|
+
# @example
|
701
|
+
# Immutable::List[1,2,3].combination(2)
|
702
|
+
# # => Immutable::List[
|
703
|
+
# # Immutable::List[1, 2],
|
704
|
+
# # Immutable::List[1, 3],
|
705
|
+
# # Immutable::List[2, 3]]
|
706
|
+
#
|
707
|
+
# @return [List]
|
708
|
+
def combination(n)
|
709
|
+
return Cons.new(EmptyList) if n == 0
|
710
|
+
LazyList.new do
|
711
|
+
next self if empty?
|
712
|
+
tail.combination(n - 1).map { |list| list.cons(head) }.append(tail.combination(n))
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
# Split the items in this list in groups of `number`. Return a list of lists.
|
717
|
+
#
|
718
|
+
# @example
|
719
|
+
# ("a".."o").to_list.chunk(5)
|
720
|
+
# # => Immutable::List[
|
721
|
+
# # Immutable::List["a", "b", "c", "d", "e"],
|
722
|
+
# # Immutable::List["f", "g", "h", "i", "j"],
|
723
|
+
# # Immutable::List["k", "l", "m", "n", "o"]]
|
724
|
+
#
|
725
|
+
# @return [List]
|
726
|
+
def chunk(number)
|
727
|
+
LazyList.new do
|
728
|
+
next self if empty?
|
729
|
+
first, remainder = split_at(number)
|
730
|
+
Cons.new(first, remainder.chunk(number))
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
# Split the items in this list in groups of `number`, and yield each group
|
735
|
+
# to the block (as a `List`). If no block is given, returns an
|
736
|
+
# `Enumerator`.
|
737
|
+
#
|
738
|
+
# @return [self, Enumerator]
|
739
|
+
# @yield [list] Once for each chunk.
|
740
|
+
def each_chunk(number, &block)
|
741
|
+
return enum_for(:each_chunk, number) unless block_given?
|
742
|
+
chunk(number).each(&block)
|
743
|
+
self
|
744
|
+
end
|
745
|
+
alias :each_slice :each_chunk
|
746
|
+
|
747
|
+
# Return a new `List` with all nested lists recursively "flattened out",
|
748
|
+
# that is, their elements inserted into the new `List` in the place where
|
749
|
+
# the nested list originally was.
|
750
|
+
#
|
751
|
+
# @example
|
752
|
+
# Immutable::List[Immutable::List[1, 2], Immutable::List[3, 4]].flatten
|
753
|
+
# # => Immutable::List[1, 2, 3, 4]
|
754
|
+
#
|
755
|
+
# @return [List]
|
756
|
+
def flatten
|
757
|
+
LazyList.new do
|
758
|
+
next self if empty?
|
759
|
+
next head.append(tail.flatten) if head.is_a?(List)
|
760
|
+
Cons.new(head, tail.flatten)
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
# Passes each item to the block, and gathers them into a {Hash} where the
|
765
|
+
# keys are return values from the block, and the values are `List`s of items
|
766
|
+
# for which the block returned that value.
|
767
|
+
#
|
768
|
+
# @return [Hash]
|
769
|
+
# @yield [item]
|
770
|
+
# @example
|
771
|
+
# Immutable::List["a", "b", "ab"].group_by { |e| e.size }
|
772
|
+
# # Immutable::Hash[
|
773
|
+
# # 1 => Immutable::List["b", "a"],
|
774
|
+
# # 2 => Immutable::List["ab"]
|
775
|
+
# # ]
|
776
|
+
def group_by(&block)
|
777
|
+
group_by_with(EmptyList, &block)
|
778
|
+
end
|
779
|
+
alias :group :group_by
|
780
|
+
|
781
|
+
# Retrieve the item at `index`. Negative indices count back from the end of
|
782
|
+
# the list (-1 is the last item). If `index` is invalid (either too high or
|
783
|
+
# too low), return `nil`.
|
784
|
+
#
|
785
|
+
# @param index [Integer] The index to retrieve
|
786
|
+
# @return [Object]
|
787
|
+
def at(index)
|
788
|
+
index += size if index < 0
|
789
|
+
return nil if index < 0
|
790
|
+
node = self
|
791
|
+
while index > 0
|
792
|
+
node = node.tail
|
793
|
+
index -= 1
|
794
|
+
end
|
795
|
+
node.head
|
796
|
+
end
|
797
|
+
|
798
|
+
# Return specific objects from the `List`. All overloads return `nil` if
|
799
|
+
# the starting index is out of range.
|
800
|
+
#
|
801
|
+
# @overload list.slice(index)
|
802
|
+
# Returns a single object at the given `index`. If `index` is negative,
|
803
|
+
# count backwards from the end.
|
804
|
+
#
|
805
|
+
# @param index [Integer] The index to retrieve. May be negative.
|
806
|
+
# @return [Object]
|
807
|
+
# @example
|
808
|
+
# l = Immutable::List["A", "B", "C", "D", "E", "F"]
|
809
|
+
# l[2] # => "C"
|
810
|
+
# l[-1] # => "F"
|
811
|
+
# l[6] # => nil
|
812
|
+
#
|
813
|
+
# @overload list.slice(index, length)
|
814
|
+
# Return a sublist starting at `index` and continuing for `length`
|
815
|
+
# elements or until the end of the `List`, whichever occurs first.
|
816
|
+
#
|
817
|
+
# @param start [Integer] The index to start retrieving items from. May be
|
818
|
+
# negative.
|
819
|
+
# @param length [Integer] The number of items to retrieve.
|
820
|
+
# @return [List]
|
821
|
+
# @example
|
822
|
+
# l = Immutable::List["A", "B", "C", "D", "E", "F"]
|
823
|
+
# l[2, 3] # => Immutable::List["C", "D", "E"]
|
824
|
+
# l[-2, 3] # => Immutable::List["E", "F"]
|
825
|
+
# l[20, 1] # => nil
|
826
|
+
#
|
827
|
+
# @overload list.slice(index..end)
|
828
|
+
# Return a sublist starting at `index` and continuing to index
|
829
|
+
# `end` or the end of the `List`, whichever occurs first.
|
830
|
+
#
|
831
|
+
# @param range [Range] The range of indices to retrieve.
|
832
|
+
# @return [Vector]
|
833
|
+
# @example
|
834
|
+
# l = Immutable::List["A", "B", "C", "D", "E", "F"]
|
835
|
+
# l[2..3] # => Immutable::List["C", "D"]
|
836
|
+
# l[-2..100] # => Immutable::List["E", "F"]
|
837
|
+
# l[20..21] # => nil
|
838
|
+
def slice(arg, length = (missing_length = true))
|
839
|
+
if missing_length
|
840
|
+
if arg.is_a?(Range)
|
841
|
+
from, to = arg.begin, arg.end
|
842
|
+
from += size if from < 0
|
843
|
+
return nil if from < 0
|
844
|
+
to += size if to < 0
|
845
|
+
to += 1 if !arg.exclude_end?
|
846
|
+
length = to - from
|
847
|
+
length = 0 if length < 0
|
848
|
+
list = self
|
849
|
+
while from > 0
|
850
|
+
return nil if list.empty?
|
851
|
+
list = list.tail
|
852
|
+
from -= 1
|
853
|
+
end
|
854
|
+
list.take(length)
|
855
|
+
else
|
856
|
+
at(arg)
|
857
|
+
end
|
858
|
+
else
|
859
|
+
return nil if length < 0
|
860
|
+
arg += size if arg < 0
|
861
|
+
return nil if arg < 0
|
862
|
+
list = self
|
863
|
+
while arg > 0
|
864
|
+
return nil if list.empty?
|
865
|
+
list = list.tail
|
866
|
+
arg -= 1
|
867
|
+
end
|
868
|
+
list.take(length)
|
869
|
+
end
|
870
|
+
end
|
871
|
+
alias :[] :slice
|
872
|
+
|
873
|
+
# Return a `List` of indices of matching objects.
|
874
|
+
#
|
875
|
+
# @overload indices(object)
|
876
|
+
# Return a `List` of indices where `object` is found. Use `#==` for
|
877
|
+
# testing equality.
|
878
|
+
#
|
879
|
+
# @example
|
880
|
+
# Immutable::List[1, 2, 3, 4].indices(2)
|
881
|
+
# # => Immutable::List[1]
|
882
|
+
#
|
883
|
+
# @overload indices
|
884
|
+
# Pass each item successively to the block. Return a list of indices
|
885
|
+
# where the block returns true.
|
886
|
+
#
|
887
|
+
# @yield [item]
|
888
|
+
# @example
|
889
|
+
# Immutable::List[1, 2, 3, 4].indices { |e| e.even? }
|
890
|
+
# # => Immutable::List[1, 3]
|
891
|
+
#
|
892
|
+
# @return [List]
|
893
|
+
def indices(object = Undefined, i = 0, &block)
|
894
|
+
return indices { |item| item == object } if not block_given?
|
895
|
+
return EmptyList if empty?
|
896
|
+
LazyList.new do
|
897
|
+
node = self
|
898
|
+
while true
|
899
|
+
break Cons.new(i, node.tail.indices(Undefined, i + 1, &block)) if yield(node.head)
|
900
|
+
node = node.tail
|
901
|
+
break EmptyList if node.empty?
|
902
|
+
i += 1
|
903
|
+
end
|
904
|
+
end
|
905
|
+
end
|
906
|
+
|
907
|
+
# Merge all the nested lists into a single list, using the given comparator
|
908
|
+
# block to determine the order which items should be shifted out of the nested
|
909
|
+
# lists and into the output list.
|
910
|
+
#
|
911
|
+
# @example
|
912
|
+
# list_1 = Immutable::List[1, -3, -5]
|
913
|
+
# list_2 = Immutable::List[-2, 4, 6]
|
914
|
+
# Immutable::List[list_1, list_2].merge { |a,b| a.abs <=> b.abs }
|
915
|
+
# # => Immutable::List[1, -2, -3, 4, -5, 6]
|
916
|
+
#
|
917
|
+
# @return [List]
|
918
|
+
# @yield [a, b] Pairs of items from matching indices in each list.
|
919
|
+
# @yieldreturn [Integer] Negative if the first element should be selected
|
920
|
+
# first, positive if the latter element, or zero if
|
921
|
+
# either.
|
922
|
+
def merge(&comparator)
|
923
|
+
return merge_by unless block_given?
|
924
|
+
LazyList.new do
|
925
|
+
sorted = reject(&:empty?).sort do |a, b|
|
926
|
+
yield(a.head, b.head)
|
927
|
+
end
|
928
|
+
next EmptyList if sorted.empty?
|
929
|
+
Cons.new(sorted.head.head, sorted.tail.cons(sorted.head.tail).merge(&comparator))
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
# Merge all the nested lists into a single list, using sort keys generated
|
934
|
+
# by mapping the items in the nested lists through the given block to determine the
|
935
|
+
# order which items should be shifted out of the nested lists and into the output
|
936
|
+
# list. Whichever nested list's `#head` has the "lowest" sort key (according to
|
937
|
+
# their natural order) will be the first in the merged `List`.
|
938
|
+
#
|
939
|
+
# @example
|
940
|
+
# list_1 = Immutable::List[1, -3, -5]
|
941
|
+
# list_2 = Immutable::List[-2, 4, 6]
|
942
|
+
# Immutable::List[list_1, list_2].merge_by { |x| x.abs }
|
943
|
+
# # => Immutable::List[1, -2, -3, 4, -5, 6]
|
944
|
+
#
|
945
|
+
# @return [List]
|
946
|
+
# @yield [item] Once for each item in either list.
|
947
|
+
# @yieldreturn [Object] A sort key for the element.
|
948
|
+
def merge_by(&transformer)
|
949
|
+
return merge_by { |item| item } unless block_given?
|
950
|
+
LazyList.new do
|
951
|
+
sorted = reject(&:empty?).sort_by do |list|
|
952
|
+
yield(list.head)
|
953
|
+
end
|
954
|
+
next EmptyList if sorted.empty?
|
955
|
+
Cons.new(sorted.head.head, sorted.tail.cons(sorted.head.tail).merge_by(&transformer))
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
# Return a randomly chosen element from this list.
|
960
|
+
# @return [Object]
|
961
|
+
def sample
|
962
|
+
at(rand(size))
|
963
|
+
end
|
964
|
+
|
965
|
+
# Return a new `List` with the given items inserted before the item at `index`.
|
966
|
+
#
|
967
|
+
# @example
|
968
|
+
# Immutable::List["A", "D", "E"].insert(1, "B", "C") # => Immutable::List["A", "B", "C", "D", "E"]
|
969
|
+
#
|
970
|
+
# @param index [Integer] The index where the new items should go
|
971
|
+
# @param items [Array] The items to add
|
972
|
+
# @return [List]
|
973
|
+
def insert(index, *items)
|
974
|
+
if index == 0
|
975
|
+
return List.from_enum(items).append(self)
|
976
|
+
elsif index > 0
|
977
|
+
LazyList.new do
|
978
|
+
Cons.new(head, tail.insert(index-1, *items))
|
979
|
+
end
|
980
|
+
else
|
981
|
+
raise IndexError if index < -size
|
982
|
+
insert(index + size, *items)
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
# Return a `List` with all elements equal to `obj` removed. `#==` is used
|
987
|
+
# for testing equality.
|
988
|
+
#
|
989
|
+
# @example
|
990
|
+
# Immutable::List[:a, :b, :a, :a, :c].delete(:a) # => Immutable::List[:b, :c]
|
991
|
+
#
|
992
|
+
# @param obj [Object] The object to remove.
|
993
|
+
# @return [List]
|
994
|
+
def delete(obj)
|
995
|
+
list = self
|
996
|
+
list = list.tail while list.head == obj && !list.empty?
|
997
|
+
return EmptyList if list.empty?
|
998
|
+
LazyList.new { Cons.new(list.head, list.tail.delete(obj)) }
|
999
|
+
end
|
1000
|
+
|
1001
|
+
# Return a `List` containing the same items, minus the one at `index`.
|
1002
|
+
# If `index` is negative, it counts back from the end of the list.
|
1003
|
+
#
|
1004
|
+
# @example
|
1005
|
+
# Immutable::List[1, 2, 3].delete_at(1) # => Immutable::List[1, 3]
|
1006
|
+
# Immutable::List[1, 2, 3].delete_at(-1) # => Immutable::List[1, 2]
|
1007
|
+
#
|
1008
|
+
# @param index [Integer] The index of the item to remove
|
1009
|
+
# @return [List]
|
1010
|
+
def delete_at(index)
|
1011
|
+
if index == 0
|
1012
|
+
tail
|
1013
|
+
elsif index < 0
|
1014
|
+
index += size if index < 0
|
1015
|
+
return self if index < 0
|
1016
|
+
delete_at(index)
|
1017
|
+
else
|
1018
|
+
LazyList.new { Cons.new(head, tail.delete_at(index - 1)) }
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
# Replace a range of indexes with the given object.
|
1023
|
+
#
|
1024
|
+
# @overload fill(object)
|
1025
|
+
# Return a new `List` of the same size, with every index set to `object`.
|
1026
|
+
#
|
1027
|
+
# @param [Object] object Fill value.
|
1028
|
+
# @example
|
1029
|
+
# Immutable::List["A", "B", "C", "D", "E", "F"].fill("Z")
|
1030
|
+
# # => Immutable::List["Z", "Z", "Z", "Z", "Z", "Z"]
|
1031
|
+
#
|
1032
|
+
# @overload fill(object, index)
|
1033
|
+
# Return a new `List` with all indexes from `index` to the end of the
|
1034
|
+
# vector set to `obj`.
|
1035
|
+
#
|
1036
|
+
# @param [Object] object Fill value.
|
1037
|
+
# @param [Integer] index Starting index. May be negative.
|
1038
|
+
# @example
|
1039
|
+
# Immutable::List["A", "B", "C", "D", "E", "F"].fill("Z", 3)
|
1040
|
+
# # => Immutable::List["A", "B", "C", "Z", "Z", "Z"]
|
1041
|
+
#
|
1042
|
+
# @overload fill(object, index, length)
|
1043
|
+
# Return a new `List` with `length` indexes, beginning from `index`,
|
1044
|
+
# set to `obj`. Expands the `List` if `length` would extend beyond the
|
1045
|
+
# current length.
|
1046
|
+
#
|
1047
|
+
# @param [Object] object Fill value.
|
1048
|
+
# @param [Integer] index Starting index. May be negative.
|
1049
|
+
# @param [Integer] length
|
1050
|
+
# @example
|
1051
|
+
# Immutable::List["A", "B", "C", "D", "E", "F"].fill("Z", 3, 2)
|
1052
|
+
# # => Immutable::List["A", "B", "C", "Z", "Z", "F"]
|
1053
|
+
# Immutable::List["A", "B"].fill("Z", 1, 5)
|
1054
|
+
# # => Immutable::List["A", "Z", "Z", "Z", "Z", "Z"]
|
1055
|
+
#
|
1056
|
+
# @return [List]
|
1057
|
+
# @raise [IndexError] if index is out of negative range.
|
1058
|
+
def fill(obj, index = 0, length = nil)
|
1059
|
+
if index == 0
|
1060
|
+
length ||= size
|
1061
|
+
if length > 0
|
1062
|
+
LazyList.new do
|
1063
|
+
Cons.new(obj, tail.fill(obj, 0, length-1))
|
1064
|
+
end
|
1065
|
+
else
|
1066
|
+
self
|
1067
|
+
end
|
1068
|
+
elsif index > 0
|
1069
|
+
LazyList.new do
|
1070
|
+
Cons.new(head, tail.fill(obj, index-1, length))
|
1071
|
+
end
|
1072
|
+
else
|
1073
|
+
raise IndexError if index < -size
|
1074
|
+
fill(obj, index + size, length)
|
1075
|
+
end
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
# Yields all permutations of length `n` of the items in the list, and then
|
1079
|
+
# returns `self`. If no length `n` is specified, permutations of the entire
|
1080
|
+
# list will be yielded.
|
1081
|
+
#
|
1082
|
+
# There is no guarantee about which order the permutations will be yielded in.
|
1083
|
+
#
|
1084
|
+
# If no block is given, an `Enumerator` is returned instead.
|
1085
|
+
#
|
1086
|
+
# @example
|
1087
|
+
# Immutable::List[1, 2, 3].permutation.to_a
|
1088
|
+
# # => [Immutable::List[1, 2, 3],
|
1089
|
+
# # Immutable::List[2, 1, 3],
|
1090
|
+
# # Immutable::List[2, 3, 1],
|
1091
|
+
# # Immutable::List[1, 3, 2],
|
1092
|
+
# # Immutable::List[3, 1, 2],
|
1093
|
+
# # Immutable::List[3, 2, 1]]
|
1094
|
+
#
|
1095
|
+
# @return [self, Enumerator]
|
1096
|
+
# @yield [list] Once for each permutation.
|
1097
|
+
def permutation(length = size, &block)
|
1098
|
+
return enum_for(:permutation, length) if not block_given?
|
1099
|
+
if length == 0
|
1100
|
+
yield EmptyList
|
1101
|
+
elsif length == 1
|
1102
|
+
each { |obj| yield Cons.new(obj, EmptyList) }
|
1103
|
+
elsif not empty?
|
1104
|
+
if length < size
|
1105
|
+
tail.permutation(length, &block)
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
tail.permutation(length-1) do |p|
|
1109
|
+
0.upto(length-1) do |i|
|
1110
|
+
left,right = p.split_at(i)
|
1111
|
+
yield left.append(right.cons(head))
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
end
|
1115
|
+
self
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
# Yield every non-empty sublist to the given block. (The entire `List` also
|
1119
|
+
# counts as one sublist.)
|
1120
|
+
#
|
1121
|
+
# @example
|
1122
|
+
# Immutable::List[1, 2, 3].subsequences { |list| p list }
|
1123
|
+
# # prints:
|
1124
|
+
# # Immutable::List[1]
|
1125
|
+
# # Immutable::List[1, 2]
|
1126
|
+
# # Immutable::List[1, 2, 3]
|
1127
|
+
# # Immutable::List[2]
|
1128
|
+
# # Immutable::List[2, 3]
|
1129
|
+
# # Immutable::List[3]
|
1130
|
+
#
|
1131
|
+
# @yield [sublist] One or more contiguous elements from this list
|
1132
|
+
# @return [self]
|
1133
|
+
def subsequences(&block)
|
1134
|
+
return enum_for(:subsequences) if not block_given?
|
1135
|
+
if not empty?
|
1136
|
+
1.upto(size) do |n|
|
1137
|
+
yield take(n)
|
1138
|
+
end
|
1139
|
+
tail.subsequences(&block)
|
1140
|
+
end
|
1141
|
+
self
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
# Return two `List`s, the first containing all the elements for which the
|
1145
|
+
# block evaluates to true, the second containing the rest.
|
1146
|
+
#
|
1147
|
+
# @example
|
1148
|
+
# Immutable::List[1, 2, 3, 4, 5, 6].partition { |x| x.even? }
|
1149
|
+
# # => [Immutable::List[2, 4, 6], Immutable::List[1, 3, 5]]
|
1150
|
+
#
|
1151
|
+
# @return [List]
|
1152
|
+
# @yield [item] Once for each item.
|
1153
|
+
def partition(&block)
|
1154
|
+
return enum_for(:partition) if not block_given?
|
1155
|
+
partitioner = Partitioner.new(self, block)
|
1156
|
+
mutex = Mutex.new
|
1157
|
+
[Partitioned.new(partitioner, partitioner.left, mutex),
|
1158
|
+
Partitioned.new(partitioner, partitioner.right, mutex)].freeze
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
# Return true if `other` has the same type and contents as this `Hash`.
|
1162
|
+
#
|
1163
|
+
# @param other [Object] The collection to compare with
|
1164
|
+
# @return [Boolean]
|
1165
|
+
def eql?(other)
|
1166
|
+
list = self
|
1167
|
+
loop do
|
1168
|
+
return true if other.equal?(list)
|
1169
|
+
return false unless other.is_a?(List)
|
1170
|
+
return other.empty? if list.empty?
|
1171
|
+
return false if other.empty?
|
1172
|
+
return false unless other.head.eql?(list.head)
|
1173
|
+
list = list.tail
|
1174
|
+
other = other.tail
|
1175
|
+
end
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
# See `Object#hash`
|
1179
|
+
# @return [Integer]
|
1180
|
+
def hash
|
1181
|
+
reduce(0) { |hash, item| (hash << 5) - hash + item.hash }
|
1182
|
+
end
|
1183
|
+
|
1184
|
+
# Return `self`. Since this is an immutable object duplicates are
|
1185
|
+
# equivalent.
|
1186
|
+
# @return [List]
|
1187
|
+
def dup
|
1188
|
+
self
|
1189
|
+
end
|
1190
|
+
alias :clone :dup
|
1191
|
+
|
1192
|
+
# Return `self`.
|
1193
|
+
# @return [List]
|
1194
|
+
def to_list
|
1195
|
+
self
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
# Return the contents of this `List` as a programmer-readable `String`. If all the
|
1199
|
+
# items in the list are serializable as Ruby literal strings, the returned string can
|
1200
|
+
# be passed to `eval` to reconstitute an equivalent `List`.
|
1201
|
+
#
|
1202
|
+
# @return [String]
|
1203
|
+
def inspect
|
1204
|
+
result = "Immutable::List["
|
1205
|
+
each_with_index { |obj, i| result << ', ' if i > 0; result << obj.inspect }
|
1206
|
+
result << "]"
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
# Allows this `List` to be printed at the `pry` console, or using `pp` (from the
|
1210
|
+
# Ruby standard library), in a way which takes the amount of horizontal space on
|
1211
|
+
# the screen into account, and which indents nested structures to make them easier
|
1212
|
+
# to read.
|
1213
|
+
#
|
1214
|
+
# @private
|
1215
|
+
def pretty_print(pp)
|
1216
|
+
pp.group(1, "Immutable::List[", "]") do
|
1217
|
+
pp.breakable ''
|
1218
|
+
pp.seplist(self) { |obj| obj.pretty_print(pp) }
|
1219
|
+
end
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
# @private
|
1223
|
+
def respond_to?(name, include_private = false)
|
1224
|
+
super || !!name.to_s.match(CADR)
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
# Return `true` if the size of this list can be obtained in constant time (without
|
1228
|
+
# traversing the list).
|
1229
|
+
# @return [Integer]
|
1230
|
+
def cached_size?
|
1231
|
+
false
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
private
|
1235
|
+
|
1236
|
+
# Perform compositions of `car` and `cdr` operations (traditional shorthand
|
1237
|
+
# for `head` and `tail` respectively). Their names consist of a `c`,
|
1238
|
+
# followed by at least one `a` or `d`, and finally an `r`. The series of
|
1239
|
+
# `a`s and `d`s in the method name identify the series of `car` and `cdr`
|
1240
|
+
# operations performed, in inverse order.
|
1241
|
+
#
|
1242
|
+
# @return [Object, List]
|
1243
|
+
# @example
|
1244
|
+
# l = Immutable::List[nil, Immutable::List[1]]
|
1245
|
+
# l.car # => nil
|
1246
|
+
# l.cdr # => Immutable::List[Immutable::List[1]]
|
1247
|
+
# l.cadr # => Immutable::List[1]
|
1248
|
+
# l.caadr # => 1
|
1249
|
+
def method_missing(name, *args, &block)
|
1250
|
+
if name.to_s.match(CADR)
|
1251
|
+
code = "def #{name}; self."
|
1252
|
+
code << Regexp.last_match[1].reverse.chars.map do |char|
|
1253
|
+
{'a' => 'head', 'd' => 'tail'}[char]
|
1254
|
+
end.join('.')
|
1255
|
+
code << '; end'
|
1256
|
+
List.class_eval(code)
|
1257
|
+
send(name, *args, &block)
|
1258
|
+
else
|
1259
|
+
super
|
1260
|
+
end
|
1261
|
+
end
|
1262
|
+
end
|
1263
|
+
|
1264
|
+
# The basic building block for constructing lists
|
1265
|
+
#
|
1266
|
+
# A Cons, also known as a "cons cell", has a "head" and a "tail", where
|
1267
|
+
# the head is an element in the list, and the tail is a reference to the
|
1268
|
+
# rest of the list. This way a singly linked list can be constructed, with
|
1269
|
+
# each `Cons` holding a single element and a pointer to the next
|
1270
|
+
# `Cons`.
|
1271
|
+
#
|
1272
|
+
# The last `Cons` instance in the chain has the {EmptyList} as its tail.
|
1273
|
+
#
|
1274
|
+
# @private
|
1275
|
+
class Cons
|
1276
|
+
include List
|
1277
|
+
|
1278
|
+
attr_reader :head, :tail
|
1279
|
+
|
1280
|
+
def initialize(head, tail = EmptyList)
|
1281
|
+
@head = head
|
1282
|
+
@tail = tail
|
1283
|
+
@size = tail.cached_size? ? tail.size + 1 : nil
|
1284
|
+
end
|
1285
|
+
|
1286
|
+
def empty?
|
1287
|
+
false
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
def size
|
1291
|
+
@size ||= super
|
1292
|
+
end
|
1293
|
+
alias :length :size
|
1294
|
+
|
1295
|
+
def cached_size?
|
1296
|
+
@size != nil
|
1297
|
+
end
|
1298
|
+
end
|
1299
|
+
|
1300
|
+
# A `LazyList` takes a block that returns a `List`, i.e. an object that responds
|
1301
|
+
# to `#head`, `#tail` and `#empty?`. The list is only realized (i.e. the block is
|
1302
|
+
# only called) when one of these operations is performed.
|
1303
|
+
#
|
1304
|
+
# By returning a `Cons` that in turn has a {LazyList} as its tail, one can
|
1305
|
+
# construct infinite `List`s.
|
1306
|
+
#
|
1307
|
+
# @private
|
1308
|
+
class LazyList
|
1309
|
+
include List
|
1310
|
+
|
1311
|
+
def initialize(&block)
|
1312
|
+
@head = block # doubles as storage for block while yet unrealized
|
1313
|
+
@tail = nil
|
1314
|
+
@atomic = Concurrent::Atomic.new(0) # haven't yet run block
|
1315
|
+
@size = nil
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
def head
|
1319
|
+
realize if @atomic.get != 2
|
1320
|
+
@head
|
1321
|
+
end
|
1322
|
+
alias :first :head
|
1323
|
+
|
1324
|
+
def tail
|
1325
|
+
realize if @atomic.get != 2
|
1326
|
+
@tail
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
def empty?
|
1330
|
+
realize if @atomic.get != 2
|
1331
|
+
@size == 0
|
1332
|
+
end
|
1333
|
+
|
1334
|
+
def size
|
1335
|
+
@size ||= super
|
1336
|
+
end
|
1337
|
+
alias :length :size
|
1338
|
+
|
1339
|
+
def cached_size?
|
1340
|
+
@size != nil
|
1341
|
+
end
|
1342
|
+
|
1343
|
+
private
|
1344
|
+
|
1345
|
+
QUEUE = ConditionVariable.new
|
1346
|
+
MUTEX = Mutex.new
|
1347
|
+
|
1348
|
+
def realize
|
1349
|
+
while true
|
1350
|
+
# try to "claim" the right to run the block which realizes target
|
1351
|
+
if @atomic.compare_and_swap(0,1) # full memory barrier here
|
1352
|
+
begin
|
1353
|
+
list = @head.call
|
1354
|
+
if list.empty?
|
1355
|
+
@head, @tail, @size = nil, self, 0
|
1356
|
+
else
|
1357
|
+
@head, @tail = list.head, list.tail
|
1358
|
+
end
|
1359
|
+
rescue
|
1360
|
+
@atomic.set(0)
|
1361
|
+
MUTEX.synchronize { QUEUE.broadcast }
|
1362
|
+
raise
|
1363
|
+
end
|
1364
|
+
@atomic.set(2)
|
1365
|
+
MUTEX.synchronize { QUEUE.broadcast }
|
1366
|
+
return
|
1367
|
+
end
|
1368
|
+
# we failed to "claim" it, another thread must be running it
|
1369
|
+
if @atomic.get == 1 # another thread is running the block
|
1370
|
+
MUTEX.synchronize do
|
1371
|
+
# check value of @atomic again, in case another thread already changed it
|
1372
|
+
# *and* went past the call to QUEUE.broadcast before we got here
|
1373
|
+
QUEUE.wait(MUTEX) if @atomic.get == 1
|
1374
|
+
end
|
1375
|
+
elsif @atomic.get == 2 # another thread finished the block
|
1376
|
+
return
|
1377
|
+
end
|
1378
|
+
end
|
1379
|
+
end
|
1380
|
+
end
|
1381
|
+
|
1382
|
+
# Common behavior for other classes which implement various kinds of `List`s
|
1383
|
+
# @private
|
1384
|
+
class Realizable
|
1385
|
+
include List
|
1386
|
+
|
1387
|
+
def initialize
|
1388
|
+
@head, @tail, @size = Undefined, Undefined, nil
|
1389
|
+
end
|
1390
|
+
|
1391
|
+
def head
|
1392
|
+
realize if @head == Undefined
|
1393
|
+
@head
|
1394
|
+
end
|
1395
|
+
alias :first :head
|
1396
|
+
|
1397
|
+
def tail
|
1398
|
+
realize if @tail == Undefined
|
1399
|
+
@tail
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
def empty?
|
1403
|
+
realize if @head == Undefined
|
1404
|
+
@size == 0
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
def size
|
1408
|
+
@size ||= super
|
1409
|
+
end
|
1410
|
+
alias :length :size
|
1411
|
+
|
1412
|
+
def cached_size?
|
1413
|
+
@size != nil
|
1414
|
+
end
|
1415
|
+
|
1416
|
+
def realized?
|
1417
|
+
@head != Undefined
|
1418
|
+
end
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
# This class can divide a collection into 2 `List`s, one of items
|
1422
|
+
# for which the block returns true, and another for false
|
1423
|
+
# At the same time, it guarantees the block will only be called ONCE for each item
|
1424
|
+
#
|
1425
|
+
# @private
|
1426
|
+
class Partitioner
|
1427
|
+
attr_reader :left, :right
|
1428
|
+
def initialize(list, block)
|
1429
|
+
@list, @block, @left, @right = list, block, [], []
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
def next_item
|
1433
|
+
unless @list.empty?
|
1434
|
+
item = @list.head
|
1435
|
+
(@block.call(item) ? @left : @right) << item
|
1436
|
+
@list = @list.tail
|
1437
|
+
end
|
1438
|
+
end
|
1439
|
+
|
1440
|
+
def done?
|
1441
|
+
@list.empty?
|
1442
|
+
end
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
# One of the `List`s which gets its items from a Partitioner
|
1446
|
+
# @private
|
1447
|
+
class Partitioned < Realizable
|
1448
|
+
def initialize(partitioner, buffer, mutex)
|
1449
|
+
super()
|
1450
|
+
@partitioner, @buffer, @mutex = partitioner, buffer, mutex
|
1451
|
+
end
|
1452
|
+
|
1453
|
+
def realize
|
1454
|
+
# another thread may get ahead of us and null out @mutex
|
1455
|
+
mutex = @mutex
|
1456
|
+
mutex && mutex.synchronize do
|
1457
|
+
return if @head != Undefined # another thread got ahead of us
|
1458
|
+
while true
|
1459
|
+
if !@buffer.empty?
|
1460
|
+
@head = @buffer.shift
|
1461
|
+
@tail = Partitioned.new(@partitioner, @buffer, @mutex)
|
1462
|
+
# don't hold onto references
|
1463
|
+
# tail will keep references alive until end of list is reached
|
1464
|
+
@partitioner, @buffer, @mutex = nil, nil, nil
|
1465
|
+
return
|
1466
|
+
elsif @partitioner.done?
|
1467
|
+
@head, @size, @tail = nil, 0, self
|
1468
|
+
@partitioner, @buffer, @mutex = nil, nil, nil # allow them to be GC'd
|
1469
|
+
return
|
1470
|
+
else
|
1471
|
+
@partitioner.next_item
|
1472
|
+
end
|
1473
|
+
end
|
1474
|
+
end
|
1475
|
+
end
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
# This class can divide a list up into 2 `List`s, one for the prefix of
|
1479
|
+
# elements for which the block returns true, and another for all the elements
|
1480
|
+
# after that. It guarantees that the block will only be called ONCE for each
|
1481
|
+
# item
|
1482
|
+
#
|
1483
|
+
# @private
|
1484
|
+
class Splitter
|
1485
|
+
attr_reader :left, :right
|
1486
|
+
def initialize(list, block)
|
1487
|
+
@list, @block, @left, @right = list, block, [], EmptyList
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
def next_item
|
1491
|
+
unless @list.empty?
|
1492
|
+
item = @list.head
|
1493
|
+
if @block.call(item)
|
1494
|
+
@left << item
|
1495
|
+
@list = @list.tail
|
1496
|
+
else
|
1497
|
+
@right = @list
|
1498
|
+
@list = EmptyList
|
1499
|
+
end
|
1500
|
+
end
|
1501
|
+
end
|
1502
|
+
|
1503
|
+
def done?
|
1504
|
+
@list.empty?
|
1505
|
+
end
|
1506
|
+
|
1507
|
+
# @private
|
1508
|
+
class Left < Realizable
|
1509
|
+
def initialize(splitter, buffer, mutex)
|
1510
|
+
super()
|
1511
|
+
@splitter, @buffer, @mutex = splitter, buffer, mutex
|
1512
|
+
end
|
1513
|
+
|
1514
|
+
def realize
|
1515
|
+
# another thread may get ahead of us and null out @mutex
|
1516
|
+
mutex = @mutex
|
1517
|
+
mutex && mutex.synchronize do
|
1518
|
+
return if @head != Undefined # another thread got ahead of us
|
1519
|
+
while true
|
1520
|
+
if !@buffer.empty?
|
1521
|
+
@head = @buffer.shift
|
1522
|
+
@tail = Left.new(@splitter, @buffer, @mutex)
|
1523
|
+
@splitter, @buffer, @mutex = nil, nil, nil
|
1524
|
+
return
|
1525
|
+
elsif @splitter.done?
|
1526
|
+
@head, @size, @tail = nil, 0, self
|
1527
|
+
@splitter, @buffer, @mutex = nil, nil, nil
|
1528
|
+
return
|
1529
|
+
else
|
1530
|
+
@splitter.next_item
|
1531
|
+
end
|
1532
|
+
end
|
1533
|
+
end
|
1534
|
+
end
|
1535
|
+
end
|
1536
|
+
|
1537
|
+
# @private
|
1538
|
+
class Right < Realizable
|
1539
|
+
def initialize(splitter, mutex)
|
1540
|
+
super()
|
1541
|
+
@splitter, @mutex = splitter, mutex
|
1542
|
+
end
|
1543
|
+
|
1544
|
+
def realize
|
1545
|
+
mutex = @mutex
|
1546
|
+
mutex && mutex.synchronize do
|
1547
|
+
return if @head != Undefined
|
1548
|
+
@splitter.next_item until @splitter.done?
|
1549
|
+
if @splitter.right.empty?
|
1550
|
+
@head, @size, @tail = nil, 0, self
|
1551
|
+
else
|
1552
|
+
@head, @tail = @splitter.right.head, @splitter.right.tail
|
1553
|
+
end
|
1554
|
+
@splitter, @mutex = nil, nil
|
1555
|
+
end
|
1556
|
+
end
|
1557
|
+
end
|
1558
|
+
end
|
1559
|
+
|
1560
|
+
# A list without any elements. This is a singleton, since all empty lists are equivalent.
|
1561
|
+
# @private
|
1562
|
+
module EmptyList
|
1563
|
+
class << self
|
1564
|
+
include List
|
1565
|
+
|
1566
|
+
# There is no first item in an empty list, so return `nil`.
|
1567
|
+
# @return [nil]
|
1568
|
+
def head
|
1569
|
+
nil
|
1570
|
+
end
|
1571
|
+
alias :first :head
|
1572
|
+
|
1573
|
+
# There are no subsequent elements, so return an empty list.
|
1574
|
+
# @return [self]
|
1575
|
+
def tail
|
1576
|
+
self
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
def empty?
|
1580
|
+
true
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
# Return the number of items in this `List`.
|
1584
|
+
# @return [Integer]
|
1585
|
+
def size
|
1586
|
+
0
|
1587
|
+
end
|
1588
|
+
alias :length :size
|
1589
|
+
|
1590
|
+
def cached_size?
|
1591
|
+
true
|
1592
|
+
end
|
1593
|
+
end
|
1594
|
+
end
|
1595
|
+
end.freeze
|