Magic First and Last Indicator in a Loop in Ruby/Rails? - ruby-on-rails

Ruby/Rails does lots of cool stuff when it comes to sugar for basic things, and I think there's a very common scenario that I was wondering if anyone has done a helper or something similar for.
a = Array.new(5, 1)
a.each_with_index do |x, i|
if i == 0
print x+1
elsif i == (a.length - 1)
print x*10
else
print x
end
end
Pardon the ugliness, but this gets at what one might want... is there a ruby way to do something to the first and last of a loop?
[EDIT] I think ideally this would be an extension on Array with parameters (array instance, all elements function, first elements function, last elements function)... but I'm open to other thoughts.

You could grab the first and last elements and process them differently, if you like.
first = array.shift
last = array.pop
process_first_one
array.each { |x| process_middle_bits }
process_last_one

If the code for the first and last iteration has nothing in common with the code for the other iterations, you could also do:
do_something( a.first )
a[1..-2].each do |x|
do_something_else( x )
end
do_something_else_else( a.last )
If the different cases have some code in common, your way is fine.

What if you could do this?
%w(a b c d).each.with_position do |e, position|
p [e, position] # => ["a", :first]
# => ["b", :middle]
# => ["c", :middle]
# => ["d", :last]
end
Or this?
%w(a, b, c, d).each_with_index.with_position do |(e, index), position|
p [e, index, position] # => ["a,", 0, :first]
# => ["b,", 1, :middle]
# => ["c,", 2, :middle]
# => ["d", 3, :last]
end
In MRI >= 1.8.7, all it takes is this monkey-patch:
class Enumerable::Enumerator
def with_position(&block)
state = :init
e = nil
begin
e_last = e
e = self.next
case state
when :init
state = :first
when :first
block.call(e_last, :first)
state = :middle
when :middle
block.call(e_last, :middle)
end
rescue StopIteration
case state
when :first
block.call(e_last, :first)
when :middle
block.call(e_last, :last)
end
return
end while true
end
end
It's got a little state engine because it must look ahead one iteration.
The trick is that each, each_with_index, &c. return an Enumerator if given no block. Enumerators do everything an Enumerable does and a bit more. But for us, the important thing is that we can monkey-patch Enumerator to add one more way to iterate, "wrapping" the existing iteration, whatever it is.

Or a tiny little Domain Specific Language:
a = [1, 2, 3, 4]
FirstMiddleLast.iterate(a) do
first do |e|
p [e, 'first']
end
middle do |e|
p [e, 'middle']
end
last do |e|
p [e, 'last']
end
end
# => [1, "first"]
# => [2, "middle"]
# => [3, "middle"]
# => [4, "last"]
and the code that makes it go:
class FirstMiddleLast
def self.iterate(array, &block)
fml = FirstMiddleLast.new(array)
fml.instance_eval(&block)
fml.iterate
end
attr_reader :first, :middle, :last
def initialize(array)
#array = array
end
def first(&block)
#first = block
end
def middle(&block)
#middle = block
end
def last(&block)
#last = block
end
def iterate
#first.call(#array.first) unless #array.empty?
if #array.size > 1
#array[1..-2].each do |e|
#middle.call(e)
end
#last.call(#array.last)
end
end
end
I started thinking, "if only you could pass multiple blocks to a Ruby function, then you could have a slick and easy solution to this question." Then I realized that DSL's play little tricks that are almost like passing multiple blocks.

As many have pointed out, each_with_index seems to be the key to this. I have this code block that I liked.
array.each_with_index do |item,index|
if index == 0
# first item
elsif index == array.length-1
# last item
else
# middle items
end
# all items
end
Or
array.each_with_index do |item,index|
if index == 0
# first item
end
# all items
if index == array.length-1
# last item
end
end
Or by Array extensions
class Array
def each_with_position
array.each_with_index do |item,index|
if index == 0
yield item, :first
elsif index == array.length-1
yield item, :last
else
yield item, :middle
end
end
end
def each_with_index_and_position
array.each_with_index do |item,index|
if index == 0
yield item, index, :first
elsif index == array.length-1
yield item, index, :last
else
yield item, index, :middle
end
end
end
def each_with_position_and_index
array.each_with_index do |item,index|
if index == 0
yield item, :first, index
elsif index == array.length-1
yield item, :last, index
else
yield item, :middle, index
end
end
end
end

If you are willing to add some boilerplate, you can add something like this to the array class:
class Array
def each_fl
each_with_index do |x,i|
yield [i==0 ? :first : (i==length-1 ? :last : :inner), x]
end
end
end
and then anywhere you need to, you get the following syntax:
[1,2,3,4].each_fl do |t,x|
case t
when :first
puts "first: #{x}"
when :last
puts "last: #{x}"
else
puts "otherwise: #{x}"
end
end
for the following output:
first: 1
otherwise: 2
otherwise: 3
last: 4

There's no "do this the (first|last) time" syntax in Ruby. But if you're looking for succinctness, you could do this:
a.each_with_index do |x, i|
print (i > 0 ? (i == a.length - 1 ? x*10 : x) : x+1)
end
The result is what you'd expect:
irb(main):001:0> a = Array.new(5,1)
=> [1, 1, 1, 1, 1]
irb(main):002:0> a.each_with_index do |x,i|
irb(main):003:1* puts (i > 0 ? (i == a.length - 1 ? x*10 : x) : x+1)
irb(main):004:1> end
2
1
1
1
10

