I like to parse string array and update value, what i have for example:
list= ["beam=0", "active=0", "rate=11", "version=4.1", "delay=5"]
in the above list i want to search for "active" and edit its value, like if "active=0" i want to make it "active=1" , and if its "active=1" i want to make it "active=0".
What i am doing is , but its not correct ,, can someone assist in this:
list.each do |lists|
if lists.include?("active=0")
lists = "active=1"
elsif list.include?("active=1")
lists = "active=0"
end
end
what i expect in the end if list contains active=0 , than output list = ["beam=0", "active=1", "rate=11", "version=4.1", "delay=5"] and if list contains active=1, then output list = ["beam=0", "active=0", "rate=11", "version=4.1", "delay=5"]
If you can use a hash, it is much more suitable for this task.
If you cannot, then the problem with your code is that you are not updating the original value. You are just updating the variable used in the #each iterator.
One way to do what you want is this:
list = ["beam=0", "active=0", "rate=11", "version=4.1", "delay=5"]
# convert to hash
hash = list.to_h { |x| x.split '=' }
# update any hash value
hash['active'] = hash['active'] == '0' ? '1' : '0'
# convert back to array
result = hash.map { |x| x.join '=' }
and, if for some reason, you wish to stay as close as possible to your original code, then you can use map instead of each. I do not recommend the below code for this case, since this is not good coding, but in case you have your reasons and this is for educational purposes only:
list = ["beam=0", "active=0", "rate=11", "version=4.1", "delay=5"]
result = list.map do |item|
case item
when 'active=0' then 'active=1'
when 'active=1' then 'active=0'
else
item
end
end
You can iterate through the list and replace the number after active= like below:
list= ["beam=0", "active=0", "rate=11", "version=4.1", "delay=5"]
list.each_with_index do |item, index|
next unless item.starts_with?('active')
number_with_active = item.split('active=')[1].to_i
list[index] = "active=#{(number_with_active+1)%2}"
end
I would actually suggest an entirely different approach; HOWEVER, the main problem with your original code is that you're using an inappropriate method right at the start. each isn't returning a modified array. Try using map instead. At a minimum, you'll also want to add a line of code to ensure all other elements return unchanged. If you don't, the other elements will be replaced with nil values. Here's what your code might look like with minimal modifications:
list= ["beam=0", "active=0", "rate=11", "version=4.1", "delay=5"]
list.map do |lists|
if lists.include?("active=0")
lists = "active=1"
elsif list.include?("active=1")
lists = "active=0"
else
lists
end
end
#=> ["beam=0", "active=1", "rate=11", "version=4.1", "delay=5"]
Related
I came across a weird behaviour in Ruby that I can't explain.
If we have an array of arrays and wanted to iterated over it with map. I tried to use map with two block variables expecting the second one to be the index, but it instead takes the values of the inner array. Why?
persons = [["john", 28], ["mary", 25],["emma", 30]]
persons.map do |param1, param2|
puts param1
puts param2
end
The output is
john
28
So how come that it takes the values of the iterators it should iterate over?
This is what you are looking for:
persons.map.with_index do |array, index|
...
end
You can pass an offset if you want the index to start in 1 instead of 0:
with_index(1)
You're using map but you don't seem to care about the result, so each is more appropriate. Unlike in JavaScript where you may be expecting an index to appear by default, in Ruby you have to ask for it.
If you wanted to display the values you'd do something like this:
persons.each_with_index do |(name, age), index|
puts '%d. %s (%s)' % [ index + 1, name, age ]
end
Note the use of (name, age) to unpack what is otherwise a pair of values that would be received as an array. This is because each-type methods treat arrays as singular objects. In the default case it'll auto-unpack for you.
If you wanted to transform the values then you'd use map:
persons.map.with_index do |(name, age), index|
'%d. %s (%s)' % [ index + 1, name, age ]
end
Remember, when using map you must capture as a variable, return it, or somehow use the result or it will get thrown away.
I'm parsing XML files and wanting to omit duplicate values from being added to my Array. As it stands, the XML will looks like this:
<vulnerable-software-list>
<product>cpe:/a:octopus:octopus_deploy:3.0.0</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.1</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.2</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.3</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.4</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.5</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.6</product>
</vulnerable-software-list>
document.xpath("//entry[
number(substring(translate(last-modified-datetime,'-.T:',''), 1, 12)) > #{last_imported_at} and
cvss/base_metrics/access-vector = 'NETWORK'
]").each do |entry|
product = entry.xpath('vulnerable-software-list/product').map { |product| product.content.split(':')[-2] }
effected_versions = entry.xpath('vulnerable-software-list/product').map { |product| product.content.split(':').last }
puts product
end
However, because of the XML input, that's parsing quite a bit of duplicates, so I end up with an array like ['Redhat','Redhat','Redhat','Fedora']
I already have the effected_versions taken care of, since those values don't duplicate.
Is there a method of .map to only add unique values?
If you need to get an array of unique values, then just call uniq method to get the unique values:
product =
entry.xpath('vulnerable-software-list/product').map do |product|
product.content.split(':')[-2]
end.uniq
There are many ways to do this:
input = ['Redhat','Redhat','Redhat','Fedora']
# approach 1
# self explanatory
result = input.uniq
# approach 2
# iterate through vals, and build a hash with the vals as keys
# since hashes cannot have duplicate keys, it provides a 'unique' check
result = input.each_with_object({}) { |val, memo| memo[val] = true }.keys
# approach 3
# Similar to the previous, we iterate through vals and add them to a Set.
# Adding a duplicate value to a set has no effect, and we can convert it to array
result = input.each_with_object.(Set.new) { |val, memo| memo.add(val) }.to_a
If you're not familiar with each_with_object, it's very similar to reduce
Regarding performance, you can find some info if you search for it, for example What is the fastest way to make a uniq array?
From a quick test, I see these performing in increasing time. uniq is 5 times faster than each_with_object, which is 25% slower than the Set.new approach. Probably because sort is implemetned using C. I only tested with only an arbitrary input though, so it might not be true for all cases.
I have an array of hashes, like this:
my_array = [{foo:1,bar:"hello",baz:3},{foo:2,bar:"hello2",baz:495,foo_baz:"some_string"},...]
#there can be arbitrary many in this list.
#There can also be arbitrary many keys on the hashes.
I want to create a new array that is a copy of the last array, except that I remove any :bar entries.
my_array2 = [{foo:1,baz:3},{foo:2,baz:495,foo_baz:"some_string"},...]
I can get the my_array2 by doing this:
my_array2 = my_array.map{|h| h.delete(:bar)}
However, this changes the original my_array, which I want to stay the same.
Is there a way of doing this without having to duplicate my_array first?
one of many ways to accomplish this:
my_array2 = my_array.map{|h| h.reject{|k,v| k == :bar}}
my_array.map {|h| h.select{|k, _| k != :bar} }
# => [{:foo=>1, :baz=>3}, {:foo=>2, :baz=>495, :foo_baz=>"some_string"}]
My txt file contains a few lines and i want to add each line to a hash with key as first 2 words and value as 3rd word...The following code has no errors but the logic may be wrong...last line is supposed to print all the keys of the hash...but nothing happens...pls help
def word_count(string)
count = string.count(' ')
return count
end
h = Hash.new
f = File.open('sheet.txt','r')
f.each_line do |line|
count = word_count(line)
if count == 3
a = line.split
h.merge(a[0]+a[1] => a[2])
end
end
puts h.keys
Hash#merge doesn't modify the hash you call it on, it returns the merged Hash:
merge(other_hash) → new_hash
Returns a new hash containing the contents of other_hash and the contents of hsh. [...]
Note the Returns a new hash... part. When you say this:
h.merge(a[0]+a[1] => a[2])
You're merge the new values you built into a copy of h and then throwing away the merged hash; the end result is that h never gets anything added to it and ends up being empty after all your work.
You want to use merge! to modify the Hash:
h.merge!(a[0]+a[1] => a[2])
or keep using merge but save the return value:
h = h.merge(a[0]+a[1] => a[2])
or, since you're only adding a single value, just assign it:
h[a[0] + a[1]] = a[2]
If you want to add the first three words of each line to the hash, regardless of how many words there are, then you can drop the if count == 3 line. Or you can change it to if count > 2 if you want to make sure that there are at least three words.
Also, mu is correct. You'll want h.merge!
I'm fairly new to ruby. I have the following csv:
Office (1), Test
Office (Test)(2), Test
In "data.csv".
Then in my ruby script I have;
CSV.foreach("data.csv") do |line|
registeredOffice = line[0].to_s()
macOffice = registeredOffice.scan(/\(([^\)]+)\)/).last
csvText = "#{csvText}\n#{macOffice}"
end
Which gives me
["1"]
["2"]
However I want to know how to convert the above to a string so the output is
1
2
Using .join or [0] returns a nil:NilClass (NoMethodError)
You probably want something like:
macOffice = registeredOffice[/(\d+)\)$/, 1]
scan with capture groups will give you multi-d arrays
The following line:
macOffice = registeredOffice.scan(/\(([^\)]+)\)/).last
returns array since scan returns array of array. For the first line of data.csv, it is ["1"].
I guess you need scalar value for macOffice, thus, you want to use match which only returns non-repetive match using match, which returns array of matches once. For example, you can grab first match from the returned array using [1] subscript, thus:
macOffice = registeredOffice.match(/\(([^\)]+)\)/)[1]
which returns 1.
Assuming you want an array you can write like this:
out = []
CSV.foreach("data.csv") do |line|
registeredOffice = line[0].to_s()
macOffice = registeredOffice.match(/\((\d+)\)/)[1]
out.push(macOffice)
end
puts out.join(",")
to produce 1,2