用Ruby中的数组创建固定长度的FIFO

时间:2021-04-18 21:36:50

I need a FIFO list of a fixed size that can be set arbitrarily on object creation. An array works, but I'm tired of checking its size and popping off the oldest element every time I push a new value. I know people have said that subclassing Array is a bad idea, but I still want to do it as it's the most elegant solution to my need.

我需要一个固定大小的FIFO列表,可以在创建对象时任意设置。一个数组可以工作,但是我厌倦了检查它的大小并在每次推一个新值时取出最老的元素。我知道有人说过子类化数组是个坏主意,但我还是想这么做,因为它是我需要的最优雅的解决方案。

Here's my code so far.

这是我目前的代码。

class FIFOList < Array
  attr_reader :FIFO_length
  attr_writer :FIFO_length

  def initialize(l)
    super()
    @FIFO_length = l
  end

  def push(element)
    super(element)
    self.shift if self.length > @FIFO_length

end

With this object, I can define a 25 element FIFO like so:

有了这个对象,我可以像这样定义一个25个元素FIFO:

a = FIFOList.new(25)

and push elements on it all day long and always have the most recent 25. As long as I'm pushing elements on one at a time (a.push()), all is good. However, I'd like to be able to initialize a new FIFO with an array, just like one can with a native Array object.

每天都在它上面推元素,总是有最近的25个。只要我一次把元素按在一个元素上(a。push()),一切都很好。但是,我希望能够使用数组来初始化一个新的FIFO,就像使用本机数组对象一样。

a = [1,2,3,4,5]

Which yields an array a, of 5 elements. But that's not how it's working and I'm not sure what to do about it. Here's an IRB session showing the problem:

它产生一个数组a,包含5个元素。但这不是它的工作方式,我不知道该怎么做。这是一个IRB会议显示的问题:

irb(main):008:0* a = FIFOList.new(5)
=> []
irb(main):009:0> a.class
=> FIFOList
irb(main):010:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):011:0> a.class
=> Array

So instead of the static array getting assigned as the "content" of the FIFOList object, "a" simply becomes a reference to the new static array. That's not the behavior I want. From what reading I've done it seems like maybe I need to add methods to my FIFOList class to override [] and possibly []=, but I'm not sure how to do that. If that's not the right approach, I'd be happy with a "load" method that takes an array as a parameter and does the magic internally, but I'm not sure how to "load" the passed values into the self. Can someone help?

因此,与将静态数组指定为FIFOList对象的“内容”不同,“a”只是成为新静态数组的引用。那不是我想要的行为。根据我所做的阅读,似乎我需要向我的FIFOList类添加方法来覆盖[]和[]=,但我不确定该怎么做。如果这不是正确的方法,我很乐意使用“load”方法,它将数组作为参数,并在内部执行魔法,但我不确定如何将传递的值“加载”到self中。有人可以帮忙吗?

4 个解决方案

#1


3  

You already came up with your own solution, but here's mine:

你已经有了自己的解决方案,但这是我的:

class FIFOList < Array
  attr_reader :fifo_length

  def initialize(len, arr=[])
    arr = arr[-len, len] if arr.size > len
    super(arr)
    @fifo_length = len
  end

  def push(*args)
    if args.size > fifo_length
      return replace(args[-fifo_length, fifo_length])
    end

    num_to_shift = (size + args.size) - fifo_length
    shift(num_to_shift) unless num_to_shift < 0
    super(*args)
  end
end

a = FIFOList.new(5, ["a1", "a2", "a3"])
p a # => ["a1", "a2", "a3"]

p a.push("b1") # => ["a1", "a2", "a3", "b1"]
p a.push("c1", "c2", "c3") # => ["a3", "b1", "c1", "c2", "c3"]
p a.push("d1", "d2", "d3", "d4", "d5", "d6", "d7") # =>  ["d3", "d4", "d5", "d6", "d7"]

Note that the constructor will automatically cut the initial array if it's too long, and FIFOList#push mirrors Array#push in that it takes any number of arguments and always returns self (and works correctly even if given too many arguments).

注意,如果初始数组太长,构造函数会自动删除它,并且FIFOList#push镜像数组#push,它接受任意数量的参数,并且总是返回self(即使给出太多参数,也能正确工作)。

Though some would object to monkey-patching Array, you could also add an Array#to_fifo convenience method that creates a FIFOList:

虽然有些人会反对monkey-patching数组,但你也可以添加一个#to_fifo方便方法来创建一个FIFOList:

class Array
  def to_fifo
    FIFOList.new(size, self)
  end
end

a = [1,2,3,4,5].to_fifo
p a.class # => FIFOList

You can see it working on repl.it: https://repl.it/@jrunning/WillingJoyfulAttributes

你可以看到它在使用repl。:https://repl.it/@jrunning WillingJoyfulAttributes

#2


3  

You can do something very similar without inheriting from Array, but using composition instead:

你可以在不继承数组的情况下做一些非常相似的事情,但是可以使用组合来代替:

class FIFOList
  attr_reader :size, :arr

  def self.[](*values)
    obj = self.new(values.size)
    obj.arr = values
    obj
  end

  def initialize(size)
    @size = size
    @arr  = Array.new
  end

  def push(element)
    arr.push(element)
    arr.shift if arr.length > size
    arr
  end
end

Then you can use it in a similar way:

然后你可以用类似的方式使用它:

a = FIFOList.new(3)
# => #<FIFOList:0x00007ffe87071150 @size=3, @arr=[nil, nil, nil]>
a.push 1
# => [nil, nil, 1]
a.push 2
# => [nil, 1, 2]
a.push 3
# => [1, 2, 3]
a.push 4
# => [2, 3, 4]
a.arr
# => [2, 3, 4]

