如何在Ruby中使用递归进行过滤?

时间:2017-08-24 07:05:35

标签: ruby recursion tail-recursion

如何在Ruby中使用递归过滤?

假设您有一个具有属性的对象数组,该属性可以具有两个值之一。如果它是第一个值 - 保持对象的第一个出现,如果它是第二个值 - 保持对象的最后一次出现。我们举个例子:

# type can be :foo or :bar
MyObject = Struct.new(:id, :type)

a = MyObject.new(1, :foo)
b = MyObject.new(2, :foo)
c = MyObject.new(3, :bar)
d = MyObject.new(4, :bar)
e = MyObject.new(5, :foo)
f = MyObject.new(6, :bar)

因此,如果是:foo,请保留第一次出现并放弃所有关注(直到达到:bar),如果它是:bar,则丢弃除最后一次出现之外的所有事件(直到你到达:foo

# given the initial collection looks like this:
[a, b, c, d, e, f]

# this must be the result after filtering:
[a, d, e, f]

我用迭代来解决这个问题:

initial_collection = [a, b, c, d, e, f]

initial_collection.each_with_object([initial_collection.first]) do |item, filtered_collection|
  if filtered_collection.last.type != item.type
    filtered_collection.push(item)
  elsif item.type == :bar
    filtered_collection[-1] = item
  end
end

我在理解如何使用递归时遇到问题。特别是,我无法围绕如何跟踪上一个和下一个项目。什么是递归解决方案?

4 个答案:

答案 0 :(得分:1)

递归必须收到一个额外的参数来跟踪当前状态,我们使用type

def recursive_filter(objects, type=nil)
  return [] if objects.empty?  # Stop condition
  first = objects.shift

  if first.type == type
    recursive_filter(objects, first.type)
  else
    [first] + recursive_filter(objects, first.type)
  end
end

答案 1 :(得分:1)

我不会将此问题视为需要递归解决方案的问题。我建议采用以下方法。

<强>代码

def weed(arr)
  e = [:first, :last].cycle
  first_or_last = e.next
  arr.drop(1).each_with_object([arr.first]) do |x,a|
    if attr_same_as_previous?(x, a.last)
      a[-1] = x if first_or_last == :last
    else
      first_or_last = e.next
      a << x
    end
  end
end

def attr_same_as_previous?(x, previous)
  x[:type] == previous[:type]
end

方法attr_same_as_previous?隔离了确定给定属性是否有变化的属性,使解决方案更加健壮。

<强>实施例

#1

MyObject = Struct.new(:id, :type)

a = MyObject.new(1, :foo)
b = MyObject.new(2, :foo)
c = MyObject.new(3, :bar)
d = MyObject.new(4, :bar)
e = MyObject.new(5, :foo)
f = MyObject.new(6, :bar)
g = MyObject.new(7, :bar)

arr = [a,b,c,d,e,f,g]
  #=> [#<struct MyObject id=1, type=:foo>, #<struct MyObject id=2, type=:foo>,
  #    #<struct MyObject id=3, type=:bar>, #<struct MyObject id=4, type=:bar>,
  #    #<struct MyObject id=5, type=:foo>, #<struct MyObject id=6, type=:bar>]       

weed arr
  #=> [#<struct MyObject id=1, type=:foo>,
  #    #<struct MyObject id=4, type=:bar>,
  #    #<struct MyObject id=5, type=:foo>,
  #    #<struct MyObject id=7, type=:bar>]

#2

MyObject = Struct.new(:id, :type)

a = MyObject.new(1, :cat)
b = MyObject.new(2, :cat)
c = MyObject.new(3, :dog)
d = MyObject.new(4, :dog)
e = MyObject.new(5, :pig)
f = MyObject.new(6, :owl)
g = MyObject.new(7, :owl)

weed [a,b,c,d,e,f,g]
  #=> [#<struct MyObject id=1, type=:cat>,
  #    #<struct MyObject id=4, type=:dog>,
  #    #<struct MyObject id=5, type=:pig>,
  #    #<struct MyObject id=7, type=:owl>]

答案 2 :(得分:1)

处理:foo需要知道前一个元素,而处理:bar需要知道下一个元素;所以在递归的任何给定点,我们必须查看一个三元素窗口,我们可以从中添加中间元素到我们的结果。

这是一些模式匹配伪代码(null表示窗口的那个单元格中没有元素,_匹配任何内容;请注意匹配大小写的顺序很重要:

f([:foo, :foo, _]) ->
  f(next_window)

f([_, :foo, _]) ->
  [middle_element] + f(next_window)

f([_, :bar, :bar]) ->
  f(next_window)

f([_, :bar, _]) ->
  [middle_element] + f(next_window)

// End of list
f([_, null, null]) ->
  []

这是一个Ruby版本:

def f(list, middle_index)
  window = get_window(list, middle_index)

  if window[0,2] == [:foo, :foo]
    f(list, middle_index + 1)

  elsif window[1] == :foo
    [list[middle_index]] +
    f(list, middle_index + 1)

  elsif window[1,2] == [:bar, :bar]
    f(list, middle_index + 1)

  elsif window[1] == :bar
    [list[middle_index]] +
    f(list, middle_index + 1)

  # End of list
  elsif window[1,2] == [nil, nil]
    []
  end
end

def get_window(list, middle_index)
  [maybe_type(list, middle_index - 1),
   maybe_type(list, middle_index),
   maybe_type(list, middle_index + 1)]
end

def maybe_type(list, index)
  if index < 0 or list[index].nil?
    nil
  else
    list[index].type
  end
end

输出:

MyObject = Struct.new(:id, :type)

a = MyObject.new(1, :foo)
b = MyObject.new(2, :foo)
c = MyObject.new(3, :bar)
d = MyObject.new(4, :bar)
e = MyObject.new(5, :foo)
f = MyObject.new(6, :bar)
g = MyObject.new(7, :bar)

arr = [a,b,c,d,e,f,g]

puts f(arr, 0).inspect
# [#<struct MyObject id=1, type=:foo>,
#  #<struct MyObject id=4, type=:bar>,
#  #<struct MyObject id=5, type=:foo>,
#  #<struct MyObject id=7, type=:bar>]

答案 3 :(得分:1)

以下是递归解决方案。如果你将它与我的其他答案进行比较,你会发现它是一个虚假的递归,一个被掩盖为递归的顺序方法。这是由于问题的性质。

def weed(arr)
  return [] if arr.empty?
  e, *rest = arr
  recurse(rest, [e], :first)
end

def recurse(arr, build, first_or_last)
  e, *rest = arr
  if e[:type] == build.last[:type]
    (build[-1] = e) if first_or_last == :last
  else
    first_or_last = (first_or_last == :first ? :last : :first)
    build << e
  end
  rest.empty? ? build : recurse(rest, build, first_or_last)
end

MyObject = Struct.new(:id, :type)
a = MyObject.new(1, :foo)
b = MyObject.new(2, :foo)
c = MyObject.new(3, :bar)
d = MyObject.new(4, :bar)
e = MyObject.new(5, :foo)
f = MyObject.new(6, :bar)
g = MyObject.new(7, :bar)

arr = [a,b,c,d,e,f,g]
  #=> [#<struct MyObject id=1, type=:foo>, #<struct MyObject id=2, type=:foo>,
  #    #<struct MyObject id=3,  type=:bar>, #<struct MyObject id=4, type=:bar>,
  #    #<struct MyObject id=5, type=:foo>, #<struct MyObject id=6, type=:bar>,
  #    #<struct MyObject id=7, type=:bar>]

weed arr
  #  [#<struct MyObject id=1, type=:foo>,
  #   #<struct MyObject id=4, type=:bar>,
  #   #<struct MyObject id=5, type=:foo>,
  #   #<struct MyObject id=7, type=:bar>]