Rubyでは、特定の要素が変更されるように配列をマップする最も表現力豊かな方法は何ですかそして他の要素はそのままにしておきます?
これは簡単な方法です。
old_a = ["a", "b", "c"] # ["a", "b", "c"]
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"]
もちろん、十分でない場合は「放置」の場合を省略します。
new_a = old_a.map { |x| x+"!" if x=="b" } # [nil, "b!", nil]
私が欲しいのはこのようなものです:
new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"})
do |y|
y + "!"
end
# ["a", "b!", "c"]
Ruby(またはおそらくRailsにはまだ見つけていない便利な方法があります)でこれを行うための良い方法はありますか?
返信ありがとうございます。三項演算子でmap
を使用するのが最善であると集合的に確信しましたが、非常に興味深い回答を投稿した人もいました。
Mapステートメントはそのままで良いと思います。それは明確でシンプルであり、誰でも簡単に保守できます。
もっと複雑なものが必要な場合は、これはどうですか?
module Enumerable
def enum_filter(&filter)
FilteredEnumerator.new(self, &filter)
end
alias :on :enum_filter
class FilteredEnumerator
include Enumerable
def initialize(enum, &filter)
@enum, @filter = enum, filter
if enum.respond_to?(:map!)
def self.map!
@enum.map! { |elt| @filter[elt] ? yield(elt) : elt }
end
end
end
def each
@enum.each { |elt| yield(elt) if @filter[elt] }
end
def each_with_index
@enum.each_with_index { |elt,index| yield(elt, index) if @filter[elt] }
end
def map
@enum.map { |elt| @filter[elt] ? yield(elt) : elt }
end
alias :and :enum_filter
def or
FilteredEnumerator.new(@enum) { |elt| @filter[elt] || yield(elt) }
end
end
end
%w{ a b c }.on { |x| x == 'b' }.map { |x| x + "!" } #=> [ 'a', 'b!', 'c' ]
require 'set'
Set.new(%w{ He likes dogs}).on { |x| x.length % 2 == 0 }.map! { |x| x.reverse } #=> #<Set: {"likes", "eH", "sgod"}>
('a'..'z').on { |x| x[0] % 6 == 0 }.or { |x| 'aeiouy'[x] }.to_a.join #=> "aefiloruxy"
配列はポインタであるため、これも機能します。
a = ["hello", "to", "you", "dude"]
a.select {|i| i.length <= 3 }.each {|i| i << "!" }
puts a.inspect
# => ["hello", "to!", "you!", "dude"]
ループでは、新しいオブジェクトを作成するのではなく、オブジェクトを変更するメソッドを使用するようにしてください。例えば。 upcase!
upcase
と比較。
正確な手順は、正確に何を達成しようとしているかによって異なります。 foo-barの例で明確な答えを見つけるのは難しいです。
old_a.map! { |a| a == "b" ? a + "!" : a }
与える
=> ["a", "b!", "c"]
map!
レシーバーを所定の位置に変更するため、old_a
が返された配列になりました。
map
ソリューションが最適です。 map_modifying_only_elements_whereがどういうわけか優れていると思う理由がわかりません。 map
を使用すると、よりクリーンで簡潔になり、複数のブロックを必要としません。
一発ギャグ:
["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }
上記のコードでは、[] "cumulative"で始まります。列挙子(この場合は配列["a"、 "b"、 "c"])を介して列挙すると、累積アイテムと「現在の」アイテムがブロック(|累積、i |)に渡されます。ブロックの実行結果は累積に割り当てられます。上記で行ったことは、アイテムが「b」でない場合は累積を変更せずに、「b!」を追加することです。累積配列に変換し、bの場合に返します。
select
を使用する上記の回答があります。これは、それを実行(および記憶)する最も簡単な方法です。
select
とmap
を組み合わせて、探しているものを実現できます。
arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
=> ["b!"]
select
ブロック内で、「選択」される要素の条件を指定します。これにより、配列が返されます。結果の配列で「map」を呼び出して、感嘆符を追加できます。
これを実現する最善の方法は、tap
を使用することです。
arr = [1,2,3,4,5,6]
[].tap do |a|
arr.each { |x| a << x if x%2==0 }
end
古い配列が必要ない場合は、マップをお勧めします。この場合、!を使用できるためです。あなたを表すメソッドは、その場で配列を変更しています。
self.answers.map!{ |x| (x=="b" ? x+"!" : x) }
私はこれよりも好きです:
new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
それは数行の長さですが、これはそれの地獄の代替手段です:
oa = %w| a b c |
na = oa.partition { |a| a == 'b' }
na.first.collect! { |a| a+'!' }
na.flatten! #Add .sort! here if you wish
p na
# >> ["b!", "a", "c"]
私の意見では、三元での収集が最も良いようです。
Ruby 2.7 +
2.7の時点で、決定的な答えがあります。
Ruby 2.7は、まさにこの目的のためにfilter_map
を導入しています。それは慣用的でパフォーマンスが高く、すぐに標準になると思います。
例えば:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
これが この件についてよく読んでください です。
それが誰かに役立つことを願っています!