last element in Hash.each [duplicate] - ruby

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Tell the end of a .each loop in ruby
I have a Hash:
=> {"foo"=>1, "bar"=>2, "abc"=>3}
and a code:
foo.each do |elem|
# smth
end
How to recognize that an element in cycle is last?
Something like
if elem == foo.last
puts 'this is a last element!'
end

For example like this:
foo.each_with_index do |elem, index|
if index == foo.length - 1
puts 'this is a last element!'
else
# smth
end
end
The problem you might have is that items in a map are not coming in any specific order. On my version of Ruby I see them in the following order:
["abc", 3]
["foo", 1]
["bar", 2]
Maybe you want to traverse the sorted keys instead. Like this for example:
foo.keys.sort.each_with_index do |key, index|
if index == foo.length - 1
puts 'this is a last element!'
else
p foo[key]
end
end

Related

I am trying to return an array of hashes, but only the last hash is being added to the array?

Here is how I get all the hands
def get_all_hands
doc = Nokogiri::HTML(open('http://www.cardplayer.com/rules-of-poker/hand-rankings'))
hand_hash = {}
hands_array = []
doc.css('div#rules-of-poker-accordion').each do |hands|
hands.css('strong').each do |hand|
hand_hash[:name] = hand.text
end
hands.css('div.rules-cards').each do |hand|
hand_value = []
hand.css('img').each do |card|
hand_value << card.attr('src')
hand_hash[:value] = hand_value
end
end
hands_array << hand_hash
end
hands_array
end
HandScraper.new.get_all_hands
This returns:
[{:name=>"10. High Card",
:value=>
["/packages/cards/Large/Diamond/3-909f8b1571f834c774576c93eae26594.png",
"/packages/cards/Large/Club/J-58b4c0f26e3e0cf8c0772ab3e9e34784.png",
"/packages/cards/Large/Spade/8-60d335b08119f600c3ca02aa58fa902d.png",
"/packages/cards/Large/Heart/4-712ce04b7f2c7e588c48a1e2b46a4244.png",
"/packages/cards/Large/Spade/2-e2d1cee5fc0db0b70990036153d57906.png"]}]
which is the tenth and final hand, when I want it to return all 10.
This particular piece of code is reason why it doesn't work. You are iterating on each strong assigning the value to the same key (:name) of hand_hash. Same is the case with next iteration. Basically, you are overriding the same hash without saving it anywhere, until last iteration.
hands.css('strong').each_with_index do |hand, index|
hand_hash[index] = hand.text
end
I made some changes in your own code to fix this:
doc = Nokogiri::HTML(open('http://www.cardplayer.com/rules-of-poker/hand-rankings'))
hands_array = []
doc.css('div#rules-of-poker-accordion').each do |hands|
hands.css('strong').zip(hands.css('div.rules-cards')).each do |hand, value|
hand_hash = {}
hand_hash[:name] = hand.text
hand_value = []
value.css('img').each do |card|
hand_value << card.attr('src')
hand_hash[:value] = hand_value
end
hands_array << hand_hash #here, now you are saving after each hand
end
end
hands_array
hands.css('strong').zip(hands.css('div.rules-cards')) will pair up each name and rule and then you are just adding that in your hands_array.
Result:
[{:name=>"1. Royal flush", :value=>
["/packages/cards/Large/Diamond/A-49a04aae5e96d2f948dc2062c2c4fcd5.png",
"/packages/cards/Large/Diamond/K-0bfc14d8f58cc13891b108e4178f92f9.png",
"/packages/cards/Large/Diamond/Q-b981aa1f57642480de1dceaf1c2e810f.png",
"/packages/cards/Large/Diamond/J-d915fc38dbca1ca74cdd75dd913de1f3.png",
"/packages/cards/Large/Diamond/T-ef2fe11bbd701e4c5b6681e506271700.png"]},
{:name=>"2. Straight flush"}, {:name=>"3. Four of a kind", :value=>
["/packages/cards/Large/Heart/J-2bf19067cda29391286416d0d00646d6.png",
"/packages/cards/Large/Diamond/J-d915fc38dbca1ca74cdd75dd913de1f3.png",
"/packages/cards/Large/Spade/J-fff29c49da8ca1f7a272c5ac83f51d06.png",
"/packages/cards/Large/Club/J-58b4c0f26e3e0cf8c0772ab3e9e34784.png",
"/packages/cards/Large/Diamond/7-7e507c2122efe10ed7abacab95edff97.png"]},
{:name=>"4. Full house", :value=>
["/packages/cards/Large/Heart/T-c3f8fd4ffc3e09ec705a817aa212dc86.png",
"/packages/cards/Large/Diamond/T-ef2fe11bbd701e4c5b6681e506271700.png",
"/packages/cards/Large/Spade/T-9a16f63a333b3edeb50c4372f8dd9883.png",
"/packages/cards/Large/Club/9-e6f0020a48aef9907b626477c5a60ac2.png",
"/packages/cards/Large/Diamond/9-3e500833bafc81a708d195f16d005125.png"]},
{:name=>"5. Flush", :value=>
["/packages/cards/Large/Spade/4-4200c8b5f3f5ba04d9fd5a69d71dab2f.png",
"/packages/cards/Large/Spade/J-fff29c49da8ca1f7a272c5ac83f51d06.png",
"/packages/cards/Large/Spade/8-60d335b08119f600c3ca02aa58fa902d.png",
"/packages/cards/Large/Spade/2-e2d1cee5fc0db0b70990036153d57906.png",
"/packages/cards/Large/Spade/9-b0d71e77734375ceb3954156232f1f2d.png"]},
{:name=>"6. Straight", :value=>
["/packages/cards/Large/Club/9-e6f0020a48aef9907b626477c5a60ac2.png",
"/packages/cards/Large/Diamond/8-6cd5b3025be0dd56cd52dfd2a49d922d.png",
"/packages/cards/Large/Spade/7-6c1d119e9c923f8e4773cf00d05e26d6.png",
"/packages/cards/Large/Diamond/6-a0c0218210a1a6c4ec17e5cec17ee3d8.png",
"/packages/cards/Large/Heart/5-f498916a3011c2b7199e1c1008dbe330.png"]},
{:name=>"7. Three of a kind", :value=>
["/packages/cards/Large/Club/7-5610625720208cc02c1107c91365eb37.png",
"/packages/cards/Large/Diamond/7-7e507c2122efe10ed7abacab95edff97.png",
"/packages/cards/Large/Spade/7-6c1d119e9c923f8e4773cf00d05e26d6.png",
"/packages/cards/Large/Club/K-3e8312c33de4718943cd0276de8a16a1.png",
"/packages/cards/Large/Diamond/3-909f8b1571f834c774576c93eae26594.png"]},
{:name=>"8. Two pair", :value=>
["/packages/cards/Large/Club/4-33a9251d25da1ea2ba49e69e94549aee.png",
"/packages/cards/Large/Spade/4-4200c8b5f3f5ba04d9fd5a69d71dab2f.png",
"/packages/cards/Large/Club/3-0c3eda54cfb6808b0a94950c045e497a.png",
"/packages/cards/Large/Diamond/3-909f8b1571f834c774576c93eae26594.png",
"/packages/cards/Large/Club/Q-9fcc4fd7692aa96ba9fcb04fa9fd727d.png"]},
{:name=>"9. Pair", :value=>
["/packages/cards/Large/Heart/A-748f3f87f79ac475e6a432750725b64c.png",
"/packages/cards/Large/Diamond/A-49a04aae5e96d2f948dc2062c2c4fcd5.png",
"/packages/cards/Large/Club/8-c3708e4821723f1100d514e5280b3f32.png",
"/packages/cards/Large/Spade/4-4200c8b5f3f5ba04d9fd5a69d71dab2f.png",
"/packages/cards/Large/Heart/7-1610ff3e74c68f6dd8a855bd16887457.png"]},
{:name=>"10. High Card", :value=>
["/packages/cards/Large/Diamond/3-909f8b1571f834c774576c93eae26594.png",
"/packages/cards/Large/Club/J-58b4c0f26e3e0cf8c0772ab3e9e34784.png",
"/packages/cards/Large/Spade/8-60d335b08119f600c3ca02aa58fa902d.png",
"/packages/cards/Large/Heart/4-712ce04b7f2c7e588c48a1e2b46a4244.png",
"/packages/cards/Large/Spade/2-e2d1cee5fc0db0b70990036153d57906.png"]}]
Hope it helps :)
The doc.css('div#rules-of-poker-accordion') call is returning a single div, as that is how the page is structured. Because of that, you are only actually entering the each loop once.
The layout of that site is a bit funny, so you'll have to get the names and the values separately. Here is a brute force solution...
def get_all_hands
doc = Nokogiri::HTML(open('http://www.cardplayer.com/rules-of-poker/hand-rankings'))
hands_array = []
doc.css('div#rules-of-poker-accordion').css("strong").each do |name|
hands_array.push({name: name.text, value: []})
end
doc.css('div#rules-of-poker-accordion').css(".rules-cards").each_with_index do |hand, i|
hand.css('img').each do |card|
hands_array[i][:value].push card.attr('src')
end
end
hands_array
end
The answer by #kiddorails is correct, but you can be much more concise and Ruby-idiomatic in this problem. Consider the following, which gives the same result:
def get_all_hands
doc = Nokogiri::HTML(open('http://www.cardplayer.com/rules-of-poker/hand-rankings'))
hands = doc.css('div#rules-of-poker-accordion').first
hands.css('strong').zip(hands.css('div.rules-cards')).map do |hand, value|
{name: hand.text, value: value.css('img').map { |card| card.attr('src') }}
end
end
Ruby has strong methods for handling and transforming Arrays. Once you have an Array, you can transform it using one of Ruby's many expressive Enumerable methods. #kiddorails started down this path by using #zip to join two arrays together, and the above refactoring finishes the job by taking the resulting array of arrays and transforming it into an array of hashes using #map.
In Ruby, anytime you find yourself writing this pattern:
result = []
array.each { |element| result << method(element) }
result
You can generally replace those three lines with:
array.map { |element| method(element) }

