Move x element to the end of an array
Asked Answered
L

2

9

I'm trying to find a method to take a particular relation and move it to the end of the array. Basically, I have a current_account and I want to take this account and move it to the end of the account relationship array so that it will display last when I iteration over the relationships. I want to make a scope and use SQL if possible, here is my attempt and I haven't really gotten anywhere.

HTML

<% current_user.accounts.current_sort(current_account).each do |account| %>
   <li><%= link_to account.name, switch_account_accounts_path(account_id: account.id) %></li>
<% end %>

This current return a list of sorted by created_at accounts. I don't want it to be sorted by created at but the current_account to be at the bottom so I make a scope called current_sort but I'm not sure what to do here.

CURRENT_SORT SCOPE ON ACCOUNT

 scope :current_sort, lambda { |account|

 }

I want this scope to return the passed in account last in the association array. How can I do this with SQL or Ruby?

Lesotho answered 3/9, 2017 at 18:13 Comment(4)
Why not just sort_by { |v| v == current_account ? 1 : 0 }?Moshe
@Moshe That works great! No reason not to.Lesotho
You can also do list - [ current_account ] + [ current_account ] but that seems more messy.Moshe
Yea, That's an interesting take though.Lesotho
M
9

A quick trick to sort a particular element to the end of an array is:

array.sort_by { |v| v == current_account ? 1 : 0 }

If you want to move multiple elements it's easier to do:

to_end = [ a, b ]

array - to_end + to_end

Edit: As Stefan points out, this could potentially re-order items. To fix that:

array.sort_by.with_index do |v, i|
  v == current_account ? (array.length + i) : i
end

You can also approach it a different way using partition:

array.partition { |v| v != current_account }.reduce(:+)

Which is a variation on the method used by Stefan in their answer.

Moshe answered 3/9, 2017 at 21:49 Comment(5)
That is great. Note that array - to_end + to_end only works in a set like manner. [1,1,1,1,1,2]-[1]==[2]Glen
Ruby's sort / sort_by is not stable. Your solution puts current_account at the end, but it can inadvertently reorder the remaining items. This can be fixed by taking each item's index into account, i.e.: array.sort_by.with_index { |v, i | [v == current_account ? 1 : 0, i] }Sauls
@Sauls That's a good point. I've added two other methods to account for that, one of which apparently you've already mentioned now that I've checked.Moshe
Why do you have (array.length + i) : i and not just 1 : 0? Seems redundant given the following , i but maybe I'm missing something.Sauls
@Sauls Ah, I see what you mean with using a secondary sort factor in that array.Moshe
S
5

You can use partition to split the array by a condition.

array = [1, 2, 3, 4, 5, 6, 7, 8]
current_account = 3

other_accounts, current_accounts = array.partition { |v| v != current_account }
#=> [[1, 2, 4, 5, 6, 7, 8], [3]]

other_accounts
#=> [1, 2, 4, 5, 6, 7, 8]

current_accounts
#=> [3]

The results can be concatenated:

other_accounts + current_accounts
#=> [1, 2, 4, 5, 6, 7, 8, 3]

or in a single line:

array.partition { |v| v != current_account }.flatten(1)
#=> [1, 2, 4, 5, 6, 7, 8, 3]

# or

array.partition { |v| v != current_account }.inject(:+)
#=> [1, 2, 4, 5, 6, 7, 8, 3]
Sauls answered 4/9, 2017 at 7:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.