lib/ramda/list.rb
File `list.rb` has 323 lines of code (exceeds 250 allowed). Consider refactoring.require_relative 'internal/curried_method'require_relative 'internal/dispatchable'require_relative 'internal/transducers' module Ramda # List functions # rubocop:disable Metrics/ModuleLength module List extend ::Ramda::Internal::CurriedMethod extend ::Ramda::Internal::Dispatchable Trans = ::Ramda::Internal::Transducers # Applies a function to the value at the given index of an array, # returning a new copy of the array with the element at the given # index replaced with the result of the function application. # # (a -> a) -> Number -> [a] -> [a] # curried_method(:adjust) do |f, idx, xs| xs.dup.tap { |a| a[idx] = f.call(a[idx]) } end # Returns true if all elements of the list match the predicate, # false if there are any that don't. # # Dispatches to the all method of the second argument, if present. # # Acts as a transducer if a transformer is given in list position. # # (a -> Boolean) -> [a] -> Boolean # curried(:all, &dispatchable(:all, ::Array) do |f, xs| xs.all?(&f) end) # Returns true if at least one of elements of the list match the predicate, # false otherwise. # # Dispatches to the any method of the second argument, if present. # # Acts as a transducer if a transformer is given in list position. # # (a -> Boolean) -> [a] -> Boolean # curried(:any, &dispatchable(:any, ::Array) do |f, xs| xs.any?(&f) end) # Returns a new list, composed of n-tuples of consecutive elements. # If n is greater than the length of the list, an empty list is returned. # # Acts as a transducer if a transformer is given in list position. # # Number -> [a] -> [[a]] # curried_method(:aperture) do |n, xs| xs.each_cons(n).to_a end # Returns a new list containing the contents of the given list, # followed by the given element. # # a -> [a] -> [a] # curried_method(:append) do |x, xs| xs + [x] end # chain maps a function over a list and concatenates the results. chain is # also known as flatMap in some libraries # # Dispatches to the chain or bind method of the second argument, if present, # according to the FantasyLand Chain spec. # # Chain m => (a -> m b) -> m a -> m b # curried(:chain, &dispatchable([:chain, :bind, :'>'], ::Array) do |f, xs| xs.flat_map(&f) end) # Returns the result of concatenating the given lists or strings. # # Dispatches to the concat method of the first argument, if present. # # String -> String -> String # List -> List -> List # curried(:concat, &dispatchable(:concat, [::Array, ::String, ::Symbol]) do |list_a, list_b| if list_b.is_a?(::Array) list_a + list_b else [list_a, list_b].join('') end end) # Returns true if the specified value is equal, in R.equals terms, # to at least one element of the given list; false otherwise. # # a -> [a] -> Boolean # curried_method(:contains) do |x, xs| xs.include?(x) end # Returns all but the first n elements of the given list, string, # or transducer/transformer (or object with a drop method). # # Dispatches to the drop method of the second argument, if present. # # Number -> [a] -> [a] # Number -> String -> String # curried(:drop, &dispatchable(:drop, [::Array, ::String]) do |num, xs| xs[num..-1] || xs.class.new end) # Number -> [a] -> [a] # Number -> String -> String # # Returns a list containing all but the last n elements of the given list. # curried(:drop_last) do |x, xs| xs[0...-x] end # (a -> Boolean) -> [a] -> [a] # (a -> Boolean) -> String -> String # # Returns a new list excluding all the tailing elements of a given list # which satisfy the supplied predicate function. # It passes each value from the right to the supplied predicate function, # skipping elements until the predicate function returns a falsy value. # The predicate function is applied to one argument: (value). # curried(:drop_last_while) do |fn, xs| lxs = case xs when ::String xs.chars.to_a else xs end index = lxs.reverse.index { |x| !fn.call(x) } || 0 xs[0...-index] end # Returns a new list without any consecutively repeating elements. # R.equals is used to determine equality. # # Acts as a transducer if a transformer is given in list position. # # [a] -> [a] # curried_method(:drop_repeats, &dispatchable1([], ::Array, Trans::DropRepeatsTransducer.new) do |xs| xs.chunk { |n| n }.map(&:first) end) # Returns a new list without any consecutively repeating elements. # Equality is determined by applying the supplied predicate to each # pair of consecutive elements. # The first element in a series of equal elements will be preserved. # # Acts as a transducer if a transformer is given in list position. # # (a, a -> Boolean) -> [a] -> [a] # curried_method(:drop_repeats_with, &dispatchable([], [::Array], Trans::DropRepeatsWithTransducer.new) do |f, xs| xs.each_with_object([]) do |a, acc| acc.push(a) unless acc.any? && f.call(acc.last, a) end end) # Returns a new list excluding the leading elements of a given list which # satisfy the supplied predicate function. It passes each value to the # supplied predicate function, skipping elements while the predicate # function returns true. The predicate function is applied # to one argument: (value). # # Dispatches to the dropWhile method of the second argument, if present. # # Acts as a transducer if a transformer is given in list position. # # (a -> Boolean) -> [a] -> [a] # curried(:drop_while, &dispatchable(:drop_while, ::Array) do |f, xs| xs.drop_while(&f) end) # Takes a predicate and a Filterable, and returns a new filterable of the same # type containing the members of the given filterable which satisfy the given # predicate. Filterable objects include plain objects or any object that # has a filter method such as Array. # # Dispatches to the filter method of the second argument, if present. # # Filterable f => (a -> Boolean) -> f a -> f a # curried(:filter, &dispatchable(:filter, [::Array, ::Hash], Trans::FilterTransducer.new) do |f, xs| if xs.is_a?(Hash) xs.select { |_, value| f.call(value) } else xs.select(&f) end end) # Creates a new object from a list key-value pairs. If a key appears in # multiple pairs, the rightmost pair is included in the object. # # [[k,v]] -> {k: v} # curried_method(:from_pairs) do |xs| Hash[xs] end # Returns the first element of the list which matches the predicate, # or undefined if no element matches. # # Dispatches to the find method of the second argument, if present. # # (a -> Boolean) -> [a] -> a | NilClass # curried(:find, &dispatchable(:find, ::Array) do |f, xs| xs.find(&f) end) # Returns the index of the first element of the list which matches the predicate, # or nil if no element matches. # # (a -> Boolean) -> [a] -> Number | NilClass # curried_method(:find_index) do |f, xs| xs.index(&f) end # Returns the last element of the list which matches the predicate, # or nil if no element matches. # # (a -> Boolean) -> [a] -> a | NilClass # curried_method(:find_last) do |f, xs| index = xs.rindex(&f) xs[index] unless index.nil? end # Returns the index of the last element of the list which matches the # predicate, or nil if no element matches. # # (a -> Boolean) -> [a] -> Number | NilClass # curried_method(:find_last_index) do |f, xs| xs.rindex(&f) end # Returns a new list by pulling every item out of it (and all its sub-arrays) # and putting them in a new array, depth-first. # # [a] -> [b] # curried_method(:flatten, &:flatten) # Iterate over an input list, calling a provided function fn for each element # in the list. # # Dispatches to the forEach method of the second argument, if present. # # (a -> *) -> [a] -> [a] # curried(:for_each, &dispatchable(:for_each, ::Array) do |f, xs| xs.each(&f) end) # Splits a list into sub-lists stored in an object, based on the result of # calling a String-returning function on each element, and grouping the # results according to values returned. # # Dispatches to the groupBy method of the second argument, if present. # # # (a -> String) -> [a] -> {String: [a]} # curried(:group_by, &dispatchable(:group_by, ::Array) do |f, xs| xs.group_by(&f) end) # Returns the first element of the given list or string. In some libraries # this function is named first. # # [a] -> a | NilClass # String -> String # curried_method(:head) do |xs| case xs when ::String xs[0] || '' else xs[0] end end # Returns the position of the first occurrence of an item in an array, # or nil if the item is not included in the array. # # a -> [a] -> Number | NilClass # curried_method(:index_of) do |x, xs| xs.index(x) end # Returns all but the last element of the given list or string. # # [a] -> [a] # String -> String # curried_method(:init) do |xs| xs[0..-2] end # Inserts the supplied element into the list, at the specified index. # Note that this is not destructive: it returns a copy of the list with # the changes. No lists have been harmed in the application of this function. # # Number -> a -> [a] -> [a] # curried_method(:insert) do |idx, x, xs| xs.dup.insert(idx, x) end # Inserts the sub-list into the list, at the specified index. # Note that this is not destructive: it returns a copy of the list # with the changes. No lists have been harmed in the application of this function. # # Number -> [a] -> [a] -> [a] # curried_method(:insert_all) do |index, elts, xs| xs.dup.insert(index, *elts) end # Creates a new list with the separator interposed between elements. # # Dispatches to the intersperse method of the second argument, if present. # # a -> [a] -> [a] # curried_method(:intersperse, &dispatchable([:intersperse], ::Array) do |sep, xs| xs.reduce([]) do |acc, a| acc << sep if acc.any? acc << a end end) # Transforms the items of the list with the transducer and appends # the transformed items to the accumulator using an appropriate iterator # function based on the accumulator type. # # The accumulator can be an array, string, object or a transformer. # Iterated items will be appended to arrays and concatenated to strings. # Objects will be merged directly. # # The accumulator can also be a transformer object that provides a 2-arity # reducing iterator function, step, 0-arity initial value function, # init, and 1-arity result extraction function result. # The step function is used as the iterator function in reduce. The result # function is used to convert the final accumulator into the return type # and in most cases is R.identity. The init function is used to provide # the initial accumulator. # # The iteration is performed with R.reduce after initializing the transducer. # # a -> (b -> b) -> [c] -> a # curried_method(:into) do |acc, xf, xs| rx = case acc when ::Array ->(arr, x) { arr << x } when ::String ->(str, x) { "#{str}#{x}" } when ::Object ->(obj, x) { obj.merge(x) } else raise ArgumetError, "Cannot create default transformer for #{acc}" end xs.reduce(acc, &xf.call(rx)) end # Returns a string made by inserting the separator between each element and # concatenating all the elements into a single string. # # String -> [a] -> String # curried_method(:join) do |separator, xs| xs.join(separator) end # Returns the position of the last occurrence of an item in an array, # or nil if the item is not included in the array. # # a -> [a] -> Number | NilClass # curried_method(:last_index_of) do |x, xs| xs.rindex(x) end curried_method(:last) do |xs| case xs when ::String xs[-1] || '' else xs[-1] end end # Returns the number of elements in the array by returning list.length # # [a] -> Number # curried_method(:length, &:size) # Takes a function and a functor, applies the function to each of the # functor's values, and returns a functor of the same shape. # Ramda provides suitable map implementations for Array and Object, # so this function may be applied to [1, 2, 3] or {x: 1, y: 2, z: 3}. # # Also treats functions as functors and will compose them together. # # Functor f => (a -> b) -> f a -> f b # curried(:map, &dispatchable([:map, :fmap], [::Hash, ::Array], Trans::MapTransducer.new) do |f, xs| case xs when ::Hash Hash[xs.map { |k, v| [k, f.call(v)] }] when ::Array xs.map(&f) end end) # The mapAccum function behaves like a combination of map and reduce; # it applies a function to each element of a list, passing # an accumulating parameter from left to right, and returning # a final value of this accumulator together with the new list. # # The iterator function receives two arguments, acc and value, # and should return a tuple [acc, value]. # # (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y]) # # curried_method(:map_accum) do |fn, acc, xs| # # end # Merges a list of objects together into one object. # # [{k: v}] -> {k: v} # curried_method(:merge_all) do |xs| xs.reduce(&:merge) end # Returns true if no elements of the list match the predicate, false otherwise. # # Dispatches to the none? method of the second argument, if present. # # (a -> Boolean) -> [a] -> Boolean # curried_method(:none) do |f, xs| xs.none?(&f) end # Returns the nth element of the given list or string. If n is negative # the element at index length + n is returned. # # Number -> [a] -> a | NilClass # Number -> String -> String # curried_method(:nth) do |index, xs| case xs when ::String xs[index] || '' when ::Array, ::Hash xs[index] else type_error(xs, :nth) end end # Takes a predicate and a list or other Filterable object and returns # the pair of filterable objects of the same type of elements which do # and do not satisfy, the predicate, respectively. # # Filterable f => (a -> Boolean) -> f a -> [f a, f a] # curried_method(:partition) do |f, xs| ::Ramda.juxt([::Ramda.filter, ::Ramda.reject]).call(f, xs) end # Returns a new list by plucking the same named property off all objects # in the list supplied. # # Pluck will work on any functor in addition to arrays, as it is equivalent # to R.map(R.prop(k), f). # # Functor f => k -> f {k: v} -> f v # curried_method(:pluck) do |key, xs| Ramda.map(->(x) { x[key] }, xs) end # Returns a new list with the given element at the front, followed by the # contents of the list. # # a -> [a] -> [a] # curried_method(:prepend) do |x, xs| [x] + xs.dup end # Returns a list of numbers from from (inclusive) to to (exclusive). # # Number -> Number -> [Number] # curried_method(:range) do |a, b| (a...b).to_a end # Returns a single item by iterating through the list, successively # calling the iterator function and passing it an accumulator value # and the current value from the array, and then passing the result # to the next call. # # The iterator function receives two values: (value, acc), # while the arguments' order of reduce's iterator function is (acc, value). # # ((a, b) -> a) -> a -> [b] -> a # curried_method(:reduce) do |f, acc, xs|Identical blocks of code found in 2 locations. Consider refactoring. xs.reduce(acc) do |loc_acc, x| res = f.call(loc_acc, x) break res.value if res.is_a?(::Ramda::Internal::Transducers::Transducer) && res.reduced res end end # Returns a single item by iterating through the list, successively # calling the iterator function and passing it an accumulator value # and the current value from the array, and then passing the result # to the next call. # Similar to reduce, except moves through the input list from the # right to the left. # # The iterator function receives two values: (value, acc), # while the arguments' order of reduce's iterator function is (acc, value). # # ((a, b) -> a) -> a -> [b] -> a # curried_method(:reduce_right) do |f, acc_arg, xs| xs.reverse.reduce(acc_arg) do |acc, x| res = f.call(x, acc) break res.value if res.is_a?(::Ramda::Internal::Transducers::Transducer) && res.reduced res end end # Returns a value wrapped to indicate that it is the final value of # the reduce and transduce functions. The returned value should be # considered a black box: the internal structure is not guaranteed # to be stable. # # Note: this optimization is unavailable to functions not explicitly # listed above. # # a -> * # curried_method(:reduced) do |x| t = ::Ramda::Internal::Transducers::Transducer.new t.reduced = true t.value = x t end # Returns a new list or string with the elements or characters in reverse order. # # [a] -> [a] # String -> String # curried_method(:reverse, &:reverse) # The complement of filter. # Acts as a transducer if a transformer is given in list position. # Filterable objects include plain objects or any object that has a # filter method such as Array. # # Filterable f => (a -> Boolean) -> f a -> f a # curried_method(:reject) do |f, xs| case xs when ::Hash xs.reject { |_, value| f.call(value) } else xs.reject(&f) end end curried_method(:remove) do |from, n, xs| xs[0...from] + xs[from + n..-1] end # Returns a fixed list of size n containing a specified identical value. # # a -> n -> [a] # curried_method(:repeat) do |a, n| ::Array.new(n, a) end # Scan is similar to reduce, but returns a list of successively # reduced values from the left # # (a,b -> a) -> a -> [b] -> [a] # curried_method(:scan) do |f, acc0, xs| xs.reduce([acc0]) { |acc, x| acc << f.call(acc[-1], x) } end # Returns the elements of the given list or string # from fromIndex (inclusive) to toIndex (exclusive). # # Number -> Number -> [a] -> [a] # Number -> Number -> String -> String # curried(:slice, &dispatchable(:slice, [::Array, ::String]) do |from, to, xs| xs[from...to] end) # Returns a copy of the list, sorted according to the comparator function, # which should accept two values at a time and return a negative number # if the first value is smaller, a positive number if it's larger, and # zero if they are equal. Please note that this is a copy of the list. # It does not modify the original. curried_method(:sort) do |comparator, xs| xs.sort(&comparator) end # Number -> [a] -> [[a]] # Number -> String -> [String] # # Splits a collection into slices of the specified length. # curried(:split_every) do |num, xs| case xs when ::Array xs .each_slice(num) .to_a when ::String xs .chars .each_slice(num) .to_a .map(&:join) else type_error(xs, :split_every) end end # Returns all but the first element of the given list or string # (or object with a tail method). # # [a] -> [a] # String -> String # curried_method(:tail) do |xs| ::Ramda.drop(1, xs) end # Returns the first n elements of the given list, string. # # Dispatches to the take method of the second argument, if present. # # Number -> [a] -> [a] # Number -> String -> String # curried(:take, &dispatchable(:take, [::Array, ::String], Trans::TakeTransducer.new) do |num, xs| xs[0, num] end) # Number -> [a] -> [a] # Number -> String -> String # # Returns a new list containing the last n elements of the given list. # If n > list.length, returns a list of list.length elements. # curried(:take_last) do |n, xs| xs[-(n > xs.size ? xs.size : n)..-1] end # Number -> [a] -> [a] # Number -> String -> String # # Returns a new list containing the last n elements of the given list. # If n > list.length, returns a list of list.length elements. # curried(:take_last_while) do |f, xs| lxs = case xs when ::String xs.chars.to_a else xs end index = lxs.reverse.index { |x| !f.call(x) } || lxs.size xs[-index..-1] end # Returns a new list containing the first n elements of a given list, # passing each value to the supplied predicate function, and terminating # when the predicate function returns false. Excludes the element that # caused the predicate function to fail. # The predicate function is passed one argument: (value). # # (a -> Boolean) -> [a] -> [a] # curried(:take_while, &dispatchable(:take_while, ::Array) do |f, xs| xs[0, xs.index { |x| !f.call(x) } || xs.size] end) # Calls an input function n times, returning an array containing the results # of those function calls. # fn is passed one argument: The current value of n, which begins at 0 # and is gradually incremented to n - 1. # # (Number -> a) -> Number -> [a] # curried_method(:times) do |f, n| n.times.to_a.map(&f) end # Returns a single item by iterating through the list, successively # calling the iterator function and passing it an accumulator value # and the current value from the array, and then passing the result # to the next call. # # The iterator function receives two values: (acc, value). # It may use R.reduced to shortcut the iteration. # # The arguments' order of reduceRight's iterator function is (value, acc). # # Dispatches to the reduce method of the third argument, if present. # When doing so, it is up to the user to handle the R.reduced shortcuting, # as this is not implemented by reduce. # # ((a, b) -> a) -> a -> [b] -> a # curried_method(:transduce) do |xf, rx, acc, xs| f = xf.call(rx)Identical blocks of code found in 2 locations. Consider refactoring. xs.reduce(acc) do |loc_acc, x| res = f.call(loc_acc, x) break res.value if res.is_a?(::Ramda::Internal::Transducers::Transducer) && res.reduced res end end # Builds a list from a seed value. Accepts an iterator function, which # returns either false to stop iteration or an array of length 2 # containing the value to add to the resulting list and the seed # to be used in the next call to the iterator function. # # The iterator function receives one argument: (seed). # # (a -> [b]) -> * -> [b] # curried_method(:unfold) do |f, n| xs = [] loop do res = f.call(n) break if res.is_a?(FalseClass) xs << res[0] n = res[1] end xs end # Returns a new list containing only one copy of each element in the original list. # # [a] -> [a] # curried_method(:uniq, &:uniq) # (a -> b) -> [a] -> [a] # # Returns a new list containing only one copy of each element in the original list, # based upon the value returned by applying the supplied function to each list element. # Prefers the first item if the supplied function produces the same value on two items. # R.equals is used for comparison. # curried(:uniq_by) do |f, xs| xs.uniq(&f) end # Returns a new list containing only one copy of each element in the original list, # based upon the value returned by applying the supplied predicate to each list # element. curried_method(:uniq_with) do |f, xs| xs.uniq(&f) end # Shorthand for R.chain(R.identity), which removes one level of nesting from any Chain. # # Chain c => c (c a) -> c a # curried_method(:unnest) do |xs| ::Ramda.chain(::Ramda.identity, xs) end # Returns a new copy of the array with the element at the provided # index replaced with the given value. # # Number -> a -> [a] -> [a] # curried_method(:update) do |idx, x, xs| xs.dup.tap { |a| a[idx] = x } end # Creates a new list out of the two supplied by creating each possible pair # from the lists. # # [a] -> [b] -> [[a,b]] # curried_method(:xprod) do |xs1, xs2| xs1.product(xs2) end # Creates a new list out of the two supplied by pairing up equally-positioned # items from both lists. The returned list is truncated to the length of the # shorter of the two input lists. # Note: zip is equivalent to zip_with(->(a, b) { [a, b] }). # # [a] -> [b] -> [[a,b]] # curried_method(:zip) do |xs1, xs2| xs1.zip(xs2) end # Creates a new object out of a list of keys and a list of values. Key/value # pairing is truncated to the length of the shorter of the two lists. Note: # zipObj is equivalent to pipe(zip_with(pair), from_pairs). # # [String] -> [*] -> {String: *} # curried_method(:zip_obj) do |xs1, xs2| Hash[xs1.zip(xs2)] end # Creates a new list out of the two supplied by applying the function to each # equally-positioned pair in the lists. The returned list is truncated to the # length of the shorter of the two input lists. # # (a,b -> c) -> [a] -> [b] -> [c] # curried_method(:zip_with) do |f, xs1, xs2| xs1.zip(xs2).map { |(a, b)| f.call(a, b) } end # TODO: Extract from this module def self.type_error(object, method) raise ArgumentError, "Unexpected type #{object.class} in method: #{method}" end endend