Заполните Ruby Array - PullRequest
       6

Заполните Ruby Array

1 голос
/ 03 декабря 2009

Мое приложение должно иметь дело с массивами фиксированного размера . Проблема в том, что иногда элементы равны нулю, но ноль - это запрещенное значение. Я думаю, что простой способ - это заменить nil-значения ближайшим ненулевым значением (непосредственно перед или сразу после).

Значения nil могут быть первыми, последними или даже кратными. Вот несколько примеров того, что я ищу:

[1,2,3,nil,5] => [1,2,3,3,5]
[nil,2,3,4,5] => [2,2,3,4,5]
[1,nil,nil,4,5] => [1,1,4,4,5]

Я уверен, что есть элегантный способ сделать это. Вы можете помочь?

Ответы [ 8 ]

4 голосов
/ 03 декабря 2009

Моей первой идеей было что-то вроде этого, теперь исправленное для общего случая произвольных последовательностей нуля ...

t = nil
p = lambda do |e|
  if e.nil?
    e,t = t,e
  else
    t = e
  end
  e
end
r = a
while r.any? && (r.include? nil)
  t = nil; r = r.map(&p)
  t = nil; r = r.reverse.map(&p).reverse
end

Но мне больше нравится этот. (API это arrayObj.merge_all)

module Enumerable
  def merge_nil
    t = nil
    map do |e|
      if e.nil?
        e,t = t,e
        e
      else
        t = e
      end
    end
  end
end
class Array
  def merge_all
    return self unless any?
    t = self
    t = t.merge_nil.reverse.merge_nil.reverse while t.include? nil
    t
  end
end
2 голосов
/ 04 декабря 2009

Все зависит от того, что вы хотите сделать с данными позже. Для вас может иметь смысл ввести средние значения, но если у вас относительно небольшие массивы и вы хотите немного развлечься, вы можете перейти на байесовский метод примерно так:

require 'classifier'
$c = Classifier::Bayes.new

perm = [1, 2, 3, 4, 5].permutation(5)
perm.each { |v| $c.add_category v * "," } 
perm.each { |v| $c.train v*"," , v*","  } 

def guess(arr)
   s = $c.classify(arr*",")
   a = s.split(',').map{|s| s.to_i}
end

tests = [
[1,2,3,4,5],  
[1,2,3,nil,5],  
[nil,2,3,4,5], 
[1,nil,nil,4,5],
[1,nil,nil,nil,5],
[nil,nil,3,nil,nil],
[nil,nil,nil,nil,nil]
]

tests.each { |t| puts "Array #{t.inspect} became #{guess(t).inspect}" }

Вывод выглядит следующим образом:

Array [1, 2, 3, 4, 5] became [1, 2, 3, 4, 5]
Array [1, 2, 3, nil, 5] became [1, 2, 3, 4, 5]
Array [nil, 2, 3, 4, 5] became [1, 2, 3, 4, 5]
Array [1, nil, nil, 4, 5] became [1, 2, 3, 4, 5]
Array [1, nil, nil, nil, 5] became [1, 2, 3, 4, 5]
Array [nil, nil, 3, nil, nil] became [1, 2, 3, 4, 5]
Array [nil, nil, nil, nil, nil] became [1, 2, 3, 4, 5]
2 голосов
/ 04 декабря 2009

Вы на самом деле не упоминаете, для чего вы используете массив, но, возможно, замена nil на 0 имеет больше смысла, так как это не повлияет на результат, если вы хотите взять средние значения или что-то ...

[1,2,3,nil,5].map { |el| el ? el : 0 }
1 голос
/ 03 декабря 2009

Сначала выполните сопряжение каждого элемента со следующими и предыдущими элементами

triples = array.zip([nil]+array.take(array.length-1), array.drop(1))

Затем отобразите массив тройок следующим образом:

triples.map {|triple|
  if triple[0].nil? then
    if !triple[1].nil? then triple[1] else triple[2] end
  else
    triple[0]
  end
}

Если в строке более двух нулей, это не сработает, поэтому поместите его в цикл и продолжайте вызывать его, пока в массиве не останется больше нулей.

