我的任务是输出所有命令行参数的总和,在经历了一系列错误并意识到ARGV.to_I不会将所有元素转换为整数后,我提出了以下解决方案。
def argv_sum()
index = 0
sum = 0
ARGV.each do |a|
ARGV[index] = ARGV[index].to_i
index += 1
end
puts ARGV.sum
end
argv_sum()
或
def argv_sum()
index = 0
sum = 0
ARGV.each do |a|
sum += ARGV[index].to_i
index += 1
end
puts sum
end
argv_sum()
然后我上网查看了一下,意识到我可以将数组中的所有元素转换为整数,我假设(如果我错了,请纠正我),下面的代码是该程序最好/最有效的代码,也是将数组中所有元素转换成一行整数而不依赖循环的唯一方法。我还假设sum += ARGV[index].to_i
比ARGV[index] = ARGV[index].to_i
更好
def argv_sum()
ARGV.map!(&:to_i)
puts ARGV.sum()
end
argv_sum
但我感到困惑的是map
和map!
。我知道前者用一个新的数组替换了数组,后者改变了原来的数组。但是,如果我出于任何原因使用map
,我将如何区分它和原始版本。例如,以下内容(与上述内容几乎相同)不起作用。
def argv_sum()
ARGV.map(&:to_i) #map is used here instead of map!
puts ARGV.sum
end
argv_sum
.map
创建了一个新的数组,但我如何具体使用这个新数组,或者在需要时使用旧数组?因为程序只是假设我使用的是旧数组,所以我会得到一个";字符串不能被强制转换为整数";错误不管我使用的是ARGV
还是类似array_test = []
的,它的名称都与旧数组相同
用我解决原始求和问题的所有方法,有没有一种方法可以检查哪一种最有效。(最适合程序,允许程序更高效、更快地运行/节省空间/什么?)无论是对于这些过程,还是在我将来可能想写的任何其他程序中。
.map
创建了一个新数组,但我如何具体使用这个新数组或在需要时使用旧数组?
Ruby与大多数编程语言(但不是所有编程语言)一样,有一种叫做变量的东西。变量提供两个功能:它允许您将标签分配给事物,并允许您使用标记引用回东西。
分配变量的语法是
variable = expression
从那时起变量variable
是指通过评估expression
返回的对象。所以,你可以这样做:
def argv_sum
integers = ARGV.map(&:to_i)
puts integers.sum
end
argv_sum
然而,我不认为这个变量会给代码添加任何内容,它并没有真正使代码更容易阅读和/或维护,所以我会去掉它:
def argv_sum = puts(ARGV.map(&:to_i).sum)
argv_sum
记住,变量的两个目的是稍后引用回对象,并给它一个标签。在这种情况下,我们以后并没有真正引用回对象,而是立即引用回,并且标签并没有真正增加代码的清晰度。因此,变量只是多余的绒毛,可以消失。
请注意,正如您在文档中所看到的,Enumerable#sum
在计算总和之前需要一个块来转换每个元素…换句话说,带有块参数的Enumerable#sum
将映射和总和融合为一个运算,所以我们实际上可以只执行
def argv_sum = puts(ARGV.sum(&:to_i))
argv_sum
就我个人而言,我不喜欢混合计算和输入/输出,因为这会使代码更难测试。例如,只能通过将该代码放入脚本中,然后编写第二个脚本来测试该代码,该脚本使用不同的命令行参数调用第一个脚本并解析命令行输出。这是复杂、脆弱和容易出错的。如果我们可以通过简单地调用一个方法来测试它,那会更好。
因此,我将把印刷部分和总结部分分开。注意,该方法的名称已经表明:argv_sum
听起来像是该方法返回ARGV
的和,但实际上不是:它返回nil
,并且只有打印ARGV
的和。所以,让我们来解决这个问题:
def argv_sum = ARGV.sum(&:to_i)
def print_argv_sum = puts(argv_sum)
print_argv_sum
现在,我们已经将输入/输出部分与计算部分分离了……或者,是吗?不,实际上我们没有:我们已经将打印部分与计算部分分离,但仍然有一个";隐藏的";输入部分:CCD_;魔术;来自外部世界的输入,所以我们也应该把它分开:
def sum_array_of_strings(ary) = ary.sum(&:to_i)
def print_array_sum(ary, output_stream = default_output) =
output_stream.puts(sum_array_of_strings(ary))
def print_argv_sum(output_stream = default_output) =
print_array_sum(argv, output_stream)
def argv = ARGV
def default_output = $>
print_argv_sum
虽然这对于一个简单的例子来说可能有些过头了,但这段代码现在允许我们在不需要任何输入/输出的情况下轻松地测试代码的几乎所有方面。我们可以通过调用sum_array_of_strings
并将我们选择的数组传递给它来测试求和是否有效。我们可以通过调用print_array_sum
并向其传递我们选择的数组和一个伪输出流(例如stringio
标准库中的StringIO
实例)来测试打印是否有效,我们稍后可以对其进行检查。我们可以通过重写default_output_stream
和argv
方法来返回我们选择的对象,来测试整个逻辑是否正确地挂在一起。
所有这些都不需要实际传递任何命令行参数或解析打印输出:
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'minitest/autorun'
require 'stringio'
class TestArgvSummer < Minitest::Test
def setup
@fake_output = StringIO.new
@fake_argv = %w[1 2 3 4 5]
end
def test_that_sum_array_of_strings_sums_correctly
assert_equal 0, sum_array_of_strings([])
assert_equal 42, sum_array_of_strings(%w[42])
assert_equal 65, sum_array_of_strings(%w[23 42])
assert_equal 15, sum_array_of_strings(@fake_argv)
end
def test_that_print_array_sum_works_correctly
@fake_output.rewind
print_array_sum(@fake_argv, @fake_output)
assert_equal "15n", @fake_output.string
end
end
在ARGV上使用数组#sum
您没有显示您的输入或如何调用代码,因此很难提供适合您特定用例的建议。然而,从更普遍的意义上讲,Ruby有很多内置的方法,使整数值的求和变得非常简单。
ARGV是一个特殊的数组,它将参数作为字符串传递到程序中,一旦您将它们转换为Integers、Floats或#responds_to? :+
,您就可以使用Array#sum将它们全部相加。例如,考虑以下Ruby脚本:
#!/usr/bin/env ruby
# call #to_i on each each element of ARGV,
# sum all the elements, and output the
# result
puts ARGV.map(&:to_i).sum
从您的shell中,您可以调用如下程序:
$ ruby sum.rb 5 10 15
30
也没有什么可以阻止你在irb或pry中直接分配给ARGV。请记住,ARGV在来自命令行时总是包含字符串值,因此当您没有在REPL中直接将字符串指定为非字符串时,必须从字符串转换为数值。