Để làm tương đương với việc hiểu danh sách Python, tôi đang làm như sau:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Có cách nào tốt hơn để làm điều này ... có lẽ với một cuộc gọi phương thức?
Để làm tương đương với việc hiểu danh sách Python, tôi đang làm như sau:
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
Có cách nào tốt hơn để làm điều này ... có lẽ với một cuộc gọi phương thức?
Câu trả lời:
Nếu bạn thực sự muốn, bạn có thể tạo một phương thức Array # domains như sau:
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
Bản in:
6
12
18
Tôi có lẽ sẽ chỉ làm theo cách bạn đã làm.
[nil, nil, nil].comprehend {|x| x }
cái nào trả về []
.
compact!
trả về nil thay vì mảng khi không có mục nào được thay đổi, vì vậy tôi không nghĩ rằng điều đó hoạt động.
Cai đo thê nao:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
Sạch hơn một chút, ít nhất là theo sở thích của tôi và theo một bài kiểm tra điểm chuẩn nhanh hơn khoảng 15% so với phiên bản của bạn ...
some_array.map{|x| x * 3 unless x % 2}.compact
, được cho là dễ đọc hơn / ruby-esque.
unless x%2
không có hiệu lực vì 0 là true trong ruby. Xem: gist.github.com/jfarmer/2647362
Tôi đã thực hiện một điểm chuẩn nhanh so sánh ba lựa chọn thay thế và map-compact thực sự có vẻ là lựa chọn tốt nhất.
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
điểm chuẩn này (xem stackoverflow.com/a/17703276 ).
inject
==reduce
Dường như có một số nhầm lẫn giữa các lập trình viên Ruby trong chủ đề này liên quan đến khả năng hiểu danh sách là gì. Mỗi phản hồi đơn giả sử một số mảng có sẵn để biến đổi. Nhưng sức mạnh của khả năng hiểu danh sách nằm trong một mảng được tạo ngay lập tức với cú pháp sau:
squares = [x**2 for x in range(10)]
Sau đây sẽ là một tương tự trong Ruby (câu trả lời thích hợp duy nhất trong chuỗi này, AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
Trong trường hợp trên, tôi đang tạo một mảng các số nguyên ngẫu nhiên, nhưng khối có thể chứa bất kỳ thứ gì. Nhưng đây sẽ là một cách hiểu danh sách Ruby.
Tôi đã thảo luận về chủ đề này với Rein Henrichs, người nói với tôi rằng giải pháp hiệu quả nhất là
map { ... }.compact
Điều này có ý nghĩa tốt vì nó tránh xây dựng Mảng trung gian như với cách sử dụng không thay đổi Enumerable#inject
và nó tránh phát triển Mảng gây ra phân bổ. Nó chung chung như bất kỳ bộ sưu tập nào khác trừ khi bộ sưu tập của bạn có thể chứa các phần tử nil.
Tôi chưa so sánh điều này với
select {...}.map{...}
Có thể là việc triển khai C của Ruby Enumerable#select
cũng rất tốt.
Một giải pháp thay thế sẽ hoạt động trong mọi lần triển khai và chạy trong thời gian O (n) thay vì O (2n) là:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
2
việc n
lần thay vì 1
điều n
lần và sau đó một 1
điều n
lần :) Một lợi thế quan trọng của inject
/ reduce
là nó bảo bất kỳ nil
giá trị trong chuỗi đầu vào mà là nhiều hành vi list-comprehensionly
Tôi vừa xuất bản viên ngọc hiểu biết cho RubyGems, cho phép bạn làm điều này:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
Nó được viết bằng C; mảng chỉ được duyệt một lần.
Enumerable có một grep
phương thức có đối số đầu tiên có thể là một proc vị từ và đối số thứ hai tùy chọn của nó là một hàm ánh xạ; vì vậy các hoạt động sau đây:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
Điều này không thể đọc được như một vài gợi ý khác (tôi thích select.map
đá quý đơn giản hoặc histocrat của anoiaque), nhưng điểm mạnh của nó là nó đã là một phần của thư viện tiêu chuẩn, và là một lần và không liên quan đến việc tạo các mảng trung gian tạm thời và không yêu cầu giá trị nằm ngoài giới hạn như nil
được sử dụng trong các compact
đề xuất sử dụng.
Điều này ngắn gọn hơn:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
[1,2,3,4,5,6].select(&:even?).map(&3.method(:*))
Giống như Pedro đã đề cập, bạn có thể hợp nhất với nhau các cuộc gọi bị xích vào Enumerable#select
và Enumerable#map
, tránh một traversal trên các yếu tố lựa chọn. Điều này đúng vì Enumerable#select
là một chuyên môn hóa của nếp gấp hoặc inject
. Tôi đã đăng một đoạn giới thiệu vội vàng về chủ đề này tại Ruby subreddit.
Việc kết hợp thủ công các phép biến đổi Mảng có thể khá tẻ nhạt, vì vậy có thể ai đó có thể chơi với cách comprehend
triển khai của Robert Gamble để làm cho mẫu select
/ này map
đẹp hơn.
Một cái gì đó như thế này:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
Gọi nó đi:
lazy (1..6){|x| x * 3 if x.even?}
Trả về:
=> [6, 12, 18]
lazy
trên Mảng và sau đó:(1..6).lazy{|x|x*3 if x.even?}
Đây là một cách để tiếp cận điều này:
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
vì vậy về cơ bản chúng ta đang chuyển đổi một chuỗi thành cú pháp ruby thích hợp cho vòng lặp, sau đó chúng ta có thể sử dụng cú pháp python trong một chuỗi để thực hiện:
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]
hoặc nếu bạn không thích giao diện của chuỗi hoặc phải sử dụng lambda, chúng tôi có thể bỏ qua nỗ lực sao chép cú pháp python và làm điều gì đó như sau:
S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]
Ruby 2.7 đã giới thiệu filter_map
cái nào đạt được khá nhiều thứ bạn muốn (bản đồ + nhỏ gọn):
some_array.filter_map { |x| x * 3 if x % 2 == 0 }
Bạn có thể đọc thêm về nó ở đây .
https://rubygems.org/gems/ruby_list_comprehension
shameless plug cho đá quý Ruby List Computing của tôi để cho phép hiểu danh sách Ruby thành ngữ
$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
Tôi nghĩ điều dễ hiểu nhất trong danh sách sẽ là:
some_array.select{ |x| x * 3 if x % 2 == 0 }
Vì Ruby cho phép chúng ta đặt điều kiện sau biểu thức, chúng ta nhận được cú pháp tương tự như phiên bản Python của khả năng hiểu danh sách. Ngoài ra, vì select
phương thức không bao gồm bất kỳ thứ gì tương đương với false
, nên tất cả các giá trị nil sẽ bị xóa khỏi danh sách kết quả và không cần gọi đến compact như trường hợp chúng ta đã sử dụng map
hoặc collect
thay thế.