tags:

views:

209

answers:

8

What's the slickest, most Ruby-like way to do this?

[1, 3, 10, 5].diff

should produce

[2, 7, -5]

that is, an array of first order differences. I've come up with a solution which I'll add below, but it requires ruby 1.9 and isn't all that slick. what else is possible?

+1  A: 
# Attempt, requires ruby 1.9.
module Enumerable
  def diff
    each_cons(2).with_object([]){|x,array| array << x[1] - x[0]}
  end
end

Example:

[1,3,10,5].diff
=> [2, 7, -5]
Peter
Nice addon. each_cons.
Gishu
+2  A: 

The concept comes from functional programming, of course:

module Enumerable
  def diff
    self.inject([0]) { |r,x| r[-1] += x; r << -x } [1..-2]
  end
end

[1,3,10,5].diff

Note that you don't need any separate intermediate variables here

Pavel Shved
A: 

Another way to do it.

module Enumerable
  def diff
    result = []
    each_with_index{ |x, i|
      return result if (i == (self.length-1))
      result << self[i+1] - x
    }
  end
end
Gishu
+5  A: 

I like this functional style:

module Enumerable
  def diff
    each_cons(2).map {|pair| pair.reverse.reduce :-}
  end
end

EDIT: I just realized that the reverse is totally unnecessary. If this were a functional language, I would have used pattern matching, but Ruby doesn't support pattern matching. It does, however, support destructuring bind, which is a good enough approximation for pattern matching in this case.

each_cons(2).map {|first, second| second - first}

No smiley, though.

I like how this sounds if you just read it out loud from left to right: "For each pair, apply the difference between the first and second elements of the pair." In fact, I normally don't like the name collect and prefer map instead, but in this case that reads even better:

each_cons(2).collect {|first, second| second - first}

"For each pair, collect the difference between its elements." Sounds almost like a definition of first order difference.

Jörg W Mittag
Looks pretty, but it's 4x slower than mine...
glenn mcdonald
Actually only 2.6x slower, but still.
glenn mcdonald
Oh, and on long arrays the difference is bigger. With 1000-number arrays this is about 10x slower than my fastest version!
glenn mcdonald
But this solution has a built-in smiley!
glenn jackman
+2  A: 

Yet another way..Seems the shortest so far:)

  module Enumerable
    def diff
        self[1..-1].zip(self).map {|x| x[0]-x[1]}
    end    
  end
pierr
This is cute, but pretty inefficient if the arrays get large...
glenn mcdonald
a good 1.8 compatible solution. thanks.
Peter
You could use destructuring bind in the block instead of indexing: `{|first, second| first - second}`. Also, on 1.8.7+ `self[1..-1]` is equivalent to `drop(1)`, making the entire thing read like this: `drop(1).zip(self).collect {|first, second| first - second}`. Again, even if you don't know Ruby and just read it out loud like English, it sounds almost comprehensible, like a mathematical definition of first order difference: "Combine the Array with itself shifted by one by computing the difference and collecting the results."
Jörg W Mittag
Thanks, BTW. I was looking for a solution like this yesterday and had a brain freeze. Although I should have been able to come up with it myself, after all, it is closely related to my favorite piece of Haskell code: `fibs = 0 : 1 : zipWith (+) fibs (tail fibs)`.
Jörg W Mittag
A: 

My feeble attempt...

module Enumerable
  def diff
    na = []
    self.each_index { |x| r << self[x]-self[x-1] if x > 0 }
    na
  end
end

p [1,3,10,5].diff  #returned [2, 7, -5]
testr
How about using `inject` to build the list instead? Shortens the method from 3 to 1 line and is easier to read (imo, of course, but it's at least more ruby-like).
thenduks
But note that that would also make it slower...
glenn mcdonald
+1  A: 

Here's the fastest way I could find (faster than all the others suggested here as of this moment, in both 1.8 and 1.9):

module Enumerable
  def diff
    last=nil
    map do |x|
      r = last ? x - last : nil
      last = x
      r
    end.compact
  end
end

With this close runner-up:

module Enumerable
  def diff
    r = []
    1.upto(size-1) {|i| r << self[i]-self[i-1]}
    r
  end
end

Of the others here, testr's self-described "feeble" attempt is the next fastest, but it's still slower than either of these.

And if speed is no object, here's my aesthetic favorite:

module Enumerable
  def diff!
    [-shift+first] + diff! rescue []
  end

  def diff
    dup.diff!
  end
end

But this is (for reasons I don't entirely understand) an order of magnitude slower than any other suggestion here!

glenn mcdonald
wow, i had no idea my solution was fast. i was only trying to make it work with the very few methods that i'm familiar with (sorry, still a newbie). enjoyed this thread, i learned about each_cons and inject :)
testr
If execution speed was the main concern, you probably wouldn't be using Ruby to begin with, but I think it's always interesting to run the tests.
glenn mcdonald
Most existing Ruby implementations are pretty crappy at method invocations. Also, the widely used implementations are pretty crappy at object allocation. In your solution you recurse once for every element of the array, and you allocate two new arrays per step, so all in all you have a call stack depth of n, 6 method calls per step (6n total) and a total of 2n arrays allocated. All of which is pretty slow on the current widespread VMs. The new (not yet released) JRuby JIT compiler has array allocation elimination (amongst other things), and could potentially speed this up dramatically.
Jörg W Mittag
Especially for really large arrays with hundreds of thousands of elements when the methods get called often enough that the JVM's native code JIT compiler kicks in (in `java -server` mode, which is the fastest, the default threshold for the compiler is something like 10000 invocations, before it even *starts* compiling).
Jörg W Mittag
Oh, and I forgot, you have exception handling in there, which most current VMs are *also* crappy at. There is no reason why your code should not be compiled to super-efficient machine code (and indeed, if we were talking about a commercial Smalltalk or Lisp implementation, or the Supero Haskell compiler, it probably would. The Ruby implementations just aren't as good. Yet! The good news is, it's only a question of money, effort, manpower and Ph.D. theses. Spend as many of these on Ruby as we did on Java, and Ruby beats C hands down. The bad news is: none of these are available in abundance.)
Jörg W Mittag
+2  A: 

Minor variation on Jörg W Mittag's:

module Enumerable
  def diff
    each_cons(2).map{|a,b| b-a}
  end
end
jes5199
Nice, this is easily the clearest (if 1.9 is OK). 1.6x slower than my version in my tests...
glenn mcdonald
I think it works in 1.8.7 but not 1.8.6
jes5199