Retrieving keys from a name value pair array in groovy - arrays

I have an array with values similar to below:
def fieldValuesArray = []
fieldValuesArray << ["key1":"value1"]
fieldValuesArray << ["key2":"value2"]
I would like to retrieve the key values of the array while iterating through it, like
fieldValuesArray.each{
println(it.key())//key1,key2 etc.
}
Any pointers would be helpful.
Thank you,
LJ

In your original question you are asking to access a key-value-pair. Those structures are called maps in Groovy. What you are doing in your first line though, is, to create an array not a map.
Furthermore accessing key value pairs with an each loop could require 2 iteration variables: key and value. Just in case you will need both inside the loop it is easier to read for me. But probably a matter of taste.
Your answer is also not a good approach in my opinion. As you first declare fieldValuesArray as an array and in next line you re-assign it to an initialized map. What looks more correct me is the following:
def fieldValuesArray = [:]
fieldValuesArray << ["key1":"value1"]
fieldValuesArray << ["key2":"value2"]
fieldValuesArray.each{ key, value -> println "${key}:${value}" }
// or
// fieldValuesArray.each{ println it.key }
Please note the first line definition with [:] rather than [] and my each-closure.

Just in case anyone is interested, here is the correction to the above.
The first line while adding the values, it should have been = and not <<.
Also, its .key and not key()
def fieldValuesArray = []
fieldValuesArray = ["key1":"value1"]
fieldValuesArray << ["key2":"value2"]
fieldValuesArray.each{val->
println(val.key)//key1,key2 etc.
}

Related

How to find a specific value in a nested array?

I'm trying to figure out how to place a value into one of three arrays and then shuffle those arrays and have the program output the index location of the value.
Here is what I have so far:
# The purpose of this program is to randomly place the name Zac
# in one of three arrays and return the array number and position of
# Zac
A1 = ["John","Steve","Frank","Charles"]
A2 = ["Sam","Clint","Stuart","James"]
A3 = ["Vic","Jim","Bill","David"]
n = [A1,A2,A3]
name = "Zac"
def placename(title, namelist)
mix = rand(2)
namelist[mix] << title
namelist.shuffle
return namelist
end
allnames = [] << placename(name, n)
def findname(allnames, key)
allnames.each do |i|
until allnames[i].include?(key) == true
i+=1
end
location = allnames[i].find_index(key)
puts "The location and value of #{key} is #{location}"
end
end
findname(allnames, name)
At the moment I'm getting a "undefined method for Nil Class" error (no method error)
Can someone please clarify what I'm doing wrong with this or if there is a more effective way of going about this? Thanks in advance!!
Your approach assumes that in the block starting...
allnames.each do |i|
... that i will contain the index of the allnames element. This isn't true. i will contain the VALUE (contents) of the element.
What you could try as an alternative is...
allnames.each_with_index do |_value, i|
or, you can do...
allnames.each do |value|
and then replace all references to allnames[i] with value
another problem is that...
allnames = [] << placename(name, n)
puts the returned array of arrays inside ANOTHER array. I think what you want to do is..
allnames = placename(name, n)
I modified the last fewlines. I hope this is what you wanted
allnames = placename(name, n)
def findname allnames, key
r = allnames.map.with_index{|x,i|x.include?(key) ? i : p}-[p]
puts "The location of value #{key} is array number #{r[0]} and item number #{allnames[r[0]].index(key)}"
end
findname(allnames, name)
Edit: Randomization
To get randomized array number and item number you have to do the following
def placename(title, namelist)
mix = rand(3) # Since the number of arrays (nested within) is 3 we can use 3 instead of 2
namelist[mix] << title
namelist.map!{|x|x.shuffle}.shuffle! # Shuffling each item and the whole array in place.
return namelist
end
Assuming you want to modify the array in place, I'd do it like this:
# insert name into random subarray
def insert_name name
subarray_idx = rand #name_arrays.size
subarray = #name_arrays[subarray_idx]
insertion_idx = rand subarray.size
#name_arrays[subarray_idx].insert insertion_idx, name
sprintf '"%s" inserted at #name_arrays[%d][%d]',
name, subarray_idx, insertion_idx
end
# define starting array, then print & return the
# message for further parsing if needed
#name_arrays = [
%w[John Steve Frank Charles],
%w[Sam Clint Stuart James],
%w[Vic Jim Bill David],
]
p(insert_name 'Zac')
This has a few benefits:
You can inspect #name_arrays to validate that things look the way you expect.
The message can be parsed with String#scan if desired.
You can modify #insert_name to return your indexes, rather than having to search for the name directly.
If you don't capture the insertion index as a return value, or don't want to parse it from your message String, you can search for it by leveraging Enumerable#each_with_index and Array#index. For example:
# for demonstration only, set this so you can get the same
# results since the insertion index was randomized
#name_arrays =
[["John", "Steve", "Frank", "Charles"],
["Sam", "Clint", "Stuart", "James"],
["Vic", "Jim", "Zac", "Bill", "David"]]
# return indices of nested match
def find_name_idx name
#name_arrays.each_with_index
.map { [_2, _1.index(name)] }
.reject { _1.any? nil }
.pop
end
# use Array#dig to retrieve item at nested index
#name_arrays.dig *find_name_idx('Zac')