Interesting question, and one I've thought a bit about as well.
I think you'd have to create three different blocks/procs/whatever they're called, and then create a method that calls the correct block/proc/whatever. (Sorry for the vagueness - I'm not yet a black belt metaprogrammer) [Edit: however, I've copied from someone who is at the bottom)
class FancyArray
def initialize(array)
#boring_array = array
#first_code = nil
#main_code = nil
#last_code = nil
end
def set_first_code(&code)
#first_code = code
end
def set_main_code(&code)
#main_code = code
end
def set_last_code(&code)
#last_code = code
end
def run_fancy_loop
#boring_array.each_with_index do |item, i|
case i
when 0 then #first_code.call(item)
when #boring_array.size - 1 then #last_code.call(item)
else #main_code.call(item)
end
end
end
end
fancy_array = FancyArray.new(["Matti Nykanen", "Erik Johnsen", "Michael Edwards"])
fancy_array.set_first_code {|item| puts "#{item} came first in ski jumping at the 1988 Winter Olympics"}
fancy_array.set_main_code {|item| puts "#{item} did not come first or last in ski jumping at the 1988 Winter Olympics"}
fancy_array.set_last_code {|item| puts "#{item} came last in ski jumping at the 1988 Winter Olympics"}
fancy_array.run_fancy_loop
produces
Matti Nykanen came first in ski jumping at the 1988 Winter Olympics
Erik Johnsen did not come first or last in ski jumping at the 1988 Winter Olympics
Michael Edwards came last in ski jumping at the 1988 Winter Olympics
Edit: Svante's answer (with molf's suggestion) to a related question shows how to pass in multiple code blocks to a single method:
class FancierArray < Array
def each_with_first_last(first_code, main_code, last_code)
each_with_index do |item, i|
case i
when 0 then first_code.call(item)
when size - 1 then last_code.call(item)
else main_code.call(item)
end
end
end
end
fancier_array = FancierArray.new(["Matti Nykanen", "Erik Johnsen", "Michael Edwards"])
fancier_array.each_with_first_last(
lambda {|person| puts "#{person} came first in ski jumping at the 1988 Winter Olympics"},
lambda {|person| puts "#{person} did not come first or last in ski jumping at the 1988 Winter Olympics"},
lambda {|person| puts "#{person} came last in ski jumping at the 1988 Winter Olympics"})

I needed this functionality from time to time, so I crafted a little class for that purpose.
The latest version is at: https://gist.github.com/3823837
Sample:
("a".."m").to_a.each_pos do |e|
puts "Char\tfirst?\tlast?\tprev\tnext\twrapped?\tindex\tposition" if e.first?
print "#{e.item}\t"
print "#{e.first?}\t"
print "#{e.last?}\t"
print "#{e.prev}\t"
print "#{e.next}\t"
print "#{e.wrapped?}\t\t"
print "#{e.index}\t"
puts "#{e.position}\t"
end
# Char first? last? prev next wrapped? index position
# a true false b false 0 1
# b false false a c true 1 2
# c false false b d true 2 3
# d false false c e true 3 4
# e false false d f true 4 5
# f false false e g true 5 6
# g false false f h true 6 7
# h false false g i true 7 8
# i false false h j true 8 9
# j false false i k true 9 10
# k false false j l true 10 11
# l false false k m true 11 12
# m false true l false 12 13
{
a: "0",
b: "1",
c: "2",
d: "3",
e: "4",
f: "5",
g: "6",
h: "7",
i: "8",
j: "9",
k: "10",
l: "11",
m: "12",
}.each_pos do |(k, v), e|
puts "KV\tChar\t\tfirst?\tlast?\tprev\t\tnext\t\twrapped?\tindex\tposition" if e.first?
print "#{k} => #{v}\t"
print "#{e.item}\t"
print "#{e.first?}\t"
print "#{e.last?}\t"
print "#{e.prev || "\t"}\t"
print "#{e.next || "\t"}\t"
print "#{e.wrapped?}\t\t"
print "#{e.index}\t"
puts "#{e.position}\t"
end
# KV Char first? last? prev next wrapped? index position
# a => 0 [:a, "0"] true false [:b, "1"] false 0 1
# b => 1 [:b, "1"] false false [:a, "0"] [:c, "2"] true 1 2
# c => 2 [:c, "2"] false false [:b, "1"] [:d, "3"] true 2 3
# d => 3 [:d, "3"] false false [:c, "2"] [:e, "4"] true 3 4
# e => 4 [:e, "4"] false false [:d, "3"] [:f, "5"] true 4 5
# f => 5 [:f, "5"] false false [:e, "4"] [:g, "6"] true 5 6
# g => 6 [:g, "6"] false false [:f, "5"] [:h, "7"] true 6 7
# h => 7 [:h, "7"] false false [:g, "6"] [:i, "8"] true 7 8
# i => 8 [:i, "8"] false false [:h, "7"] [:j, "9"] true 8 9
# j => 9 [:j, "9"] false false [:i, "8"] [:k, "10"] true 9 10
# k => 10 [:k, "10"] false false [:j, "9"] [:l, "11"] true 10 11
# l => 11 [:l, "11"] false false [:k, "10"] [:m, "12"] true 11 12
# m => 12 [:m, "12"] false true [:l, "11"] false 12 13
Actual class:
module Enumerable
# your each_with_position method
def each_pos &block
EachWithPosition.each(self, &block)
end
end
class EachWithPosition
attr_reader :index
class << self
def each *a, &b
handler = self.new(*a, :each, &b)
end
end
def initialize collection, method, &block
#index = 0
#item, #prev, #next = nil
#collection = collection
#callback = block
self.send(method)
end
def count
#collection.count
end
alias_method :length, :count
alias_method :size, :count
def rest
count - position
end
def first?
#index == 0
end
def last?
#index == (count - 1)
end
def wrapped?
!first? && !last?
end
alias_method :inner?, :wrapped?
def position
#index + 1
end
def prev
#prev
end
def next
#next
end
def current
#item
end
alias_method :item, :current
alias_method :value, :current
def call
if #callback.arity == 1
#callback.call(self)
else
#callback.call(#item, self)
end
end
def each
#collection.each_cons(2) do |e, n|
#prev = #item
#item = e
#next = n
self.call
#index += 1
# fix cons slice behaviour
if last?
#prev, #item, #next = #item, #next, nil
self.call
#index += 1
end
end
end
end

