Rubyで連番の配列を作る最も効率のよい方法はどれでしょうか?

投稿者: Anonymous

タイトルのとおりです。
Rubyで連番の配列を作る最も効率のよい方法はどれでしょうか?

[*1..100]
Array.new(100){|i| i} # 0 … 99
(1..100).to_a

パッと思いつくのは上のような感じですが、ほかにもっと良い方法とかありますでしょうか。
配列が大きくなってきた時のパフォーマンスの違いはどうでしょう?

自分でベンチマークとればいいのかも知れませんが、やっぱり質問すると知見が集まることが多いのでみなさまよろしくおねがいします。

解決

考えられる限りの0からnまで(含まないパターンと含むパターン)の配列を作るメソッドを用意し、ベンチマークを取る(公平にするために要素数が同じとし、含むパターンは初めから-1した数を渡すようにする)ようにしてみました。

integer_sequence.rb

# frozen_string_literal: true

module IntegerSequence
  module_function

  # from 0 to n exclude n

  def arr_new_itself(n)
    Array.new(n, &:itself)
  end

  def arr_new_block(n)
    Array.new(n) { |i| i }
  end

  def rng_l_ho_to_a(n)
    (0...n).to_a
  end

  def arr_l_exp_rng_l_ho(n)
    [*0...n]
  end

  def arr_m_rng_l_ho(n)
    Array(0...n)
  end

  def rng_new_ho_to_a(n)
    Range.new(0, n, true).to_a
  end

  def arr_l_exp_rng_new_ho(n)
    [*Range.new(0, n, true)]
  end

  def arr_m_rng_new_ho(n)
    Array(Range.new(0, n, true))
  end

  def times_to_a(n)
    n.times.to_a
  end

  def rng_l_inf_take(n)
    (0..).take(n)
  end

  def rng_l_inf_ho_take(n)
    (0...).take(n)
  end

  # from 0 to m include m

  def rng_l_to_a(m)
    (0..m).to_a
  end

  def arr_l_exp_rng_lit(m)
    [*0..m]
  end

  def arr_m_rng_lit(m)
    Array(0..m)
  end

  def rng_new_to_a(m)
    Range.new(0, m).to_a
  end

  def arr_l_exp_rng_new(m)
    [*Range.new(0, m)]
  end

  def arr_m_rng_new(m)
    Array(Range.new(0, m))
  end

  def upto_to_a(m)
    0.upto(m).to_a
  end

  def step_to_a(m)
    0.step(m).to_a
  end
end

if $0 == __FILE__
  require 'minitest/autorun'
  include IntegerSequence
  describe IntegerSequence do
    before do
      @n = 100
      @m = @n - 1
      @a = ([email protected]).to_a
    end

    it 'arr_new_itself' do
      _(arr_new_itself(@n)).must_equal @a
    end

    it 'arr_new_block' do
      _(arr_new_block(@n)).must_equal @a
    end

    it 'rng_l_ho_to_a' do
      _(rng_l_ho_to_a(@n)).must_equal @a
    end

    it 'arr_l_exp_rng_l_ho' do
      _(arr_l_exp_rng_l_ho(@n)).must_equal @a
    end

    it 'arr_m_rng_l_ho' do
      _(arr_m_rng_l_ho(@n)).must_equal @a
    end

    it 'rng_new_ho_to_a' do
      _(rng_new_ho_to_a(@n)).must_equal @a
    end

    it 'arr_l_exp_rng_new_ho' do
      _(arr_l_exp_rng_new_ho(@n)).must_equal @a
    end

    it 'arr_m_rng_new_ho' do
      _(arr_m_rng_new_ho(@n)).must_equal @a
    end

    it 'times_to_a' do
      _(times_to_a(@n)).must_equal @a
    end

    it 'rng_l_inf_take' do
      _(rng_l_inf_take(@n)).must_equal @a
    end

    it 'rng_l_inf_ho_take' do
      _(rng_l_inf_ho_take(@n)).must_equal @a
    end

    it 'rng_l_to_a' do
      _(rng_l_to_a(@m)).must_equal @a
    end

    it 'arr_l_exp_rng_lit' do
      _(arr_l_exp_rng_lit(@m)).must_equal @a
    end

    it 'arr_m_rng_lit' do
      _(arr_m_rng_lit(@m)).must_equal @a
    end

    it 'rng_new_to_a' do
      _(rng_new_to_a(@m)).must_equal @a
    end

    it 'arr_l_exp_rng_new' do
      _(arr_l_exp_rng_new(@m)).must_equal @a
    end

    it 'arr_m_rng_new' do
      _(arr_m_rng_new(@m)).must_equal @a
    end

    it 'upto_to_a' do
      _(upto_to_a(@m)).must_equal @a
    end

    it 'step_to_a' do
      _(step_to_a(@m)).must_equal @a
    end
  end
