I'd like to delete an item in my loop
i have a array of instance of my class, and i need sometimes to delete this items from my array
class Test
attr_reader :to_del
def initialize(str)
#to_del = str
end
end
tab = Array.new
a = Test.new(false)
b = Test.new(true)
c = Test.new(false)
tab.push(a)
tab.push(b)
tab.push(c)
for l in tab
if l.to_del == true
l = nil
end
end
p tab
any idea ?
For inplace deletion:
tab.reject! { |l| l.to_del }
to return just a cleared array:
tab.reject &:to_del
The whole code is php-smelled. I would go with:
tab = (1..3).map { [true,false].sample }.map { |e| Test.new e }
tab.reject &:to_del
You can use Array#delete_if.
Check this out:
tab
#=> [#<Test:0x00000007548768 #to_del=false>, #<Test:0x000000074ea348 #to_del=true>, #<Test:0x000000074b21a0 #to_del=false>]
tab.delete_if {|x| x.to_del}
#=> [#<Test:0x00000007548768 #to_del=false>, #<Test:0x000000074b21a0 #to_del=false>]
Related
I have a JSON data structure like this...
{
"items": [
{
"person": { // person hash }
},
{
"dog": { // dog hash }
},
{
"fruit": { // fruit hash }
},
{
“person”: { // person hash }
}
]
}
}
Each item in the array contains only one key:value pair. The key is the bot that tells me what type of item the value is.
What I'd like to do is iterate the array and run a different function for each type of item.
So I have something like this...
items = data.dig('items')
items.map do |item|
if person = item.dig('person')
transform_person(person)
elsif dog = item.dig('dog')
transform_dog(dog)
elsif fruit = item.dig('fruit')
transform_fruit(fruit)
end
end
But I feel like there should be a more elegant way to do this?
Apologies. I appear to have left some ambiguity in my question.
The initial array may contain multiple items with the same key. What I am trying to do is map to an array of items that are transformed into what is required by the front end. The input contains a strange structure and info that is not needed by the front end.
So the output array order must match the input array order.
Sorry for the confusion.
First you'll want to define the key preference in a constant:
PECKING_ORDER = %w[ person dog fruit ]
Then you can use that to find it:
def detect(item)
PECKING_ORDER.lazy.map do |key|
[ key, item.dig(key) ]
end.find do |key, v|
v
end
end
Where that can dig up the first item that's found. lazy is used here so it doesn't dig them all up needlessly, just does them one at a time until there's a hit.
This gives you a key/value pair which you can use with dynamic dispatch:
items.each do |item|
key, value = detect(item)
if (key)
send(:"transform_#{key}", value)
end
end
if you know the mapping, you could make a pseudo factory hash:
methods_mapped = {
"person" => ->(person) { do_something_with_person(person) },
"dog" => ->(dog) { do_something_with_dog(dog) },
"fruit" => ->(fruit) { do_something_with_fruit(fruit) }
}
items.map do |item|
key = item.keys.first # what if keys.size > 1 ?
method = methods_mapped.fetch(key)
method.call(item[key])
end
or you could it from the opposite direction:
methods_mapped.each do |key, method|
method.call(items.dig(key))
end
Let f be a given method that takes as an argument a hash. Without loss of generality, suppose it is as follows. This corresponds to the OP's transform_person, transform_dog and transform_fruit methods combined.
def f(h)
case h.keys.first
when :person then "somebody"
when :dog then "doggie"
when :fruit then "juicy"
end
end
Suppose we are also given (no need for dig here)
items = data[:items]
#=> [{:person=>{:name=>"Melba"}},
# {:dog=>{:tricks=>[:roll_over, :shake_a_paw]}},
# {:fruit=>{:good=>"raspberries"}}]
and
key_order = [:bird, :marsupial, :dog, :person]
We wish to find the first element k of key_order for which items contains a hash h for which h.key?(k) #=> true. If such a hash h is found we are to then execute f(h).
First compute a hash key_map.
key_map = items.each_with_object({}) { |g,h| h[g.keys.first] = g }
#=> {:person=>{:person=>{:name=>"Melba"}},
# :dog=>{:dog=>{:tricks=>[:roll_over, :shake_a_paw]}},
# :fruit=>{:fruit=>{:good=>"raspberries"}}}
Then we simply execute
k = key_order.find { |k| key_map[k] }
#=> :dog
k ? f(key_map[k]) : nil
#=> "doggie"
I would kept it simple:
items.map do |item|
do_something_with_person(item) if item.dig('person')
do_something_with_dog(item) if item.dig('dog')
do_something_with_fruit(item) if item.dig('fruit')
end
or
items.each do |item|
case item
when item.dig('person') then do_something_with_person(item)
when item.dig('dog') then do_something_with_dog(item)
when item.dig('fruit') then do_something_with_fruit(item)
end
end
or
def do_something(item)
case
when item.dig('person') then do_something_with_person(item)
when item.dig('dog') then do_something_with_dog(item)
when item.dig('fruit') then do_something_with_fruit(item)
end
end
items.map { |item| do_something(item) }
With Ruby (2.4), I want to extend the core hash functionality to search for keys based on an array and return the value from the first element that can be found from that array. I have this in my lib/core_ext/hash_with_indifferent_access.rb file ...
class CaseInsensitiveHash < HashWithIndifferentAccess
# This method shouldn't need an override, but my tests say otherwise.
def [](key)
if key.kind_of?(Array)
find_by_arr(arr)
else
super convert_key(key)
end
end
protected
def convert_key(key)
key.respond_to?(:downcase) ? key.downcase : key
end
def find_by_arr(arr)
arr.inject(self){|h, k| h.to_h[k]}
end
end
However, it is not working as expected. In my code below, the search 'h[["a", "b"]]' should produce "1", because the first element, "a", is a key in my hash.
2.4.0 :001 > h = {"a" => 1, "b" => 2}
=> {"a"=>1, "b"=>2}
2.4.0 :002 > h["a"]
=> 1
2.4.0 :003 > h[["a", "b"]]
=> nil
How do I modify my code so that I can pass in an array as a key to a hash and it will start searching for values iteratively by each element in the array?
You've almost got it, but the problem is that h = { ... } creates a plain-old Hash, not the kind you've added these methods to.
The first fix is to do this:
h = CaseInsensitiveHash["a" => 1, "b" => 2]
Then you get the right type of object and your method actually runs.
There's a typo in your [] method which can be corrected:
def [](key)
case key
when Array
find_by_arr(key)
else
super convert_key(key)
end
end
Your reference to arr doesn't work because that's not defined.
The find_by_arr method also returns the last match, not the first. This can be fixed:
def find_by_arr(arr)
self[arr.first { |key| self[key] }]
end
Heres my set up
project_JSON = JSON.parse
teamList = Array.new
project = Hash.new()
project["Assignee Name"] = issue["fields"]["assignee"]["displayName"]
project["Amount of Issues"] = 0
if !teamList.include?(issue["fields"]["assignee"]["displayName"])
project_JSON.each do |x|
project["Amount of Issues"] += 1
teamList.push(project)
end
Im having trouble with this line.
if !teamList.include?(issue["fields"]["assignee"]["displayName"])
It always returns true even after the .push. I want to make an array of my team members and list how many times their name appears in my JSON. What am I doing wrong and how do I dynamically refer to a hash value in an if statement(thats where I think its wrong because if I am saying .include?(issue["fields"]["assignee"]["displayName"]) wrong then its nil and the if statement will always be true)?
In your code teamList is an empty array, so it does not include? anything, it will always return false. Now because you are using ! operator it always returns true.
EDIT
If understood it right, you have to loop through the array checking each element for the value specified.
Below is a way to do it, mind you I replaced keys for symbols as it's a good practice in Ruby:
issue = {
:fields => {
:assignee => {
:displayName => 'tiago'
}
}
}
teamList = Array.new
def teamList.has_assignee?(assignee)
self.each do |e|
return e[:assignee] == assignee
end
false
end
project = Hash.new
project[:assigneeName] = issue[:fields][:assignee][:displayName]
project[:amountOfIssues] = 0
teamList.push(project) unless teamList.has_assignee? issue[:fields][:assignee][:dsiplayName]
teamList.push(project) unless teamList.has_assignee? issue[:fields][:assignee][:dsiplayName]
puts teamList.inspect # only one object here
As Sergio pointed out you could use .detect
def teamList.has_assignee?(assignee)
self.detect { |e| e[:assigneeName] == assignee }
end
Say I have such an array:
arr = ['footballs_jumba_10', 'footballs_jumba_11', 'footballs_jumba_12',
'footballs_jumba_14', 'alpha_romeo_11', 'alpha_romeo_12',
'alpha_juliet_10', 'alpha_juliet_11']
If I wanted to return duplicates, (assuming any of these strings in the array were exactly identical, I would just
return arr.detect{ |a| arr.count(a) > 1 }
but, what if I wanted to get only duplicates of the first 10 characters of each element of the array, without knowing the variations beforehand? Like this:
['footballs_', 'alpha_rome', 'alpha_juli']
This is quite straightforward with the method Arry#difference that I proposed in my answer here:
arr << "Let's add a string that appears just once"
#=> ["footballs_jumba_10", "footballs_jumba_11", "footballs_jumba_12",
# "footballs_jumba_14", "alpha_romeo_11", "alpha_romeo_12",
# "alpha_juliet_10", "alpha_juliet_11", "Let's add a string that appears just once"]
a = arr.map { |s| s[0,10] }
#=> ["footballs_", "footballs_", "footballs_", "footballs_", "alpha_rome",
# "alpha_rome", "alpha_juli", "alpha_juli", "Let's add "]
b = a.difference(a.uniq)
#=> ["footballs_", "footballs_", "footballs_", "alpha_rome", "alpha_juli"]
b.uniq
#=> ["footballs_", "alpha_rome", "alpha_juli"]
Use Array#uniq:
arr.map {|e| e[0..9]}.uniq
# => ["footballs_", "alpha_rome", "alpha_juli"]
You could do something like this:
def partial_duplicates(elements)
unique = {}
duplicates = {}
elements.each do |e|
partial = e[0..9]
# If the element is in the hash, it is a duplicate.
if first_element = unique[partial]
duplicates[first_element] = true
duplicates[e] = true
else
# include the element as unique
unique[partial] = e
end
end
duplicates.keys
end
This will return unique duplicates. If you want all the duplicates, you can just use an Array.
Also, this returns all the full representations of each duplicate as it seems more useful and probably what you want:
partial_duplicates(arr)
=> ["footballs_jumba_10", "footballs_jumba_11", "footballs_jumba_12", "footballs_jumba_14", "alpha_romeo_11", "alpha_romeo_12", "alpha_juliet_10", "alpha_juliet_11"]
If you want only the partial duplicates you can change the condition to:
if unique[partial]
duplicates[partial] = true
else
unique[partial] = true
end
then:
partial_duplicates(arr)
=> ["footballs_", "alpha_rome", "alpha_juli"]
I'd like to create a container class for objects based on a Ruby array. I'd like to manipulate more than one of these containers, like concatenating 2 together. If I try this:
class Thing
attr_accessor :name
end
class Things
def initialize
#things = Array.new
end
def addone( a )
#things.push( a )
end
def append( list )
list.each { |i| addone( i ) }
end
end
item1 = Thing.new
item2 = Thing.new
item3 = Thing.new
item4 = Thing.new
item1.name = "Marty"
item2.name = "Fred"
item3.name = "Janice"
item4.name = "John"
list1 = Things.new
list1.addone( item1 )
list1.addone( item2 )
list2 = Things.new
list2.addone( item3 )
list2.addone( item4 )
list3 = Things.new
list3 = list2.append( list1 )
I get the error:
in append': undefined methodeach' for # (NoMethodError) from ./test.rb:40:in `'
I've tried different approaches, for example creating an each method as it seems to want, but no luck so far. Any suggestions? And thanks in advance!
If you want to be able to add Things to Things, you have two abilities: either to implement iterator methods on Things or simply decorate wrapped Array:
def append(list)
case list
when Enumerable then list.each { |i| addone(i) }
when Things then list.instance_variable_get(:#things).each { |e| addone(i) }
else raise "Sorry, can’t add #{list}"
end
I guess there should be a getter/setter methods:
attr_accessor :things
Then you should change your addone method:
def append(list)
list.things.each { |i| addone( i ) } # iterate through array items, not Things instance object
self # return appended list object instead of unchanged provided argument – list1
end
Output of list3.things:
=> [#<Context::Thing:0x00000001adea48 #name="Janice">,
#<Context::Thing:0x00000001ade9f8 #name="John">,
#<Context::Thing:0x00000001adea98 #name="Marty">,
#<Context::Thing:0x00000001adea70 #name="Fred">]
Demonstration
Consider this approach:
class Thing
attr_accessor :name
def initialize(name)
#name = name
end
end
class Things
def initialize(things = [])
#things = things
end
def push(thing)
#things.push(thing)
end
def append(other)
#things << other.to_a
end
def +(other)
Things.new(#things + other.to_a)
end
def to_a
#things
end
end
some_things = %w(Marty Fred Janice John).map { |name| Thing.new(name) }
things_1 = Things.new
some_things.first(2).each { |thing| things_1.push(thing) }
things_2 = Things.new
some_things.last(2).each { |thing| things_2.push(thing) }
things_1.append(things_2) # This actually appends to things_1 rather than creating a new object
new_things = things_1 + things_2 # Creates a new object
# => #<Things:0x007ff85a1aa770 #things=[
# #<Thing:0x007ff85a1aa928 #name="Marty">,
# #<Thing:0x007ff85a1aa900 #name="Fred">,
# #<Thing:0x007ff85a1aa8d8 #name="Janice">,
# #<Thing:0x007ff85a1aa8b0 #name="John">]>
Notes:
Modified the API a bit to simplify the code.
Added a new method + as its intuitive in this context.