In Ruby, during iteration of an array, how do I send multiple array elements as values for one specific hash key?

I know how to iterate through an array and I know how to create hash keys and values. I do not know how to create an array for the value and pass it multiple elements. My desired value for hash below is:
{'abc' => [1, 2, 3] , 'def' => [4,5,6,7]}
How would I achieve this hash, while iterating through array a and b below using each?
a = [1,2,3,4,5,6,7]
c = [1,2,3]
b = ['abc', 'def']
hash = {}
From your guidelines given in the comment:
While iterating through array a, if the element of iteration is included in array c, it is passed to the array value within key 'abc'. Otherwise, it is passed to other array value in key 'def'
You can do this:
hash = {}
hash['abc'] = a.select { |x| c.include?(x) }
hash['def'] = a.reject{ |x| c.include?(x) }
See Enumerable#select and Enumerable#reject. Also can take a look at Enumerable#partition which would be another good choice here, where you want to split an array into two arrays based on some condition:
in_a, not_in_a = a.partition { |x| c.include?(x) }
hash = { 'abc' => in_a, 'def' => not_in_a }
You can also do it with regular each if these fancy enumerable methods are bit too much for you:
hash = { 'abc' => [], 'def' => [] }
a.each do |x|
if c.include?(x)
hash['abc'].push(x)
else
hash['def'].push(x)
end
end
Unfortunately this question turned out not to be as interesting as I was hoping. I was hoping that the problem was this:
Knowing the hash key and a value, how can I make sure the key's value is an array and that the given value is appended to that?
For instance, start with h being {}. I have a key name :k and a value 1. I want h[:k], if it doesn't already exist, to be [1]. But if it does already exist, then it's an array and I want to append 1 to that array; for instance, if h[:k] is already [3,2], now it should be [3,2,1].
I can think of various ways to ensure that, but one possibility is this:
(hash[key] ||= []) << value
To see that this works, let's make it a method:
def add_to_hash_array_value(hash:, key:, value:)
(hash[key] ||= []) << value
end
Now I'll just call that a bunch of times:
h = {}
add_to_hash_array_value(hash:h, key:"abc", value:1)
add_to_hash_array_value(hash:h, key:"abc", value:2)
add_to_hash_array_value(hash:h, key:"def", value:4)
add_to_hash_array_value(hash:h, key:"def", value:5)
add_to_hash_array_value(hash:h, key:"abc", value:3)
puts h #=> {"abc"=>[1, 2, 3], "def"=>[4, 5]}
We got the right answer.
This is nice because suppose I have some way of knowing, given a value, what key it should be appended to. I can just repeatedly apply that decision-making process.
However, the trouble with trying to apply that to the original question is that the original question seems not to know exactly how to decide, given a value, what key it should be appended to.

Can't shovel string into new array in Ruby

