I am trying to sum array of array and get average at the same time. The original data is in the form of JSON. I have to parse my data to array of array in order to render the graph. The graph does not accept array of hash.
I first convert the output to JSON using the definition below.
ActiveSupport::JSON.decode(#output.first(10).to_json)
And the result of the above action is shown below.
output =
[{"name"=>"aaa", "job"=>"a", "pay"=> 2, ... },
{"name"=>"zzz", "job"=>"a", "pay"=> 4, ... },
{"name"=>"xxx", "job"=>"a", "pay"=> 6, ... },
{"name"=>"yyy", "job"=>"a", "pay"=> 8, ... },
{"name"=>"aaa", "job"=>"b", "pay"=> 2, ... },
{"name"=>"zzz", "job"=>"b", "pay"=> 4, ... },
{"name"=>"xxx", "job"=>"b", "pay"=> 6, ... },
{"name"=>"yyy", "job"=>"b", "pay"=> 10, ... },
]
Then I retrieved the job and pay by converting to array of array.
ActiveSupport::JSON.decode(output.to_json).each { |h|
a << [h['job'], h['pay']]
}
The result of the above operation is as below.
a = [["a", 2], ["a", 4], ["a", 6], ["a", 8],
["b", 2], ["b", 4], ["b", 6], ["b", 10]]
The code below will give me the sum of each element in the form of array of array.
a.inject({}) { |h,(job, data)| h[job] ||= 0; h[job] += data; h }.to_a
And the result is as below
[["a", 20], ["b", 22]]
However, I am trying to get the average of the array. The expected output is as below.
[["a", 5], ["b", 5.5]]
I can count how many elements in an array and divide the sum array by the count array. I was wondering if there is an easier and more efficient way to get the average.
output = [
{"name"=>"aaa", "job"=>"a", "pay"=> 2 },
{"name"=>"zzz", "job"=>"a", "pay"=> 4 },
{"name"=>"xxx", "job"=>"a", "pay"=> 6 },
{"name"=>"yyy", "job"=>"a", "pay"=> 8 },
{"name"=>"aaa", "job"=>"b", "pay"=> 2 },
{"name"=>"zzz", "job"=>"b", "pay"=> 4 },
{"name"=>"xxx", "job"=>"b", "pay"=> 6 },
{"name"=>"yyy", "job"=>"b", "pay"=> 10 },
]
output.group_by { |obj| obj['job'] }.map do |key, list|
[key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end
The group_by method will transform your list into a hash with the following structure:
{"a"=>[{"name"=>"aaa", "job"=>"a", "pay"=>2}, ...], "b"=>[{"name"=>"aaa", "job"=>"b", ...]}
After that, for each pair of that hash, we want to calculate the mean of its 'pay' values, and return a pair [key, mean]. We use a map for that, returning a pair with:
They key itself ("a" or "b").
The mean of the values. Note that the values list has the form of a list of hashes. To retrieve the values, we need to extract the last element of each pair; that's what list.map { |obj| obj['pay'] } is used for. Finally, calculate the mean by suming all elements with .reduce(:+) and dividing them by the list size as a float.
Not the most efficient solution, but it's practical.
Comparing the answer with #EricDuminil's, here's a benchmark with a list of size 8.000.000:
def Wikiti(output)
output.group_by { |obj| obj['job'] }.map do |key, list|
[key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end
end
def EricDuminil(output)
count_and_sum = output.each_with_object(Hash.new([0, 0])) do |hash, mem|
job = hash['job']
count, sum = mem[job]
mem[job] = count + 1, sum + hash['pay']
end
result = count_and_sum.map do |job, (count, sum)|
[job, sum / count.to_f]
end
end
require 'benchmark'
Benchmark.bm do |x|
x.report('Wikiti') { Wikiti(output) }
x.report('EricDuminil') { EricDuminil(output) }
end
user system total real
Wikiti 4.100000 0.020000 4.120000 ( 4.130373)
EricDuminil 4.250000 0.000000 4.250000 ( 4.272685)
This method should be reasonably efficient. It creates a temporary hash with job name as key and [count, sum] as value:
output = [{ 'name' => 'aaa', 'job' => 'a', 'pay' => 2 },
{ 'name' => 'zzz', 'job' => 'a', 'pay' => 4 },
{ 'name' => 'xxx', 'job' => 'a', 'pay' => 6 },
{ 'name' => 'yyy', 'job' => 'a', 'pay' => 8 },
{ 'name' => 'aaa', 'job' => 'b', 'pay' => 2 },
{ 'name' => 'zzz', 'job' => 'b', 'pay' => 4 },
{ 'name' => 'xxx', 'job' => 'b', 'pay' => 6 },
{ 'name' => 'yyy', 'job' => 'b', 'pay' => 10 }]
count_and_sum = output.each_with_object(Hash.new([0, 0])) do |hash, mem|
job = hash['job']
count, sum = mem[job]
mem[job] = count + 1, sum + hash['pay']
end
#=> {"a"=>[4, 20], "b"=>[4, 22]}
result = count_and_sum.map do |job, (count, sum)|
[job, sum / count.to_f]
end
#=> [["a", 5.0], ["b", 5.5]]
It requires 2 passes, but the created objects aren't big. In comparison, calling group_by on a huge array of hashes isn't very efficient.
How about this (Single pass iterative average calculation)
accumulator = Hash.new {|h,k| h[k] = Hash.new(0)}
a.each_with_object(accumulator) do |(k,v),obj|
obj[k][:count] += 1
obj[k][:sum] += v
obj[k][:average] = (obj[k][:sum] / obj[k][:count].to_f)
end
#=> {"a"=>{:count=>4, :sum=>20, :average=>5.0},
# "b"=>{:count=>4, :sum=>22, :average=>5.5}}
Obviously average is just recalculated on every iteration but since you asked for them at the same time this is probably as close as you are going to get.
Using your "output" instead looks like
output.each_with_object(accumulator) do |h,obj|
key = h['job']
obj[key][:count] += 1
obj[key][:sum] += h['pay']
obj[key][:average] = (obj[key][:sum] / obj[key][:count].to_f)
end
#=> {"a"=>{:count=>4, :sum=>20, :average=>5.0},
# "b"=>{:count=>4, :sum=>22, :average=>5.5}}
as Sara Tibbetts comment suggests, my first step would be to convert it like this
new_a = a.reduce({}){ |memo, item| memo[item[0]] ||= []; memo[item[0]] << item[1]; memo}
which puts it in this format
{a: [2, 4, 6, 8], b: [2, 4, 6, 20]}
you can then use slice to filter the keys you want
new_a.slice!(key1, key2, ...)
Then do another pass through to do get the final format
new_a.reduce([]) do |memo, (k,v)|
avg = v.inject{ |sum, el| sum + el }.to_f / v.size
memo << [k,avg]
memo
end
I elected to use Enumerable#each_with_object with the object being an array of two hashes, the first to compute totals, the second to count the number of numbers that are totalled. Each hash is defined Hash.new(0), zero being the default value. See Hash::new for a fuller explanation, In short, if a hash defined h = Hash.new(0) does not have a key k, h[k] returns 0. (h is not modified.) h[k] += 1 expands to h[k] = h[k] + 1. If h does not have a key k, h[k] on the right of the equality returns 0.1
output =
[{"name"=>"aaa", "job"=>"a", "pay"=> 2},
{"name"=>"zzz", "job"=>"a", "pay"=> 4},
{"name"=>"xxx", "job"=>"a", "pay"=> 6},
{"name"=>"yyy", "job"=>"a", "pay"=> 8},
{"name"=>"aaa", "job"=>"b", "pay"=> 2},
{"name"=>"zzz", "job"=>"b", "pay"=> 4},
{"name"=>"xxx", "job"=>"b", "pay"=> 6},
{"name"=>"yyy", "job"=>"b", "pay"=>10}
]
htot, hnbr = output.each_with_object([Hash.new(0), Hash.new(0)]) do |f,(g,h)|
s = f["job"]
g[s] += f["pay"]
h[s] += 1
end
htot.merge(hnbr) { |k,o,n| o.to_f/n }.to_a
#=> [["a", 5.0], ["b", 5.5]]
If .to_a at the end is dropped the the hash {"a"=>5.0, "b"=>5.5} is returned. The OP might find that more useful than the array.
I've used the form of Hash#merge that uses a block to determine the values of keys that are present in both hashes being merged.
Note that htot={"a"=>20, "b"=>22} and hnbr=>{"a"=>4, "b"=>4}.
1 If the reader is wondering why h[k] on the left of = doesn't return zero as well, it's a different method: Hash#[]= versus Hash#[]
Related
I am looking to graph multiple series in a highcharts graph. I have the following two variables
first = {5 => [dates in here], 6 => [dates in here], etc}
second = {4 => [dates in here], 5 => [dates in here], etc}
The keys are the number associated with the months (4, April, 5, May, etc.)
The problem I am running into is that The two hashes may not always have the same corresponding months. So when I am graphing the data the first[5] is graphing next to second[4], and first[6] is graphing next to second[5] etc.
How can I standardize the two variables so that they always contain the same keys even if it would look something like this:
first = {4 => [no data], 5 => [dates in here], 6 => [dates in here], etc}
second = {4 => [dates in here], 5 => [dates in here], 6 => [no data], etc}
first_keys = first.keys
second_keys = second.keys
keys = first_keys + second_keys
keys.uniq.each do |key|
first[key] = [] if first[key].nil?
second[key] = [] if second[key].nil?
end
There's a lot of ways to do this as it's rather open-ended, but you could create a common keys array from the two hashes and iterate over that assigning nil to the hash at that key. I would do it specifically like this
keys = (first.keys + second.keys).uniq
keys.each do |key|
first[key] ||= nil
second[key] ||= nil
end
As you are graphing the data, presumably with months on the x-axis and days on the y-axis, I think it would be most convenient to iterate over a range of months that provides a minimal cover of the keys (months) contained in both hashes.
data = [{ 5=>[3, 5, 11, 24], 6=>[1, 7, 13, 30], 2=>[4, 13, 18, 29] },
{ 4=>[6, 9, 19, 26], 5=>[4, 8, 11, 22], 1=>[1, 19, 22, 24] }]
month_range = Range.new(*data.reduce([]) { |arr, h| arr | h.keys }.minmax)
#=> 1..6
Note
a = data.reduce([]) { |arr, h| arr | h.keys }
#=> [5, 6, 2, 4, 1]
b = a.minmax
#=> [1, 6]
Range.new(*b)
#=> Range.new(*[1, 6]) => Range.new(1, 6) => 1..6
I've represented the input as an arbitrary array of hashes, should there be more than two.
For graphing, simply iterate over month_range, then for each month m iterate over the elements h of data (hashes) for which h has a key m, and plot the days in h[m], if any (not "no data").
month_range.each do |m|
data.each do |h|
<plot h[m]> if h.has_key?(m) && h[m].any?
end
end
I have a list of 10 items -- it is an array of hashes.
[{ id: 1, name: 'one'}, { id: 2, name: 'two' } .. { id: 10, name: 'ten' }]
I also have a random number of containers -- let's say 3, in this case. These containers are hashes with array values.
{ one: [], two: [], three: [] }
What I want to do, is iterate over the containers and drop 2 items at a time resulting in:
{
one: [{id:1}, {id:2}, {id:7}, {id:8}],
two: [{id:3}, {id:4}, {id:9}, {id:10}],
three: [{id:5}, {id:6}]
}
Also, if the item list is an odd number (11), the last item is still dropped into the next container.
{
one: [{id:1}, {id:2}, {id:7}, {id:8}],
two: [{id:3}, {id:4}, {id:9}, {id:10}],
three: [{id:5}, {id:6}, {id:11}]
}
note: the hashes are snipped here so it's easier to read.
My solution is something like this: (simplified)
x = 10
containers = { one: [], two: [], three: [] }
until x < 1 do
containers.each do |c|
c << 'x'
c << 'x'
end
x -= 2
end
puts containers
I'm trying to wrap my head around how I can achieve this but I can't seem to get it to work.
Round-robin pair distribution into three bins:
bins = 3
array = 10.times.map { |i| i + 1 }
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array.
each_slice(2). # divide into pairs
group_by. # group into bins
with_index { |p, i| i % bins }. # round-robin style
values. # get rid of bin indices
each(&:flatten!) # join pairs in each bin
Completely different approach, stuffing bins in order:
base_size, bins_with_extra = (array.size / 2).divmod(bins)
pos = 0
bins.times.map { |i|
length = 2 * (base_size + (i < bins_with_extra ? 1 : 0)) # how much in this bin?
array[pos, length].tap { pos += length } # extract and advance
}
# => [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
If you absolutely need to have this in a hash,
Hash[%i(one two three).zip(binned_array)]
# => {:one=>[1, 2, 7, 8], :two=>[3, 4, 9, 10], :three=>[5, 6]}
The lovely (but likely not as performant) solution hinted at by Stefan Pochmann:
bins.times.with_object(array.to_enum).map { |i, e|
Array.new(2 * (base_size + (i < bins_with_extra ? 1 : 0))) { e.next }
}
This is just to show a different approach (and I would probably not use this one myself).
Given an array of items and the containers hash:
items = (1..10).to_a
containers = { one: [], two: [], three: [] }
You could dup the array (in order not to modify the original one) and build an enumerator that cycles each_value in the hash:
array = items.dup
enum = containers.each_value.cycle
Using the above, you can shift 2 items off the array and push them to the next container until the array is emtpy?:
enum.next.push(*array.shift(2)) until array.empty?
Result:
containers
#=> {:one=>[1, 2, 7, 8], :two=>[3, 4, 9, 10], :three=>[5, 6]}
You can use Enumerable#each_slice to iterate over a range from 0 to 10 in 3s and then append to an array of arrays:
containers = [
[],
[],
[]
]
(1...10).each_slice(3) do |slice|
containers[0] << slice[0]
containers[1] << slice[1]
containers[2] << slice[2]
end
p containers
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
How can I return the total scores, strokes and rounds from the following array?
players = [{"Angel Cabrera"=>{"score"=>2, "strokes"=>146, "rounds"=>3}},
{"Jason Day"=>{"score"=>1, "strokes"=>145, "rounds"=>3}},
{"Bryson DeChambeau"=>{"score"=>0, "strokes"=>144, "rounds"=>3}},
{"Sergio Garcia"=>{"score"=>0, "strokes"=>144, "rounds"=>3}},
{"Ian Poulter"=>{"score"=>5, "strokes"=>162, "rounds"=>3}},
{"Vijay Singh"=>nil},
{"Jordan Spieth"=>{"score"=>-4, "strokes"=>140, "rounds"=>3}}]
I can get the strokes by doing the following but I know that isn't the best way to do it.
players.each do |x|
x.values()[0]["strokes"]
end
How can I return the sum of the strokes given the array above?
Here are three ways of doing that.
Use the form of Hash#update that employs a block to determine the values of keys that are present in both hashes being merged
players.map { |g| g.first.last }.
compact.
each_with_object({}) { |g,h| h.update(g) { |_,o,v| o+v } }
#=> {"score"=>4, "strokes"=>881, "rounds"=>18}
The steps:
a = players.map { |g| g.first.last }
#=> [{"score"=> 2, "strokes"=>146, "rounds"=>3},
# {"score"=> 1, "strokes"=>145, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 5, "strokes"=>162, "rounds"=>3},
# nil,
# {"score"=>-4, "strokes"=>140, "rounds"=>3}]
b = a.compact
#=> [{"score"=> 2, "strokes"=>146, "rounds"=>3},
# {"score"=> 1, "strokes"=>145, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 5, "strokes"=>162, "rounds"=>3},
# {"score"=>-4, "strokes"=>140, "rounds"=>3}]
b.each_with_object({}) { |g,h| h.update(g) { |_,o,v| o+v } }
#=> {"score"=>4, "strokes"=>881, "rounds"=>18}
Here, Hash#update (aka merge!) uses the block { |_,o,v| o+v }) to determine the values of keys that are present in both hashes. The first block variable (which is not used, and therefore can be represented by the local variable _) is the key, the second (o, for "old") is the value of the key in h and the third (n, for "new") is the value of the key in g.
Use a counting hash
players.map { |g| g.first.last }.
compact.
each_with_object(Hash.new(0)) { |g,h| g.keys.each { |k| h[k] += g[k] } }
Hash.new(0) creates an empty hash with a default value of zero, represented by the block variable g. This means that if a hash h does not have a key k, h[k] returns the default value (but does not alter the hash). h[k] += g[k] above expands to:
h[k] = h[k] + g[k]
If h does not have a key k, h[k] on the right side is therefore replaced by 0.
Sum values and then convert to a hash
If you are using Ruby v1.9+ and the keys are guaranteed to have the same order in each hash, a third way it could be done is as follows:
["scores", "strokes", "rounds"].zip(
players.map { |g| g.first.last }.
compact.
map(&:values).
transpose.
map { |arr| arr.reduce(:+) }
).to_h
#=> {"scores"=>4, "strokes"=>881, "rounds"=>18}
The steps (starting from b above) are:
c = b.map(&:values)
#=> [[ 2, 146, 3],
# [ 1, 145, 3],
# [ 0, 144, 3],
# [ 0, 144, 3],
# [ 5, 162, 3],
# [-4, 140, 3]]
d = c.transpose
#=> [[ 2, 1, 0, 0, 5, -4],
# [146, 145, 144, 144, 162, 140],
# [ 3, 3, 3, 3, 3, 3]]
totals = d.map { |arr| arr.reduce(:+) }
#=> [4, 881, 18]
e = ["scores", "strokes", "rounds"].zip(totals)
#=> [["scores", 4], ["strokes", 881], ["rounds", 18]]
e.to_h
#=> {"scores"=>4, "strokes"=>881, "rounds"=>18}
Use this code:
#total= 0
players.each do |x|
a= x.values[0]
if a.class == Hash
#total += a["strokes"]
end
end
puts #total
I have an array containing values and arrays like below:
arr = [
[ 0, [ [22,3],[23,5] ] ],
[ 0, [ [22,1],[23,2] ] ],
[ 1, [ [22,4],[23,4] ] ],
[ 1, [ [22,2],[23,4] ] ]
]
I want to calculate the average based on first two elements and want to have a result set either in hash or array as below:
result = {
22 => [(3+1)/2, (4+2)/2],
23 => [(5+2)/2, (4+4)/2]
}
where for example:
key is 22 and value is an array containing average of third elements in the input array grouped by the first element 3 and 1, 4 and 2 and sorted by the first element 0 and 1
How the array is created
Maybe it might be helpful to mention about my logic.
The array is obtained by the following code out of my ActiveRecord objects:
arr = u.feedbacks.map{|f| [f.week,
f.answers.map{|a| [a.question.id, a.name.to_i]}]}
where models are associated as below:
feedback belongs_to :user
feedback has_and_belongs_to_many :answers
answer belongs_to :question
For each question I wanted to create an array containing average of answers grouped by the feedback week.
With a bit of debugging, the following should help get much faster results:
Answer.
joins(:question, :feedbacks). # assuming that answer has_many feedbacks
group(["questions.id", "feedbacks.week"]). # assuming week is integer column
average("CAST(answers.name AS INT)"). # assuming that name is string-y column
each_with_object({}) do |(keys, average), hash|
question_id, week = keys
hash[question_id] ||= []
hash[question_id][week] = average
end
If you want to keep things the way they are (not advised), then one working (albeit hard-to-follow) solution is this:
arr = [
[0, [[22, 3], [23, 5]]],
[0, [[22, 1], [23, 2]]],
[1, [[22, 4], [23, 4]]],
[1, [[22, 2], [23, 4]]]
]
arr.each_with_object({}) do |(a, b), hash|
c, d, e, f = b.flatten
# for first row this will be c, d, e, f = 22, 3, 23, 5
hash[c] ||= []
hash[c][a] ||= []
hash[c][a] << d
hash[e] ||= []
hash[e][a] ||= []
hash[e][a] << f
end.each_with_object({}) do |(k, v), hash|
# k are your 'keys' like 22, 23
# v is an array of arrays that you want to find out the averages of
hash[k] = \
v.map do |array|
array.reduce(:+).fdiv(array.size)
end
end
If it were me and I could have my way, I would refactor the way arr was created from the first place, since
The dimensionality of the array is counter-intuitive
The transformation again take its toll, affecting readibility, in turn, maintainability.
But I have no more insights than what I could see from the code you have shown. So, I played around with it a little bit, perhaps the code below is what you wanted?
totals = {}
arr.each do |row|
index, answers = row
answers.each do |answer|
question, count = answer
totals[question] ||= []
totals[question][index] ||= []
totals[question][index] << count
end
end
Below is the output of totals and by then it's trivial to get your average.
{
22 =>[[3, 1], [4, 2]],
23=>[[5, 2], [4, 4]]
}
EDIT Below is the solution that I have worked out by using each_with_object I learned from #Humza
arr = [
[ 0, [ [22,3],[23,5] ] ],
[ 0, [ [22,1],[23,2] ] ],
[ 1, [ [22,4],[23,4] ] ],
[ 1, [ [22,2],[23,4] ] ]
]
result = arr.each_with_object({}) do |(index, feedbacks), totals|
feedbacks.each do |(question, count)|
totals[question] ||= {}
totals[question][index] ||= []
totals[question][index] << count
end
totals
end.each_with_object({}) do |(question, totals), result|
result[question] = totals.map do |(index, total)|
total.reduce(:+).fdiv(total.length)
end
end
puts result.inspect
## Output
# {22=>[2.0, 3.0], 23=>[3.5, 4.0]}
How could I implement this? I think my solution is very dirty, and I would like to do it better. I think there is an easy way to do this in Ruby, but I can't remember. I want to use it with Rails, so if Rails provides something similar that's ok, too. usage should be like this:
fruits = ['banana', 'strawberry', 'kiwi', 'orange', 'grapefruit', 'lemon', 'melon']
# odd_fruits should contain all elements with odd indices (index % 2 == 0)
odd_fruits = array_mod(fruits, :mod => 2, :offset => 0)
# even_fruits should contain all elements with even indices (index % 2 == 1)
even_fruits = array_mod(fruits, :mod => 2, :offset => 1)
puts odd_fruits
banana
kiwi
grapefruit
melon
puts even_fruits
strawberry
orange
lemon
******* EDIT *******
for those wo want to know, that is what i finally did:
in a rails project, i created a new file config/initializers/columnize.rb which looks like this:
class Array
def columnize args = { :columns => 1, :offset => 0 }
column = []
self.each_index do |i|
column << self[i] if i % args[:columns] == args[:offset]
end
column
end
end
Rails automatically loads these files immediately after Rails has been loaded. I also used the railsy way of supplying arguments to a method, because i think that serves the purpose of better readable code, and i'm a good-readable-code-fetishist :) I extended the core class "Array", and now i can do things like the following with every array in my project:
>> arr = [1,2,3,4,5,6,7,8]
=> [1, 2, 3, 4, 5, 6, 7, 8]
>> arr.columnize :columns => 2, :offset => 0
=> [1, 3, 5, 7]
>> arr.columnize :columns => 2, :offset => 1
=> [2, 4, 6, 8]
>> arr.columnize :columns => 3, :offset => 0
=> [1, 4, 7]
>> arr.columnize :columns => 3, :offset => 1
=> [2, 5, 8]
>> arr.columnize :columns => 3, :offset => 2
=> [3, 6]
I will now use it to display entries from the database in different columns in my views. What i like about it, is that i don't have to call any compact methods or stuff, because rails complains when you pass a nil object to a view. now it just works. I also thought about letting JS do all that for me, but i like it better this way, working with the 960 Grid system (http://960.gs)
fruits = ["a","b","c","d"]
even = []
x = 2
fruits.each_index{|index|
even << fruits[index] if index % x == 0
}
odds = fruits - even
p fruits
p even
p odds
["a", "b", "c", "d"]
["a", "c"]
["b", "d"]
def array_mod(arr, mod, offset = 0)
arr.shift(offset)
out_arr = []
arr.each_with_index do |val, idx|
out_arr << val if idx % mod == 0
end
out_arr
end
Usage:
>> fruits = ['banana', 'strawberry', 'kiwi', 'orange', 'grapefruit', 'lemon', 'melon']
>> odd_fruits = array_mod(fruits, 2)
=> ["banana", "kiwi", "grapefruit", "melon"]
>> even_fruits = array_mod(fruits, 2, 1)
=> ["strawberry", "orange", "lemon"]
>> even_odder_fruits = array_mod(fruits, 3, 2)
=> ["kiwi", "lemon"]
The simplest method I can think of is this:
fruits = ["a","b","c","d"]
evens = fruits.select {|x| fruits.index(x) % 2 == 0}
odds = fruits - evens
You don't need to mess with select_with_index if the array can look up indices for you. I suppose the drawback to this method is if you have multiple entries in 'fruits' with the same value (the index method returns the index of the first matching entry only).
What you want is:
even_fruits = fruits.select_with_index { |v,i| i % 2 == 0) }
odd_fruits = fruits - even_fruits
Unfortunately Enumerable#select_with_index does not exist as standard, but several people have extended Enumerable with such a method.
http://snippets.dzone.com/posts/show/3746
http://webget.com/gems/webget_ruby_ramp/doc/Enumerable.html#M000058
Solution using just core capabilities:
(0...((fruits.size+1-offset)/mod)).map {|i| fruits[i*mod+offset]}
Rails provides an ActiveSupport extension to Array that provides an "in_groups_of" method. That's what I usually use for things like this. It allows you to do this:
to pull the even fruits (remember to compact to pull off nils at the end):
fruits = ['banana', 'strawberry', 'kiwi', 'orange', 'grapefruit', 'lemon', 'melon']
fruits.in_groups_of(2).collect{|g| g[1]}.compact
=> ["strawberry", "orange", "lemon"]
to pull the odd fruits:
fruits.in_groups_of(2).collect{|g| g[0]}.compact
=> ["banana", "kiwi", "grapefruit", "melon"]
to get every third fruit, you could use:
fruits.in_groups_of(3).collect{|g| g[0]}.compact
=> ["banana", "orange", "melon"]
functional way
#fruits = [...]
even = []
odd = []
fruits.inject(true ){|_is_even, _el| even << _el if _is_even; !_is_even}
fruits.inject(false){|_is_odd, _el| odd << _el if _is_odd; !_is_odd }
Here's a solution using #enum_for, which allows you to specify a method to use "in place" of #each:
require 'enumerator'
mod = 2
[1, 2, 3, 4].enum_for(:each_with_index).select do |item, index|
index % mod == 0
end.map { |item, index| item }
# => [1, 2]