6

I'm working with an array of midi pitches, which looks like this...

pitches = [
  60, nil, nil, nil, 67, nil, nil, nil, 
  nil, nil, nil, nil, nil, nil, nil, nil, 
  nil, nil, nil, nil, nil, nil, nil, nil, 
  nil, nil, nil, nil, nil, nil, nil, nil
] 

In this case, the pitch is still 60 on indexes 1, 2 and 3.

Following index 4, the pitch is still 67.

How can I write a method to identify the previous non-nil value?

The only way I can currently think to do it looks a little clumsy:

def pitch_at_step(pitches,step)
  if pitches.any?
    x = pitches[step]
    until x != nil
      index -= 1
      x = pitches[step]        
    end 
    x
  else
    nil
  end 
end

The expected output is in the format:

pitch_at_step(pitches, 0) # 60
pitch_at_step(pitches, 2) # 60
pitch_at_step(pitches, 4) # 67
pitch_at_step(pitches, 8) # 67

Is this the best solution? is there a tidier and/or more efficient way?

2
  • Can you give also the output? So that we don't need to run your code. Commented Jan 18, 2015 at 15:34
  • It should be the most recent non-nil value, so 0: 60, 1: 60, 2: 60, 3: 60, 4: 67, 5: 67, 6: 67 etc. Commented Jan 18, 2015 at 15:49

7 Answers 7

4

If array is not big you can use something like this:

pitches[0..index].compact.last

This seems tidier, but it's not as good as your for big arrays of data

Sign up to request clarification or add additional context in comments.

1 Comment

This looks good, as it happens the app is based on short loops which the user can modify. I would be surprised if they ever exceed 64 steps.
3
pitches.slice_before(&:itself).flat_map{|a| a.fill(a.first)}
# => [60, 60, 60, 60, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 
#     67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67]

2 Comments

Kernel#itself only available from 2.2.0 onwards. (This answer certainly taught me a thing or two...)
Slightly less elegant but for earlier rubies it looks as if you can use slice_before{|x| x != nil}
2

Here's a simple way to construct the converted array:

last = pitches.first
pitches[1..-1].map { |i| curr = i || last; last = curr; curr }
              .unshift(pitches.first)
  #=> [60, 60, 60, 60, 67,... 67] 

The OP did not say whether the first element of pitches is always non-nil. Suppose:

pitches = [nil, nil, 61, nil, nil, 60, nil]

The above method would return:

[nil, nil, 61, 61, 61, 60, 60] 

which is what we would want. Some of the other answers stumble when pitches[step] = nil and pitches[i] = nil for all i < step (step being the index of the given element of pitches).

1 Comment

Good point, the right answer will be that the array is read as a repeating pattern. The pitch will remain as the last value it was set to. HOWEVER - there is a bit mask defining when to start sounding the tone, the first member of which will be on the same point as the first defined pitch, so there is no need to worry about nils at the start of the array
2

If you're going to have large arrays which are mostly nil, why not use a hash instead, store only the non-nil values ? And you look on the keys. (here, an unoptimized version)

pitches = {0 => 60, 4 => 67}

def find(idx)
  lower = pitches.keys.select { |i| i <= idx}
  return pitches[lower.sort.last]
end

If performance is an issue, you can keep track of the sorted keys.

1 Comment

This is a good idea, and may also solve an issue I'm likely to run in to with data transfer via JSON. It does however mean I will have to implement this solution in javascript on the client.
1

This can also be achieved with Enumerable#chunk:

tmp = nil
pitches.chunk {|el| tmp = el unless el.nil?; tmp }.
  map {|val, ar| [val] * ar.size }.flatten
# => [60, 60, 60, 60, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 
#     67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67]

Comments

1

To get one value:

 value = index.downto(0){|i| pitches[i] and break pitches[i] }

To calculate all the values:

 values = (y = nil) || pitches.map{|x| x ? y = x : y }

Comments

0

I think this is a good way to do it in Ruby:

pitches[0..index].reverse_each.find{|x|x}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.