Переместите элемент на фронт массива в Ruby

Даже из javascript это выглядит ужасно для меня:

irb
>> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
>> a.unshift(a.delete('c'))
=> ["c", "a", "b"]

Есть ли более четкий способ размещения элемента в передней части массива?

Изменить мой фактический код:

if @admin_users.include?(current_user)
  @admin_users.unshift(@admin_users.delete(current_user))
end

Ответы

Ответ 1

Это более сложная проблема, чем кажется. Я определил следующие тесты:

describe Array do
  describe '.promote' do
    subject(:array) { [1, 2, 3] }

    it { expect(array.promote(2)).to eq [2, 1, 3] }
    it { expect(array.promote(3)).to eq [3, 1, 2] }
    it { expect(array.promote(4)).to eq [1, 2, 3] }
    it { expect((array + array).promote(2)).to eq [2, 1, 3, 1, 2, 3] }
  end
end

sort_by, предложенный @Duopixel, является элегантным, но для второго теста он производит [3, 2, 1].

class Array
  def promote(promoted_element)
    sort_by { |element| element == promoted_element ? 0 : 1 }
  end
end

@tadman использует delete, но это удаляет все соответствующие элементы, поэтому выход четвертого теста [2, 1, 3, 1, 3].

class Array
  def promote(promoted_element)
    if (found = delete(promoted_element))
      unshift(found)
    end

    self
  end
end

Я попытался использовать:

class Array
  def promote(promoted_element)
    return self unless (found = delete_at(find_index(promoted_element)))
    unshift(found)
  end
end

Но это провалило третий тест, потому что delete_at не может обрабатывать nil. Наконец, я решил:

class Array
  def promote(promoted_element)
    return self unless (found_index = find_index(promoted_element))
    unshift(delete_at(found_index))
  end
end

Кто знал, что простая идея типа promote может быть настолько сложной?

Ответ 2

Возможно Array # rotate будет работать для вас:

['a', 'b', 'c'].rotate(-1)
#=> ["c", "a", "b"]

Ответ 3

Возможно, вам это выглядит лучше:

a.insert(0, a.delete('c'))

Ответ 4

Если "изящный" означает более читаемый даже за счет нестандартности, вы всегда можете написать свой собственный метод, который улучшает массив:

class Array
  def promote(value)
    if (found = delete(value))
      unshift(found)
    end

    self
  end
end

a = %w[ a b c ]
a.promote('c')
# => ["c", "a", "b"] 
a.promote('x')
# => ["c", "a", "b"] 

Имейте в виду, что это приведет к изменению только одного экземпляра значения. Если в массиве их несколько, последующие, вероятно, не будут перемещены до тех пор, пока первый не будет удален.

Ответ 5

В конце я считал это наиболее читаемой альтернативой перемещению элемента вперед:

if @admin_users.include?(current_user)
  @admin_users.sort_by{|admin| admin == current_user ? 0 : 1}
end

Ответ 6

Добавление двух центов:

array.select{ |item| <condition> } | array

Плюсы:

  • Может перемещать несколько элементов в передний массив.

Минусы:

  • Это приведет к удалению всех дубликатов, если это не приведет к желаемому результату.

Пример. Переместите все нечетные числа вперед (и сделайте массив уникальным):

data = [1, 2, 3, 4, 3, 5, 1]
data.select{ |item| item.odd? } | data
# Short version:
data.select(&:odd?) | data

Результат:

[1, 3, 5, 2, 4]

Ответ 7

Если все элементы массива уникальны, вы можете использовать арифметику массива:

> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
> a -= "c"
=> ["a", "b"]
> a = ["c"] + a
=> ["c", "a", "b"]

Ответ 8

Другой способ:

a = [1, 2, 3, 4]
b = 3

[b] + (a - [b])
=> [3, 1, 2, 4]