views:

681

answers:

8

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)

+3  A: 
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"]
Jordan
I tried to find a more functional way to do this (e.g. Array#select), but was thwarted since most of Ruby's functional Array methods don't expose the index.
Jordan
+2  A: 
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"]
OscarRyz
that is exactly what i was looking for :) thanks mate!
padde
+2  A: 

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

Steve Graham
+1 Of course!, you don't need the value if you cat get it with `fruits[i]` then using `each_index` will do
OscarRyz
But using the one you point to, your block would have to be `i % 2 == 0`, not just `i % 2`, which would always be true...
glenn mcdonald
silly me, of course zero evaluates as true! cheers glenn! ;)
Steve Graham
+1  A: 

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"]
Jeff Whitmire
A: 

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).

bta
Now that I think about it, the drawback I mentioned above isn't really an issue since the `-` operator for arrays shares a similar problem with multiple identical elements (meaning using `index` isn't adding any new problems).
bta
This could be very slow for a large dataset since Array#index is a linear-time operation.
ScottJ
+2  A: 

Solution using just core capabilities:

(0...((fruits.size+1-offset)/mod)).map {|i| fruits[i*mod+offset]}
glenn mcdonald
Oh, I like this.
Jordan
extra curly brace on the end :)
Steve Graham
Oops. Fixed, thanks.
glenn mcdonald
A: 

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 }
aaz
A: 

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]
Drew Olson