2

素数ふるいですが、エラトステネスのふるいではありません。

私はプログラミングとRuby全般に慣れていないので、書き方が悪いと感じています。これは私が Ruby で書いた 2 番目のプログラムにすぎませんが、可能な限り最適化したいと考えています。問題は、プログラムのパス/データ構造が理想的ではないことを知っていることを除いて、高速化するために何を変更する必要があるかをしっかりと理解していないことです.MAKEに取り組むための概念がないだけです.それらは理想的です

理想的な答えは、必ずしも「X を Y に変更する」とは限りませんが、この種の情報に関する適切なリソース、または効率に関する情報を導き出す方法を教えてくれれば、より役に立ちます。プログラムのさまざまな部分。

count = 0
x = 0
$results = Array.new []
inpt = []

class PrimeFun

  def initialize(x, y)

    array1 = (x..y).to_a
    array1.each do |n|

      if PrimeFun.primelogic(n%60, n) == 1
        array1.delete_if { |p| p % n == 0}
        $results << n
      elsif n == 2 || n == 3 || n == 5
        $results << n

      end
    end
  end


  def self.primelogic(r, n)

    @prime = case r
      when 1, 13, 17, 29, 37, 41, 49, 53
        formulaone(n)
      when 7, 19, 31, 43
        formulatwo(n)
      when 11, 23, 47, 59
        formulathree(n)
      else -1

    end  
  end


  def self.formulaone(n)
   @x = 1
   @y = -1

   until 4*(@x**2) >= n
      @y = -@y if Math.sqrt(n-(4*(@x**2))).floor - Math.sqrt(n-(4*(@x**2))) == 0  
     @x += 1

   end
   @y
  end

  def self.formulatwo(n)
    @x = 1
    @y = -1

    until 3*(@x**2) >= n
      @y = -@y if Math.sqrt(n-(3*(@x**2))).floor - Math.sqrt(n-(3*(@x**2))) == 0
      @x += 1

    end
    @y
  end

  def self.formulathree(n)
    @x = 1
    @y = -1

    until 3*(@x**2) >= n
      @y = -@y if Math.sqrt(((@x**2)+n)/3).floor - Math.sqrt(((@x**2)+n)/3) == 0 && @x > @y
      @x += 1

    end
   @y
  end

end


x = STDIN.gets.to_i

while count < x
  inpt << STDIN.gets.chomp
  count += 1
end

inpt.each do |n|
  a = n.split(" ").map { |a| a.to_i }
  PrimeFun.new(a[0], a[1])
  $results << ""
end

puts $results
4

2 に答える 2

5

メソッドの (異なるバージョンの) 実行時間を測定するには、Ruby 標準ライブラリに含まれるBenchmarkモジュールに慣れておく必要があります。以下のコードの提案を Benchmark で実行したことはありません。これらは、コードの速度と読みやすさを向上させる方法について頭の中で思いついた簡単なアイデアにすぎません。気軽にベンチマークして、結果を報告してください。:-)

ボトルネックを見つけるためにコードをプロファイリングすることも良い考えです。総実行時間に大きく貢献していないコードの部分を最適化するのに何時間も費やしても意味がありません。これに役立つ優れたツールについては、 ruby​​-prof gem を確認してください。


次に、コードを簡単に見て、改善のためのいくつかの提案を行います。

使用している実際のアルゴリズムを考慮せずに、最初にすべきことは、同じ値を何度も何度も再計算するコードの傾向を排除することです。

また、ローカル変数がうまく機能するインスタンス変数 (@x、@y など) を使用しているようです。同じクラスのインスタンス メソッド内からのみ呼び出されるクラス メソッドの使用は言うまでもありません。それらもインスタンスメソッドに変換する必要があります。(これらは実際には最適化のヒントではなく、Ruby コードを改善する方法に関する提案です。)

例として、次の方法を取り上げます。

def self.formulaone(n)
  @x = 1
  @y = -1
  until 4*(@x**2) >= n
    @y = -@y if Math.sqrt(n-(4*(@x**2))).floor - Math.sqrt(n-(4*(@x**2))) == 0  
    @x += 1
  end
  @y
end

ループでは、式を4*(@x**2) 3回計算しています。1 つあれば十分なので、結果を一時ローカル変数fsqに格納します。また、ループ内で同じ数の平方根を 2 回計算しています。再度、値を一時変数rootに格納し、それを使用します。

