Allen Liu
Allen Liu

Reputation: 4038

Count objects in a Ruby array

I am creating a list of hashes in an array and would like to keep a count if they are the same.

Here is what an example hash looks like:

data = {
    s: y.id,
    t: z.id,
    count: 0
}

I am iterating through a bunch of these hashes and pushing them onto a list. I would like it so that when the values for s and t already exist in a hash in the list, the count would be incremented.

Let me clarify. Suppose this is my @list

@list = [
    {
        s: 1,
        t: 2,
        count: 5
    },
    {
        s: 1,
        t: 3,
        count: 5
    }
]

Now suppose, I want to push the following hash to the list:

data = {
    s: 1,
    t: 2,
    count: 0
}

The result of @list should look like this because the hash with s==1 and t==2 already exists in the list:

@list = [
   {
       s: 1,
       t: 2,
       count: 6
   },
   {
       s: 1,
       t: 3,
       count: 5
   }

]

This is where I am currently.

@final = []

while widgets.count > 1
    widget = widgets.shift
    widgets.each do |w|
        data = {
            s: widget.id,
            t: w.id,
            count: 0
        }
        @final << data
    end
end

This simply adds all the permutations to the list but I want to prevent the dups when s and t are identical and simply increment count.

I hope I am clear.

Any suggestions would be greatly appreciated.

Upvotes: 1

Views: 516

Answers (4)

Cary Swoveland
Cary Swoveland

Reputation: 110675

I'd do it like this (assuming I understand the question correctly):

def add_hash(data)
  h, i = @list.each_with_index.find {|h,i| data[:s]==h[:s] && data[:t]==h[:t]}
  if h
    @list[i][:count] += 1
  else
    data[:count] = 1   
    @list << data
  end
end

add_hash( { s: 1, t: 3, count: 0 } ) 
@list # @list => [{:s=>1, :t=>2, :count=>5}, {:s=>1, :t=>3, :count=>6}]   

add_hash( { s: 2, t: 3, count: 0 } )
@list # @list # => [{:s=>1, :t=>2, :count=>5}, {:s=>1, :t=>3, :count=>5},
                    {:s=>2, :t=>3, :count=>1}] 

If you can change @list, consider making it a hash:

@hash = { { s: 1, t: 2 } => 5, { s: 1, t: 3 } => 5 }

Upvotes: 1

Richard Jordan
Richard Jordan

Reputation: 8202

def reduce_matches(collection)
  result.reduce([]) do |arr, element|
    matching(arr, element) ? matching[:count] += 1 : arr << element
    arr
  end
end

def matching(coll, candidate)
  coll.detect do |element|
    element[:s] == candidate[:s] && element[:t] == candidate[:t]
  end
end

Now you can type:

reduce_matches(widgets) 

which gives you what you need. For example if

widgets = [
  {
    s: 1,
    t: 2,
    count: 0
  },
  {
    s: 2,
    t: 3,
    count: 0
  },
  {
    s: 1,
    t: 2,
    count: 0
  },
]

then

reduce_matches(widgets) = [
  {
    s: 1,
    t: 2,
    count: 1
  },
  {
    s: 2,
    t: 3,
    count: 0
  }
]

Want to add a new element to widgets?

widgets << new_element
reduce_matches(widgets)

Upvotes: 0

Rene Hernandez
Rene Hernandez

Reputation: 1566

If I get your question right you could use the find method in list passing a block where you specify the conditions you want to match (that values of s and t are already present in the @final list).
This is an example where I use lists and hashes directly.

widgets = [{s:1, t:2, count:0}, {s: 1, t:2, count:0}, {s: 1, t:2, count:0},    
{s:1, t:3, count:0}, {s:1, t:3, count:0}]    
@final = []    

widgets.each do |widget|    
  res = @final.find {|obj| obj[:s] == widget[:s] && obj[:t] == widget[:t] }    
  if res.nil?    
    widget[:count] = 1    
    @final << widget    
  else    
    res[:count] += 1    
  end    
end    

puts @final.inspect  

And the answer from this code is

[{:s=>1, :t=>2, :count=>3}, {:s=>1, :t=>3, :count=>2}]  

as expected

Upvotes: 1

yanhan
yanhan

Reputation: 3537

Not sure whether I am intepreting your question correctly, but if you want the count attribute in each data hash to be incremented when data.s == data.t, this should do the trick:

@final = []

while widgets.count > 1
    widget = widgets.shift
    widgets.each do |w|
        data = {
            s: widget.id,
            t: w.id,
            count: 0
        }
        if data.s == data.t
            data.count += 1
        end
        @final << data
    end
end

Upvotes: 0

Related Questions