Or if you want to use it without using push for each value:

或者如果你想用它而不用推每一个值:

a = FIFOList[1,2,3]
# => #<FIFOList:0x00007feea9015d70 @size=3, @arr=[1, 2, 3]>
a.push 4
# => [2, 3, 4]

#3


1  

You probably looking for Array#replace method.

您可能正在寻找数组#replace方法。

Replaces the contents of self with the contents of other_ary, truncating or expanding if necessary.

用他人的内容替换自我的内容,必要时进行截断或扩展。

a = FIFOList.new(5)
a.push(10) # now the content is [10]
a.replace([1,2,3,4,5]) # => now the content is [1,2,3,4,5]

#4


0  

I still wish I could do straight assignment and have it work, but this code does allow me to optionally initialize.

我仍然希望我可以直接赋值并让它工作,但是这段代码允许我选择初始化。

class FIFOList < Array
  attr_reader :FIFO_length
  attr_writer :FIFO_length

  def initialize(l, init_value = nil)
    if init_value != nil && init_value.class.to_s == "Array" && init_value.length <= l then
      super(init_value)
      @FIFO_length = l
    elsif init_value == nil
      super()
      @FIFO_length = l
    else
      raise "optional 2nd parameter required to be an Array"
    end
  end

  def push(element)
    super(element)
    self.shift if self.length > @FIFO_length #truncate the FIFO to the defined length
  end

end

#1


3  

You already came up with your own solution, but here's mine:

你已经有了自己的解决方案,但这是我的:

class FIFOList < Array
  attr_reader :fifo_length

  def initialize(len, arr=[])
    arr = arr[-len, len] if arr.size > len
    super(arr)
    @fifo_length = len
  end

  def push(*args)
    if args.size > fifo_length
      return replace(args[-fifo_length, fifo_length])
    end

    num_to_shift = (size + args.size) - fifo_length
    shift(num_to_shift) unless num_to_shift < 0
    super(*args)
  end
end

a = FIFOList.new(5, ["a1", "a2", "a3"])
p a # => ["a1", "a2", "a3"]

p a.push("b1") # => ["a1", "a2", "a3", "b1"]
p a.push("c1", "c2", "c3") # => ["a3", "b1", "c1", "c2", "c3"]
p a.push("d1", "d2", "d3", "d4", "d5", "d6", "d7") # =>  ["d3", "d4", "d5", "d6", "d7"]

Note that the constructor will automatically cut the initial array if it's too long, and FIFOList#push mirrors Array#push in that it takes any number of arguments and always returns self (and works correctly even if given too many arguments).

注意,如果初始数组太长,构造函数会自动删除它,并且FIFOList#push镜像数组#push,它接受任意数量的参数,并且总是返回self(即使给出太多参数,也能正确工作)。

Though some would object to monkey-patching Array, you could also add an Array#to_fifo convenience method that creates a FIFOList:

虽然有些人会反对monkey-patching数组,但你也可以添加一个#to_fifo方便方法来创建一个FIFOList:

class Array
  def to_fifo
    FIFOList.new(size, self)
  end
end

a = [1,2,3,4,5].to_fifo
p a.class # => FIFOList

You can see it working on repl.it: https://repl.it/@jrunning/WillingJoyfulAttributes

你可以看到它在使用repl。:https://repl.it/@jrunning WillingJoyfulAttributes

#2


3  

You can do something very similar without inheriting from Array, but using composition instead:

你可以在不继承数组的情况下做一些非常相似的事情,但是可以使用组合来代替:

class FIFOList
  attr_reader :size, :arr

  def self.[](*values)
    obj = self.new(values.size)
    obj.arr = values
    obj
  end

  def initialize(size)
    @size = size
    @arr  = Array.new
  end

  def push(element)
    arr.push(element)
    arr.shift if arr.length > size
    arr
  end
end

Then you can use it in a similar way:

然后你可以用类似的方式使用它:

a = FIFOList.new(3)
# => #<FIFOList:0x00007ffe87071150 @size=3, @arr=[nil, nil, nil]>
a.push 1
# => [nil, nil, 1]
a.push 2
# => [nil, 1, 2]
a.push 3
# => [1, 2, 3]
a.push 4
# => [2, 3, 4]
a.arr
# => [2, 3, 4]

Or if you want to use it without using push for each value:

或者如果你想用它而不用推每一个值:

a = FIFOList[1,2,3]
# => #<FIFOList:0x00007feea9015d70 @size=3, @arr=[1, 2, 3]>
a.push 4
# => [2, 3, 4]

#3


1  

You probably looking for Array#replace method.

您可能正在寻找数组#replace方法。

Replaces the contents of self with the contents of other_ary, truncating or expanding if necessary.

用他人的内容替换自我的内容,必要时进行截断或扩展。

a = FIFOList.new(5)
a.push(10) # now the content is [10]
a.replace([1,2,3,4,5]) # => now the content is [1,2,3,4,5]

#4


0  

I still wish I could do straight assignment and have it work, but this code does allow me to optionally initialize.

我仍然希望我可以直接赋值并让它工作,但是这段代码允许我选择初始化。

class FIFOList < Array
  attr_reader :FIFO_length
  attr_writer :FIFO_length

  def initialize(l, init_value = nil)
    if init_value != nil && init_value.class.to_s == "Array" && init_value.length <= l then
      super(init_value)
      @FIFO_length = l
    elsif init_value == nil
      super()
      @FIFO_length = l
    else
      raise "optional 2nd parameter required to be an Array"
    end
  end

  def push(element)
    super(element)
    self.shift if self.length > @FIFO_length #truncate the FIFO to the defined length
  end

end