2009-12-03 10 views
1

Mon application doit faire face à tableaux de taille fixe. Le problème est que parfois les éléments sont nuls mais que la valeur nulle est une valeur interdite. Je pense qu'un moyen facile est de remplacer les valeurs nulles avec une valeur non nulle la plus proche (juste avant ou juste après).Compléter un Ruby Array

Les valeurs nulles peuvent être des premières, des dernières ou même des multiples. Voici quelques exemples de ce que je cherche:

[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] 

Je suis sûr qu'il y a une manière élégante de faire ceci. Pouvez-vous aider?

+0

"Les valeurs nulles peuvent être des premières, des dernières ou même des multiples." Pouvez-vous expliquer cela plus? Comment peut-il y avoir un zéro au milieu du tableau dans votre troisième exemple? –

+0

Peut-il y avoir plus de 2 valeurs nulles d'affilée? – DigitalRoss

+0

Oui ... Il devrait y avoir au moins une valeur non nulle. –

Répondre

4

Ma première idée était quelque chose comme ça, maintenant fixé pour le cas général des séquences arbitraires de néant ...

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 

Mais je un peu comme celui-ci mieux. (API est 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 
+0

+1 Nice et élégant. –

+0

bien qu'il semblerait que cela ne fonctionne pas dans tous les cas ... – dustmachine

+0

À droite, en fonction de ce que l'entrée peut ressembler, il peut avoir besoin d'une boucle autour des deux lignes de la carte – DigitalRoss

1

D'abord, jumeler chaque élément avec les éléments suivants et précédents

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

carte ensuite sur le tableau de triplets comme ceci:

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

S'il y a plus de 2 nils dans une rangée , cela ne fonctionnera pas, alors mettez-le dans une boucle et continuez à l'appeler jusqu'à ce qu'il n'y ait plus de nils dans le tableau.

EDIT (Jörg W Mittag): Vous pouvez le rendre plus concis et lisible en utilisant bind déstructurante et clauses garde:

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 
} 

Si vous refactoriser cette façon, il devient facile de voir que tout le bloc fait est à la recherche pour le premier élément non nil dans le triple précédent-suivant-courant, qui peut être plus succinctement comme ceci:

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

Ceci, à son tour, peut encore être simplifiée en utilisant Array#compact.

2

Vous ne mentionnez pas vraiment ce que vous utilisez le tableau pour, mais en remplaçant peut-être nulle par 0 aurait plus de sens, car il ne serait pas influencer le résultat si vous voulez prendre des moyennes ou quelque chose ...

[1,2,3,nil,5].map { |el| el ? el : 0 } 
+0

Je suis avec vous sur celui-ci. C'est la manière facile. Mieux encore: [1,2,3, nul, 5] .map {| n | n || 0} –

+0

Vous avez absolument raison Ben, votre version est beaucoup plus propre :-) –

0

Voici ma solution. Il fonctionnera pour n'importe quel nombre de nil s dans le tableau et échouera gracieusement si chaque élément du tableau est nil. Si un nil dans le tableau a un non-nul avant et un non-nul après, il choisira au hasard avant ou après.

init et vérifier la sécurité:

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

La viande de la solution:

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 

La conclusive:

end 
arr #print result for review 

Cela a été testé avec chacun de votre exemple instances (nul au début, nul à la fin, double zéro au milieu) et devrait fonctionner pour n'importe quelle taille de tableau.

Cautions:

  • L'élément qui vient « avant » le premier élément du tableau est le dernier élément
0

Ceci est une copie directe de la solution DigitalRoss mais la manipulation des cas de pointe de plus que deux nils d'affilée. Je suis sûr que DigitalRoss serait en mesure de le faire avec plus d'élégance, et sans le rubis non idiomatiques en boucle, mais cela fonctionne pour tous les cas testés

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}" } 

Ceci produit la sortie suivante

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

La partie de la clause de garde qui teste des valeurs nulles n'est pas vraiment nécessaire car elle contournerait tout de même la boucle while –

2

Tout dépend de ce que vous voulez faire avec les données plus tard. Il peut être judicieux pour vous de mettre en valeurs moyennes, mais si vous avez des tableaux relativement petits et sont en baisse pour un petit plaisir que vous pouvez aller bayésien avec quelque chose comme ce qui suit:

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}" } 

sortie se présente comme suit:

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] 
0

C'est une variante de @Callum's solution:

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

il me semble que ce serait moins surprenant de propager la dernière valeur non nulle et non pour anticiper une valeur non nulle:

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]