ruby: 特別な配列を任意の長い 3 項 if...elsif...else ステートメント (または 3 項式) として評価します

アソーネ・トゥヒド

次のような配列に意思決定データを保存しています[condition, result, ..., condition, result, default]。したがって、基本的には三項式 ( c ? r : ... c ? r : d) であり、次のコードでそれらを評価しています。

class Array
  def ternary &block
    # the block checks if a condition is true
    i = 0
    while true
      if i % 2 == 1 or i == length - 1
        return self[i]
      else
        i += yield(self[i]) ? 1 : 2
      end
    end
  end
end

['false', 0, 'true', 1, 'true', 2, 3].ternary {|i| i == 'true'}
  # => 1
[1, 2, 3, 4, 5].ternary {|i| i > 6}
  # => 5 (defaults to last value because all conditions failed)

これを行うためのより高速な組み込みの方法があるのか​​、それともこのコードをどのように改善できるのか疑問に思っています。

注: 任意に多くの条件があり、動作する[only_possible_answer]はずです

編集:これまでの回答(同じ配列の1 000 000回以上の反復をテストしました):

セットアップ

flat = ['false', 0, 'false', 1, 'false', 2, 'false', 3, 'false', 4, 'true', 5, 6]
nested = [['false', 0], ['false', 1], ['false', 2], ['false', 3], ['false', 4], ['true', 5], [6]]
Option = Struct.new :condition, :result
Default = Struct.new :result
class Default
  def call; self; end
  # otherwise:
  # undefined method ‘call’ for #<struct Default result=whatever>
end
options = [Option.new('false', 0), Option.new('false', 1), Option.new('false', 2), Option.new('false', 3), Option.new('false', 4), Option.new('true', 5)]

class Array
  def ternary_flat_slow
    i = 0
    while true
      if i % 2 == 1 or i == length - 1
        return self[i]
      else
        i += yield(self[i]) ? 1 : 2
      end
    end
  end
  def ternary_flat_fast # by @illusionist
    index = 0
    index += 2 until (index >= self.length - 1) || yield(self[index]) 
    return self[index+1] unless index == self.length - 1 
    self.last
  end
  def ternary_nested
    find {|i| i.length == 1 or yield i[0]} .last
  end
  def ternary_options default # by @ReinHenrichs
    find(default) {|i| yield i} .result
  end
  def case_when_then_else(&block) # by @Amadan
    each_slice(2).find { |x|
      x.size == 1 || (block_given? ? block[x[0]] : x[0])
    }&.last
  end
end

require 'benchmark'

Benchmark.bmbm(9) do |i|
  i.report('flat slow') { 1000000.times { flat.ternary_flat_slow {|con| con == 'true' }}}
  i.report('flat fast') { 1000000.times { flat.ternary_flat_fast {|con| con == 'true' }}}
  i.report('   nested') { 1000000.times { nested.ternary_nested {|con| con == 'true' }}}
  i.report('  options') { 1000000.times { options.ternary_options(Default.new(6)) {|con| con.condition == 'true' }}}
  i.report('  c_w_t_e') { 1000000.times { flat.case_when_then_else {|con| con == 'true' }}}
end

結果

Rehearsal ---------------------------------------------
flat slow   4.510000   0.030000   4.540000 (  4.549424) # original
flat fast   3.600000   0.030000   3.630000 (  3.656058) # @illusionist
   nested   6.920000   0.080000   7.000000 (  7.252300) # me (as suggested)
  options   7.030000   0.050000   7.080000 (  7.130508) # @ReinHenrichs
  c_w_t_e  19.320000   0.140000  19.460000 ( 19.593633) # @Amadan
----------------------------------- total: 41.710000sec

                user     system      total        real
flat slow   4.290000   0.030000   4.320000 (  4.339875) # original
flat fast   3.360000   0.020000   3.380000 (  3.401809) # @illusionist
   nested   6.180000   0.040000   6.220000 (  6.258939) # me (as suggested)
  options   6.640000   0.040000   6.680000 (  6.704991) # @ReinHenrichs
  c_w_t_e  18.340000   0.120000  18.460000 ( 18.548176) # @Amadan

@illusionist の答えが最も速く、速度が最大の関心事です。

イリュージョニスト

全回答中最速

私の解決策

require 'minitest/autorun'
class OldArray < Array
  def ternary &block
    # the block checks if a condition is true
    i = 0
    while true
      if i % 2 == 1 or i == length - 1
        return self[i]
      else
        i += yield(self[i]) ? 1 : 2
      end
    end
  end
end

class NewArray < Array
  def ternary
    index = 0

    # Loop until you find item satisfying the condition skipping one item
    index += 2 until (index >= self.length - 1) || yield(self[index]) 

    # return the next value unless its the last/default item
    return self[index+1] unless index == self.length - 1 

    # return default item
    self.last
  end
end

class TestMe < Minitest::Test
  def test_array
    assert_equal 5, NewArray.new([1, 2, 3, 4, 5]).ternary {|i| i > 6}
    assert_equal 4, NewArray.new([1, 2, 3, 4, 5]).ternary {|i| i > 2}
    assert_equal 5, NewArray.new([1, 2, 3, 4, 5]).ternary {|i| puts "is: #{i}";i > 3}
    assert_equal 1, NewArray.new(['false', 0, 'true', 1, 'true', 2, 3]).ternary {|i|i == 'true'}
  end
end

require 'benchmark/ips'
Benchmark.ips do |x|
  x.report("My Technique") { NewArray.new([1, 2, 3, 4, 5]).ternary {|i| i > 6} }
  x.report("Your Technique") { OldArray.new([1, 2, 3, 4, 5]).ternary {|i| i > 6} }
end

基準

Warming up --------------------------------------
        My Technique    98.295k i/100ms
      Your Technique    73.008k i/100ms
Calculating -------------------------------------
        My Technique      1.292M (± 1.9%) i/s -      6.487M in   5.023137s
      Your Technique    891.204k (± 1.8%) i/s -      4.526M in   5.080896s

:Arrayテスト目的で新しいクラスを作成しましたただし、Arrayクラスを開いて動作を追加することはできます。それは問題なく動作します。

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