I am trying to search an array for a substring and if that substring exists, shovel it into a new array. The problem I am having is that it keeps coming back with this error message:
`block in substrings': undefined method `<<' for nil:NilClass
I have verified that the index in the method is not nil by printing it. I have also done index == nil to double check.
What am I missing here?
Thanks in advance for your help!
new_array = []
def substrings(word, array)
new_array = array.each do |index|
if index.include? (word)
p index
p index == nil
new_array << index
end
end
end
dictionary = ["below", "down", "go", "going", "horn", "how", "howdy", "it", "i", "low", "own", "part", "partner", "sit"]
substrings("i", dictionary)
You basically combine two different ways of solving this problem. The first is to assign the new_array the result of looping though the array, but in that case, the new_array variable is not available to use inside the block.
So you could either choose to create the variable first, like this
new_array = []
array.each do |index|
if index.include?(word)
new_array << index
end
end
Alternatively you could use a method called reduce which takes a more functional programming approach. That could look like this
new_array = array.reduce([]) do |arr, index|
if index.include?(word)
arr << index
else
arr
end
end
What reduce does is that the block argument arr is always set to the return value of the previous block execution. that can make the syntax a little longer than it has to be, so Ruby also has an alternate approach to reduce, called each_with_object, that does the same, but by mutating the same variable, instead of requiring a return value. I actually prefer this way and would solve it like this.
new_array = array.each_with_object([]) do |index, arr|
arr << index if index.include?(word)
end
I would like to extend a bit the (correct) answer given by #DanneManne: While it is correct that you can access local variables from outer blocks from within an inner block, you can't do it within a def, and it is unnecessary in your example, because you can initialize new_array inside the body of your method and return it as result. But in case you ever really need this kind of construct, there indeed is a workaround:
So, this does NOT work:
a=5
def f
puts a; # WRONG. a is not known here
end
and this works different than you seem to expect:
a=5
def f
a=6
end
puts a # prints 5
f
puts a # prints 5 again
But if you define your method like this, it works:
a=5
define_method(:f) do
puts a; # OK, refers to outer variable a
end
By using a block, you create a closure with this, so if you do now a
f;
a=6;
f
5 and 6 is printed, in this order.
I have verified that the index in the method is not nil by printing it. I have also done index == nil to double check.
What am I missing here?
Not index is nil, but new_array. The error message says:
undefined method `<<' for nil:NilClass
and refers to the line new_array << index. Here, << is the method and new_array is the receiver. And for some reason, new_array is nil.
You probably expected new_array to be [] because you explicitly said new_array = []. But methods have their own local variable scope. If you define a local variable outside of a method, it won't be available inside, or vice-versa.
Typically when referring to an undefined variable you'd get:
undefined local variable or method `new_array'
but here, the 2nd assignment conceals the actual problem:
new_array = array.each do |index|
^^^^^^^^^^^
When Ruby encounters this line, it immediately creates a local variable new_array with an initial value of nil. (local variables are created when the line is parsed, not when the assignment occurs)
To get the expected result, you have to move new_array = [] into the method, get rid of the new_array = array.each { ...} assignment and return new_array at the end:
def substrings(word, array)
new_array = []
array.each do |index|
if index.include?(word)
new_array << index
end
end
new_array
end
The variable names are a bit arbitrary, maybe even misleading. Having index.include?(word) looks like you're comparing a numerical index to a string. I'd use something like this:
def substrings(substring, words)
result = []
words.each do |word|
if word.include?(substring)
result << word
end
end
result
end
Code-wise, you can incorporate the array into the loop via each_with_object which will also return the array:
def substrings(substring, words)
words.each_with_object([]) do |word, result|
if word.include?(substring)
result << word
end
end
end
However, selecting elements based on a condition is such a common task that Ruby provides a dedicated method select – you merely have to return true or false from the block to indicate whether the element should be selected:
def substrings(substring, words)
words.select do |word|
word.include?(substring)
end
end

Why does Ruby's each_with_object drop data that was appended with the += operator to the array memo?

I'm stumped by the following Ruby behaviour:
result = [1,2,3].each_with_object([]) do |elem, memo|
memo << [elem]
end
puts result.to_s
# => [[1], [2], [3]]
The above code works how I would expect it to work. However, the code below just seems to drop the numbers I'm trying to append to the array.
result = [1,2,3].each_with_object([]) do |elem, memo|
memo += [elem]
end
puts result.to_s
# => []
Could anyone explain to me what's going on here and how I am supposed to use the += operator with each_with_object in the above context?
memo is local variable, that points to an array object. these loop variables are set at each iteration.
In the first example, you change this array object.
In the second example, you override the local variable with a new array. Because memo += [elem] is just a shorthand for memo = momo + [elem]
The old array stays empty.
Very often you better use inject instead of each_with_object. With inject, the new memo variable is set with the result of the block, so you can use non-destructive functions.
[1,2,3].inject([]) do |memo, elem|
memo + [elem]
end
According to docs, += – concatenation – returns a new array built by concatenating the two arrays together to produce a third array.
Thus, on every iteration the block result is set to a new array, that is reset back to the empty array that you've passed to each_with_object method in the next iteration. Consider the following demonstration.

