1

There is an array like this:

    arr = [
        {id: 1, status: 3},
        {id: 2, status: 5},
        {id: 3, status: 5},
        {id: 4, status: 5},
        {id: 5, status: 5},
    ]

In this array, if status of any hash is 3, it will be change to be 2, and others will change to be 1. if each hash status is not 3, the array does not change.

I hope the output should be:

    arr = [
        {id: 1, status: 2},
        {id: 2, status: 1},
        {id: 3, status: 1},
        {id: 4, status: 1},
        {id: 5, status: 1},
    ]

if the array like this:

    arr = [
        {id: 1, status: 2},
        {id: 2, status: 5},
        {id: 3, status: 5},
        {id: 4, status: 5},
        {id: 5, status: 5},
    ]

each hash status is not 3, so the array doesn't change.

I can make it like this:

    tmp = false
    arr.each do |e|
        if e[:status].to_i == 3
           e[:status] = 2
           tmp = true
           break
        end
    end
    // note: if tmp == false, the array does not change
    if tmp == true
        arr.each do |e|
            e[:status] = 1  if e[:status].to_i == 5
        end
    end

But I think it is a bad idea, it will loop two times. Anyone has the better solution? Thanks in advance!

2
  • 2
    so 3 -> 2 and 5 -> 1 ?? or 3 -> 2 and others -> 1 ? Commented Dec 16, 2014 at 9:05
  • if 3 exists, 3 -> 2 and others -> 1, if 3 is not exists, the array does not change. Commented Dec 16, 2014 at 9:12

6 Answers 6

1

Most of the answers submitted only work correctly if the first array element is the status: 3.

You could do it in a single loop, if you construct a copy of the array as you go, and swap that for the original if you found a match

arr = [
        {id: 1, status: 3},
        {id: 2, status: 5},
        {id: 3, status: 5},
        {id: 4, status: 5},
        {id: 5, status: 5},
];


switch = false
new_arr = Array(arr.length)

arr.each_with_index do |e,i|
  if e[:status] == 3
    new_elem = { id: e[:id], status: 3 }
    switch = true
  else
    new_elem = { id: e[:id], status: 1 }
  end
  new_arr[i] = new_elem
end

arr = new_arr if switch

So you don't have to iterate the full array twice, but you construct an unused copy array if there is no status: 3.

I think you might want to consider using a different data structure - maybe a specific class that models the state changes you're using. Maybe look at some of the state machine gems.

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

Comments

0
arr.map do |e|
  new_status = if e[:status] == 3
                 2
               else
                 1
               end
  {id: e[:id], status: new_status}
end

1 Comment

@МалъСкрылевъ :) i rolled back. Very ugly solution.
0

I guess you can use a ternary:

arr.each{|element| element[:status] == 3 ? element[:status] = 2 : element[:status] = 1}

Comments

0
arr.map do |e|
   {id: e[:id], status: (e[:status]==3) ? 2 : 1}
end

Comments

0

Using find_all, twice again but bit cleaner.

arr.find_all {|e| e[:status] = 2 if e[:status] == 3  }
arr.find_all {|e| e[:status] = 1 if e[:status] == 5  }

Comments

0

There's no way to avoid two loops, since you must determine if arr[i][:status] => 3 for any index i before you know whether you need to change the values of arr[j][:status] (to 2 or 1) for all j. This is one way you could do that:

ndx = arr.index { |h| h[:status] == 3 } #=> 0
if ndx
  arr.each { |h| h[:status] = 1 }
  arr[ndx][:status] = 2
end
arr

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.