KISS
arr.each.with_index do |obj, index|
p 'first' if index == 0
p 'last' if index == arr.count-1
end

If you don't mind that the "last" action happens before the stuff in the middle, then this monkey-patch:
class Array
def for_first
return self if empty?
yield(first)
self[1..-1]
end
def for_last
return self if empty?
yield(last)
self[0...-1]
end
end
Allows this:
%w(a b c d).for_first do |e|
p ['first', e]
end.for_last do |e|
p ['last', e]
end.each do |e|
p ['middle', e]
end
# => ["first", "a"]
# => ["last", "d"]
# => ["middle", "b"]
# => ["middle", "c"]

I could not resist :) This is not tuned for performance although i guess it is should not be much slower than most of the other answers here. It's all about the sugar!
class Array
class EachDSL
attr_accessor :idx, :max
def initialize arr
self.max = arr.size
end
def pos
idx + 1
end
def inside? range
range.include? pos
end
def nth? i
pos == i
end
def first?
nth? 1
end
def middle?
not first? and not last?
end
def last?
nth? max
end
def inside range
yield if inside? range
end
def nth i
yield if nth? i
end
def first
yield if first?
end
def middle
yield if middle?
end
def last
yield if last?
end
end
def each2 &block
dsl = EachDSL.new self
each_with_index do |x,i|
dsl.idx = i
dsl.instance_exec x, &block
end
end
end
Example 1:
[1,2,3,4,5].each2 do |x|
puts "#{x} is first" if first?
puts "#{x} is third" if nth? 3
puts "#{x} is middle" if middle?
puts "#{x} is last" if last?
puts
end
# 1 is first
#
# 2 is middle
#
# 3 is third
# 3 is middle
#
# 4 is middle
#
# 5 is last
Example 2:
%w{some short simple words}.each2 do |x|
first do
puts "#{x} is first"
end
inside 2..3 do
puts "#{x} is second or third"
end
middle do
puts "#{x} is middle"
end
last do
puts "#{x} is last"
end
end
# some is first
# short is second or third
# short is middle
# simple is second or third
# simple is middle
# words is last

Partition the array into ranges where elements within each range are supposed to behave different. Map each range thus created to a block.
class PartitionEnumerator
include RangeMaker
def initialize(array)
#array = array
#handlers = {}
end
def add(range, handler)
#handlers[range] = handler
end
def iterate
#handlers.each_pair do |range, handler|
#array[range].each { |value| puts handler.call(value) }
end
end
end
Could create ranges by hand, but these helpers below make it easier:
module RangeMaker
def create_range(s)
last_index = #array.size - 1
indexes = (0..last_index)
return (indexes.first..indexes.first) if s == :first
return (indexes.second..indexes.second_last) if s == :middle
return (indexes.last..indexes.last) if s == :last
end
end
class Range
def second
self.first + 1
end
def second_last
self.last - 1
end
end
Usage:
a = [1, 2, 3, 4, 5, 6]
e = PartitionEnumerator.new(a)
e.add(e.create_range(:first), Proc.new { |x| x + 1 } )
e.add(e.create_range(:middle), Proc.new { |x| x * 10 } )
e.add(e.create_range(:last), Proc.new { |x| x } )
e.iterate

I see a lot of hacks here that are pretty close, but all heavily dependent on the given iterator having a fixed size and NOT being an iterator. I'd like to also propose saving the previous element as you iterate through to know the first/last element that was iterated over.
previous = {}
elements.each do |element|
unless previous.has_key?(:element)
# will only execute the first time
end
# normal each block here
previous[:element] = element
end
# the last element will be stored in previous[:element]

If you know the items in the array are unique (unlike this case), you can do this:
a = [1,2,3,4,5]
a.each_with_index do |x, i|
if x == a.first
print x+1
elsif x == a.last
print x*10
else
print x
end
end

