沿给定范围反转映射的方法

时间:2015-10-05 16:23:15

标签: ruby algorithm

说我有这个对象集合:

[
 {value: 1, contents: "one"},
 {value: 2, contents: "two"},
 {value: 3, contents: "three"},
 {value: 4, contents: "four"},
 {value: 5, contents: "five"}
]

并希望将值与内容的关系反转,如下所示:

[
 {value: 5, contents: "one"},
 {value: 4, contents: "two"},
 {value: 3, contents: "three"},
 {value: 2, contents: "four"},
 {value: 1, contents: "five"}
]

我无法想到实现此目的的算法。我正在使用Ruby,但我不太关心代码,因为我是关于实现这个的方法。

3 个答案:

答案 0 :(得分:3)

a = [
  {value: 1, contents: "one"},
  {value: 2, contents: "two"},
  {value: 3, contents: "three"},
  {value: 4, contents: "four"},
  {value: 5, contents: "five"}
]

a.map{|h| h[:value]}.reverse.zip(a.map{|h| h[:contents]})
.map{|k, v| {value: k, contents: v}}
# => 
# [
#  {:value=>5, :contents=>"one"},
#  {:value=>4, :contents=>"two"},
#  {:value=>3, :contents=>"three"},
#  {:value=>2, :contents=>"four"},
#  {:value=>1, :contents=>"five"}
#]

或者,

a.each_index.map{|i| {value: a[-i - 1][:value], contents: a[i][:contents]}}

答案 1 :(得分:1)

arr.each_index.map {|i,a| {value: arr[-1-i][:value], contents: arr[i][:contents]}} #=> [{:value=>5, :contents=>"one"}, # {:value=>4, :contents=>"two"}, # {:value=>3, :contents=>"three"}, # {:value=>2, :contents=>"four"}, # {:value=>1, :contents=>"five"}] 等于你的哈希数组,这里有几种方法可以做到。

两次通过,没有指数

  <a href="javascript:void(0)" onclick="handleViewClick(event)" id="heart" class="next" >
    <img src="../images/Love_Heart_SVG1.svg"/>
  </a>

一次通过,但不是很漂亮

    $('#heart','nHeart').on('click', function(e){
        handleViewClick(e);
    });

答案 2 :(得分:1)

TL; DR

arr.zip(arr.reverse).map {|a, b| a.merge(value: b[:value]) }

由于reverse生成了数组的副本,因此这将占用其他方法的两倍的内存 - 对于大多数数据集来说可能根本不是问题。但如果是的话,有一种简单的方法可以避免它。请参阅我的答案末尾的“奖金”部分。

构建算法

最简单(也可能是最好的)解决方案是遍历数组,并且对于每个项目,从数组另一端的对应项获取:value。您可以通过从最后一项的索引中减去项目的索引(即数组的大小减1)来获得项目的“对应项”。因此,如果在名为arr的数组中有五个项目,则算法的步骤如下所示:

end_idx = arr.size - 1 # => 4
new_arr = []

new_arr[0] = { value: arr[end_idx - 0][:value], contents: arr[0][:contents] }
new_arr[1] = { value: arr[end_idx - 1][:value], contents: arr[1][:contents] }
new_arr[2] = { value: arr[end_idx - 2][:value], contents: arr[2][:contents] }
new_arr[3] = { value: arr[end_idx - 3][:value], contents: arr[3][:contents] }
new_arr[4] = { value: arr[end_idx - 4][:value], contents: arr[4][:contents] }

正如你所看到的,每一步都是相同的,但是一个数字递增,所以我打赌你已经知道如何把它变成一个循环:

end_idx = arr.size - 1 # => 4
new_arr = []

0.upto(end_idx) do |idx|
  new_arr[idx] = { value: arr[end_idx - idx][:value],
                   contents: arr[idx][:contents] }
end

简单,诚实是一个非常好的解决方案。但是,它并不是“Rubyish”。我们如何使它更加Rubyish?我很高兴你问过!

让它更加Rubyish

作为输出,需要一个数组,其中一个项目对应于输入数组中的每个项目,这是一种非常常见的情况。因为它很常见,所以我们有Enumerable#map方法,它正是这样做的:它将输入数组(或其他Enumerable)中的每个项“映射”到输出数组中的项目。

map遍历数组的项目,这正是我们需要的,但它缺少我们需要的一件事:当前项目的索引。为此,我们可以将with_index方法“链接”到map方法上,现在,除了数组项本身之外,该块还将传递第二个参数,即它的索引。现在我们拥有了所需的一切:

end_idx = vals.size - 1

arr.map.with_index do |hsh, idx|
  { value: arr[end_idx - idx][:value],
    contents: hsh[:contents] }
end

或者,如果我们不想显式指定哈希的结构(如果哈希来自,例如,用户输入或数据库查询,并且可能具有:value以外的密钥,那么我们可能会这样做{1}}我们希望保留而不必跟踪对输入表单或数据库模式的更改),我们可以这样做:

:contents

但是我终于保存了最好的。

最后......

end_idx = vals.size - 1

arr.map.with_index do |hsh, idx|
  hsh.merge(value: arr[end_idx - idx][:value])
end

这里发生了什么? Array#zip方法需要两个数组并将它们“拉上”,例如arr.zip(arr.reverse_each).map do |a, b| a.merge(value: b[:value]) end 产生[1, 2, 3].zip([:a, :b, :c]),所以我们用我们的数组及其反向(或者更确切地说,一个Enumerable从数组的末尾产生连续的项,这是[[1, :a], [2, :b], [3, :c]]返回的内容) ,然后我们使用reverse_eachmap的值设置为前者的副本(使用:value)。

加分:为什么merge而不只是reverse_each?因为我们可以让它懒惰!假设reverse有十亿个项目。如果你打电话给arr,现在你有一个包含20亿个项目的(二维)数组。也许这不是什么大不了的事(或者你可能没有近十亿件物品),但如果是,懒惰可以帮助我们:

arr.zip(arr.reverse)

我们所做的只是添加new_enum = arr.lazy.zip(arr.reverse_each).map do |a, b| a.merge(value: b[:value]) end # => #<Enumerator::Lazy: ...> ,现在我们得到一个Enumerator而不是一个数组。在我们调用lazy或其他一些Enumerable方法之前,这甚至都不会做任何工作,当我们这样做时,它只会按照我们要求的那么多项进行操作。例如,假设我们只想要前三项:

each

由于懒惰,我们从来没有必要复制整个数组并将其反转(并占用相应的内存量);我们只需处理三件事。

如果您确实想要所有项目,但仍想避免复制整个数组,只需致电new_enum.take(3).to_a # => [ { value: 1000000000, contents: "one" }, # { value: 999999999, contents: "two" }, # { value: 999999998, contents: "three" } ]