How does this recursive functional permutation generator work? - ruby

This code is written in a functional programming style.
Below is how this should work.
(permutations ‘(a b c)) --> ((a b c) (a c b) (b a c) (b c a) (c a b) (c b a))
Please explain how fold and map work in this code and also recursive permutation...
def map(f, lst)
lst.map{|v| f.call(v)}
end
def fold(f, init, lst)
lst.reduce(init){|w, a| f.call(w, a)}
end
def permutations(lst)
lst.empty? ? [[]] : fold(lambda{|w, a| w + a}, [], map(lambda{|v| map(lambda{|p| [v]+p}, permutations(lst-[v]))}, lst))
end

Let me give this a shot.
def map(f, lst)
lst.map{|v| f.call(v)}
end
map takes an array lst and applies a function f on each element of lst.
The following example doubles each element of an array.
irb(main):007:0> map(lambda {|i| i * 2}, [1,2,3])
=> [2, 4, 6]
Next,
def fold(f, init, lst)
lst.reduce(init){|w, a| f.call(w, a)}
end
fold takes an array lst, an initial value init, applies function f on each element of lst and combines it with the previous result to return a value. The result of f.call(w, a) is stored in w. w has an initial value of init.
In plain ruby,
w = init
lst.each do |a|
w = f.call(w, a)
end
return w
In the following example, fold takes the sum of the array by adding each element.
irb(main):010:0> fold(lambda {|a, i| a + i}, 0, [1,2,3])
=> 6
Now, the main function,
def permutations(lst)
lst.empty? ? [[]] : fold(lambda{|w, a| w + a}, [], map(lambda{|v| map(lambda{|p| [v]+p}, permutations(lst-[v]))}, lst))
end
The first part is easy. If lst is empty, return [[]].
The second part is
fold(lambda{|w, a| w + a}, [], map(lambda{|v| map(lambda{|p| [v]+p}, permutations(lst-[v]))}, lst))
We'll work backwards from the innermost.
map(lambda{|p| [v]+p}, permutations(lst-[v]))
This is simple. For each element returned by permutations, we append [v] (v is defined in the outer map). permutations takes the array minus the element v.
Here's one example.
irb(main):012:0> permutations([1,2,3]-[3])
=> [[1, 2], [2, 1]]
irb(main):013:0> map(lambda{|p| [3]+p}, permutations([1,2,3] - [3]))
=> [[3, 1, 2], [3, 2, 1]]
Next we go one step higher where we see that the v in the inner map is each element from lst.
map(lambda{|v| map( ... )}, lst)
In plain ruby, this is basically
lst.map do |v|
a = permutations(lst - [v])
a.map {|p| [v] + p}
end
Finally, we have
fold(lambda{|w, a| w + a}, [], map( ... ))
For each element in the array returned by map, we add it to w which is initially an empty array [].
irb(main):019:0> fold(lambda {|w, a| w + a}, [], [[[1, 2]], [[2, 1]]])
=> [[1, 2], [2, 1]]
If you want to know why this algorithm works, consider the patterns.
irb(main):021:0> permutations([1,2,3])
=> [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
which is same as
irb(main):023:0> permutations([1,2]).map {|i| i + [3]}
=> [[1, 2, 3], [2, 1, 3]]
irb(main):024:0> permutations([1,3]).map {|i| i + [2]}
=> [[1, 3, 2], [3, 1, 2]]
irb(main):025:0> permutations([2,3]).map {|i| i + [1]}
=> [[2, 3, 1], [3, 2, 1]]

Please explain how fold and map work in this code
Those are simply message sends (in other languages they are called method calls). They are calling the methods named fold and map, respectively.
and also recursive permutation...
The permutation method uses the methods named fold and map. There is no definition for those methods, so this will simply raise a NoMethodError.

Related

Method to sort strings in descending order (in complex keys)

In order to descend-sort an array a of strings, reverse can be used.
a.sort.reverse
But when you want to use a string among multiple sort keys, that cannot be done. Suppose items is an array of items that have attributes attr1 (String), attr2 (String), attr3 (Integer). Sort can be done like:
items.sort_by{|item| [item.attr1, item.attr2, item.attr3]}
Switching from ascending to descending can be done independently for Integer by multiplying it with -1:
items.sort_by{|item| [item.attr1, item.attr2, -item.attr3]}
But such method is not straightforward for String. Can such method be defined? When you want to do descending sort with respect to attr2, it should be written like:
items.sort_by{|item| [item.attr1, item.attr2.some_method, item.attr3]}
I think you can always convert your strings into an array of integers (ord). Like this:
strings = [["Hello", "world"], ["Hello", "kitty"], ["Hello", "darling"]]
strings.sort_by do |s1, s2|
[
s1,
s2.chars.map(&:ord).map{ |n| -n }
]
end
PS:
As #CarySwoveland caught here is a corner case with empty string, which could be solved with this non elegant solution:
strings.sort_by do |s1, s2|
[
s1,
s2.chars.
map(&:ord).
tap{|chars| chars << -Float::INFINITY if chars.empty? }.
map{ |n| -n }
]
end
And #Jordan kindly mentioned that sort_by uses Schwartzian Transform so you don't need preprocessing at all.
The following supports all objects that respond to <=>.
def generalized_array_sort(arr, inc_or_dec)
arr.sort do |a,b|
comp = 0
a.zip(b).each_with_index do |(ae,be),i|
next if (ae<=>be).zero?
comp = (ae<=>be) * (inc_or_dec[i]==:inc ? 1 : -1)
break
end
comp
end
end
Example
arr = [[3, "dog"], [4, "cat"], [3, "cat"], [4, "dog"]]
inc_or_dec = [:inc, :dec]
generalized_array_sort(arr, inc_or_dec)
#=> [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
Another example
class A; end
class B<A; end
class C<B; end
[A,B,C].sort #=> [C, B, A]
arr = [[3, A], [4, B], [3, B], [4, A], [3, C], [4,C]]
inc_or_dec = [:inc, :dec]
generalized_array_sort(arr, inc_or_dec)
#=> [[3, A], [3, B], [3, C], [4, A], [4, B], [4, C]]
I'm not sure either of these passes your straightforwardness test, but I think both work correctly. Using #CarySwoveland's test data:
arr = [[3, "dog"], [4, "cat"], [3, "cat"], [4, "dog"]]
arr.sort_by {|a, b| [ a, *b.codepoints.map(&:-#) ] }
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
Alternatively, here's a solution that works regardless of the type (i.e. it needn't be a string):
arr.sort do |a, b|
c0 = a[0] <=> b[0]
next c0 unless c0.zero?
-(a[1] <=> b[1])
end
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
The latter could be generalized as a method like so:
def arr_cmp(a, b, *dirs)
return 0 if a.empty? && b.empty?
return a <=> b if dirs.empty?
a0, *a = a
b0, *b = b
dir, *dirs = dirs
c0 = a0 <=> b0
return arr_cmp(a, b, *dirs) if c0.zero?
dir * c0
end
This works just like <=> but as its final arguments takes a list of 1 or -1s indicating to the sort directions for each respective array element, e.g.:
a = [3, "dog"]
b = [3, "cat"]
arr_cmp(a, b, 1, 1) # => 1
arr_cmp(a, b, 1, -1) # => -1
Like <=> it's most useful in a sort block:
arr.sort {|a, b| arr_cmp(a, b, 1, -1) }
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
I haven't tested it much, though, so there are probably edge cases for which it fails.
While I have no idea about generic academic implementation, in the real life I would go with:
class String
def hash_for_sort precision = 5
(#h_f_p ||= {})[precision] ||= self[0...precision].codepoints.map do |cp|
[cp, 99999].min.to_s.ljust 5, '0'
join.to_i
end
end
Now feel free to sort by -item.attr2.hash_for_sort.
The approach above has some glitches:
no valid sorting for the strings, that differ in > precision letters;
initial call to the function is O(self.length);
codepoints above 99999 would be considered equal (sorting is not accurate).
But taking into account the real circumstanses, I can not imagine when this won’t suffice.
P.S. If I were to solve this task precisely, I would search for an algorithm, converting strings to floats in a one-to-one manner.

Find combinations in Ruby that are less than a certain number

Say I have an array [1,2,3] and I want every combination of these numbers that don't exceed 4. So I would have [1,2,3].someMethod(4) and it would give me:
[1,1,1,1]
[1,1,2]
[1,3]
[2,2]
So far I have:
(1..4).flat_map{|size| [1,2,3].repeated_combination(size).to_a }
but this gives me every possible combinations, including the ones that exceed my given limit. Is there an good way to either only get combinations that add up to my limit?
arr = [1,2,3]
(arr+[0]).repeated_combination(4).select { |a| a.reduce(:+) == 4 }.map { |a| a - [0] }
#=> [[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
Change == to <= if desired.
This answer, like the others, assumes arr contains natural numbers, including 1.
results = (1..4).each.with_object([]) do |size, results|
[1,2,3].repeated_combination(size) do |combo|
results << combo if combo.reduce(:+) == 4
end
end
p results
--output:--
[[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
Parameterizing the algorithm:
def do_stuff(values, target_total)
(1..target_total).each.with_object([]) do |size, results|
values.repeated_combination(size) do |combo|
results << combo if combo.reduce(:+) == 4
end
end
end
p do_stuff([1, 2, 3], 4)
You can filter out the arrays you don't want by using the select method. Just select all the arrays that have a sum == 4 (the sum is calculated by the inject method).
all_arrs = (1..4).flat_map do |size|
[1,2,3].repeated_combination(size).to_a
end
valid_arrs = all_arrs.select do |arr|
arr.inject { |a, b| a + b } == 4
end
print valid_arrs
# Output:
# [[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
A recursive approach.
def some_method(a, n)
return [[]] if n == 0
a.select { |e| e <= n }.\
flat_map { |e| some_method(a,n-e).map { |es| ([e] + es).sort } }.\
sort.\
uniq
end
p some_method([1,2,3], 4)
# => [[1, 1, 1, 1], [1, 1, 2], [1, 3], [2, 2]]
EDIT: Here is another recursive version without filtering duplicates but with opposite order. I added comments to make it clearer.
def some_method(a, n)
return [[]] if n == 0 # bottom (solution) found
return [] if a.empty? || n < 0 # no solution
max = a.max
# search all solutions with biggest value
l = some_method(a, n-max).map { |e| [max] + e }
# search all solutions without biggest value
r = some_method(a-[max],n)
l + r
end
p some_method([1,2,3], 4)
# => [[3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]]

How to search within a two-dimensional array

I'm trying to learn how to search within a two-dimensional array; for example:
array = [[1,1], [1,2], [1,3], [2,1], [2,4], [2,5]]
I want to know how to search within the array for the arrays that are of the form [1, y] and then show what the other y numbers are: [1, 2, 3].
If anyone can help me understand how to search only with numbers (as a lot of the examples I found include strings or hashes) and even where to look for the right resources even, that would be helpful.
Ruby allows you to look into an element by using parentheses in the block argument. select and map only assign a single block argument, but you can look into the element:
array.select{|(x, y)| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|(x, y)| x == 1}.map{|(x, y)| y}
# => [1, 2, 3]
You can omit the parentheses that correspond to the entire expression between |...|:
array.select{|x, y| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|x, y| x == 1}.map{|x, y| y}
# => [1, 2, 3]
As a coding style, it is a custom to mark unused variables as _:
array.select{|x, _| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|x, _| x == 1}.map{|_, y| y}
# => [1, 2, 3]
You can use Array#select and Array#map methods:
array = [[1,1], [1,2], [1,3], [2,1], [2,4], [2,5]]
#=> [[1, 1], [1, 2], [1, 3], [2, 1], [2, 4], [2, 5]]
array.select { |el| el[0] == 1 }
#=> [[1, 1], [1, 2], [1, 3]]
array.select { |el| el[0] == 1 }.map {|el| el[1] }
#=> [1, 2, 3]
For more methods on arrays explore docs.
If you first select and then map you can use the grep function to to it all in one function:
p array.grep ->x{x[0]==1}, &:last #=> [1,2,3]
Another way of doing the same thing is to use Array#map together with Array#compact. This has the benefit of only requiring one block and a trivial operation, which makes it a bit easier to comprehend.
array.map { |a, b| a if b == 1 }
#=> [1, 2, 3, nil, nil, nil]
array.map { |a, b| a if b == 1 }.compact
#=> [1, 2, 3]
You can use each_with_object:
array.each_with_object([]) { |(x, y), a| a << y if x == 1 }
#=> [1, 2, 3]

How do I add a cumulative sum to an array for only one value?

I have an array of arrays with x and y values:
[[some_date1, 1], [some_date2, 3], [some_date3, 5], [some_date4, 7]]
The result should only sum the y values (1, 3, 5, 7) so that the result is like this:
[[some_date1, 1], [some_date2, 4], [some_date3, 9], [some_date4, 16]]
How is this possible in Ruby?
Yes, this is possible in Ruby. You can use [map][1] and do something like this:
sum = 0
array.map {|x,y| [x, (sum+=y)]}
This is how it works. For the given the input:
array = ["one", 1], ["two", 2]
It will iterate through each of the elements in the array e.g.) the first element would be ["one", 1].
It will then take that element (which is an array itself) and assign the variable x to the first element in that array e.g.) "one" and y to the second e.g.) 1.
Finally, it will return an array with the result like this:
=> ["one", 1], ["two", 3]
You can use map:
a = [[:some_date1, 1], [:some_date2, 3], [:some_date3, 5], [:some_date4, 7]]
sum = 0
a.map { |f, v| [f, (sum = sum + v)]}
=> [[:some_date1, 1], [:some_date2, 4], [:some_date3, 9], [:some_date4, 16]]
Since sum will be nil in the first iteration it is necessary to call to_i on it.
a = [['some_date1', 1], ['some_date2', 3], ['some_date3', 5], ['some_date4', 7]]
a.each_cons(2){|a1, a2| a2[1] += a1[1]}
last = 0
arr.map do |a, b|
last = last + b
[a, last]
end
I'd use:
ary = [['some_date1', 1], ['some_date2', 3], ['some_date3', 5], ['some_date4', 7]]
ary.inject(0) { |m, a|
m += a[-1]
a[-1] = m
}
After running, ary is:
[["some_date1", 1], ["some_date2", 4], ["some_date3", 9], ["some_date4", 16]]
The reason I prefer this is it doesn't require the addition of an accumulator variable. inject returns a value but it gets thrown away without an assignment.

Intersect each array in a array - Ruby

I want to find the intersect of each array elements in a array and take the intersection.
The inputs are array of arrays e.g., "'list_arrays' as mentioned in this script below"
The 'filter' is a limit needed to be applied on the total length of intersections observed
The out put is expected as array like this "[[2,4]]"
list_arrays = [[1, 2, 3, 4], [2, 5, 6], [1, 5, 8], [8, 2, 4]]
filter = 2
first_element_array = Array.new
list_arrays.each_with_index do |each_array1, index1|
list_arrays.each_with_index do |each_array2, index2|
unless index1 < index2
intersection = each_array1 & each_array2
if intersection.length == filter.to_i
first_element_array.push(intersection)
end
end
end
end
puts first_element_array
This above procedure takes long execution time as I have too long array of array (In million lines). I need a simple rubistic way to handle this problem. Anyone have any simple idea for it?
Deciphering your code it seems what you are asking for is "Return the intersections between pair combinations of a collection if that intersection has a certain size (2 in the example)". I'd write (functional approach):
list_arrays = [[1, 2, 3, 4], [2, 5, 6], [1, 5, 8], [8, 2, 4]]
list_arrays.combination(2).map do |xs, ys|
zs = xs & ys
zs.size == 2 ? zs : nil
end.compact
#=> [[2, 4]]
Proposed optimizations: 1) Use sets, 2) Use a custom abstraction Enumerable#map_compact (equivalent to map+compact but it would discard nils on the fly, write it yourself). 3) Filter out subarrays which won't satisfy the predicate:
require 'set'
xss = list_arrays.select { |xs| xs.size >= 2 }.map(&:to_set)
xss.combination(2).map_compact do |xs, ys|
zs = xs & ys
zs.size == 2 ? zs : nil
end
#=> [#<Set: {2, 4}>]

Resources