【问题标题】:Map splat arguments over method parameters在方法参数上映射 splat 参数
【发布时间】:2013-10-07 03:10:17
【问题描述】:

我们创建一个带有 splatted 参数的方法并在其上调用 Method#parameters

def splatter(x, *y, z); end

params = method(:splatter).parameters
  # => [[:req, :x], [:rest, :y], [:req, :z]]

我正在寻找一个函数f,它将一个参数列表映射到它们对应的变量名上。 该函数应该足够灵活,可以使用任意放置的 splat 参数处理任何其他方法。例如:

args = [:one, :two, :three, :four]

f(params, args)
  # => [[:x, :one], [:y, :two], [:y, :three], [:z, :four]]

或类似的东西(翻转元素也可以)。我觉得使用inject 之类的东西一定有一个灵活、优雅的解决方案,但我似乎想不出。

【问题讨论】:

  • 有趣的问题。对于任意方法,您可以利用从指令序列对象 (RubyVM::InstructionSequence.of(method(:splatter))) 中获得的信息来做一些事情。如果您调用to_a,第 12 个元素包含各种参数配置的索引。不幸的是,我太累了,无法再考虑这一点,但希望这可能会有所帮助。
  • 很好,我不知道你能做到这一点……我必须深入研究 InstructionSequence 才能看到我能从中得到什么信息。谢谢!
  • 如果您正在寻找测试用例,您可能会发现this answer of mine 很有用。

标签: ruby splat


【解决方案1】:
def f(params,*args)
    # elements to be assigned to splat parameter
    splat = args.count - params.count + 1

    # will throw an error if splat < 0 as that means not enough inputs given        
    params.map{ |p|     

            [ p[1] , ( p.first == :rest ? args.shift(splat) : args.shift  ) ]

           }
end

例子

def splatter(x,*y,z)
    # some code
end

f(method(:splatter).parameters, 1,2,3,4)
#=>[[:x, 1], [:y, [2, 3]], [:z, 4]]

def splatter(x,y,*z)
    # some code
end

f(method(:splatter).parameters, 1,2,3,4)
# => [[:x, 1], [:y, 2], [:z, [3, 4]]]

def splatter(x,*z)
    # some code
end

f(method(:splatter).parameters, 1)
# => [[:x, 1], [:z, []]]

【讨论】:

  • 我不小心对你的问题投了反对票,但除非你编辑你的答案,否则我无法删除我的投票。对不起!
  • 我认为这绝对是朝着正确方向迈出的一步。它并没有给出我想要的答案,但调整它并不难。我要再等一会儿,看看有没有人能想出别的办法。称之为预感,但我真的觉得有一个更简单的解决方案。
  • @rickyrickyrice 它可以修改为您的格式。不确定如果 splat 参数是 blank[],结果应该是什么样的?
  • 非常好!读起来像一本书。啪啪啪,我爱你。
【解决方案2】:

我认为这是一个很好的例子,eval 很有用。下面的代码生成一个 lambda,它采用与指定相同的参数并吐出已解析的参数列表。这种方法的优点是使用了 Ruby 自己的解决 splats 的算法。

def resolve(parameters,args)
  param_list = parameters.map do |type,name|
    splat = '*' if type == :rest
    "#{splat}#{name}"
  end.join(',')

  source = ""
  source << "->(#{param_list}) do\n"
  source << "  res = []\n"
  parameters.each do |type,name|
    if type == :rest
      source << "  res += #{name}.map {|v| [:'#{name}',v] }\n"
    else
      source << "  res << [:'#{name}',#{name}]\n"
    end
  end
  source << "end"

  eval(source).call(*args)
end

例子:

params = ->(x,*y,z){}.parameters
resolve(params,[:one, :two, :three, :four])
#=> [[:x, :one], [:y, :two], [:y, :three], [:z, :four]]

在幕后,生成了以下代码:

->(x,*y,z) do
  res = []
  res << [:'x',x]
  res += y.map {|v| [:'y',v] }
  res << [:'z',z]
end

另一个有两个参数的例子,先喷:

params = ->(*x,y){}.parameters
resolve(params,[:one, :two, :three, :four])
#=> [[:x, :one], [:x, :two], [:x, :three], [:y, :four]]

生成的代码在

->(*x,y) do
  res = []
  res += x.map {|v| [:'x',v] }
  res << [:'y',y]
end

【讨论】:

  • 非常有趣的方法!绝对让我对问题域有所了解。这可能听起来很烦人,但我真的在寻找一些优雅的东西,它涉及诸如折叠、地图或某种拉链之类的功能操作。不过谢谢你的建议!
  • 好方法 +1,具有非常通用的潜力(包括块输入,选项参数等),同一行有一个旧插件:merb-action-args
【解决方案3】:

编辑:在我最初的困惑之后:

def doit(params, args)
  rest_ndx = params.map(&:first).index(:rest)
  to_insert = [params[rest_ndx].last]*(args.size-params.size) if rest_ndx
  params = params.map(&:last)
  params.insert(rest_ndx,*to_insert) if rest_ndx
  params.zip(args)
end

【讨论】:

  • 感谢您的回答!是的,我更喜欢适用于任意放置的 splats/必需参数的东西。我会更新问题。
  • 我修改了我的答案(7 年前)。
猜你喜欢
  • 1970-01-01
  • 2016-11-02
  • 2023-03-14
  • 2014-05-05
  • 1970-01-01
  • 2013-06-14
  • 1970-01-01
  • 2014-07-22
  • 1970-01-01
相关资源
最近更新 更多