def formulaone_b(n)
  x = 1
  y = -1
  until (fsq = 4*(x**2)) >= n
    root = Math.sqrt(n - fsq)
    y = -y if root.floor - root == 0
    x += 1
  end
  y
end

それは良いスタートになるはずです。

おそらく最適化ではありませんが、事前に x の範囲を計算してから、 eachを使用してそれを反復処理することで、コードを少しきれいにすることができます。

def formulaone_c(n)
  y = -1
  (1..Math.sqrt(n / 4)).each do |x|
    root = Math.sqrt(n - 4*(x**2))
    y = -y if root.floor == root # See below
  end
  y
end

上記のコードでは、比較root.floor - root == 0を単純だが同等の比較に置き換え、root.floor == root不要な減算を 1 つ削除しました。

n - 4*(x**2)もう 1 つのアイデア:反復ごとに計算する代わりに、この値がx * 8 + 4ステップごとに減少することに気付くことで、わずかな速度を得ることができるので、ヘルパー変数dを使用して前の式の値を次のように更新します。

def formulaone_d(n)
  y = -1
  d = n - 4 # Value of n - 4*(x**2) when x = 1
  (1..Math.sqrt(n / 4)).each do |x|
    root = Math.sqrt(d)
    y = -y if root.floor == root
    d -= x * 8 + 4 # Value of n - 4*(x**2) after x increases
  end
  y
end
于 2012-05-08T19:00:28.607 に答える
3

正しさ

まず、コードが正しくありません。

def self.formulathree(n)
  @x = 1
  @y = -1

  until 3*(@x**2) >= n
    @y = -@y if Math.sqrt(((@x**2)+n)/3).floor - Math.sqrt(((@x**2)+n)/3) == 0 && @x > @y
    @x += 1

  end
  @y
end

@y未満であるかどうかは重要ではなく、@xそれは常に真実です。@y = ±1@x = 1@y = -1 < 1

あなたが興味を持っているのは表現の数です

n = 3*a^2 - b^2

整数でa > b > 0。さて、a^2 = (n + b^2)/3あなたが欲しい

(n + b^2)/3 > b^2
n + b^2 > 3*b^2
n > 2*b^2

ではありませんn > 3*b^2(ここbを表し@xます)。例えば、

143 = 11* 13 = 3*7^2 - 2^2 = 3*8^2 - 7^2

ただし、3 * 7 ^ 2 = 147> 143であるため、考慮されないため、143はおよび@x = 7によってプライムと見なされます。formulathree

179 = 3*9^2 - 8^2

プライムとは見なされませんが、プライムとは見なされません3*8^2 = 192 > 179

デバッグのために考慮nされたそれぞれを出力すると、別の問題が明らかになります。initialize

array1 = (x..y).to_a
array1.each do |n|

  if PrimeFun.primelogic(n%60, n) == 1
    array1.delete_if { |p| p % n == 0}

array1.each多かれ少なかれです

for(index = 0; index < array1.length; ++i)

ただし、の倍数を削除すると、それ自体nも削除nされるため、インデックスに移動した直後の要素nは持っていたため、スキップされます。nより大きいの倍数のみを削除することで、これを修正できますn

array1.delete_if { |p| p > n && p % n == 0 }

パフォーマンス

パフォーマンスの大きな問題はアルゴリズムです。を呼び出すinitialize(2,n)と、すべての素数について、配列をトラバースし、試行除算によって倍数を削除します。各素数を小さい素数(2、3、5を除く)で割って、配列から削除するかどうかを確認します。これは悪名高いターナーの「ふるい」であり、その複雑さはO((n / log n)^ 2)であり、ほぼ2次式です。2、3、5の倍数がより大きな素因数を持たない限り、配列から2、3、5の倍数を削除することすらしないため、複雑さはさらにわずかに悪化する可能性があります。

より良いアルゴリズムを選択する前に、マイクロ最適化は努力する価値がありません。

次の問題は、を使用した素数性の決定ですformulaX。配列から2、3、および5の倍数も削除した場合、テストは必要ありません。考慮されるすべての数値は、試行割り算ごとの既知の素数になります。そうしないので、候補の除算性を2、3、または5でチェックする方が、よりもはるかに高速ですprimelogic

primelogicアトキンのふるいにも使用されるロジックを使用して素数性を決定しますが、すべての数値を個別にテストするため、各数値のテストnはO(√n)です。平方根の計算は除算よりもはるかに複雑であるため、試行除算による素数テストよりも時間がかかります。

于 2012-05-08T22:20:24.487 に答える