8387


こんにちは。

Ruby のプログラムで、日付と数値が入った配列のうち欠落した要素に対して補完をしたいと思っております。  
開始・終了日が決まっていますので、その間のデータがなければ配列に値を挿入、その後ソートして出力しております。  
目的は下記のプログラムで実現できているのですが、 Ruby っぽさがないと思っております。 Ruby
っぽさを求めるならこうだ！というのがあればよろしくお願いします。

[code]

    require "date"
    
    arr = [
             ["2015-03-01", 200],
             ["2015-03-02", 200],
             ["2015-03-04", 200],
             ["2015-03-05", 200],
             ["2015-03-06", 200],
           ]
    
    begin_date = "2015-03-01"
    end_date = "2015-03-07"
    
    (Date.parse(begin_date)..Date.parse(end_date)).each do |date|
    
        unless arr.find{|a| a.first == date.to_s} then
            arr << [date.to_s, nil]
        end
    end
    
    puts arr.sort_by{|a| a.first}
    
[/code]


CC BY-SA 3.0
2015-03-25T12:25:59.343
8383
2015-03-25T22:06:49.680
2015-03-25T15:08:24.830
4114
8475
question
ruby
配列の欠落したデータを補完したい


Rubyらしいかは分かりませんが、ちょっとだけ書き換えてみました。多分こっちの方が速いと思うのですが。

[code]

    require "date"
    
    arr = [
            ["2015-03-01", 200],
            ["2015-03-02", 200],
            ["2015-03-04", 200],
            ["2015-03-05", 200],
            ["2015-03-06", 200],
          ]
    
    begin_date = "2015-03-01"
    end_date = "2015-03-07"
    h = arr.to_h
    
    (Date.parse(begin_date)..Date.parse(end_date)).each do |date|
      h[date.to_s] = nil unless h[date.to_s]
    end
    
    puts h.sort
    
[/code]


CC BY-SA 3.0
2015-03-25T14:14:54.277
8387
2015-03-25T14:14:54.277
7900
8383
answer


Ruby っぽさが出てるかどうか分かりませんが。  
日付だけの配列を作って、配列の差を取ることで足りない配列要素の元を作ってみました。  
各日に1要素だけ、最終的に日付順ソートする、という前提であればこのようなものでも OK でしょう。

[code]

    require "date"
    
    arr = [
      ["2015-03-01", 200],
      ["2015-03-02", 200],
      ["2015-03-04", 200],
      ["2015-03-05", 200],
      ["2015-03-06", 200],
    ]
    
    begin_date = "2015-03-01"
    end_date = "2015-03-07"
    
    date_arr = arr.map(&:first) # 日付分だけの配列を作る
    cont_date_arr = [*Date.parse(begin_date)..Date.parse(end_date)].map(&:to_s) # 日付分だけの連続した配列
    miss_arr = cont_date_arr - date_arr # arrに無かった日付の配列
    arr.concat(miss_arr.map{ |d| [d, nil] }).sort_by!(&:first)
    puts arr
    
[/code]

`puts` だと `nil` が見えないのが気になりますが…。


CC BY-SA 3.0
2015-03-25T14:54:00.767
8391
2015-03-25T14:54:00.767
4114
8383
answer


面白そうなので参戦（？）します。

日付をキーにするHashを作り、最初から最後までループさせながらvalueを集めていくロジックにしました。  
キーがなければnilが返ります。

あと、以下のような点を変更しています。

  * メインロジックのメソッド化
  * 変数等のリネーム
  * DRYにできるところは極力DRYに
  * テストの追加

参考にしてみてください。

[code]

    require 'date'
    require 'minitest/autorun'
    
    class TestComplementBlank < Minitest::Test
      def complement_blank(data_array, date_from, date_to)
        data_table = data_array.to_h
        from_to = [date_from, date_to].map { |s| Date.parse(s) }
        Range.new(*from_to).map(&:to_s).map { |date| [date, data_table[date]] }
      end
    
      def test_complement_blank
        date_from = '2015-03-01'
        date_to = '2015-03-07'
        data_array = [
            ['2015-03-01', 200],
            ['2015-03-02', 200],
            ['2015-03-04', 200],
            ['2015-03-05', 200],
            ['2015-03-06', 200]
        ]
        expected = [
            ['2015-03-01', 200],
            ['2015-03-02', 200],
            ['2015-03-03', nil],
            ['2015-03-04', 200],
            ['2015-03-05', 200],
            ['2015-03-06', 200],
            ['2015-03-07', nil]
        ]
        assert_equal expected, complement_blank(data_array, date_from, date_to)
      end
    end
    
[/code]

### 追記

sanadanさんの回答は面白いですね。  
Hash#sortがそんなふうに使えるとは知りませんでした。

sanadanさんの回答をベースにするとこんな書き方もでます。

[code]

    def complement_blank(data_array, date_from, date_to)
      from_to = [date_from, date_to].map { |s| Date.parse(s) }
      Range.new(*from_to)
        .each_with_object(data_array.to_h) { |date, h| h[date.to_s] ||= nil }
        .sort
    end
    
[/code]


CC BY-SA 3.0
2015-03-25T20:27:34.570
8397
2015-03-25T21:23:19.933
2015-03-25T21:23:19.933
85
85
8383
answer

[code]

    require "date"
    
    arr = [
         ["2015-03-01", 200],
         ["2015-03-02", 200],
         ["2015-03-04", 200],
         ["2015-03-05", 200],
         ["2015-03-06", 200],
       ]
    
    begin_date = "2015-03-01"
    end_date = "2015-03-07"
    
    hash = {}
    arr_hash = Hash[*arr.flatten]
    (Date.parse(begin_date)..Date.parse(end_date)).each do |date|
      hash[date.to_s] = arr_hash[date.to_s]
    end
    p hash.to_a
    
[/code]

実行例：

[code]

    $ ruby arr.rb
    [["2015-03-01", 200], ["2015-03-02", 200], ["2015-03-03", nil], ["2015-03-04", 200], ["2015-03-05", 200], ["2015-03-06", 200], ["2015-03-07", nil]]
    
[/code]


CC BY-SA 3.0
2015-03-25T22:06:49.680
8398
2015-03-25T22:06:49.680
2987
8383
answer