-1

对于一个课堂项目,我们应该拿一篇已发表的论文并创建一个算法来创建一个以文本为单位的所有单词的列表,同时排除停用词。我正在尝试生成所有唯一单词(在整个文本中)的列表以及它们的出现频率。这是我为一行文本创建的算法:

x = l[125] #Selecting specific line in the text
p = Array.new() # Assign new array to variable p
p = x.split # Split the array
for i in (0...p.length)
  if(p[i] != "the" and p[i] != "to" and p[i] != "union" and p[i] != "political")
    print p[i] + " "
  end
end
puts 

该程序的输出是一个句子(从第 125 行开始),不包括停用词。我应该使用冒泡排序吗?我将如何修改它以对长度相等的字符串进行排序(或者这无关紧要)?

4

2 回答 2

1

考虑到您是 Ruby 新手,我会说您有一个良好的开端。您问是否应该使用冒泡排序。我猜你正在考虑对一个单词的多次出现进行分组,然后通过数组来计算它们。这会奏效,但还有其他一些更简单且更“类似于 Ruby”的方法。(我的意思是他们利用了语言的强大功能,同时更自然。)

让我们专注于计算一行中的唯一词。一旦你能做到这一点,你应该能够轻松地将其推广到多行。

第一种方法:使用哈希

第一种方法是使用散列。 h = {}创建一个新的空的。哈希的键是单词,它的值是每个单词在行中出现的次数。例如,如果“猫”这个词出现 9 次,我们就会有h["cat"] = 9,正是您需要的。为了构造这个散列,我们查看w该行中的每个单词是否已经在散列中。它在哈希中,如果

h[w] != nil

如果是,我们增加字数:

h[w] = h[w] + 1

要不就

h[w] += 1

如果它不在散列中,我们将这个词添加到散列中,如下所示:

h[w] = 1

这意味着我们可以这样做:

if h[w]
  h[w] += 1
else
  h[w] = 1
end

请注意,此处if h[w]与 相同if h[w] != nil

实际上,我们可以使用一个技巧来使这更容易。如果我们像这样创建哈希:

h = Hash.new(0)

那么我们添加的任何没有值的键都将被分配一个默认值零。这样我们就不必检查这个词是否已经在哈希中了;我们简单地写

h[w] += 1

如果w不在散列中,h[w]则将其添加并初始化为0,然后+= 1将其递增为1。酷,嗯?

让我们把所有这些放在一起。认为

line = "the quick brown fox jumped over the lazy brown fox"

我们使用以下方法将此字符串转换为数组String#split

arr = line.split # => ["the", "quick", "brown", "fox", "jumped", \
                       "over", "the", "lazy", "brown", "fox"] 

然后

h = Hash.new(0)
arr.each {|w| h[w] += 1}
h # => {"the"=>2, "quick"=>1, "brown"=>2, "fox"=>2, "jumped"=>1, "over"=>1, "lazy"=>1} 

我们完成了!

方法二:使用Enumerable#group_by方法

每当您想对数组、哈希或其他集合的元素进行分组时,group_by都应该想到该方法。

为了应用于group_by快速的 brown fox 数组,我们提供了一个包含分组标准的块,在这种情况下,分组标准就是单词本身。这会产生一个哈希:

g = arr.group_by {|e| e}
 # => {"the"=>["the", "the"], "quick"=>["quick"], "brown"=>["brown", "brown"], \
 #     "fox"=>["fox", "fox"], "jumped"=>["jumped"], "over"=>["over"], "lazy"=>["lazy"]} 

接下来要做的是将哈希值转换为单词的出现次数(例如,转换["the", "the"]2)。为此,我们可以创建一个新的空哈希h,并向其中添加哈希对:

h = {}
g.each {|k,v| h[k] = v.size}
h # => {"the"=>2, "quick"=>1, "brown"=>2, "fox"=>2, "jumped"=>1, "over"=>1, "lazy"=>1

还有一件事

你有这个代码片段:

  if(p[i] != "the" and p[i] != "to" and p[i] != "union" and p[i] != "political")
    print p[i] + " "
  end

这里有几种方法可以让这更干净一些,都使用h上面的哈希。

第一种方式

 skip_words = %w[the to union political] # => ["the", "to", "union", "political"] 
 h.each {|k,v| (print v + ' ') unless skip_words.include?(k)}

第二种方式

 h.each |k,v|
   case k
   when "the", "to", "union", "political"
     next
   else
     puts "The word '#{k}' appears #{v} times."
   end
 end

编辑以解决您的评论。尝试这个:

p = "The quick brown fox jumped over the quick grey fox".split
freqs = Hash.new(0)
p.each {|w| freqs[w] += 1}
sorted_freqs = freqs.sort_by {|k,v| -v}
sorted_freqs.each {|word, freq| puts word+' '+freq.to_s}
=>
quick 2
fox 2
jumped 1
The 1
brown 1
over 1
the 1
grey 1

通常,ypu 不会对哈希进行排序。而是首先将其转换为数组:

sorted_freqs = freqs.to_a.sort_by {|x,y| v}.reverse

或者

sorted_freqs = freqs.to_a.sort_by {|x,y| -v}

现在sorted_freqs是一个数组,而不是一个哈希。最后一行保持不变。一般来说,最好不要依赖哈希的顺序。事实上,在 Ruby 版本 1.9.2 之前,哈希是没有排序的。如果顺序很重要,请使用数组或将散列转换为数组。

话虽如此,您可以对哈希值从最小到最大进行排序,或者(就像我所做的那样)对哈希值的负值从最大到最小进行排序。请注意,没有Enumerable#reverseHash#reverse。或者(总是有很多用 Ruby 给猫剥皮的方法),你可以排序v然后使用Enumerable#reverse_each

sorted_freqs.reverse_each {|word, freq| puts word+' '+freq.to_s}

最后,您可以通过链接最后两个语句来消除临时变量sorted_freqs(因为没有Enumerable#sort_by!方法而需要):

freqs.sort_by {|k,v| -v}.each {|word, freq| puts word+' '+freq.to_s}
于 2013-11-05T08:32:06.313 回答
1

你真的应该研究一下 Ruby 的可枚举类。你很少for x in y用红宝石做。

word_list = ["the", "to", "union", "political"]
l[125].split.each do |word|
  print word + " " unless word_list.include?(word)
end

为了计数、排序和所有这些东西,请查看 group_by 方法,也许还有数组的 sort_by 方法。

于 2013-11-05T09:21:19.747 回答