In Ruby is there a way to get the index of an item in an array that consists of structs? - ruby

With a normal array, I can use the arrayname.find_index('whatimlookingfor') to get the position within the array.
I can't figure out how to do this when the elements of the array are Struct's.
Scenario: I have a struct that consists of an ID and the Filename. In one function I need to find within that array the ID of a different file than the one I'm currently processing. I know the other filename, so what I was hoping that I could do something like:
arrayname.filename.find_index(parsedfilename)
But this obviously fails. Without iterating through the entire array is there a way to quickly reference the index of where the match happens? Or am I out of luck because the array is a collection of structs?

index (same as find_index) takes a block in which you can code up any true/false logic for your finder. To find the index of the first item whose filename does not match parsedfilename...
found_index = items.index { |item| item.filename != parsedfilename }
Many methods which work with Arrays and Enumerables also take blocks.

Related

Performance: Replacing Series values with keys from a Dictionary in Python

I have a data series that contains various names of the same organizations. I want harmonize these names into a given standard using a mapping dictionary. I am currently using a nested for loop to iterate through each series element and if it is within the dictionary's values, I update the series value with the dictionary key.
# For example, corporation_series is:
0 'Corp1'
1 'Corp-1'
2 'Corp 1'
3 'Corp2'
4 'Corp--2'
dtype: object
# Dictionary is:
mapping_dict = {
'Corporation_1': ['Corp1', 'Corp-1', 'Corp 1'],
'Corporation_2': ['Corp2', 'Corp--2'],
}
# I use this logic to replace the values in the series
for index, value in corporation_series.items():
for key, list in mapping_dict.items():
if value in list:
corporation_series = corporation_series.replace(value, key)
So, if the series has a value of 'Corp1', and it exists in the dictionary's values, the logic replaces it with the corresponding key of corporations. However, it is an extremely expensive method. Could someone recommend me a better way of doing this operation? Much appreciated.
I found a solution by using python's .map function. In order to use .map, I had to invert my dictionary:
# Inverted Dict:
mapping_dict = {
'Corp1': ['Corporation_1'],
'Corp-1': ['Corporation_1'],
'Corp 1': ['Corporation_1'],
'Corp2': ['Corporation_2'],
'Corp--2':['Corporation_2'],
}
# use .map
corporation_series.map(newdict)
Instead of 5 minutes of processing, took around 5s. While this is works, I sure there are better solutions out there. Any suggestions would be most welcome.

How to check if nested hash attributes are empty

I have a Hash
person_params = {"firstname"=>"",
"lastname"=>"tom123",
"addresses_attributes"=>
{"0"=>
{"address_type"=>"main",
"catalog_delivery"=>"0",
"street"=>"tomstr",
"city"=>"tomcity"
}
}
}
With person_params[:addresses_attributes], I get:
# => {"0"=>{"address_type"=>"main", "catalog_delivery"=>"0", "street"=>"tomstr", "zip"=>"", "lockbox"=>"", "city"=>"tomcity", "country"=>""}}
1) How can I get a new hash without the leading 0?
desired_hash = {"address_type"=>"main", "catalog_delivery"=>"0", "street"=>"tomstr", "zip"=>"", "lockbox"=>"", "city"=>"tomcity", "country"=>""}
2) How can I check whether the attributes in the new hash are empty?
Answer 1:
person_params[:addresses_attributes]['0']
Answer 2:
hash = person_params[:addresses_attributes]['0']
hash.empty?
This looks just like a params hash from Rails =D. Anyway, it seems that your addresses_attributes contains some nested attributes. This means that what you have in practice is more of an array of hashes than a single hash, and that's what you see right? Instead of it being an actually Ruby Array, it is a hash with the index as a string.
So how do you get the address attributes? Well if you only want to get the first address, here are some ways to do that:
person_params[:addresses_attributes].values.first
# OR
person_params[:addresses_attributes]["0"]
In the first case, we will just take the values from the addreses_attributes hash, which gives us an Array from which we can take the first item. If there are no values in addresses_attributes, then we will get nil.
In the second case, we will just ask for the hash value with the key "0". If there are no values in addresses_attributes, we will get nil with this method also. (You might want to avoid using the second case, if you are not confident that the addresses_attributes hash will always be indexed from "0" and incremented by "1")

Ruby: Array each loop, save other elements of original array in new array

