映射一个数组,仅修改符合特定条件的元素

发布于 2024-07-14 11:55:55 字数 696 浏览 9 评论 0原文

在 Ruby 中,以修改某些元素而其他元素保持不变的方式映射数组的最具表现力的方法是什么?

这是一种直接的方法:

old_a = ["a", "b", "c"]                         # ["a", "b", "c"]
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) }  # ["a", "b!", "c"]

如果还不够的话,当然可以省略“leave-alone”情况:

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 和三元运算符,但你们中的一些人发布了非常有趣的答案!

In Ruby, what is the most expressive way to map an array in such a way that certain elements are modified and the others left untouched?

This is a straight-forward way to do it:

old_a = ["a", "b", "c"]                         # ["a", "b", "c"]
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) }  # ["a", "b!", "c"]

Omitting the "leave-alone" case of course if not enough:

new_a = old_a.map { |x| x+"!" if x=="b" }       # [nil, "b!", nil]

What I would like is something like this:

new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"}) 
        do |y|
          y + "!"
        end
# ["a", "b!", "c"]

Is there some nice way to do this in Ruby (or maybe Rails has some kind of convenience method that I haven't found yet)?


Thanks everybody for replying. While you collectively convinced me that it's best to just use map with the ternary operator, some of you posted very interesting answers!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

金兰素衣 2024-07-21 11:55:55

因为数组是指针,所以这也有效:

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 的例子给出明确的答案。

Because arrays are pointers, this also works:

a = ["hello", "to", "you", "dude"]
a.select {|i| i.length <= 3 }.each {|i| i << "!" }

puts a.inspect
# => ["hello", "to!", "you!", "dude"]

In the loop, make sure you use a method that alters the object rather than creating a new object. E.g. upcase! compared to upcase.

The exact procedure depends on what exactly you are trying to achieve. It's hard to nail a definite answer with foo-bar examples.

岁月染过的梦 2024-07-21 11:55:55
old_a.map! { |a| a == "b" ? a + "!" : a }

给出

=> ["a", "b!", "c"]

map! 修改接收器,因此 old_a 现在是返回的数组。

old_a.map! { |a| a == "b" ? a + "!" : a }

gives

=> ["a", "b!", "c"]

map! modifies the receiver in place, so old_a is now that returned array.

愁杀 2024-07-21 11:55:55

我同意地图声明本身就很好。 简单明了,而且很容易
供任何人维护。

如果你想要更复杂的东西,这个怎么样?

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"

I agree that the map statement is good as it is. It's clear and simple,, and would easy
for anyone to maintain.

If you want something more complex, how about this?

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"
何处潇湘 2024-07-21 11:55:55

您的 map 解决方案是最好的。 我不知道为什么你认为 map_modifying_only_elements_where 更好。 使用 map 更干净、更简洁,并且不需要多个块。

Your map solution is the best one. I'm not sure why you think map_modifying_only_elements_where is somehow better. Using map is cleaner, more concise, and doesn't require multiple blocks.

客…行舟 2024-07-21 11:55:55

一行:

["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }

在上面的代码中,您以 []“cumulative”开始。 当您通过枚举器进行枚举时(在我们的例子中为数组 ["a", "b", "c"]),累积项以及“当前”项被传递到我们的块(|cumulative, i|)并且我们的块的执行结果被分配给累积。 我上面所做的是当项目不是“b”时保持累积不变并附加“b!” 到累积数组并在为 b 时返回它。

上面有一个使用 select 的答案,这是执行(并记住)它的最简单方法。

您可以将 selectmap 结合起来,以实现您正在寻找的内容:

 arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
 => ["b!"]

select 块内,您可以指定元素的条件被“选中”。 这将返回一个数组。 您可以在结果数组上调用“map”以将感叹号附加到它。

One liner:

["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }

In the code above, you start with [] "cumulative". As you enumerate through an Enumerator (in our case the array, ["a", "b", "c"]), cumulative as well as "the current" item get passed to our block (|cumulative, i|) and the result of our block's execution is assigned to cumulative. What I do above is keep cumulative unchanged when the item isn't "b" and append "b!" to cumulative array and return it when it is a b.

There is an answer above that uses select, which is the easiest way to do (and remember) it.

You can combine select with map in order to achieve what you're looking for:

 arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
 => ["b!"]

Inside the select block, you specify the conditions for an element to be "selected". This will return an array. You can call "map" on the resulting array to append the exclamation mark to it.

马蹄踏│碎落叶 2024-07-21 11:55:55

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]

这是一个很好的读物主题

希望这对某人有用!

Ruby 2.7+

As of 2.7 there's a definitive answer.

Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.

For example:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

Here's a good read on the subject.

Hope that's useful to someone!

悲凉≈ 2024-07-21 11:55:55

如果你不需要旧的数组,我更喜欢地图! 在这种情况下,因为您可以使用 ! 方法来表示您正在就地更改数组。

self.answers.map!{ |x| (x=="b" ? x+"!" : x) }

我更喜欢这个而不是:

new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }

If you don't need the old array, I prefer map! in this case because you can use the ! method to represent you are changing the array in place.

self.answers.map!{ |x| (x=="b" ? x+"!" : x) }

I prefer this over:

new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
鸵鸟症 2024-07-21 11:55:55

它有几行长,但这里有一个替代方案:

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"]

在我看来,带有三元的收集似乎是最好的。

It's a few lines long, but here's an alternative for the hell of it:

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"]

The collect with ternary seems best in my opinion.

相思故 2024-07-21 11:55:55

我发现实现此目的的最佳方法是使用 tap

arr = [1,2,3,4,5,6]
[].tap do |a|
  arr.each { |x| a << x if x%2==0 }
end

I've found that the best way to accomplish this is by using tap

arr = [1,2,3,4,5,6]
[].tap do |a|
  arr.each { |x| a << x if x%2==0 }
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文