Sometimes a for loop is just your best option
if(array.count > 0)
first= array[0]
#... do something with the first
cx = array.count -2 #so we skip the last record on a 0 based array
for x in 1..cx
middle = array[x]
#... do something to the middle
end
last = array[array.count-1]
#... do something with the last item.
end
I know this question was answered, but this method has no side effects, and doesn't check if the 13th, 14th, 15th.. 10thousandth, 10,001th... record is the first record, or the last.
Previous answers would have failed the assignment in any data structures class.

Related

Perform arithmetic operations on string in Ruby

input: "20+10/5-1*2"
I want to perform arithmetic operations on that string how can I do it without using eval method in ruby?
expected output: 20
While I hesitate to answer an interview question, and I am completely embarrassed by this code, here is one awful way to do it. I made it Ruby-only and avoided Rails helpers because it seemed more of a Ruby task and not a Rails task.
#
# Evaluate a string representation of an arithmetic formula provided only these operations are expected:
# + | Addition
# - | Subtraction
# * | Multiplication
# / | Division
#
# Also assumes only integers are given for numerics.
# Not designed to handle division by zero.
#
# Example input: '20+10/5-1*2'
# Expected output: 20.0
#
def eval_for_interview(string)
add_split = string.split('+')
subtract_split = add_split.map{ |v| v.split('-') }
divide_split = subtract_split.map do |i|
i.map{ |v| v.split('/') }
end
multiply_these = divide_split.map do |i|
i.map do |j|
j.map{ |v| v.split('*') }
end
end
divide_these = multiply_these.each do |i|
i.each do |j|
j.map! do |k, l|
if l == nil
k.to_i
else
k.to_i * l.to_i
end
end
end
end
subtract_these = divide_these.each do |i|
i.map! do |j, k|
if k == nil
j.to_i
else
j.to_f / k.to_f
end
end
end
add_these = subtract_these.map! do |i, j|
if j == nil
i.to_f
else
i.to_f - j.to_f
end
end
add_these.sum
end
Here is some example output:
eval_for_interview('1+1')
=> 2.0
eval_for_interview('1-1')
=> 0.0
eval_for_interview('1*1')
=> 1.0
eval_for_interview('1/1')
=> 1.0
eval_for_interview('1+2-3*4')
=> -9.0
eval_for_interview('1+2-3/4')
=> 2.25
eval_for_interview('1+2*3/4')
=> 2.5
eval_for_interview('1-2*3/4')
=> -0.5
eval_for_interview('20+10/5-1*2')
=> 20.0
eval_for_interview('20+10/5-1*2*4-2/6+12-1-1-1')
=> 31.0

I am writing same if statement 5 times, how can I rewrite this with only 1 if statement

