如何在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
我在理解如何使用递归时遇到问题。特别是,我无法围绕如何跟踪上一个和下一个项目。什么是递归解决方案?
答案 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>]