Need a `values_at_if ` method to map values in ruby - arrays

I have two arrays of the same size
response = ["N","N","Y","Y","Y"]
mapping = ["A","B","C","D","E"]
I need to select the elements in mapping whose corresponding value in response, i.e., the element with the same index, is "Y", as below
["C","D","E"]
It reminds me of the values_at method. My solution is as follows
def values_at_if(response, mapping)
result=[]
response.each_index |k|
if k=="Y"
result << mapping[k]
end
end
result
end
I am not happy with it. Please let me know of a way to do it efficiently.

Update
The simplest solution I can come up with is:
mapping.select.with_index {|_,i| response[i] == "Y"}
#=>["C", "D", "E"]
This will select all the elements in mapping where the corresponding index in response equals "Y"
TL;DR Other options include:
mapping.values_at(*response.each_with_index.select {|v,_| v == "Y"}.map(&:last))
mapping.zip(response).map {|k,v| k if v == "Y"}.compact
The first uses each with index which will be
[["N",0],["N",1],["Y",2],["Y",3],["Y",4]]
then we select the groups where the first element is "Y" and map the indexes to pass to values_at
The second version zips the mapping and the response together creating.
[["A", "N"], ["B", "N"], ["C", "Y"], ["D", "Y"], ["E", "Y"]]
Then map the first element only when the second element is "Y". the compact removes the nil values from the mapping.
There are a lot of other ways to accomplish this task if you have a look through the Enumerable module

I would go with
mapping.zip(response).select { |_, r| r == 'Y' }.map(&:first)
#=> ["C", "D", "E"]

Convert response to an Enumerator; each without a block does that. Then use it in the select block. If the block returns true then the item is selected:
response = ["N","N","Y","Y","Y"]
mapping = ["A","B","C","D","E"]
enum_resp = response.each
mapping.select{ enum_resp.next == "Y" } # =>["C", "D", "E"]
Note it would save memory if response would consist of false and trues, which also would make the comparison in the select block unnecessary.

response.each_with_object([]).with_index do |(v, arr), i|
arr << mapping[i] if v == 'Y'
end
Or
mapping.each_with_object([]).with_index do |(v, arr), i|
arr << v if response[i] == 'Y'
end

Related

Iterate over an array n items at a time and continue the iteration