I have following database columns team_a_set_1, team_a_set_2, team_a_set_3, team_a_set_4, team_a_set_5, team_b_set_1, team_b_set_2, team_b_set_3, team_b_set_4, team_b_set_5. I am using these columns to store tennis game result for 5 sets.
Now I am trying to find out who is winner between team a and team b. Who ever wins the max number of sets is winner. My code looks roughly like this
def find_winner
team_a_win_count = 0
team_b_win_count = 0
if team_a_set_1.present? && team_b_set_1.present?
if team_a_set_1 > team_b_set_1
team_a_win_count = team_a_win_count + 1
elsif team_b_set_1 > team_a_set_1
team_b_win_count = team_b_win_count +1
end
end
if team_a_set_2.present? && team_b_set_2.present?
if team_a_set_2 > team_b_set_2
team_a_win_count = team_a_win_count + 1
elsif team_b_set_2 > team_a_set_2
team_b_win_count = team_b_win_count +1
end
end
if team_a_set_3.present? && team_b_set_3.present?
if team_a_set_3 > team_b_set_3
team_a_win_count = team_a_win_count + 1
elsif team_b_set_3 > team_a_set_3
team_b_win_count = team_b_win_count +1
end
end
if team_a_set_4.present? && team_b_set_4.present?
if team_a_set_4 > team_b_set_4
team_a_win_count = team_a_win_count + 1
elsif team_b_set_4 > team_a_set_4
team_b_win_count = team_b_win_count +1
end
end
if team_a_set_5.present? && team_b_set_5.present?
if team_a_set_5 > team_b_set_5
team_a_win_count = team_a_win_count + 1
elsif team_b_set_5 > team_a_set_5
team_b_win_count = team_b_win_count +1
end
end
if team_a_win_count > team_b_win_count
puts 'Team A won'
elsif team_b_win_count > team_a_win_count
puts 'Team B won'
else
puts 'Draw'
end
end
As you can see I am repeating same logic 5 times in if statement. I want to refactor this code so I don't have to repeat same thing 5 times. I tried something like this but it didn't work
1.upto(5) do |n|
if "team_a_set_#{n}".present? && "team_b_set_#{n}".present?
if "team_a_set_#{n}" > "team_b_set_#{n}" # this is not working because it's comparing string
team_a_win_count = team_a_win_count + 1
elsif "team_b_set_#{n}" > "team_a_set_#{n}"
team_b_win_count = team_b_win_count + 1
end
end
end
So how can I refactor this code so that I don't have to write if statement 5 times ?
Update: Based on everyone suggestions I end up re designing my database. Now I am storing sets in Hash. And everything looks nice and clean now.
The answer here is most likely to model the data better.
class Game < ApplicationRecord
belongs_to :player_a, class_name: 'Player'
belongs_to :player_b, class_name: 'Player'
has_many :game_sets
end
# rails g model game_set game:belongs_to player_a_score:integer player_b_score:integer
class GameSet < ApplicationRecord
belongs_to :game
end
This sets up a simple one to many instead of having N number of columns. This lets you simply tally up the rows by selecting aggregates:
Game.select(
'games.*',
'SUM(
SELECT 1 FROM gamesets gs WHERE gs.game_id = games.id AND gs.player_a_score > gs.player_b_score
) AS player_a_wins',
'SUM(
SELECT 1 FROM gamesets gs WHERE gs.game_id = games.id AND gs.player_b_score > gs.player_a_score
) AS player_b_wins',
'SUM(
SELECT 1 FROM gamesets gs WHERE gs.game_id = games.id AND gs.player_b_score = gs.player_a_score
) AS draws'
)
You could also just cache the result on the game_sets table:
class GameSet < ApplicationRecord
belongs_to :game
belongs_to :winner,
class_name: 'Player',
optional: true # nil for draws
end
There is also a long list of more performant ways to get the aggregates such as using FILTER on Postgres or lateral joins. You should also consider saving the results of the game directly on the games table for better read performance.
As I have mentioned before, When you find yourself adding an integer suffix to variable names, think I should have used an array.. Similarly, if you find yourself using string prefixes or suffixes, you should have used a hash.
The way the code is written, it is hard to follow the logic and without knowing any more, it is hard to make a concrete recommendation, but, if you can get the data in the following format:
games = [
{
'a' => [12, 34, 56, 78, 90],
'b' => [21, 43, 65, 87, 9],
},
{
'a' => [1, 2, 3, 4, 5],
'b' => [5, 4, 2, 2, 1],
},
]
games.each_index do |g|
game = games[g]
teams = game.keys
wins = Hash[teams.map {|t| [t, 0]}]
game[teams[0]].each_index do |i|
winner = game[teams[0]][i] > game[teams[1]][i] ? 0 : 1
wins[teams[winner]] += 1
end
puts "Winner of game #{g}: #{teams[wins[teams[0]] > wins[teams[1]] ? 0 : 1]}"
end
You will have the advantage of being able to handle arbitrary numbers of games between arbitrary teams. Also, you won't have to change the code to handle different ways of referring to games/teams.
have following database columns team_a_set_1, team_a_set_2, team_a_set_3, team_a_set_4, team_a_set_5, team_b_set_1, team_b_set_2, team_b_set_3, team_b_set_4, team_b_set_5.
And that really is your problem. The database is not properly designed to actually do this sort of thing. In fact, if the table GAMES had the columns: game, set, team, score, you would not really need to write any Ruby to do this.

Slice into chunks from arranged hash in ruby