РЕДАКТИРОВАТЬ (Jörg W Mittag): Вы можете сделать это более кратким и читаемым, используя предложения по деструктурированию bind и guard:

ary.zip([nil] + ary.take(ary.length-1), ary.drop(1)).map {|prv, cur, nxt|
  next prv unless prv.nil?
  next cur unless cur.nil?
  nxt
}

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

ary.zip([nil] + ary.take(ary.length-1), ary.drop(1)).map {|triple|
  triple.find {|el| !el.nil? }
}

Это, в свою очередь, может быть дополнительно упрощено с помощью Array#compact.

0 голосов
/ 04 декабря 2009

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

def fill_in_array(ary)
  last_known = ary.find {|elem| elem} # find first non-nil
  ary.inject([]) do |new, elem|
    if elem.nil?
      new << last_known
    else
      new << elem
      last_known = elem
    end
    new
  end
end

p fill_in_array [1,2,3,nil,5]      # => [1,2,3,4,5]
p fill_in_array [1,nil,nil,4,5]    # => [1,1,1,4,5]
p fill_in_array [nil,nil,nil,4,5]  # => [4,4,4,4,5]
0 голосов
/ 04 декабря 2009

Это вариант решения @ Каллума :

require 'test/unit'
class TestArrayCompletion < Test::Unit::TestCase
  def test_that_the_array_gets_completed_correctly
    ary = [nil,1,2,nil,nil,3,4,nil,nil,nil,5,6,nil]
    expected = [1,1,2,2,3,3,4,4,nil,5,5,6,6]
    actual = ary.zip([nil]+ary.take(ary.length-1), ary.drop(1)).
               map(&:compact).map(&:first)

    assert_equal expected, actual
  end
end
0 голосов
/ 04 декабря 2009

Это прямая копия решения DigitalRoss, но она обрабатывает крайние случаи более двух нулей подряд. Я уверен, что DigitalRoss сможет сделать это более элегантно, без не-идоматического цикла ruby ​​while, но это работает для всех протестированных случаев

def un_nil(arr)
  return arr if arr.compact.size == 0 || ! arr.include?(nil)
  while arr.include?(nil)
    t = nil
    p = lambda do |e|
      if e.nil?
        e,t = t,e
      else
        t = e
      end
      e
    end
    t = nil; r = arr.map(&p)
    t = nil; r = r.reverse.map(&p).reverse
    arr = r
  end
  arr
end


tests = [
[1,2,3,4,5],  
[1,2,3,nil,5],  
[nil,2,3,4,5], 
[1,nil,nil,4,5],
[1,nil,nil,nil,5],
[nil,nil,3,nil,nil],
[nil,nil,nil,nil,nil]
]

tests.each {|a| puts "Array #{a.inspect} became #{un_nil(a).inspect}" }

Это дает следующий вывод

Array [1, 2, 3, 4, 5] became [1, 2, 3, 4, 5]
Array [1, 2, 3, nil, 5] became [1, 2, 3, 3, 5]
Array [nil, 2, 3, 4, 5] became [2, 2, 3, 4, 5]
Array [1, nil, nil, 4, 5] became [1, 1, 4, 4, 5]
Array [1, nil, nil, nil, 5] became [1, 1, 1, 5, 5]
Array [nil, nil, 3, nil, nil] became [3, 3, 3, 3, 3]
Array [nil, nil, nil, nil, nil] became [nil, nil, nil, nil, nil]
0 голосов
/ 04 декабря 2009

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

инициализация и проверка безопасности:

arr = [1,nil,nil,4,5]
if arr.nitems == 0
  raise "all nil! don't know what to do!"
else

Мясо из раствора:

  while (arr.index(nil))
    arr.each_index do |i|
      arr[i] = [arr[i-1], arr[i+1]] [rand 2]   if arr[i].nil?
    end
  end

Подведение итогов:

end
arr  #print result for review

Это было протестировано с каждым из ваших примеров (ноль в начале, ноль в конце, двойной ноль в середине) и должно работать для любого размера массива.

Осторожно:

  • Элемент, который идет «перед» первым элементом в массиве, является последним элементом
...