end

bm_make_arr.rb

# frozen_string_literal: true

require 'benchmark/ips'
require_relative 'integer_sequence'

include IntegerSequence

# make arr from 1 to `n` (without `n`)
def bench_make_arr(n)
  m = n - 1
  Benchmark.ips do |x|
    # n
    x.report('arr_new_itself') { arr_new_itself(n) }
    x.report('arr_new_block') { arr_new_block(n) }
    x.report('rng_l_ho_to_a') { rng_l_ho_to_a(n) }
    x.report('arr_l_exp_rng_l_ho') { arr_l_exp_rng_l_ho(n) }
    x.report('arr_m_rng_l_ho') { arr_m_rng_l_ho(n) }
    x.report('rng_new_ho_to_a') { rng_new_ho_to_a(n) }
    x.report('arr_l_exp_rng_new_ho') { arr_l_exp_rng_new_ho(n) }
    x.report('arr_m_rng_new_ho') { arr_m_rng_new_ho(n) }
    x.report('times_to_a') { times_to_a(n) }
    x.report('rng_l_inf_take') { rng_l_inf_take(n) }
    x.report('rng_l_inf_ho_take') { rng_l_inf_ho_take(n) }
    # m
    x.report('rng_l_to_a') { rng_l_to_a(m) }
    x.report('arr_l_exp_rng_lit') { arr_l_exp_rng_lit(m) }
    x.report('arr_m_rng_lit') { arr_m_rng_lit(m) }
    x.report('rng_new_to_a') { rng_new_to_a(m) }
    x.report('arr_l_exp_rng_new') { arr_l_exp_rng_new(m) }
    x.report('arr_m_rng_new') { arr_m_rng_new(m) }
    x.report('upto_to_a') { upto_to_a(m) }
    x.report('step_to_a') { step_to_a(m) }

    x.compare!
  end
end

if $0 == __FILE__
  n = ARGV[0].to_i
  n = 100 if n.zero?
  puts "Ruby description: #{RUBY_DESCRIPTION}"
  puts "size: #{n}"
  bench_make_arr(n)
end

gem install benchmark-ipsをしておいた上で、二つを同じディレクトリに置いてruby bm_make_arry.rbでベンチマークが取ることができます。引数でnを指定できます(デフォルトは100)。なお、2.6から使用できる0..0...いうリテラルを使用しているため、2.6以上必須です。2.5以下で行う場合はコメントアウトするなり個別に対応しておいてください。

手元のUbuntu(WSL上)で2.6.5と2.7.0-preview3のJIT有り/無しでn=100で試したところ、(0..).take(n)(0...).take(n)が同速一位で、次点はn.times.to_aを初めとしたto_a系が並ぶという結果でした。JITの有無はあまり影響がありませんでした。stepArray.newはかなり遅かったです。数を増やしたりしたら、また違う結果が出るかも知れませんが、あとは自分で試して見てください。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *