新手问题 Ruby 数组如何返回一段元素的引用,而不是拷贝.

lilijreey · 2020年08月05日 · 最后由 lilijreey 回复于 2020年08月05日 · 1704 次阅读

如何实现如下功能

 a = [1,2,3,4,5]
b =a[0..2]
b[0] = 100
 期望a[0] 也是100

Array 的 slice 函数和中括号语法有什么区别吗,

只要 a 和 b 是不同的对象,你这个需求就很难实现。想知道什么情况下需要实现这样的效果? Array#slice和Array#[]没区别,Array.instance_method(:[])==Array.instance_method(:slice) 返回 true。其实看文档就知道了

这需求有点奇怪,直接改 a,取的时候才去slice(0, 2)岂不是更好?

a = [1,2,3,4,5]
b = a
b[0] = 100

b.slice(0, 2).each

自建一个包装类,实例化时传入数组,将所有方法调用委托到该数组上,不过把 slice 和 [] 做成新建包装实例但引用原数组

spike76 回复

类似实现 view 的概念,写一些数据结构的时候需要,如果支持会写的很方便。而且有些语言比如 C 就支持这种功能。

太重了,用 ruby 就是喜欢短码

spike76 回复

现在的问题是切片返回的对象应该是原对象的视图,而不应该是元数据的拷贝。这里有些不一致你们不觉得吗。 a[3] =x 语义修改 a 的原素 而 a.slice(0) 的语义和 a[3] 不一样,slice 返回的是拷贝,而不是引用

lilijreey 回复

[]=是另一个方法,a[3]='x'是 a.[]=(3,'x') 的语法糖,而不是先调用[]后再用等号赋值。

@lilijreey 这个有点意思,你说的这种 slice 是 Golang 切片,Ruby 的数组没有这个效果。

不过,我们可以通过 Ruby 简单实现一个类似的效果。

class RefArray < Array
  attr :ref, :range

  def initialize(*args)
    options = args.last.is_a?(Hash) ? args.pop : {}
    @ref = options[:ref] || Array.new(*args)
    @range = options[:range]
  end

  def [](index)
    case index
    when Integer
      current_array[index]
    when Range
      new_range = Range.new(index.first + range_from, 
                        index.last + range_from)
      self.class.new(ref: @ref, range: new_range)
    end
  end

  def []=(index, value)
    if (index < current_array.size)
      ref[range_from + index] = value
    else
      raise "index can't be larger than array length"
    end
  end

  def inspect
    current_array
  end

  def to_a
    current_array
  end

  private

  def current_array
    range ? ref[range] : ref
  end

  def range_from
    range ? range.first : 0
  end
end

然后使用起来也十分顺滑:

a = RefArray.new([1, 2, 3, 4, 5])
b = a[0..2]
b[0] = 100
p b # [100, 2, 3]
p a # [100, 2, 3, 4, 5]

PS:为什么我要用ref作为变量?因为 React 是真的香啊!哈哈哈!

spike76 回复

这样啊,多谢大佬

dfzy5566 回复

大神我 ruby 菜,能解释一下代码原理吗

lilijreey 回复

RefArray 实例对象内部维护了一个 refrange属性,记录原始的数组引用,以及你的切片范围。

当然你使用arr[x..y]进行“切片”的时候,会生成一个包含ref传递以及新的切片范围 range 的 RefArray 实例。

这样你每次切片,原始数组ref都会传递下去,而变化的只是range切片范围,所以当你进行赋值操作的时候,也就只是对原始数组进行更改。

而当你使用arr[x]获取某个 index 的值的时候,获取的其实是根据当前切片范围和 index 映射在原始数组ref上真实 index 对应的值。

其实就是一个手动维护的引用效果。

dfzy5566 回复

懂了,感谢

需要 登录 后方可回复, 如果你还没有账号请 注册新账号