I have hash which keys are in sorted order and hash size is more than 1000. How can I divide hash in chunks based on range.
Example :-
h_main = {"1" => "a", "2" => "b", "9" => "c", .............. "880" => "xx", "996" => "xyz", "998" => "lll", "1050" => "mnx"}
I have to divide above hash into sorter hash chunks based on range :-
h_result = {"1-100" => {"1" => "a", "2" => "b", "9" => "c" ..... "99" => "re"},
"101-200" => {}
....
....
"900-1000" => {"996" => "xyz", "998" => "lll"},
"1000-1100" => {"1050" => "mnx"}
}
I can do by applying each loop and then can add condition to merge key-value pair in respective hash but that's lengthy process.
Please help to provide optimize solution thanks in advance.
def doit(h, group_size)
h.keys.
slice_when { |k1,k2| k2.to_i/group_size > k1.to_i/group_size }.
each_with_object({}) do |key_group,g|
start_range = group_size * (key_group.first.to_i/group_size)
g["%d-%d" % [start_range, start_range+group_size-1]] = h.slice(*key_group)
end
end
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
doit(h, 10)
#=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
doit(h, 15)
#=> {"0-14"=>{"11"=>"a", "12"=>"b"},
# "15-29"=>{"19"=>"c", "28"=>"xx", "29"=>"xyz"},
# "30-44"=>{"42"=>"lll"}, "45-59"=>{"47"=>"mnx"}}
doit(h, 20)
#=> {"0-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-39"=>{"28"=>"xx", "29"=>"xyz"},
# "40-59"=>{"42"=>"lll", "47"=>"mnx"}}
See Enumerable#slice_when and Hash#slice.
The steps are as follows.
group_size = 10
a = h.keys
#=> ["11", "12", "19", "28", "29", "42", "47", "74", "76"]
b = a.slice_when { |k1,k2| k2.to_i/group_size > k1.to_i/group_size }
#=> #<Enumerator: #<Enumerator::Generator:0x000056fa312199b8>:each>
We can see the elements that will be generated by this enumerator and passed to the block by converting it to an array.
b.to_a
#=> [["11", "12", "19"], ["28", "29"], ["42", "47"]]
Lastly,
b.each_with_object({}) do |key_group,g|
start_range = group_size * (key_group.first.to_i/group_size)
g["%d-%d" % [start_range, start_range+group_size-1]] =
h.slice(*key_group)
end
#=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
Note that:
e = b.each_with_object({})
#=> #<Enumerator: #<Enumerator:
# #<Enumerator::Generator:0x0000560a0fc12658>:each>:
# each_with_object({})>
e.to_a
#=> [[["11", "12", "19"], {}], [["28", "29"], {}], [["42", "47"], {}]]
The last step begins by the enumerator e generating a value and passing it to the block, after which the block variables are assigned values using array decomposition.
key_group,g = e.next
#=> [["11", "12", "19"], {}]
key_group
#=> ["11", "12", "19"]
g #=> {}
The block calculations are then performed.
start_range = group_size * (key_group.first.to_i/group_size)
#=> 10 * (11/10) => 10
g["%d-%d" % [start_range, start_range+group_size-1]] =
h.slice(*key_group)
#=> g["%d-%d" % [10, 10+10-1]] = h.slice("11", "12", "19")
#=> g["10-19"] = {"11"=>"a", "12"=>"b", "19"=>"c"}
#=> {"11"=>"a", "12"=>"b", "19"=>"c"}
Now,
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}
The enumerator e then generates another element, passes it to the block and the block variables are assigned.
key_group,g = e.next
#=> [["28", "29"], {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}]
key_group
#=> ["28", "29"]
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}
Notice that the value of g has been updated. The block calculations now proceed as before, after which:
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}
Then
key_group,g = e.next
#=> [["42", "47"], {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}]
key_group
#=> ["42", "47"]
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}
After the the block calculations are performed:
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
Then an exception is raised:
key_group,g = e.next
#=> StopIteration (iteration reached an end)
causing the enumerator to return g.
Since your Hash is already sorted by the keys things like slice_when as proposed by #CarySwoveland would probably have an efficiency benefit; however were the Hash to be, or become, unsorted the following solutions would be unaffected as far as grouping goes.
Using a lambda to group the keys:
def group_numeric_range(h, group_size)
groups = ->(n) do
g = n.to_i / group_size
"#{g * group_size + 1}-#{g * group_size + group_size}"
end
h.group_by do |k,_|
groups.(k)
end.transform_values(&:to_h)
end
Example:
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
group_numeric_range(h,10)
#=> {"11-20"=>{"11"=>"a", "12"=>"b", "19"=>"c"}, "21-30"=>{"28"=>"xx", "29"=>"xyz"}, "41-50"=>{"42"=>"lll", "47"=>"mnx"}}
Alternative:
def group_numeric_range(h, group_size)
groups = ->(n) do
g = n.to_i / group_size
"#{g * group_size + 1}-#{g * group_size + group_size}"
end
h.each_with_object(Hash.new{|h,k| h[k] = {}}) do |(k,v),obj|
obj[groups.(k)].merge!(k=>v)
end
end
Update
Another option would be to build an Array of the groups and then select the index for grouping (I added outputting empty ranges too) e.g.
def group_numeric_range(h, group_size)
groups = ((h.keys.max.to_i / group_size) + 1).times.map do |g|
["#{g * group_size + 1}-#{g * group_size + group_size}",{}]
end
h.each_with_object(groups) do |(k,v),obj|
obj[k.to_i / group_size].last.merge!(k=>v)
end.to_h
end
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
group_numeric_range(h,10)
#=> {"1-10"=>{}, "11-20"=>{"11"=>"a", "12"=>"b", "19"=>"c"}, "21-30"=>{"28"=>"xx", "29"=>"xyz"}, "31-40"=>{}, "41-50"=>{"42"=>"lll", "47"=>"mnx"}}
This is how I would do it, but unsure what you have done already.
Creating a large hash:
hash = {}
1000.times do |x|
hash[x] = "hi!"
end
slicing by range:
hash.slice(*(1 .. 100))
=> # keys from 1 .. 100
producing desired hash:
def split_hash(range, hash)
end_result = {}
(hash.count / range).times do |x|
range_start = (range * x) + 1
range_end = range_start + range
end_result["#{range_start}-#{range_end}"] = hash.slice(*(range_start .. range_end)) # slice returns a hash which was desired. If you can convert to an array you gain range access as slice still iterates but is performative. if you are OK with an array: hash.to_a[range_start .. range_end]
end
end_result
end

How to not allowed taking in a number into a variable in Ruby