Add key value pair to Array of Hashes when unique Id's match

I have two arrays of hashes
sent_array = [{:sellersku=>"0421077128", :asin=>"B00ND80WKY"},
{:sellersku=>"0320248102", :asin=>"B00WTEF9FG"},
{:sellersku=>"0324823180", :asin=>"B00HXZLB4E"}]
active_array = [{:price=>39.99, :asin1=>"B00ND80WKY"},
{:price=>7.99, :asin1=>"B00YSN9QOG"},
{:price=>10, :asin1=>"B00HXZLB4E"}]
I want to loop through sent_array, and find where the value in :asin is equal to the value in :asin1 in active_array, then copy the key & value of :price to sent_array. Resulting in this:
final_array = [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99},
{:sellersku=>"0320248102", :asin=>"B00WTEF9FG"},
{:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}]
I tried this, but I get a TypeError - no implicit conversion of Symbol into Integer (TypeError)
sent_array.each do |x|
x.detect { |key, value|
if value == active_array[:asin1]
x[:price] << active_array[:price]
end
}
end
For reasons of both efficiency and readability, it makes sense to first construct a lookup hash on active_array:
h = active_array.each_with_object({}) { |g,h| h[g[:asin1]] = g[:price] }
#=> {"B00ND80WKY"=>39.99, "B00YSN9QOG"=>7.99, "B00HXZLB4E"=>10}
We now merely step through sent_array, updating the hashes:
sent_array.each { |g| g[:price] = h[g[:asin]] if h.key?(g[:asin]) }
#=> [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99},
# {:sellersku=>"0320248102", :asin=>"B00WTEF9FG"},
# {:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}]
Retrieving a key-value pair from a hash (h) is much faster, of course, than searching for a key-value pair in an array of hashes.
This does the trick. Iterate over your sent array and attempt to find a record in your active_array that has that :asin. If you find something, set the price and you are done.
Your code I believe used detect/find incorrectly. What you want out of that method is the hash that matches and then do something with that. You were trying to do everything inside of detect.
sent_array.each do |sent|
item = active_array.find{ |i| i.has_value? sent[:asin] }
sent[:price] = item[:price] if item
end
=> [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99}, {:sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, {:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}]
I am assuming second element of both sent_array and active_array has B00WTEF9FG as asin and asin1 respectively. (seeing your final result)
Now:
a = active_array.group_by{|a| a[:asin1]}
b = sent_array.group_by{|a| a[:asin]}
a.map { |k,v|
v[0].merge(b[k][0])
}
# => [{:price=>39.99, :asin1=>"B00ND80WKY", :sellersku=>"0421077128", :asin=>"B00ND80WKY"}, {:price=>7.99, :asin1=>"B00WTEF9FG", :sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, {:price=>10, :asin1=>"B00HXZLB4E", :sellersku=>"0324823180", :asin=>"B00HXZLB4E"}]
Why were you getting TypeError?
You are doing active_array[:asin1]. Remember active_array itself is an Array. Unless you iterate over it, you cannot look for keys.
Another issue with your approach is, you are using Hash#detect
find is implemented in terms of each. And each, when called on a
Hash, returns key-value pairs in form of arrays with 2 elements
each. That's why find returns an array.
source
Same is true for detect. So x.detect { |key, value| .. } is not going to work as you are expecting it to.
Solution without assumption
a.map { |k,v|
b[k] ? v[0].merge(b[k][0]) : v[0]
}.compact
# => [{:price=>39.99, :asin1=>"B00ND80WKY", :sellersku=>"0421077128", :asin=>"B00ND80WKY"}, {:price=>7.99, :asin1=>"B00YSN9QOG"}, {:price=>10, :asin1=>"B00HXZLB4E", :sellersku=>"0324823180", :asin=>"B00HXZLB4E"}]
Here since asin1 => "B00ND80WKY" has no match, it cannot get sellersku from other hash.

Resources