[英]Ruby Binary Search not noticing values already in array some of the time
我正在将项目添加到数组。 要添加它们,我让我的应用程序二进制搜索我当前的数组(如果有的话)-它不添加该项。 如果项目不存在,则会添加该项目:
while (line = fileObj.gets)
itemD = line.split(" ")
number = itemD.at(0)
name = itemD.at(1)
if (search(items, number) == -1)
# Doesn't add if item already exists
puts "Already have: #{number} - #{name}"
else
# Adds a new item to the list
items << Item.new(number, name)
end
end
在上面,我的循环从文件读取输入,获取数字和名称。 然后搜索号码。 上面的代码工作得很好,所以下面的代码(二进制搜索)无法正常工作:
def search(array, key)
min = 0
max = array.length-1
mid = 0
while(min <= max)
mid = lo + ((min - max)/2)
if array[mid].number == key
return -1
elsif array[mid].number < key
min = mid + 1
else
max = mid - 1
end
end
puts "#{key} not found in array"
end
使用基本测试用例时,我注意到了这个问题:
58 Item 1
17 Item 2
58 Item 3
76 Item 4
06 Item 5
08 Item 6
17 Item 7
21 Item 8
76 Item 4
76 Item 4
00 Item 9
49 Item 10
40 Item 11
79 Item 12
31 Item 13
尽管名称不同,但它不会添加项目3(这很好,因为该数字已存在)。 然后到达第二项4。将其添加,然后到达第三项4,并注意到它在数组中,并且没有添加。 我的输出如下(以帮助跟踪问题):
58 not found in array
17 not found in array
Already have: 58 - Item 3
76 nfia
06 nfia
08 nfia
17 nfia
21 nfia
76 nfia (WRONG)
Already have: 76 - Item 4 (CORRECT)
00 nfia
49 nfia
40 nfia
79 nfia
31 nfia
接下来,我多次仅创建了一个测试用例:
58 Item 1
58 Item 1
58 Item 1
58 Item 1
58 Item 1
它正确地添加了第一个,并注意到了下四个(并且正确地没有添加它们)。 因此,很明显,如果该数字出现在列表的后面而不是刚添加后的数字,则还有更多工作要做。 所以接下来我测试了:
58 Item 1
08 Item 6
58 Item 1
08 Item 6
58 Item 1
08 Item 6
58 Item 1
08 Item 6
58 Item 1
08 Item 6
这显示了输出所指的内容,但它是一个有趣的迭代:
58 nfia
08 nfia
Already have 58
08 nfia (WRONG)
58 nfia (WRONG)
already have
already have
already have
already have
already have
有人可以帮助我为什么有时会注意到我数组中的元素,但不是每次都应该注意到它。 感谢您的协助,不胜感激!
除了提到Ruby提供了Array#bsearch方法来执行二进制搜索之外,我将让其他人解决您的二进制搜索方法的问题。 我想提出一种更像Ruby的方式来执行任务。
码
require 'set'
def process_file(fname)
s = Set.new
items = []
File.foreach(fname) do |line|
pair = line.split
if s.add?(pair)
items << Item.new(*pair)
else
puts "Already have: %s - %s" % pair
end
end
items
end
例
让我们创建一个测试文件。
str =<<_
Humpty Dumpty
sat on
a wall.
Humpty Dumpty
had a
great fall.
_
FName = "test"
File.write(FName, str)
#=> 61
让我们检查一下。
puts File.read(FName)
Humpty Dumpty
sat on
a wall.
Humpty Dumpty
had a
great fall.
我们需要一个类Item
。
class Item
def initialize(*pair)
@pair = pair
end
end
我们准备出发了。
process_file(FName)
Already have: Humpty - Dumpty
#=> [#<Item:0x007f978404e2f0 @pair=["Humpty", "Dumpty"]>,
# #<Item:0x007f978404c810 @pair=["sat", "on"]>,
# #<Item:0x007f9784046370 @pair=["a", "wall."]>,
# #<Item:0x007f978403ced8 @pair=["had", "a"]>,
# #<Item:0x007f9784037e88 @pair=["great", "fall."]>]
参见Set :: new , IO :: foreach , String#split和Set#add? 。 IO
方法(例如foreach
)通常在File
类(它是IO
的子类)上调用。
上面的代码工作得很好,所以下面的代码(二进制搜索)无法正常工作
该语句是错误的:上面的代码不能 “正常工作”。 特别是,二进制搜索需要对序列进行排序,因此您需要在调用search
之前对其进行排序,或者确保将新项插入正确的位置,以便始终对数组进行排序。 但是,您只需将项目粘贴到数组的末尾,而不管其number
为何,就永远不会对数组进行排序。
还有另一个错误,就是您的number
引用的对象实际上是一个String
而不是数字。 这可能只是变量的错误命名,但是从代码的其余部分看来,它实际上是一个数字。 特别是,我不确定您实际上是否打算将11
小于2
,这是字符串的情况。
您的search
方法中至少还有一个错误:在任何地方都没有定义lo
。
假设您的意思是min
,公式仍然是错误的,应该是min + ((max - min) / 2)
,而不是相反。 但是,实际上,整个体操并不是必需的。 仅在破坏了算术的语言中才需要这样做。 Ruby完全有能力将两个整数相加而不会出错,因此您可以做(min + max) / 2
。
另一个问题是, search
方法的返回值实际上没有任何意义。 首先,返回值对于实际获取项目是无用的。 我希望search
方法可以返回该项目,也可以(如果是可索引序列,如数组)返回该项目。 如果该项存在,则search
方法将返回-1
否则将返回nil
。 因此,换句话说,它仅告诉您该项目是否存在。 这不是非常有用:想象一下,如果您将搜索查询键入Google,它只会返回“是的,您正在搜索的内容存在。但是我没有告诉您在哪里!” 或者,您要求某人帮助您搜索意外掉落的隐形眼镜,然后他们告诉您“找到了!”。 然后就走开。
通常,二进制搜索有两种形式:一种返回元素的索引(如果存在),而另一种(例如-1
或nil
或NULL
或其他特殊指定值)则告诉您该项目不存在。 另一种形式总是返回项目在序列中所属位置的索引。 由于二进制搜索要求对数组进行排序,但是对数组进行排序没有意义(您可能使用二进制搜索,因为它只需要O(log n)比较,而不是线性搜索的O(n),但是排序可以进行O(n * log n)比较(或基于非比较排序的O(n),因为您的键是数字),因此总体上比简单的线性搜索要慢),您必须插入项在序列中的正确位置,这意味着您需要一种二进制搜索形式,告诉您该项所属的索引,即使该项不存在( 尤其是不存在),也可以在右侧插入地点)。
不幸的是,修复所有这些错误和设计错误将超出简单的堆栈溢出答案的范围。
我想提出一个替代方案:设计程序时最重要的步骤之一就是确定要使用的数据结构。 在这种情况下,您希望数组中没有重复项,这似乎表明您实际上根本不需要数组,而是一个集合。 也许您想要一个排序的集合,但是从您的描述和代码来看,它实际上看起来并不像您需要的那个属性。
所以,我建议这样的事情:
class Item < Struct.new(:number, :name)
def eql?(other)
number.eql?(other.number)
end
def hash
number.hash
end
# the following are only needed for a sorted set
def <=>(other)
number <=> other.number
end
include Comparable
end
require 'set'
items = File.foreach('filename.txt').
map {|line| number, name = line.chomp.split(nil, 2); Item.new(number.to_i, name) }.
to_set
#=> #<Set: {#<struct Item number=58, name="Item 1">,
#<struct Item number=17, name="Item 2">,
#<struct Item number=76, name="Item 4">,
#<struct Item number=6, name="Item 5">,
#<struct Item number=8, name="Item 6">,
#<struct Item number=21, name="Item 8">,
#<struct Item number=0, name="Item 9">,
#<struct Item number=49, name="Item 10">,
#<struct Item number=40, name="Item 11">,
#<struct Item number=79, name="Item 12">,
#<struct Item number=31, name="Item 13">}>
# if you want a sorted set instead:
items = SortedSet.new(File.foreach(filename.txt)) {|line|
number, name = line.chomp.split(nil, 2); Item.new(number.to_i, name)
}
#=> #<SortedSet: {#<struct Item number=0, name="Item 9">,
#<struct Item number=6, name="Item 5">,
#<struct Item number=8, name="Item 6">,
#<struct Item number=17, name="Item 2">,
#<struct Item number=21, name="Item 8">,
#<struct Item number=31, name="Item 13">,
#<struct Item number=40, name="Item 11">,
#<struct Item number=49, name="Item 10">,
#<struct Item number=58, name="Item 1">,
#<struct Item number=76, name="Item 4">,
#<struct Item number=79, name="Item 12">}>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.