I am currently trying to compare every element of an array with the others (in Ruby). Those elements are objects of a class. I need to find similarities between them. My idea was to loop through the original array and in this loop creating a new array containing the other elements (not the one of the outer loop) and then loop through this second array and compare every item with the one in the outer each loop.
Here is some pseudocode:
originalArray.each{
|origElement|
tempArray = createNewArray from original array without origElement
tempArray.each{
|differentElement|
Compare origElement with differentElement
}
}
How can I create that tempArray?
I think you should use Array#permutation for this
original_array.permutation(2) { |elements| Compare elements[0] with elements[1] }
First, I want to say bjhaid's answer is beautiful and for your specific instance, it is the one that should be used.
However, I wanted to provide a more general answer that answers the direct question you asked: "How can I create that tempArray?"
If you wanted to delete all values that are equal to the element in the original array, you could simply do:
tempArray = originalArray - [origElement]
However, if you only want to delete that element, you could do:
originalArray.each_with_index {
|origElement, index|
tempArray = originalArray.dup
tempArray.delete_at(index)
tempArray.each{
|differentElement|
Compare origElement with differentElement
}
}
Also, a note on styling. You probably want to use underscores instead of CamelCase for all methods/variables. In the Ruby community, CamelCase is typically reserved for class / module names. You also probably want to keep the "piped-in" variables (called block arguments) on the same line as the beginning of the block. It is certainly not a requirement, but it is an almost universal convention in the Ruby community.
This code snippet would be much more familiar and readable to your typical Ruby dev:
original_array.each_with_index do |orig_element, index|
temp_array = original_array.dup
temp_array.delete_at(index)
temp_array.each do |different_element|
Compare orig_element with different_element
end
end

Put nils in front of array

I have a couple of arrays that I want to put nils into so that my indexing will
be further into the array based on how I use the aray. Is there a faster, more efficient and elegant way ?
(1..#epgroup_overide_count.to_i).each do
#grp['Overides'].unshift(nil)
#grp['channel_overides'].unshift(nil)
end
#unshift takes a splatted arg list to prepend multiple elements, so:
#group['Overides'].unshift(*([nil]*#epgroup_override_count.to_i))
This will do it (similar for your second one)
Breaking down the one liner:
[nil] * #epgroup_override_count.to_i
Multiplying an array by a number duplicates the array that many times, but as a single flat array ([1,2,3]*3 #=> [1,2,3,1,2,3,1,2,3]). One thing to be aware of is that if the array has variable references, it creates multiple references to the same objects, not duplicates.
*(above_multiplied_array)
using the splat operator to spread the array into the args of the method
arr.unshift(multiple, args, here)
By using the splat operator, we spread the array into multiple args to #unshift. When #unshift is given multiple args, it prepends all of them to the array, which is the desired result.
You can do like below:
#grp['Overides'] +=([nil]*#epgroup_override_count.to_i).flatten
#grp['channel_overides'] += ([nil]*#epgroup_override_count.to_i).flatten
or
nil_array = ([nil]*#epgroup_override_count.to_i).flatten
#grp['Overides'] += nil_array
#grp['channel_overides'] += nil_array

itration behaviour when list length = 1

I'm quite new to python and am having issues with for loop behaviour. In my code I'm reading config from a file using configobj. The contents of the config file are variable and that is where I'm seeing issues.
Here's my test code:
if webconf.has_key(group):
scenario_list = webconf[group]['Scenarios']['names']
for scenario in scenario_list:
print "Scenario name = %s\n" % scenario
The "scenario_list" variable will contain any number of strings. When 'names' has multiple elements "scenario" is set to the value of each element, which is fine. When "names" has only 1 element then the loop iterates over each character of the first entry, breaking my code.
So, how do I get the for loop simply to return the value of the entry in "scenario_list" when list length is 1?
Thankyou in advance for any advice offered.
Are you using tuples rather than lists?
aTuple = (1,2,3)
aList = [1,2,3]
The big difference between tuples and lists are that tuples are immutable and lists are mutable. That is, with a list you may change the element of a list, or even add and remove elements.
The problem that you are likely encountering is related to a concept called tuple unpacking.
aList = [0] # aList is now [0]
notATuple = (0) # notATuple is now 0
# there was exactly one element in the tuple, so it was unpacked in the variable
aTuple = (0,) # aTuple is now (0,) - a tuple with one element
# the comma indicates that you wish that the tuple should not be unpacked
The only other problem I think of is that you are not putting the scenario string in a list or tuple when you have only one scenario. Python treats strings like lists (well, more like tuples) of characters. As such, if you iterate over a string you get the individual characters (the behaviour you experienced). Hence, you must put your scenario string in a list (or tuple) if want to iterate over your one string, and not its characters. Had you not been using strings you would have seen a runtime error.

Resources