เพื่อให้เทียบเท่ากับความเข้าใจในรายการ Python ฉันกำลังทำสิ่งต่อไปนี้:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
มีวิธีที่ดีกว่าในการทำเช่นนี้หรือไม่ ...
เพื่อให้เทียบเท่ากับความเข้าใจในรายการ Python ฉันกำลังทำสิ่งต่อไปนี้:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
มีวิธีที่ดีกว่าในการทำเช่นนี้หรือไม่ ...
คำตอบ:
หากคุณต้องการจริงๆคุณสามารถสร้าง Array # comprehend method ดังนี้:
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
พิมพ์:
6
12
18
ฉันอาจจะทำแบบที่คุณทำ
[nil, nil, nil].comprehend {|x| x }
[]
compact!
ส่งคืนค่า nil แทนอาร์เรย์เมื่อไม่มีการเปลี่ยนแปลงรายการดังนั้นฉันไม่คิดว่าจะได้ผล
วิธีการแข่งขัน:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
สะอาดกว่าเล็กน้อยอย่างน้อยก็ตามรสนิยมของฉันและจากการทดสอบเกณฑ์มาตรฐานอย่างรวดเร็วเร็วกว่ารุ่นของคุณประมาณ 15% ...
some_array.map{|x| x * 3 unless x % 2}.compact
ซึ่งเป็นเนื้อหาที่อ่านได้ง่ายกว่า / ruby-esque
unless x%2
ไม่มีผลเนื่องจาก 0 เป็นความจริงในทับทิม ดู: gist.github.com/jfarmer/2647362
ฉันสร้างเกณฑ์มาตรฐานอย่างรวดเร็วโดยเปรียบเทียบทางเลือกทั้งสามและขนาดกะทัดรัดของแผนที่ดูเหมือนจะเป็นตัวเลือกที่ดีที่สุด
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
ในเกณฑ์มาตรฐานนี้เช่นกัน (ดูstackoverflow.com/a/17703276 )
inject
==reduce
ดูเหมือนว่าจะมีความสับสนระหว่างโปรแกรมเมอร์ Ruby ในหัวข้อนี้เกี่ยวกับความเข้าใจในรายการ ทุกการตอบกลับจะถือว่าอาร์เรย์ที่มีอยู่ก่อนหน้าบางส่วนที่จะแปลง แต่พลังของความเข้าใจในรายการอยู่ในอาร์เรย์ที่สร้างขึ้นได้ทันทีด้วยไวยากรณ์ต่อไปนี้:
squares = [x**2 for x in range(10)]
ต่อไปนี้จะเป็นอะนาล็อกใน Ruby (คำตอบเดียวที่เพียงพอในเธรดนี้ AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
ในกรณีข้างต้นฉันกำลังสร้างอาร์เรย์ของจำนวนเต็มแบบสุ่ม แต่บล็อกอาจมีอะไรก็ได้ แต่นี่จะเป็นการเข้าใจรายชื่อ Ruby
ฉันได้พูดคุยในหัวข้อนี้กับ Rein Henrichs ซึ่งบอกฉันว่าวิธีแก้ปัญหาที่มีประสิทธิภาพดีที่สุดคือ
map { ... }.compact
สิ่งนี้สมเหตุสมผลเพราะหลีกเลี่ยงการสร้างอาร์เรย์กลางเช่นเดียวกับการใช้งานที่ไม่เปลี่ยนรูปEnumerable#inject
และหลีกเลี่ยงการขยาย Array ซึ่งทำให้เกิดการจัดสรร โดยทั่วไปจะเหมือนกับของอื่น ๆ เว้นแต่คอลเลกชันของคุณจะมีองค์ประกอบศูนย์
ฉันไม่ได้เปรียบเทียบสิ่งนี้กับ
select {...}.map{...}
เป็นไปได้ว่าการใช้งาน C ของ Ruby Enumerable#select
ก็ดีมากเช่นกัน
ทางเลือกอื่นที่จะทำงานในทุกการใช้งานและทำงานใน O (n) แทนเวลา O (2n) คือ:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
2
สิ่งที่n
ครั้งแทน1
สิ่งที่n
ครั้งและหลังจากนั้นอีก1
สิ่งที่n
ครั้ง :) ข้อดีอย่างหนึ่งที่สำคัญของการinject
/ reduce
คือว่ามันจะเก็บรักษาใด ๆnil
ค่าในลำดับการป้อนข้อมูลที่เป็นพฤติกรรมรายการ comprehensionly มากขึ้น
ฉันเพิ่งเผยแพร่อัญมณีที่เข้าใจไปยัง RubyGems ซึ่งให้คุณทำสิ่งนี้:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
มันเขียนด้วย C; อาร์เรย์จะถูกส่งผ่านเพียงครั้งเดียว
Enumerable มีgrep
วิธีการที่อาร์กิวเมนต์แรกสามารถเป็นเพรดิเคต proc ได้และอาร์กิวเมนต์ที่สองที่เป็นทางเลือกคือฟังก์ชันการแม็ป ดังนั้นการทำงานต่อไปนี้:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
สิ่งนี้ไม่สามารถอ่านได้เหมือนกับคำแนะนำอื่น ๆ (ฉันชอบselect.map
อัญมณีที่เข้าใจง่ายของ anoiaque หรือฮิสโตรแครต) แต่จุดแข็งของมันคือมันเป็นส่วนหนึ่งของไลบรารีมาตรฐานอยู่แล้วและเป็นแบบ single-pass และไม่เกี่ยวข้องกับการสร้างอาร์เรย์กลางชั่วคราว และไม่ต้องการค่านอกขอบเขตเช่นเดียวกับที่nil
ใช้ในcompact
คำแนะนำการใช้งาน
มีความกระชับมากขึ้น:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
[1,2,3,4,5,6].select(&:even?).map(&3.method(:*))
[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact
=> [6, 12, 18]
ที่เหมาะกับฉัน นอกจากนี้ยังมีความสะอาด ใช่มันเหมือนกับmap
แต่ฉันคิดว่าcollect
ทำให้เข้าใจรหัสได้มากขึ้น
select(&:even?).map()
ดูดีขึ้นจริง ๆ หลังจากดูด้านล่าง
เช่นเดียวกับที่ Pedro กล่าวไว้คุณสามารถหลอมรวมการโทรที่ถูกล่ามโซ่เข้าด้วยกันEnumerable#select
และEnumerable#map
หลีกเลี่ยงการข้ามผ่านองค์ประกอบที่เลือก นี่เป็นเรื่องจริงเนื่องจากEnumerable#select
เป็นความเชี่ยวชาญในการพับหรือinject
. ฉันโพสต์แนะนำอย่างเร่งรีบสำหรับหัวข้อที่ Ruby subreddit
การแปลง Array ด้วยตนเองอาจเป็นเรื่องที่น่าเบื่อดังนั้นอาจมีใครบางคนสามารถเล่นกับcomprehend
การนำไปใช้ของ Robert Gamble เพื่อทำให้รูปแบบนี้select
/ map
รูปแบบสวยขึ้น
สิ่งนี้:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
เรียกมันว่า:
lazy (1..6){|x| x * 3 if x.even?}
ซึ่งผลตอบแทน:
=> [6, 12, 18]
lazy
Array แล้ว:(1..6).lazy{|x|x*3 if x.even?}
ทางออกอื่น แต่อาจไม่ใช่วิธีที่ดีที่สุด
some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }
หรือ
some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.push(x * 3) : nil }
นี่เป็นวิธีหนึ่งในการเข้าถึงสิ่งนี้:
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
ดังนั้นโดยพื้นฐานแล้วเรากำลังแปลงสตริงเป็นไวยากรณ์ทับทิมที่เหมาะสมสำหรับลูปจากนั้นเราสามารถใช้ไวยากรณ์ python ในสตริงเพื่อทำ:
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]
หรือถ้าคุณไม่ชอบลักษณะของสตริงหรือต้องใช้แลมบ์ดาเราสามารถละทิ้งความพยายามที่จะสะท้อนไวยากรณ์ของ python และทำสิ่งนี้:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]
https://rubygems.org/gems/ruby_list_comp ทำความเข้าใจ
ปลั๊กไร้ยางอายสำหรับอัญมณีความเข้าใจรายชื่อทับทิมของฉันเพื่อให้เข้าใจรายชื่อทับทิมสำนวน
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
ฉันคิดว่ารายการที่เข้าใจได้มากที่สุดน่าจะเป็นดังต่อไปนี้:
some_array.select{ |x| x * 3 if x % 2 == 0 }
เนื่องจาก Ruby อนุญาตให้เราวางเงื่อนไขหลังนิพจน์เราจึงได้ไวยากรณ์ที่คล้ายกับ Python เวอร์ชันของความเข้าใจรายการ นอกจากนี้เนื่องจากselect
เมธอดไม่รวมสิ่งที่false
มีค่าเท่ากับค่าศูนย์ทั้งหมดจะถูกลบออกจากรายการผลลัพธ์และไม่จำเป็นต้องเรียกคอมแพ็คอย่างที่เป็นในกรณีที่เราเคยใช้map
หรือใช้collect
แทน