I'm trying to build a CLI. I want to print the name of each object stored in my array. This is how my array looks like:
my_arr = [#<MyObject::Obj:0x007f828daf33b0>, #<MyObject::Obj:0x007f358daf33b0>..]
Instead of showing a long list at once, I want the user to take action to display 200/1000 names at a time. This is my code:
my_arr.each_with_index do |my_obj, index|
puts "#{index} #{my_obj.name}"
end
I'm thinking to use case statement to build the user interaction part, but having issues finding ways to split my Array. How can I start iterating on my Array, break out from the iteration (ask for user input) and after that continue to iterate where I left off?
Ruby has an Enumerable#each_slice method that will give you an array in groups, which could allow you to do something similar to:
my_arr = my_arr.collect.with_index do |my_obj, index|
"#{index} #{my_obj.name}" # do this all the way up here to get the original index
end.each_slice(5)
length = my_arr.size - 1 # how many groups do we need to display
my_arr.each.with_index do |group, index|
puts group.join("\n") # show the group, which is already in the desired format
if index < length # if there are more groups to show,
# show a message and wait for input
puts "-- MORE --"
gets
end
end
You can use break and next. A short demo -
def foo_next(arr)
arr.each_with_index { |item, index|
next if index % 2 == 0
puts item
}
end
def foo_break(arr)
arr.each_with_index { |item, index|
puts item
break if index % 2 == 0
}
end
nums = (1..10).to_a
foo_next(nums) # prints 2 4 6 8 10
foo_break(nums) # prints 1
Use an enumerator to enable a stop/continue process:
arr = ('a'..'j').to_a
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
enum = arr.to_enum
def taker n, enum
n.times.with_object [] { |_, o| o << enum.next }
end
Then take how ever many elements you want...
taker 2, enum
#=> ["a", "b"]
...and pick up from where you left off:
taker 3, enum
#=> ["c", "d", "e"]
taker 1, enum
#=> ["f"]
How you print the output and/or user-prompt is up to you.

How do I find the index of any element from an array within another array?

I have an array:
["a", "b", "c", "d"]
How do I figure out the index of the first element of the above array to occur within a second array:
["next", "last", "d", "hello", "a"]
The index of the first element from the first array to occur within the above array would be 2; "d" belongs to the first array and occurs at position 2.
There's a couple of ways to do this, but the naive approach might work well enough to get going:
tests = ["a", "b", "c", "d"]
in_array = ["next", "last", "d", "hello", "a"]
in_array.each_with_index.find do |e, i|
tests.include?(e)
end
# => ["d", 2]
You can speed this up by making tests a Set which avoids a lot of O(N) lookups:
tests = Set.new([ ... ])
The same code will work with include? but that's now much faster on longer lists.
This approach, wrapped in a method, returns an array containing all indexes of common elements between two arrays.
def find_positions(original_array, look_up_array)
positions_array = []
original_array.each do |x|
if look_up_array.index(x) != nil
positions_array << look_up_array.index(x)
end
end
positions_array
# positions_array.first => for the first matched element
end
If you want only the first matched element you could return positions_array.first but this way you'll not avoid the extra lookups.
PS: you could also use #collect and avoid the extra array (positions_array)
You can iterate through the array you want to be compared and use .select or .find iterator method. .find will select the first element match in the arrays while .select will match all elements in the arrays. If you want to add the index in the selection you can add .each_with_index. '.index(a)' returns the element if present else it will return nil.
alphabet = %w(a b c d)
%w(next last d hello a).each_with_index.find {|a, _index| alphabet.index(a) }
=> ["d", 2]
%w(next last d hello a).each_with_index.select {|a, _index| alphabet.index(a) }[0]
=> ["d", 2]
# if you just need the index of the first match
%w(next last d hello a).index {|a| alphabet.index(a) }
=> 2

How can I generate a percentage for a regex string match in Ruby?

I'm trying to build a simple method to look at about 100 entries in a database for a last name and pull out all the ones that match above a specific percentage of letters. My current approach is:
Pull all 100 entries from the database into an array
Iterate through them while performing the following action
Split the last name into an array of letters
Subtract that array from another array that contains the letters for the name I am trying to match which leaves only the letters that weren't matched.
Take the size of the result and divide by the original size of the array from step 3 to get a percentage.
If the percentage is above a predefined threshold, push that database object into a results array.
This works, but I feel like there must be some cool ruby/regex/active record method of doing this more efficiently. I have googled quite a bit but can't find anything.
To comment on the merit of the measure you suggested would require speculation, which is out-of-bounds at SO. I therefore will merely demonstrate how you might implement your proposed approach.
Code
First define a helper method:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
In short, if
a = [3,1,2,3,4,3,2,2,4]
b = [2,3,4,4,3,4]
then
a - b #=> [1]
whereas
a.difference(b) #=> [1, 3, 2, 2]
This method is elaborated in my answer to this SO question. I've found so many uses for it that I've proposed it be added to the Ruby Core.
The following method produces a hash whose keys are the elements of names (strings) and whose values are the fractions of the letters in the target string that are contained in each string in names.
def target_fractions(names, target)
target_arr = target.downcase.scan(/[a-z]/)
target_size = target_arr.size
names.each_with_object({}) do |s,h|
s_arr = s.downcase.scan(/[a-z]/)
target_remaining = target_arr.difference(s_arr)
h[s] = (target_size-target_remaining.size)/target_size.to_f
end
end
Example
target = "Jimmy S. Bond"
and the names you are comparing are given by
names = ["Jill Dandy", "Boomer Asad", "Josefine Simbad"]
then
target_fractions(names, target)
#=> {"Jill Dandy"=>0.5, "Boomer Asad"=>0.5, "Josefine Simbad"=>0.8}
Explanation
For the above values of names and target,
target_arr = target.downcase.scan(/[a-z]/)
#=> ["j", "i", "m", "m", "y", "s", "b", "o", "n", "d"]
target_size = target_arr.size
#=> 10
Now consider
s = "Jill Dandy"
h = {}
then
s_arr = s.downcase.scan(/[a-z]/)
#=> ["j", "i", "l", "l", "d", "a", "n", "d", "y"]
target_remaining = target_arr.difference(s_arr)
#=> ["m", "m", "s", "b", "o"]
h[s] = (target_size-target_remaining.size)/target_size.to_f
#=> (10-5)/10.0 => 0.5
h #=> {"Jill Dandy"=>0.5}
The calculations are similar for Boomer and Josefine.

Most efficient way to count duplicated elements between two arrays

As part of a very basic program I am writing in Ruby, I am trying to find the total number of shared elements between two arrays of equal length, but
I need to include repeats.
My current example code for this situation is as follows:
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
counter = 0
array_a.each_index do |i|
array_a.sort[i] == array_b.sort[i]
counter += 1
end
end
puts counter
I want the return value of this comparison in this instance to be 4, and not 2, as the two arrays share 2 duplicate characters ("A" twice, and "B" twice). This seems to work, but I am wondering if there are any more efficient solutions for this issue. Specifically whether there are any methods you would suggest looking into. I spoke with someone who suggested a different method, inject, but I really don't understand how that applies and would like to understand. I did quite a bit of reading on uses for it, and it still isn't clear to me how it is appropriate. Thank you.
Looking at my code, I have realized that it doesn't seem to work for the situation that I am describing.
Allow me to reiterate and explain what I think the OP's original intent was:
Given arrays of equal size
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
We need to show the total number of matching pairs of elements between the two arrays. In other words, each B in array_a will "use up" a B in array_b, and the same will be true for each A. As there are two B's in array_a and three in array_b, this leaves us with a count of 2 for B, and following the same logic, 2 for A, for a sum of 4.
(array_a & array_b).map { |e| [array_a.count(e), array_b.count(e)].min }.reduce(:+)
If we get the intersection of the arrays with &, the result is a list of values that exist in both arrays. We then iterate over each match, and select the minimum number of times the element exists in either array --- this is the most number of times the element that can be "used". All that is left is to total the number of paired elements, with reduce(:+)
Changing array_a to ["B", "A", "A", "B", "B"] results in a total of 5, as there are now enough of B to exhaust the supply of B in array_b.
If I understand the question correctly, you could do the following.
Code
def count_shared(arr1, arr2)
arr1.group_by(&:itself).
merge(arr2.group_by(&:itself)) { |_,ov,nv| [ov.size, nv.size].min }.
values.
reduce(0) { |t,o| (o.is_a? Array) ? t : t + o }
end
Examples
arr1 = ["B","A","A","A","B"]
arr2 = ["A","B","A","B","B"]
count_shared(arr1, arr2)
#=> 4 (2 A's + 2 B's)
arr1 = ["B", "A", "C", "C", "A", "A", "B", "D", "E", "A"]
arr2 = ["C", "D", "F", "F", "A", "B", "A", "B", "B", "G"]
count_shared(arr1, arr2)
#=> 6 (2 A's + 2 B's + 1 C + 1 D + 0 E's + 0 F's + 0 G's)
Explanation
The steps are as follows for a slightly modified version of the first example.
arr1 = ["B","A","A","A","B","C","C"]
arr2 = ["A","B","A","B","B","D"]
First apply Enumerable#group_by to both arr1 and arr2:
h0 = arr1.group_by(&:itself)
#=> {"B"=>["B", "B"], "A"=>["A", "A", "A"], "C"=>["C", "C"]}
h1 = arr2.group_by(&:itself)
#=> {"A"=>["A", "A"], "B"=>["B", "B", "B"], "D"=>["D"]}
Prior to Ruby v.2.2, when Object#itself was introduced, you would have to write:
arr.group_by { |e| e }
Continuing,
h2 = h0.merge(h1) { |_,ov,nv| [ov.size, nv.size].min }
#=> {"B"=>2, "A"=>2, "C"=>["C", "C"], "D"=>["D"]}
I will return shortly to explain the above calculation.
a = h2.values
#=> [2, 2, ["C", "C"], ["D"]]
a.reduce(0) { |t,o| (o.is_a? Array) ? t : t + o }
#=> 4
Here Enumerable#reduce (aka inject) merely sums the values of a that are not arrays. The arrays correspond to elements of arr1 that do not appear in arr2 or vise-versa.
As promised, I will now explain how h2 is computed. I've used the form of Hash#merge that employs a block (here { |k,ov,nv| [ov.size, nv.size].min }) to compute the values of keys that are present in both hashes being merged. For example, when the first key-value pair of h1 ("A"=>["A", "A"]) is being merged into h0, since h0 also has a key "A", the array
["A", ["A", "A", "A"], ["A", "A"]]
is passed to the block and the three block variables are assigned values (using "parallel assignment", which is sometimes called "multiple assignment"):
k, ov, nv = ["A", ["A", "A", "A"], ["A", "A"]]
so we have
k #=> "A"
ov #=> ["A", "A", "A"]
nv #=> ["A", "A"]
k is the key, ov ("old value") is the value of "A" in h0 and nv ("new value") is the value of "A" in h1. The block calculation is
[ov.size, nv.size].min
#=> [3,2].min = 2
so the value of "A" is now 2.
Notice that the key, k, is not used in the block calculation (which is very common when using this form of merge). For that reason I've changed the block variable from k to _ (a legitimate local variable), both to reduce the chance of introducing a bug and to signal to the reader that the key is not used in the block. The other elements of h2 that use this block are computed similarly.
Another way
It would be quite simple if we had available an Array method I've proposed be added to the Ruby core:
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
array_a.size - (array_a.difference(array_b)).size
#=> 4
or
array_a.size - (array_b.difference(array_a)).size
#=> 4
I've cited other applications in my answer here.
This is a perfect job for Enumerable#zip and Enumerable#count:
array_a.zip(array_b).count do |a, b|
a == b
end
# => 2
The zip method pairs up elements, "zippering" them together, and the count method can take a block as to if the element should be counted.
The inject method is very powerful, but it's also the most low-level. Pretty much every other Enumerable method can be created with inject if you work at it, so it's quite flexible, but usually a more special-purpose method is better suited. It's still a useful tool if applied correctly.
In this case zip and count do a much better job and if you know what these methods do, this code is self explanatory.
Update:
If you need to count all overlapping letters regardless of order you need to do some grouping on them. Ruby on Rails provides the handy group_by method in ActiveSupport, but in pure Ruby you need to make your own.
Here's an approach that counts up all the unique letters, grouping them using chunk:
# Convert each array into a map like { "A" => 2, "B" => 3 }
# with a default count of 0.
counts = [ array_a, array_b ].collect do |a|
Hash.new(0).merge(
Hash[a.sort.chunk { |v| v }.collect { |k, a| [ k, a.length ] }]
)
end
# Iterate over one of the maps key by key and count the minimum
# overlap between the two.
counts[0].keys.inject(0) do |sum, key|
sum + [ counts[0][key], counts[1][key] ].min
end

Nested loops in Ruby

I am trying to count the number of similar prefix beginnings to a string in Ruby. e.g; input "ababaa" should output 11;
ababaa = 6
babaa = 0
abaa = 3
baa = 0
aa = 1
a = 1
I have got as far as the code below, using a nested loop to go through each of the above as an array, however it looks as though Ruby is currently outputting the count of just the first Array object, "ababaa".
Solved, thanks :)
def string_suffix(string)
num = 0
ary = []
string.length.times do
ary << string[num..string.length]
num = num + 1
end
result = 0
ary.each do |x| # ["ababaa", "babaa", "abaa", "baa", "aa", "a"]
x.chars.each_with_index do |c,index|
break unless c == string[index]
result = result + 1
end
end
return result
end
I have looked far and wide and still cannot solve the issue, It looks like the (final, nested) array is breaking after the first iteration of the 'ary' Array and just returning that output.
You are returning the result while you are still in the loop. You need to move result = 0 out of the loop, and move the return result statement outside of the loop too. At the moment the function is going through the first iteration of the loop ("ababaa", for which all characters match), but you want result to equal the sum of all results.
Additionally, instead of doing:
count = 0
x.chars.each do |x|
if x == string[count]
count = count + 1
result = result + 1
else
count = count + 1
end
end
You could use the function each_with_index, to get
x.chars.each_with_index do |c,index|
if c == string[index]
result = result + 1
end
end
However, since you are trying to count how many characters in the substring are a prefix of string, you want to break when you first find a character c that is not equal to string[index], so that you don't end up counting extra characters. The loop then becomes:
x.chars.each_with_index do |c,index|
if c == string[index]
result = result + 1
else
break
end
end
I noticed you are returning the result inside your second loop, at the end. This means that after you've gone through the first item in your array the function returns just the result for the first item. Move your return statement to outside the loop.
As I understand, the problem is this: given a string s, for each i = 0..s.size-1, compute the number of leading characters of s[0..-i-1] that match the corresponding characters (i.e., at same offsets) of s[i..-1], and sum these s.size subtotals.
Here's a Ruby-like way to do that, using Enumerable#reduce (aka inject) and Enumerable#take_while:
str = "ababaa"
arr = str.chars
(0...arr.size).reduce(0) do |tot,i|
tot + arr[0..-i-1].zip(arr[i..-1]).take_while { |x,y| x == y }.size
end
#=> 11
The steps:
arr = str.chars
#=> ["a", "b", "a", "b", "a", "a"]
r = 0...arr.size
#=> 0...6
When the first element of r is passed to the block, the block variables are set to:
tot = 0
i = 0
The block calculation is therefore as follows:
a = arr[0..-i-1].zip(arr[i..-1])
#=> arr[0..-1].zip(arr[0..-1])
#=> arr.zip(arr)
#=> ["a", "b", "a", "b", "a", "a"].zip(["a", "b", "a", "b", "a", "a"])
#=> [["a", "a"], ["b", "b"], ["a", "a"], ["b", "b"], ["a", "a"], ["a", "a"]]
b = a.take_while { |x,y| x == y }
#=> [["a", "a"], ["b", "b"], ["a", "a"], ["b", "b"], ["a", "a"], ["a", "a"]]
tot + b.size
#=> 0 + 6
#=> 6
Note that this calculation will always equal arr.size for the first element of arr passed to the block.
When the next element of arr is passed to the block, the block variable i is set to 1. tot, which we just computed, equals 6. The block calculation is therefore:
a = arr[0..-i-1].zip(arr[i..-1])
#=> arr[0..-2].zip(arr[1..-1])
#=> ["a", "b", "a", "b", "a"].zip(["b", "a", "b", "a", "a"])
#=> [["a", "b"], ["b", "a"], ["a", "b"], ["b", "a"], ["a", "a"]]
b = a.take_while { |x,y| x == y }
#=> []
tot + b.size
#=> 6 + 0
#=> 6
The remaining calculations are similar. After all elements of arr have been sent to the block, reduce returns the value of tot.

Resources