Class for median filter
Your code has some bad side-effects. There's no real difference between filter and filter! since they both modify the input values directly.

It seems that your input is something like:

[{foo: 2}, {foo: 80}, {foo: 6}, {foo: 3}]


in other words, an array of hashes.

When you call filter, you duplicate the array - but the hash objects are still the same objects. So when you calculate the median, and set item[key] = ..., you're modifying the same hash objects. Duplicating the array has no real effect; you're not modifying the array itself, only the objects inside the array. And those objects are the same even if you duplicate the array.

An example:

filter = MedianFilter.new(3, [:foo])
original = [{foo: 2}, {foo: 80}, {foo: 6}, {foo: 3}]

modified = filter.filter(values)

modified # =&gt; [{:foo=&gt;2}, {:foo=&gt;6}, {:foo=&gt;6}, {:foo=&gt;3}]
original # =&gt; [{:foo=&gt;2}, {:foo=&gt;6}, {:foo=&gt;6}, {:foo=&gt;3}]


In the example, modified is a different array object than original, but the contents of both arrays have been changed. In order words: Your filter method has side-effects.

And I doubt that's what you want.

Anyway, about the code:


Another bug: Your exception says that step "must be odd and higher than 3", but that's not what you actually check. You code checks whether step is below 3 and even. So I can pass in 4 and not get an error, even though it's an even number. Or I can pass in -3 and not get an error, even though is less than 3.
Your error message is also a little misleading. It says "higher than 3" when it fact it can be 3.
You check of keys only checks for a non-empty array - it doesn't check that the array only contains symbols. I could pass in ["foo", 42, Date.new] without the initializer raising an error.
There are a few built-in methods that would make the checks more idiomatic and readable. For instance, step % 2 != 1 can be written as step.even?, and keys.count &gt; 0 can be written as !keys.empty?
I'd probably rename step to window_size, since I think that's a better name. Or you might simply redefine it as the number of neighbors to include - i.e. what you currently call half_step.
I'd simply skip the keys argument, and instead provide the input data in a different way. It would keep this class simple and generic.
I'm not sure this actually needs to be a class, since it doesn't really need to maintain state. It could just be a method.
As for the process method, you can use each_cons to get the "windows" you want to find the median for. You just need to pad the array a little. Here's a simplified example:

# example input
values = [2, 80, 6, 3]
window_size = 3
neighbors = (window_size / 2).floor

# create an array with extra elements at the start and end
temp = ([values.first] * neighbors) + values + ([values.last] * neighbors)

# calculate medians
medians = temp.each_cons(window_size).map { |window| window.sort[neighbors] }

medians # =&gt; [2, 6, 6, 3]