Given a string S of length N that is indexed from 0 to N-1 , print its even-indexed and odd-indexed characters as space-separated strings on a single line (see the Sample below for more detail).
Sample Input:
2
Hacker
Rank
Sample output:
Hce akr
Rn ak
explanation:
S="Hacker" S[0]="H", S[1]="a", S[2]= "c", S[3]="k", S[4]="e", S[5]="r"
However, with the following code I haven't been able to complete the challenge. How do I constraint taken input as an integer?
S = gets.chomp.chars.to_a
for i in 0..S.length
if i%2 == 0
s1 = S[i]
else
s2 = S[i]
end
end
puts s1.to_s + " " + s2.to_s
Code
def doit(str)
str.each_char.each_slice(2).with_object(['','']) do |(c_even, c_odd), (s_even, s_odd)|
s_even << c_even
s_odd << c_odd unless c_odd.nil?
end.join(' ')
end
Examples
doit "abracadabra"
#=> "arcdba baaar"
doit "Jack be nimble, Jack be quick"
#=> "Jc enml,Jc eqik akb ibe akb uc"
Explanation
For
str = "abracadabra"
enum0 = str.each_char
#=> #<Enumerator: "abracadabra":each_char>
We can convert the enumerator enum0 to an array to see what values it will generate:
enum0.to_a
#=> ["a", "b", "r", "a", "c", "a", "d", "a", "b", "r", "a"]
enum1 = enum0.each_slice(2)
#=> #<Enumerator: #<Enumerator: "abracadabra":each_char>:each_slice(2)>
enum1.to_a
#=> [["a", "b"], ["r", "a"], ["c", "a"], ["d", "a"], ["b", "r"], ["a"]]
enum2 = enum1.with_object(['',''])
#=> #<Enumerator: #<Enumerator: #<Enumerator: "abracadabra":each_char>:each_slice(2)>
# :with_object(["", ""])>
enum2.to_a
#=> [[["a", "b"], ["", ""]], [["r", "a"], ["", ""]], [["c", "a"], ["", ""]],
# [["d", "a"], ["", ""]], [["b", "r"], ["", ""]], [["a"], ["", ""]]]
If you examine the return values obtained when constructing enum1 and enum2, you will see that they can be thought of as "compound" enunerators.
The first element of enum2 is generated and passed to the block, assigning values to the four block variables1:
(c_even, c_odd), (s_even, s_odd) = enum2.next
#=> [["a", "b"], ["", ""]]
c_even #=> "a"
c_odd #=> "b"
s_even #=> ""
s_odd #=> ""
The block calculation is now performed.
s_even << c_even
#=> "a"
s_odd << c_odd unless c_odd.nil?
# s_odd << c_odd unless false
# s_odd << c_odd
#=> "b"
The return values "a" and "b" are the new values of s_even and s_odd, respectively.
Now the next element of enum_2 is generated, passed to the block and the block calculations are performed:
(c_even, c_odd), (s_even, s_odd) = enum2.next
#=> [["r", "a"], ["a", "b"]]
s_even << c_even
# "a" << "r"
#=> "ar"
s_odd << c_odd unless c_odd.nil?
# s_odd << c_odd unless "a".nil?
# s_odd << c_odd
#=> "ba"
Calculations continue in this way until the last value of enum2 is generated: ["a"]. This has the effect of assigning nil to c_odd, so the second line of the block is not executed2. Lastly, the array of two strings is joined with a separating space.
Another way
def doit(str)
str.each_char.with_index.with_object(' ') { |(c,i),s|
s.insert(i.even? ? s.index(' ') : s.size, c) }
end
doit "abracadabra"
#=> "arcdba baaar"
1 The following expression employs parallel assignment (sometimes called multiple assignment) and disambiguation (sometimes referred to as decomposition) to assign values to the variables.
2 The second line could alternatively be written s_odd << c_odd.to_s or s_odd << c_odd || ''.
First input should be treated as an integer (namely, the amount of following strings to come):
amount = gets.to_i
Now we are to get amount strings and do our job (using Enumerable#partition):
amount.times do
input = gets.chomp
puts (input.split('').partition.with_index do |_, i|
i.even?
end.map(&:join).join(' '))
end
Note that instead of inspecting each character's index, you could also use scan:
'Hacker'.scan(/(.)(.?)/) #=> [["H", "a"], ["c", "k"], ["e", "r"]]
.transpose #=> [["H", "c", "e"], ["a", "k", "r"]]
.map(&:join) #=> ["Hce", "akr"]
.join(' ') #=> "Hce akr"
Or, using temporary variables:
s1 = ''
s2 = ''
'Hacker'.scan(/(.)(.?)/) { |a, b| s1 << a ; s2 << b }
puts "#{s1} #{s2}"
Here is the basic algorithm that would take O(n).
tests = gets.to_i
# run the loop for number of tests given
tests.times do
string = gets.chomp # sanitize string from input, i.e. removes \n \r
s_length = string.length # String length N
new_string = " " * s_length # create of string of length N
even_index = 0 # because evens are going to come first
odd_index = s_length - (s_length/2) + 1 # odds are going to start where even numbers end + 1
0.upto(s_length-1) do |i|
if i%2 == 0
new_string[even_index] = string[i]
even_index += 1
elsif
new_string[odd_index] = string[i]
odd_index += 1
end
end
puts new_string
end
Benchmark:
require 'benchmark'
def using_ugly_algo(tests, string)
# tests = gets.to_i
tests.times do
string = string
s_length = string.length # String length N
new_string = " " * s_length # creat of string of length N
even_index = 0
odd_index = s_length - (s_length/2) + 1
0.upto(s_length-1) do |i|
if i%2 == 0
new_string[even_index] = string[i]
even_index += 1
elsif
new_string[odd_index] = string[i]
odd_index += 1
end
end
# new_string
end
end
def with_partition(amount, string)
amount.times do
input = string
(input.split('').partition.with_index do |_, i|
i.even?
end.map(&:join).join(' '))
end
end
n = 10_000
string = (0...500).map { ('a'..'z').to_a[rand(26)] }.join
Benchmark.bmbm(100) do |x|
x.report("using_ugly_algo "){ n.times { using_ugly_algo(5, string) } }
x.report("with_partition "){ n.times { with_partition(5, string) } }
end
Report:
Rehearsal ----------------------------------------------------------------------------------------------------------------------------------------
using_ugly_algo 13.790000 0.030000 13.820000 ( 13.843560)
with_partition 16.790000 0.030000 16.820000 ( 16.830992)
------------------------------------------------------------------------------------------------------------------------------ total: 30.640000sec
user system total real
using_ugly_algo 13.930000 0.060000 13.990000 ( 14.070378)
with_partition 18.640000 0.210000 18.850000 ( 19.392816)
Well, the problem you are having is, if I am using the right term, a usage error. Your code is setting s1 and s2 to whatever the last checked letter is instead of concatenating. Modifying you code, I suppose what you are looking for is something like this:
S = gets.chomp.chars.to_a
s1 = ""
s2 = ""
for i in 0...S.length
if i%2 == 0
s1.concat(S[i])
else
s2.concat(S[i])
end
end
puts s1.to_s + " " + s2.to_s

How to create a nested loop with Ruby the "Right Way!"?

I'm in the process of learning Ruby, taking a Berkeley's MOOC, and, in some of these MOOC's homework we have an exercise that says:
Define a method sum_to_n? which takes an array of integers and an
additional integer, n, as arguments and returns true if any two
elements in the array of integers sum to n. An empty array should sum
to zero by definition.
I already created two methods that can do the job, but I'm not comfortable with any of them because I think they are not written in the Ruby Way. I hope some of you can help me to learn which would be the right way!
The first method I made uses the each method for both iterations, but what I don't like about this method is that every number is summed with every other number, even with the same number, doing something like this:
arr[1, 2, 3, 4] => 1+1, 1+2, 1+3, 1+4, 2+1, 2+2, 2+3, 2+4, 3+1, 3+2... 4+3, 4+4
As you can see, there's a lot of repeated sums, and I don't want that.
This is the code:
def sum_to_n?(arr, n)
arr.each {|x| arr.each {|y| return true if x + y == n && x != y}}
return true if n == 0 && arr.length == 0
return false
end
With the other method I got what I wanted, just a few sums without repeating any of them or even summing the same numbers, but it looks HORRIBLE, and I'm pretty sure someone would love to kill me for doing it this way, but the method does a great job as you can see:
arr[1, 2, 3, 4] => 1+2, 1+3, 1+4, 2+3, 2+4, 3+4
This is the code:
def sum_to_n?(arr, n)
for i in 0..arr.length - 1
k = i + 1
for k in k..arr.length - 1
sum = arr[i] + arr[k]
if sum == n
return true
end
end
end
return true if n == 0 && arr.length == 0
return false
end
Well, I hope you guys have fun doing a better and prettier method as I did trying.
Thank you for your help.
I'd write it like this:
def sum_to_n?(arr, n)
return true if arr.empty? && n.zero?
arr.combination(2).any? {|a, b| a + b == n }
end
That seems to be a pretty Rubyish solution.
I came across this on CodeWars. The accepted answer sure does look very Rubyish, but that is at the cost of performance. Calling arr.combination(2) results in a lot of combinations, it'd be simpler to go over the array element by element and search whether the 'complement' sum - element exists. Here's how that'd look like -
def sum_to_n?(arr, n)
(arr.empty? and n.zero?) or arr.any? { |x| arr.include?(n - x) }
end
Beside #jorg-w-mittag's answer. I found another solution using 'permutation'.
https://stackoverflow.com/a/19351660/66493
def sum_to_n?(arr, n)
(arr.empty? && n.zero?) || arr.permutation(2).any? { |a, b| a + b == n }
end
I didn't know about permutation before.
Still like #jorg-w-mittag answer because its more readable.
This one will do it in O(n.log(n)) rather than O(n²):
a = 1, 2, 3, 4
class Array
def sum_to? n
unless empty?
false.tap {
i, j, sorted = 0, size - 1, sort
loop do
break if i == j
a, b = sorted[i], sorted[j]
sum = a + b
return a, b if sum == n
sum < n ? i += 1 : j -= 1
end
}
end
end
end
a.sum_to? 7 #=> [3, 4]
I had a thought that the beginning of any answer to this question should probably start with pruning the array for superfluous data:
Can't use this:
arr.select! { |e| e <= n } # may be negative values
But this might help:
arr.sort!
while arr[0] + arr[-1] > n # while smallest and largest value > n
arr.delete_at(-1) # delete largest vaue
end
i wonder why no answers here using hash ?
def sum_to_n?(arr, n)
return true if arr.empty? && n.zero?
h = {}
arr.any? { |x| complement = h[n-x]; h[x] = true; complement }
end
puts sum_to_n?([1,2,3,4,5,7], 6) # true
puts sum_to_n?([6,2,3,5,7,9], 6) # false
puts sum_to_n?([3,4,5,3], 6) # true
puts sum_to_n?([3,4,5,7], 6) # false
puts sum_to_n?([], 6) # false
puts sum_to_n?([], 0) # true
I like rohitpaulk's answer but it fails when n doubles x. We should remove x from the array before sending include? n - x.
def sum_to_n?(arr, n)
return true if arr.empty? && n.zero?
arr.any? { |x| arr.tap { arr.delete_at arr.index x }.include? n - x }
end
Lam Phan's answer using a hash is the best

Resources