Aquí hay una función en Ruby para encontrar si 2 números únicos en una matriz suman una suma:

def sum_eq_n? (arr, n)
    return true if arr.empty? && n == 0

    p "first part array:" + String(arr.product(arr).reject { |a,b| a == b })
    puts "\n"

    p "first part bool:" + String(arr.product(arr).reject { |a,b| a == b }.any?)
    puts "\n"

    p "second part:" + String(arr.product(arr).reject { |a,b| a + b == n } )
    puts "\n"

    result = arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }
    return result
end

#define inputs
l1 = [1, 2, 3, 4, 5, 5]
n = 10

#run function
print "Result is: " + String(sum_eq_n?(l1, n))

Estoy confundido sobre cómo funciona el cálculo para producir resultados. Como puede ver, he dividido la función en algunas partes para visualizar esto. He investigado y entiendo los métodos .reject y .any? individualmente.

Sin embargo, todavía estoy confundido sobre cómo encaja todo en el 1 liner. ¿Cómo se evalúan los 2 bloques en combinación? Solo he encontrado ejemplos con .reject con 1 bloque de código después. ¿Se aplica .reject a ambos? También pensé que podría haber un AND implícito entre los 2 bloques de código, pero intenté agregar un tercer bloque ficticio y falló, por lo que en este momento no estoy realmente seguro de cómo funciona.

1
Birdman 9 may. 2019 a las 23:43

3 respuestas

La mejor respuesta

Puede interpretar la expresión a través de estas sustituciones equivalentes:

# orig
arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }

# same as
pairs = arr.product(arr)
pairs.reject { |a,b| a == b }.any? { |a,b| a + b == n }

# same as
pairs = arr.product(arr)
different_pairs = pairs.reject { |a,b| a == b }
different_pairs.any? { |a,b| a + b == n }

Cada bloque es un argumento para el método respectivo respectivo: uno para reject y otro para any?. Se evalúan en orden y se combinan no . Las partes que componen la expresión se pueden envolver entre paréntesis para mostrar esto:

((arr.product(arr)).reject { |a,b| a == b }).any? { |a,b| a + b == n }

# broken up lines:
(
  (
    arr.product(arr)          # pairs
  ).reject { |a,b| a == b }   # different_pairs
).any? { |a,b| a + b == n }

Los bloques en Ruby son argumentos de método

Los bloques en Ruby son estructuras de sintaxis de primera clase para pasar los cierres como argumentos a los métodos. Si está más familiarizado con los conceptos orientados a objetos que con los funcionales, aquí hay un ejemplo de un objeto (tipo de) que actúa como un cierre:

class MultiplyPairStrategy
  def perform(a, b)
    a * b
  end
end

def convert_using_strategy(pairs, strategy)
  new_array = []
  for pair in pairs do
    new_array << strategy.perform(*pair)
  end
  new_array
end

pairs = [
  [2, 3],
  [5, 4],
]

multiply_pair = MultiplyPairStrategy.new
convert_using_strategy(pairs, multiply_pair) # => [6, 20]

Que es lo mismo que:

multiply_pair = Proc.new { |a, b| a * b }
pairs.map(&multiply_pair)

Que es lo mismo que el más idiomático:

pairs.map { |a, b| a * b }
2
Kache 11 may. 2019 a las 05:06

El resultado devuelto del primer método es devuelto y utilizado por el segundo método.

Éste:

result = arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }

Es funcionalidad equivalente a:

results = arr.product(arr).reject { |a,b| a == b} # matrix of array pairs with identical values rejected
result = results.any? { |a,b| a + b == n }  #true/false

Esto se puede visualizar mejor en pry (comentarios míos)

[1] pry(main)> arr = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
[2] pry(main)> n = 10
=> 10
[3] pry(main)> result_reject = arr.product(arr).reject { |a,b| a == b } # all combinations of array elements, with identical ones removed
=> [[1, 2],
 [1, 3],
 [1, 4],
 [1, 5],
 [1, 5],
 [2, 1],
 [2, 3],
 [2, 4],
 [2, 5],
 [2, 5],
 [3, 1],
 [3, 2],
 [3, 4],
 [3, 5],
 [3, 5],
 [4, 1],
 [4, 2],
 [4, 3],
 [4, 5],
 [4, 5],
 [5, 1],
 [5, 2],
 [5, 3],
 [5, 4],
 [5, 1],
 [5, 2],
 [5, 3],
 [5, 4]]
[4] pry(main)> result_reject.any? { |a,b| a + b == n } # do any of the pairs of elements add together to equal ` n` ?
=> false
[5] pry(main)> arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }  # the one liner
=> false
2
Brent Smith 9 may. 2019 a las 21:00

Cada operación "encadena" a la siguiente, que se visualiza así:

arr.product(arr).reject { |a,b| a == b }.any? { |a,b| a + b == n }
|--|------A----->-----------B----------->-------------C----------|

Donde la parte A, que llama a .product(arr), se evalúa como un objeto. Este objeto tiene un método reject que se llama posteriormente, y este objeto tiene un método any? que se llama a su vez. Es una versión elegante de a.b.c.d donde se usa una llamada para generar un objeto para una llamada posterior.

Lo que no es aparente de eso es el hecho de que product devuelve un Enumerator , que es un objeto que puede usarse para obtener los resultados, pero no es el resultado real per se. Es más como una intención de devolver los resultados, y una capacidad de obtenerlos de muchas maneras. Estos se pueden encadenar para obtener el producto final deseado.

Como nota, este código se puede reducir a:

arr.repeated_permutation(2).map(&:sum).include?(n)

Donde el repeated_permutation le ofrece todas las combinaciones de números de 2 dígitos sin números duplicados. Esto se puede ampliar fácilmente a N dígitos cambiando ese parámetro. include? prueba si el objetivo está presente.

Si está trabajando con matrices grandes, es posible que desee optimizar ligeramente esto:

arr.repeated_permutation(2).lazy.map(&:sum).include?(n)

Donde eso se detendrá en el primer partido encontrado y evitará sumas adicionales. La llamada lazy tiene el efecto de propagar valores individuales hasta el final de la cadena en lugar de que cada etapa de la cadena se ejecute hasta su finalización antes de reenviar a la siguiente.

La idea de lazy es una de las cosas interesantes sobre Enumerable. Puede controlar cómo fluyen los valores a través de esas cadenas.

2
tadman 10 may. 2019 a las 01:53