链接分区,keep_if等

时间:2014-02-21 13:46:18

标签: ruby chaining

[1,2,3].partition.inject(0) do |acc, x|
  x>2  # this line is intended to be used by `partition`
  acc+=x # this line is intended to be used by `inject`
end

我知道我可以使用不同的方法在上面写一节,但这在这里并不重要 我想问一下为什么有人想在“链”的开头使用partition(或其他方法,如keep_ifdelete_if)?
在我的示例中,在我链接inject后,我无法使用partition。我可以使用each编写上面的节:

[1,2,3].each.inject(0) do |acc, x|
  x>2 # this line is intended to be used by `partition`
  acc+=x # this line is intended to be used by `inject`
end

它会是一样的,对吧?

我知道x>2partition丢弃(而不是使用)acc+=x。只有partition才能完成这项任务(在这种情况下,总结所有元素) 我只写了那个来展示我的“意图”:我想在链中使用[].partition.inject(0),如}.map
我知道上面的代码不能像我预期的那样工作,我知道我可以在块之后链接(partition,如Neil Slater所述)。

我想知道原因,以及keep_if(以及其他方法,如delete_ifeach等)何时成为partition(只返回数组的元素为{{ 1}}在上述情况下做。) 在我的示例中,partition.injectpartition变为each,因为partition无法采取条件(x>2)。 但是partition.with_index(正如Boris Stitnicky所提到的)可以工作(我可以分区数组并使用索引来实现我想要的任何东西):

shuffled_array
  .partition                        
  .with_index { |element, index|    
    element > index                 
}

PS。这不是关于如何获得大于2的元素之和的问题。

2 个答案:

答案 0 :(得分:3)

这是一个有趣的情况。看看你的代码示例,你显然是Ruby的新手,也可能是编程的新手。然而,你设法提出一个非常棘手的问题,基本上涉及Enumerator类,这是一个最不被公开理解的类,特别是自Enumerator::Lazy引入以来。对我来说,你的问题很难,我无法提供全面的答案。然而,关于你的代码的评论不适合OP下的评论。这就是我添加这个非答案的原因。

首先,让我们注意一下代码中的一些可怕的东西:

无用的行。在这两个块中,x>2行无效,因为它的返回值被丢弃。

[1,2,3].partition.inject(0) do |x, acc|
  x>2         # <---- return value of this line is never used
  acc+=x
end

[1,2,3].each.inject(0) do |x, acc|
  x>2         # <---- return value of this line is never used
  acc+=x
end

在进一步讨论代码示例时,我会忽略这条无用的行。

无用的#each方法。编写

是没用的
[1,2,3].each.inject(0) do |x, acc|
  acc+=x
end

这就够了:

[1,2,3].inject(0) do |x, acc|
  acc+=x
end

无用地使用#partition方法。而不是:

[1,2,3].partition.inject(0) do |x, acc|
  acc+=x
end

你可以这样写:

[1,2,3].inject(0) do |x, acc|
  acc+=x
end

或者,就像我写的那样:

[ 1, 2, 3 ].inject :+

但是,您提出了一个关于在枚举器模式下使用#partition方法的深层次问题。在讨论了代码的琐碎新手问题之后,我们留下了一个问题:如何使用#partition#keep_if等的枚举器返回版本,或者更确切地说,有趣的方式是什么?使用它们,因为每个人都知道我们可以用它们进行链接:

array = [ *1..6 ]
shuffled_arrray = array.shuffle     # randomly shuffles the array elements
shuffled_array
  .partition                        # partition enumerator comes into play
  .with_index { |element, index|    # method Enumerator#with_index comes into play
    element > index                 # and partitions elements into those greater
}                                   # than their index, and those smaller

也是这样:

e = partition_enumerator_of_array = array.partition
# And then, we can partition the array in many ways:
e.each &:even? # partitions into odd / even numbers
e.each { rand() > 0.5 } # partitions the array randomly
# etc.

一个容易理解的优点是,而不是写更长的时间:

array.partition &:even?

你可以写更短的内容:

e.each &:even?

但我基本上确信枚举器为程序员提供了更多的权力,而不仅仅是链接收集方法和缩短代码。因为不同的调查员做了很多不同的事情。某些内容(例如#map!#reject!)甚至可以修改其运行的集合。在这种情况下,可以想象的是,可以将不同的枚举器与相同的块组合以执行不同的操作。这种能力不仅可以改变块,而且还可以改变它们传递给它的枚举器,它提供了组合能力,这很可能用于使一些其他冗长的代码非常简洁。但我无法提供一个非常有用的具体例子。

总之,Enumerator类主要用于链接,并且使用链接,程序员并不需要详细检查Enumerator。但是我怀疑使用Enumerator的正确习惯可能和参数化子类化的正确习惯一样难以学习。我怀疑我还没有掌握使用枚举器的最有效方法。

答案 1 :(得分:3)

我认为结果[3, 3]就是你在这里寻找的 - 将数组分成越来越大的数字,然后对每个组进行求和。您似乎对如何将块“规则”赋予两种不同的方法感到困惑,并将两个块合并为一个。

如果您需要多个方法的净效果,每个方法都占用一个块,那么您可以在任何块之后链接,方法是在块关闭后添加.method,如下所示: }.eachend.each

另请注意,如果创建分区,则可能需要单独对每个分区求和。为此,您需要链中的额外链接(在本例中为map):

[1,2,3].partition {|x| x > 2}.map do |part|
  part.inject(0) do |acc, x|
    x + acc
  end
end
# => [3, 3]

(你在注入中也得到了累加器和当前值错误的方法,并且不需要分配给累加器,Ruby会为你做这件事。)

.inject不再位于方法链中,而是内部一个块。其他块中的块没有问题,事实上你会在Ruby代码中经常看到这个。

我在上面的示例中已经链接了.partition.map。您也可以这样写上面的内容:

[1,2,3].partition do
  |x| x > 2
end.map do |part|
  part.inject(0) do |acc, x|
    x + acc
  end
end

。 。 。虽然在使用短块链接时,我个人觉得使用{ }语法而不是do end更容易,尤其是在链的开头。

如果一切看起来都很复杂,那么将链的第一部分的结果分配给局部变量通常不会有很高的成本,在这种情况下根本就没有链。

parts = [1,2,3].partition {|x| x > 2}
parts.map do |part|
  part.inject(0) do |acc, x|
    x + acc
  end
end