Per fare l'equivalente della comprensione degli elenchi di Python, sto facendo quanto segue:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
C'è un modo migliore per farlo ... forse con una chiamata al metodo?
Per fare l'equivalente della comprensione degli elenchi di Python, sto facendo quanto segue:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
C'è un modo migliore per farlo ... forse con una chiamata al metodo?
Risposte:
Se vuoi davvero, puoi creare un metodo Array # comprendere come questo:
class Array
def comprehend(&block)
return self if block.nil?
self.collect(&block).compact
end
end
some_array = [1, 2, 3, 4, 5, 6]
new_array = some_array.comprehend {|x| x * 3 if x % 2 == 0}
puts new_array
Stampe:
6
12
18
Probabilmente lo farei semplicemente come hai fatto tu.
[nil, nil, nil].comprehend {|x| x }
quale ritorna []
.
compact!
restituisce zero invece dell'array quando nessun elemento viene modificato, quindi non penso che funzioni.
Che ne dici di:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Leggermente più pulito, almeno secondo i miei gusti, e secondo un rapido test di benchmark circa il 15% più veloce della tua versione ...
some_array.map{|x| x * 3 unless x % 2}.compact
, che è probabilmente più leggibile / rubino.
unless x%2
non ha effetto poiché 0 è vero in ruby. Vedi: gist.github.com/jfarmer/2647362
Ho fatto un rapido benchmark confrontando le tre alternative e map-compact sembra davvero essere l'opzione migliore.
require 'test_helper'
require 'performance_test_help'
class ListComprehensionTest < ActionController::PerformanceTest
TEST_ARRAY = (1..100).to_a
def test_map_compact
1000.times do
TEST_ARRAY.map{|x| x % 2 == 0 ? x * 3 : nil}.compact
end
end
def test_select_map
1000.times do
TEST_ARRAY.select{|x| x % 2 == 0 }.map{|x| x * 3}
end
end
def test_inject
1000.times do
TEST_ARRAY.inject([]) {|all, x| all << x*3 if x % 2 == 0; all }
end
end
end
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/performance/list_comprehension_test.rb" -- --benchmark
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
ListComprehensionTest#test_inject (1230 ms warmup)
wall_time: 1221 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.ListComprehensionTest#test_map_compact (860 ms warmup)
wall_time: 855 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.ListComprehensionTest#test_select_map (961 ms warmup)
wall_time: 955 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.
Finished in 66.683039 seconds.
15 tests, 0 assertions, 0 failures, 0 errors
reduce
in questo benchmark (vedere stackoverflow.com/a/17703276 ).
inject
==reduce
Sembra esserci una certa confusione tra i programmatori Ruby in questo thread riguardo a cosa sia la comprensione delle liste. Ogni singola risposta presuppone una matrice preesistente da trasformare. Ma il potere della comprensione delle liste risiede in un array creato al volo con la seguente sintassi:
squares = [x**2 for x in range(10)]
Il seguente sarebbe un analogo in Ruby (l'unica risposta adeguata in questo thread, AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
Nel caso precedente, sto creando un array di numeri interi casuali, ma il blocco potrebbe contenere qualsiasi cosa. Ma questa sarebbe una comprensione della lista Ruby.
Ho discusso questo argomento con Rein Henrichs, che mi dice che la soluzione più performante è
map { ... }.compact
Ciò ha senso perché evita la creazione di array intermedi come con l'utilizzo immutabile di Enumerable#inject
ed evita la crescita dell'array, che causa l'allocazione. È generale come gli altri a meno che la tua raccolta non possa contenere elementi nulli.
Non l'ho confrontato con
select {...}.map{...}
È possibile che anche l'implementazione in C di Ruby Enumerable#select
sia molto buona.
Una soluzione alternativa che funzionerà in ogni implementazione e verrà eseguita in O (n) invece di O (2n) è:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
2
cose n
volte invece di 1
cose n
volte e poi un'altra 1
cosa n
volte :) Un importante vantaggio di inject
/ reduce
è che conserva tutti i nil
valori nella sequenza di input che è un comportamento più comprensibile per la lista
Ho appena pubblicato la gemma di comprensione su RubyGems, che ti consente di farlo:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
È scritto in C; l'array viene attraversato una sola volta.
Enumerable ha un grep
metodo il cui primo argomento può essere un predicato proc e il cui secondo argomento opzionale è una funzione di mappatura; quindi il seguente funziona:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Questo non è leggibile come un paio di altri suggerimenti (mi piace la select.map
gemma di comprensione semplice o istocratica di anoiaque), ma i suoi punti di forza sono che fa già parte della libreria standard ed è single-pass e non comporta la creazione di array intermedi temporanei e non richiede un valore fuori limite come quello nil
usato nei compact
suggerimenti -using.
Questo è più conciso:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
[1,2,3,4,5,6].select(&:even?).map(&3.method(:*))
Come ha detto Pedro, puoi fondere insieme le chiamate concatenate a Enumerable#select
e Enumerable#map
, evitando un attraversamento sugli elementi selezionati. Questo è vero perché Enumerable#select
è una specializzazione di piega o inject
. Ho postato una frettolosa introduzione all'argomento nel subreddit di Ruby.
Fondere manualmente le trasformazioni di array può essere noioso, quindi forse qualcuno potrebbe giocare con l' comprehend
implementazione di Robert Gamble per rendere questo select
/ map
pattern più carino.
Qualcosa come questo:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
Chiamalo:
lazy (1..6){|x| x * 3 if x.even?}
Che ritorna:
=> [6, 12, 18]
lazy
su Array e poi:(1..6).lazy{|x|x*3 if x.even?}
Questo è un modo per avvicinarsi a questo:
c = -> x do $*.clear
if x['if'] && x[0] != 'f' .
y = x[0...x.index('for')]
x = x[x.index('for')..-1]
(x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}")
x.insert(x.length, "end; $*")
eval(x)
$*)
elsif x['if'] && x[0] == 'f'
(x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << x")
x.insert(x.length, "end; $*")
eval(x)
$*)
elsif !x['if'] && x[0] != 'f'
y = x[0...x.index('for')]
x = x[x.index('for')..-1]
(x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}")
x.insert(x.length, "end; $*")
eval(x)
$*)
else
eval(x.split[3]).to_a
end
end
quindi fondamentalmente stiamo convertendo una stringa nella corretta sintassi ruby per loop, quindi possiamo usare la sintassi python in una stringa per fare:
c['for x in 1..10']
c['for x in 1..10 if x.even?']
c['x**2 for x in 1..10 if x.even?']
c['x**2 for x in 1..10']
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# [2, 4, 6, 8, 10]
# [4, 16, 36, 64, 100]
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
o se non ti piace il modo in cui appare la stringa o dover usare un lambda potremmo rinunciare al tentativo di rispecchiare la sintassi di Python e fare qualcosa del genere:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]
Ruby 2.7 ha introdotto filter_map
che praticamente raggiunge ciò che desideri (mappa + compatto):
some_array.filter_map { |x| x * 3 if x % 2 == 0 }
Puoi leggere di più al riguardo qui .
https://rubygems.org/gems/ruby_list_comprehension
plug spudorato per la mia gemma di comprensione della lista di Ruby per consentire la comprensione idiomatica della lista di Ruby
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
Penso che la più lista di comprensione sarebbe la seguente:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Poiché Ruby ci consente di posizionare il condizionale dopo l'espressione, otteniamo una sintassi simile alla versione Python della comprensione delle liste. Inoltre, poiché il select
metodo non include nulla che sia uguale a false
, tutti i valori nulli vengono rimossi dall'elenco risultante e non è necessaria alcuna chiamata a compact come sarebbe il caso se avessimo usato map
o collect
invece.