Ruby's "each" methods not iterating over all items in an array?

I'm trying out the following code:
a = [1,2,3,4]
a.each do
puts "Removing #{a.last}"
a.pop
end
but instead of getting all four numbers popped I only get the first 3. Indeed, doing something like puts a.length returns 1 and puts-ing it shows the element "1" is still there.
How do I need to use the method correctly?
(I'm using Ruby 2.0).
I suspect this is happening because you're iterating over the elements of the list while modifying the list.
Try the following:
a = [1,2,3,4]
until a.empty? do
puts "Removing #{a.last}"
a.pop
end
Problem
While you are iterating over a you are changing it.
Explanation of Problem
That means once you have removed an element the each method gets thrown off because suddenly the number of elements a contains is one less. And thus indexing is also thrown off.
If I just execute this:
a = [1,2,3,4]
a.each do
|thing|
puts thing
a.delete(thing)
end
I will get the output [1,3].
That is because the following happens:
Before I remove the 1 from the list that is at index 0, the 2 is at index 1.
After the 1 is removed the 2 is at index 0 instead of 1 so not the 2 is the next element that is iterated over but the 3!
By the way you can define a local block variable like I did with thing to access each element that you iterate over.
Solution
In order to get what you want you need to create a copy and work on that.
a = [1,2,3,4]
b = a.clone
a.each do
|thing|
puts thing
b.delete(thing)
end
Now a remains the same while you iterate over it and you change b instead.
So at the end of this loop a = [1,2,3,4] and b =[].
After you said a = b you will have the desired result.
Of Course you can adapt this for popping elements from the back. Just make sure to work on a copy so you do not change the element while you are iteration over it.
Some other answers tell why your code does not work.
An alternative way to do it would be like this (provided that you do not have nil or false in a):
a = [1,2,3,4]
while e = a.pop
puts "Removing #{e}"
end
a = [1,2,3,4]
a.length.times do
puts "Removing #{a.last}"
a.pop
end
Look at the output,which makes clear why it seems to you each not getting run with all the array elements. As you are pop(ing)Array#pop, so the elements from the last is deleted. When each passes 2 to the block,then original array a is got empty,so each stops the iteration.:
a = [1,2,3,4]
a.each do |i|
puts i
puts "Removing #{a.last}"
p a.pop
p "========"
end
Output:
1
Removing 4
4
"========"
2
Removing 3
3
"========"
So you can use the below:
a = [1,2,3,4]
(0...a.size).each do |i|
p a.pop
end
Output:
4
3
2
1
Try this:
a.count.times do a.pop and puts "Removing #{a.last + 1 rescue 1}" end
Should do the same in a do loop

each_with_index_do starting at 1 for index

I am using a ruby iterator on a view in a rails app like so:
<% (1..#document.data.length).each_with_index do |element, index| %>
...
<% end %>
I thought the addition of the 1.. instead of just saying:
#document.data
would get the trick of having the index above start at 1. But alas, the above code index is still 0 to data.length (-1 effectively). So what am I doing wrong, i need the index to equal 1-data.length...no clue how to set up the iterator to do this.
Unless you're using an older Ruby like 1.8 (I think this was added in 1.9 but I'm not sure), you can use each.with_index(1) to get a 1-based enumerator:
In your case it would be like this:
<% #document.data.length.each.with_index(1) do |element, index| %>
...
<% end %>
I think maybe you misunderstand each_with_index.
each will iterate over elements in an array
[:a, :b, :c].each do |object|
puts object
end
which outputs;
:a
:b
:c
each_with_index iterates over the elements, and also passes in the index (starting from zero)
[:a, :b, :c].each_with_index do |object, index|
puts "#{object} at index #{index}"
end
which outputs
:a at index 0
:b at index 1
:c at index 2
if you want it 1-indexed then just add 1.
[:a, :b, :c].each_with_index do |object, index|
indexplusone = index + 1
puts "#{object} at index #{indexplusone}"
end
which outputs
:a at index 1
:b at index 2
:c at index 3
if you want to iterate over a subset of an array, then just choose the subset, then iterate over it
without_first_element = array[1..-1]
without_first_element.each do |object|
...
end
This may not be exactly the same each_with_index method in question, but I think the result may close to something in mod is asking...
%w(a b c).each.with_index(1) { |item, index| puts "#{index} - #{item}" }
# 1 - a
# 2 - b
# 3 - c
For more information https://ruby-doc.org/core-2.6.1/Enumerator.html#method-i-with_index
Use Integer#next:
[:a, :b, :c].each_with_index do |value, index|
puts "value: #{value} has index: #{index.next}"
end
produces:
value: a has index: 1
value: b has index: 2
value: c has index: 3
There is no such thing as making the index start from 1. If you want to skip the first item in the array use next.
<% (1..#document.data.length).each_with_index do |element, index| %>
next if index == 0
<% end %>
An array index is always going to be zero based.
If you want to skip the first element, which it sounds like you do:
#document.data[1..-1].each do |data|
...
end
If I understand your question right, you want to start the index from 1, but in ruby arrays goes as 0 base indexes, so the simplest way would be
given #document.data is an array
index = 1
#document.data.each do |element|
#your code
index += 1
end
HTH
I had the same problem, and solved it by using the each_with_index method. But added 1 to the index in the code.
#someobject.each_with_index do |e, index|
= index+1

Why will a Range not work when descending? [duplicate]

This question already has answers here:
Is there a reason that we cannot iterate on "reverse Range" in ruby?
(12 answers)
Closed 7 years ago.
Why will (1..5).each iterate over 1,2,3,4,5, but (5..1) will not? It returns the Range instead.
1.9.2p290 :007 > (1..5).each do |i| puts i end
1
2
3
4
5
=> 1..5
1.9.2p290 :008 > (5..1).each do |i| puts i end
=> 5..1
The easiest way to do that is use downto
5.downto(1) do |i| puts i end
Ranges use <=> to determine if an iteration is over; 5 <=> 1 == 1 (greater-than), so it's done before it starts. Even if they didn't, ranges iterate using succ; 5.succ is 6, still out of luck. A range's step cannot be negative, so that won't work either.
It returns the range because each returns what it was called on. Use downto if it's the functionality itself you're looking for, otherwise the above answers your actual question regarding "why".
You can easily extend the Range class, in particular the each method, to make it compatible with both ascending and descending ranges:
class Range
def each
if self.first < self.last
self.to_s=~(/\.\.\./) ? last = self.last-1 : last = self.last
self.first.upto(last) { |i| yield i}
else
self.to_s=~(/\.\.\./) ? last = self.last+1 : last = self.last
self.first.downto(last) { |i| yield i }
end
end
end
Then, the following code will perform just as you'd expect:
(0..10).each { |i| puts i}
(0...10).each { |i| puts i}
(10..0).each { |i| puts i}
(10...0).each { |i| puts i}
This doesn't even really have anything to do with Ruby, it's just simple basic math: the range which starts with 5 and ends with 1 is empty. There is nothing to iterate over.
Because Ruby only does what it's told, not what you mean.
It can't tell whether you want to go in reverse (ie 5, 4, 3, 2, 1), or whether you really only want the numbers starting from 5 that are less than or equal to 1. It's theoretically possible that someone may want the latter, and because Ruby can't tell what you really want, it'll go with the latter.

Ruby each_with_index offset

Can I define the offset of the index in the each_with_index loop iterator?
My straight forward attempt failed:
some_array.each_with_index{|item, index = 1| some_func(item, index) }
Edit:
Clarification: I don't want an array offset I want that the index within the each_with_index doesn't start from 0 but e.g. 1.
Actually, Enumerator#with_index receives offset as an optional parameter:
[:foo, :bar, :baz].to_enum.with_index(1).each do |elem, i|
puts "#{i}: #{elem}"
end
outputs:
1: foo
2: bar
3: baz
BTW, I think it is there only in 1.9.2.
The following is succinct, using Ruby's Enumerator class.
[:foo, :bar, :baz].each.with_index(1) do |elem, i|
puts "#{i}: #{elem}"
end
output
1: foo
2: bar
3: baz
Array#each returns an enumerator, and calling Enumerator#with_index returns another enumerator, to which a block is passed.
1) The simplest is to substitute index+1 instead of index to the function:
some_array.each_with_index{|item, index| some_func(item, index+1)}
but probably that is not what you want.
2) The next thing you can do is to define a different index j within the block and use it instead of the original index:
some_array.each_with_index{|item, i| j = i + 1; some_func(item, j)}
3) If you want to use index in this way often, then define another method:
module Enumerable
def each_with_index_from_one *args, &pr
each_with_index(*args){|obj, i| pr.call(obj, i+1)}
end
end
%w(one two three).each_with_index_from_one{|w, i| puts "#{i}. #{w}"}
# =>
1. one
2. two
3. three
Update
This answer, which was answered a few years ago, is now obsolete. For modern Rubies, Zack Xu's answer will work better.
If some_index is somehow meaningful, then consider using a hash, rather than an array.
I ran into it.
My solution not necessary is the best, but it just worked for me.
In the view iteration:
just add: index + 1
That's all for me, as I don't use any reference to those index numbers but just for show in a list.
Yes, you can
some_array[offset..-1].each_with_index{|item, index| some_func(item, index) }
some_array[offset..-1].each_with_index{|item, index| some_func(item, index+offset) }
some_array[offset..-1].each_with_index{|item, index| index+=offset; some_func(item, index) }
UPD
Also I should notice that if offset is more than your Array size it will though an error. Because:
some_array[1000,-1] => nil
nil.each_with_index => Error 'undefined method `each_with_index' for nil:NilClass'
What can we do here:
(some_array[offset..-1]||[]).each_with_index{|item, index| some_func(item, index) }
Or to prevalidate offset:
offset = 1000
some_array[offset..-1].each_with_index{|item, index| some_func(item, index) } if offset <= some_array.size
This is little hacky
UPD 2
As far as you updated your question and now you need not Array offset, but index offset so #sawa solution will works fine for you
Ariel is right. This is the best way to handle this, and it's not that bad
ary.each_with_index do |a, i|
puts i + 1
#other code
end
That is perfectly acceptable, and better than most of the solutions I've seen for this. I always thought this was what #inject was for...oh well.
Another approach is to use map
some_array = [:foo, :bar, :baz]
some_array_plus_offset_index = some_array.each_with_index.map {|item, i| [item, i + 1]}
some_array_plus_offset_index.each{|item, offset_index| some_func(item, offset_index) }
This works in every ruby version:
%W(one two three).zip(1..3).each do |value, index|
puts value, index
end
And for a generic array:
a.zip(1..a.length.each do |value, index|
puts value, index
end
offset = 2
some_array[offset..-1].each_with_index{|item, index| some_func(item, index+offset) }

Resources