提取独特的单词

时间:2018-05-15 00:38:09

标签: ruby

我需要获取文件名和整数N,并返回给定文件中的前N个唯一单词。我们说input.txt有这样的内容:

I like pancakes in my breakfast. Also, I like pancakes in my dinner.

以N = 13运行此项的输出可能是

I
like
pancakes
in
my
breakfast.
Also,
dinner.

我知道如何打开文件并逐行阅读,但除此之外,我不知道如何删除这些独特的单词。

1 个答案:

答案 0 :(得分:3)

让我们先创建一个测试文件。

str =<<END
We like pancakes for breakfast,
but we know others like waffles.
END

FName = 'temp'
File.write(FName, str)
  #=> 65 (characters written)

我们需要从名为nbr_unique的文件中返回一个包含第一个file个唯一单词的数组,所以让我们编写一个可以做到这一点的方法。

def unique_words(fname, nbr_unique)
  <code needed here>
end

你需要为这个方法返回的数组添加唯一的单词,所以让我们先创建一个空数组,然后在方法的末尾返回该数组。

def unique_words(fname, nbr_unique)
  arr = []
  <code needed here>
  arr
end

你知道如何逐行阅读文件,所以我们使用类方法IO::foreach 1 来做到这一点。

def unique_words(fname, nbr_unique)
  arr = []
  File.foreach(fname) do |line|
    <code need here to process line>
  end
  arr
end

在读取第一行后,块变量line等于"We like pancakes for breakfast,\n"。首先,需要删除换行符。检查班级的方法 String看看是否有人可以这样做。

第二行包含单词"we"。我认为"We""we"不应被视为唯一字词。这通常通过将字符串的所有字符转换为全部小写或全部大写来处理。您可以对每一行或每个单词执行此操作(在从一行中提取单词之后)。再次,在类String中寻找合适的方法来执行此操作。

接下来,您需要从每一行中提取单词。再一次,找一个String方法来做到这一点。

接下来,我们需要确定是否要将"like"(或"LIKE")添加到数组arr。查看类Array的实例方法以获取合适的方法。如果已添加,我们需要查看arr现在是否包含nbr_unique个字词。如果是这样,我们就不需要再阅读该文件的任何行,因此我们需要突破foreach的块(可能使用关键字break)。< / p>

还有一件事我们需要照顾。第一行包含"breakfast,",第二行包含"waffles."。我们显然不希望返回的单词包含标点符号。有两种方法可以做到这一点。第一种是删除标点符号,第二种是只接受字母。

给定一个包含标点符号(一行或一个单词)的字符串,我们可以创建第二个字符串,该字符串等于删除了标点符号的原始字符串。一种方法是使用方法String#tr。假设字符串是"breakfast,"。然后

"breakfast,".tr(".,?!;:'", "") #=> "breakfast"

要仅接受字母,我们可以使用以下任何正则表达式(全部返回"breakfast"):

"breakfast,".gsub(/[a-zA-Z]+/, "")
"breakfast,".gsub(/[a-z]+/i, "")
"breakfast,".gsub(/[[:alphaa:]]+/, "")
"breakfast,".gsub(/\p{L}+/, "")

前两个仅使用ASCII字符。使用Unicode的第三个(POSIX)和第四个工作( \ p {}构造)(在Regexp内搜索)。

请注意,在提取单词之前从行中删除标点符号会更有效。

额外赠送金额:使用Enumerator#with_object

每当你看到一个对象(这里arr)被初始化为空,被操纵然后在方法结束时返回时,你应该考虑使用方法Enumerator#with_object或(更常见的), Enumerable#each_with_object。这两个都返回方法名称中引用的对象。

方法IO::foreach在没有块时返回枚举器(类Enumerator的实例)(请参阅doc)。因此,我们可以写

def unique_words(fname, nbr_unique)
  File.foreach(fname).with_object([]) do |line, arr|
    <code need here to process line>
  end
end

我们已经删除了两行(arr = []arr),但也限制了arr的范围。这不是什么大问题,而是Ruby方式。

更多额外信用:使用班级Set

的方法

假设我们写了以下内容。

require 'set'

def unique_words(fname, nbr_unique)
  File.foreach(fname).with_object(Set.new) do |line, set|
    <code need here to process line>
  end.to_a
end

当我们从第二行提取单词"we"时,我们需要检查它是否应该添加到集合中。由于集合具有独特的元素,我们可以尝试这样做。我们无法做到这一点,因为set已经包含了文件第一行中的那个单词。这样做的一个方便的方法是Set#add?

set.add?("we")
  #=> nil

此处方法返回nil,表示该集已包含该单词。它还告诉我们,我们不需要检查该集合现在是否包含nbr_unique个单词。如果我们能够将单词添加到集合中,则会返回set(带有添加的单词)。

该块返回set(一组)的值。方法Set#to_a将该集转换为数组,该数组由方法返回。

1请注意,我已在下面编写IO::foreach来调用类方法File.foreach(fname)...。这是允许的,因为FileIOFile.superclass #=> IO)的子类。我本来可以写IO.foreach(fname)...,但更常见的是使用File作为接收者。