1

I’m doing Ruby task, which is “You have an array of numbers. Your task is to sort ascending odd numbers but even numbers must be on their places. Zero isn't an odd number and you don't need to move it. If you have an empty array, you need to return it”.

Decided to split initial array: odd nums pushed into another array, sorted it and even numbers added to hash, where the key is num and its initial index is value. After that, trying to insert the even nums from hash to odd array, at initial indexes of even nums. So, the code looks like this:

def sort_array(source_array)
  even_nums = Hash.new
  odd_nums = []
  return source_array if source_array.length == 0 
  source_array.each_with_index {|n, ind| even_nums[n] = ind if n.even?}
  source_array.select{|n| odd_nums.push(n) if n.odd?}

  odd_nums.sort!

  even_nums.each do |k, v|
     odd_nums.insert(v, k)
  end
  odd_nums
end

With small array like [5, 3, 2, 8, 1, 4, 11] it works as expected but if I pass something bigger like [84, -64, 40, 53, 5, 88, 2, 14, 29, -79, -44, -23, 20, -67, -12, 28, -28, -37, -27, -62, -54, 93, -61, 50, 65, -63, -62, 77, -16, 49, -43, 26, -73, -27, 88, -88, -62, 84, 54, 25, 25, -2, -99, 69, -23, 47, -92, 7, -62, -62, -58, -30, -75, -31, 65, -63, 16, 64, -7, -22, -6, -82]

I’m getting nils at the end of the sorted array. Like this:

[-99, -64, 40, -79, -75, -73, 2, 14, -67, -63, -44, -63, 20, -61, -12, 28, -28, -43, -37, -31, -54, -27, -27, 50, -23, -23, -7, 5, -16, 7, 25, 26, 25, 29, 47, -88, 49, 53, 54, 65, 65, -2, 69, 77, 93, nil, -92, nil, nil, 88, -58, -30, nil, nil, nil, nil, 16, 64, nil, -22, -6, -82, 84, nil, -62]

Struggling to understand, why it isn’t working with bigger arrays?

3
  • The funny thing about Ruby is I can look at that code and just know there's a simpler way to do it. Commented Dec 8, 2020 at 21:39
  • 1
    @tadman There is a simplier way, indeed! But I’ve started to learn it not so long ago and it was the first idea how to do it) Commented Dec 9, 2020 at 5:24
  • 1
    Absolutely nothing wrong with the attempt. It shows effort on your part, and sets the stage for a solution. Commented Dec 9, 2020 at 18:47

3 Answers 3

5

There's a fairly easy way to do this if you think about it as two operations:

def sort_array(arr)
  # Extract and sort the odd values
  odd = arr.select(&:odd?).sort

  # Merge the sorted odd values back in
  arr.map do |v|
    v.odd? ? odd.shift : v
  end
end

Not much to it.

You had some of the right pieces, but I think you got stuck in the weeds when it started to get overly complicated.

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

6 Comments

Your answer is definitely simpler and more elegant than mine. Using #map neatly solves the problem of how to replace values without an interim array, so +1 for that. I really like that you didn't have to touch the even numbers at all this way.
@ToddA.Jacobs It's often the case that the code for a Ruby solution to something is shorter than the verbiage to describe the problem. It's very expressive as a language.
Thanks a lot! It really helped. But in general, can you explain what’s wrong with my logic, apart of that is too complicated? I just don’t understand why nils and even don’t have an idea of how to google it.
It looks like your insert calls aren't aligned properly and you're skipping spots. For example: a = [ ] then a.insert(4, :x) leaves nil entries.
Think, I got it! Thanks for you help.
|
2

That can be done as follows.

def sort_odds(arr)
  odd_pos = arr.each_index.select { |i| arr[i].odd? }
  odd_pos.zip(odd_pos.sort_by { |i| arr[i] }).
          each_with_object(arr.dup) do |(old_pos,new_pos),a|
            a[new_pos] = arr[old_pos]
          end
end
sort_odds [5, 3, 2, 8, 1, 4, 11]
  #        o  o  e  e  o  e   o
  #=>     [1, 3, 2, 8, 5, 4, 11]

The steps are as follows.

arr = [5, 3, 2, 8, 1, 4, 11]
  #    o  o  e  e  o  e   o
odd_pos = arr.each_index.select { |i| arr[i].odd? }
  #=> [0, 1,       4,     6]
new_pos = odd_pos.zip(odd_pos.sort_by { |i| arr[i] })
  #=> [[0, 4], [1, 1], [4, 0], [6, 6]]
new_pos.each_with_object(arr.dup) do|(old_pos,new_pos),a|
  a[new_pos] = arr[old_pos]
end
  #=> [1, 3, 2, 8, 5, 4, 11]

Comments

2

I think this other answer is simpler and more elegant than mine, but this works too. Notably, this solution would allow you to validate the position of your even numbers (for example, in a spec) by looking up the indexes and values in evens. Unless you already know what the output array should look like, this may matter when it comes time to debug the interim results.

def odd_sorted array
  odds  = array.select { |e| e.odd? }.sort
  evens = array.each_with_index.select { |e| e.first.even? }

  arr2 = Array.new(array.size)

  # put even numbers in at their previous index
  evens.each do |e|
    arr2.each_index { |i| arr2[i] = e[0] if e[1] == i }
  end

  arr2.each_with_index { |e, i| arr2[i] = odds.shift if e.nil? }
end

odd_sorted [5, 3, 2, 8, 1, 4, 11]
#=> [1, 3, 2, 8, 5, 4, 11]

odd_sorted [84, -64, 40, 53, 5, 88, 2, 14, 29, -79, -44, -23, 20]
#=> [84, -64, 40, -79, -23, 88, 2, 14, 5, 29, -44, 53, 20]

The Array#map solution is definitely more elegant, but this is (in my personal opinion) more debuggable. Your mileage in that regard will certainly vary.

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.