Estoy almacenando algunos datos de toma de decisiones en matrices que se ven así: [condition, result, ..., condition, result, default], por lo que básicamente son expresiones ternarias (c ? r : ... c ? r : d) y las estoy evaluando como tales con este código:

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)

Me pregunto si hay una forma integrada más rápida de hacer esto o cómo puedo mejorar este código.

Nota: existen arbitrariamente muchas condiciones y [only_possible_answer] también debería funcionar

EDITAR: las respuestas hasta ahora (probado más de 1 000 000 iteraciones de la misma matriz):

Preparar

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

Resultados

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

Sin importar cuán "rebelde" sea, la respuesta de @ ilusionista es la más rápida y la velocidad es una preocupación principal

0
Asone Tuhid 21 jun. 2017 a las 01:58

3 respuestas

La mejor respuesta

Más rápida entre todas las respuestas

Mi solución

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

Punto de referencia

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

Nota : he creado nuevas clases a partir de Array con fines de prueba. Sin embargo, puede abrir la clase Array y agregar el comportamiento. Funcionará igual de bien.

1
illusionist 21 jun. 2017 a las 03:57

Iba a decir que esta es una solución Rubyish un poco más de lo que tiene, pero Rein tiene razón: todo el concepto no es muy Rubyish para empezar, y probablemente se beneficiaría de hacer una pregunta diferente y más específica.

module ArrayWithCase
  refine Array do
    def case_when_then_else(&block)
      each_slice(2).find { |x|
        x.size == 1 || (block_given? ? block[x[0]] : x[0])
      }&.last
    end
  end
end

module Test
  using ArrayWithCase
  p ['false', 0, 'true', 1, 'true', 2, 3].case_when_then_else {|i| i == 'true'}
  # => 1
  p [false, 0, true, 1, true, 2, 3].case_when_then_else
  # => 1
  p [1,2,3,4,5].case_when_then_else { |i| i > 6 }
  # => 6
  p ["only_possible_answer"].case_when_then_else(&:itself)
  # => "only_possible_answer"
  p [false, 4].case_when_then_else(&:itself)
  # => nil
end
0
Amadan 21 jun. 2017 a las 01:47

Creo que este es un Problema XY. Ha aplanado pares de valores en una matriz. Esto lo obliga a hacer un gran esfuerzo adicional para probar los índices de la matriz para recuperar la información originalmente contenida en los pares (qué elemento es el primero, qué elemento es el segundo). Todo este trabajo adicional podría evitarse simplemente no aplanando los pares en primer lugar, reteniendo así su conocimiento de qué elemento es cuál.

Debido a esto, voy a proporcionar una solución al problema que está tratando de resolver en lugar de decirle cómo implementar la solución que ha elegido.

Puede representar cada par condición / resultado como un objeto y luego find el primero que coincida:

# A condition/result pair
Option = Struct.new :condition, :result

# To hold a default value which duck-types with `result`.
Default = Struct.new :result

options = [Option.new('false', 0), Option.new('true', 1), Option.new('true', 2)]

options.find(Default.new(3)) {|opt| opt.condition == 'true'}.result
# => 1

Si eres alérgico a la creación de nuevos objetos, puedes usar matrices de 2 elementos como pares:

options = [['false', 0], ['true', 1], ['true', 2]]
options.find([nil, 3]) {|condition, result| condition == 'true'}.last
# => 1

Pero Ruby es un lenguaje orientado a objetos. Crear nuevos objetos para resolver nuevos problemas es exactamente para lo que está diseñado el lenguaje.

3
Rein Henrichs 21 jun. 2017 a las 01:59