2009-12-03 131 views
1

我的应用程序需要处理固定大小的阵列。问题是,有时元素是零,但零是禁止的价值。我认为一个简单的方法是用替换零值与最接近的非零值(就在之前或之后)。完成红宝石阵列

零值可以是第一个,最后一个或者甚至是多个。这里有一些我正在寻找的例子:

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

我相信有一个优雅的方式来做到这一点。你能帮我吗?

+0

“零值可以是第一,最后甚至是倍数”。你能解释一下吗?在你的第三个例子中,数组中间怎么能有一个零? – 2009-12-03 20:02:17

+0

连续可以有多于2个零值吗? – DigitalRoss 2009-12-03 20:35:11

+0

是的......应该至少有一个非零值。 – 2009-12-04 07:55:39

回答

4

我的第一个想法是这样的事情,现在固定的零的任意序列的一般情况...

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

+1漂亮,优雅。 – 2009-12-03 21:13:32

+0

虽然这似乎不适用于所有情况... – dustmachine 2009-12-03 21:45:04

+0

正确的,取决于输入可能看起来像它可能需要一个环绕两条地图线 – DigitalRoss 2009-12-04 02:14:10

1

首先,将各元件配对与下一个和前一个元素

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 
} 

如果有超过2个尼尔斯连续,这是行不通的,所以把它放在一个循环中并不停地调用它,直到数组中没有更多的nils。

EDIT(约尔格W¯¯米塔格):你可以让这个更简洁,可读性用解构绑定和保护条款:

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简化。

2

你真的不提你用什么为阵,但也许被替换为0零将更有意义,因为如果你想利用平均值也不会影响结果或...

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

我和你在一起。这是简单的方法。更好的是: [1,2,3,nil,5] .map {| n | n || 0} – 2009-12-04 04:42:21

+0

你绝对没错Ben,你的版本更加清洁:-) – 2009-12-06 23:26:07

0

这是我的解决方案。它将适用于数组中的任意数量的nil,并且如果数组中的每个元素都是nil,则会优雅地失败。如果数组中的nil之前有非零和非零,它会在之前或之后随机选取。

init和安全检查:

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 

这已经与每个实例的测试实例(开始时为零,结束时为零,中间为双重零),并应适用于任何数组大小。

注意事项:

  • 附带数组中的第一元素“之前”的项目是最后一个元素
0

这是DigitalRoss解决方案直接复制,但处理的更边缘的情况下,比连续两个零。我敢肯定,DigitalRoss将能够更优雅做到这一点,并没有非idomatic红宝石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

无论如何它都会绕过while循环,所以根本不需要测试nil值的guard子句的部分并不真正需要, – 2009-12-03 21:15:56

2

这一切都取决于你以后想用数据做什么。它可以让你的感觉摆在平均值,但如果你有比较小的阵列和下降一点点乐趣,你可以去所有贝叶斯的东西,如下列:

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

这是@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

这让我感到这将是那么令人惊讶传播的最后一个非零值,而不是展望一个非零值:

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]