gpt4 book ai didi

c# - C#-fu-以功能样式查找常用单词

转载 作者:行者123 更新时间:2023-12-03 17:11:26 24 4
gpt4 key购买 nike

这个小程序可以找到文件中最常用的十个单词。您或您将如何优化它以通过逐行流处理来处理文件,但保持其现在的功能风格?

    static void Main(string[] args)
{
string path = @"C:\tools\copying.txt";

File.ReadAllText(path)
.Split(' ')
.Where(s => !string.IsNullOrEmpty(s))
.GroupBy(s => s)
.OrderByDescending(g => g.Count())
.Take(10)
.ToList()
.ForEach(g => Console.WriteLine("{0}\t{1}", g.Key, g.Count()));

Console.ReadLine();
}


这是我要使用的行阅读器:

    static IEnumerable<string> ReadLinesFromFile(this string filename)
{
using (StreamReader reader = new StreamReader(filename))
{
while (true)
{
string s = reader.ReadLine();

if (s == null)
break;

yield return s;
}
}
}


编辑:

我意识到,热门单词的实现并没有考虑到标点​​符号和其他所有细微差别,我对此也不太担心。

澄清:

我对不会立即将整个文件加载到内存中的解决方案感兴趣。我想您将需要一个数据结构,该结构可以像单词trie一样快速获取单词流和“分组”。然后以某种懒惰的方式完成它,以便行阅读器可以逐行处理它。我现在意识到,这比我上面给出的简单示例有很多要求,并且要复杂得多。也许我会试一试,看看是否可以使代码像上面一样清晰(带有大量新的lib支持)。

最佳答案

因此,您要说的是您想从哪里来:

full text -> sequence of words -> rest of query




sequence of lines -> sequence of words -> rest of query


是?

这似乎很简单。

var words = from line in GetLines()
from word in line.Split(' ')
select word;

and then

words.Where( ... blah blah blah


或者,如果您更喜欢始终使用“流利的”样式,则需要使用SelectMany()方法。

我个人不会一口气做到这一点。我将进行查询,然后编写一个foreach循环。这样,查询就不会产生副作用,并且副作用处于它们所属的循环中。但是有些人似乎更喜欢将副作用添加到ForEach方法中。

更新:关于这个查询有多“懒惰”存在一个问题。

您的正确之处在于,最终的结果是文件中每个单词的内存表示形式;但是,通过我的小改组,您至少不必创建一个包含整个文本开头的大字符串;您可以逐行进行。

有很多方法可以减少此处的重复项,我们将在一分钟内进行讨论。但是,我想继续谈论如何推理懒惰。

思考这些事情的好方法归功于乔恩·斯凯特(Jon Skeet),我将毫不留情地从他那里偷走。

想象一个舞台上有一群人。他们穿着衬衫,上面写着GetLines,Split,Where,GroupBy,OrderByDescending,Take,ToList和ForEach。

ToList戳取。采取行动,然后动手列出一张卡片,上面列出单词。 ToList继续戳Take,直到Take说“我完成了”。到那时,ToList将从已处理的所有卡中列出一个清单,然后将第一个交给ForEach。下次戳时,它会分发下一张卡。

请问做什么?每次戳戳它都会向OrderByDescending索要另一张卡,然后立即将该卡交给ToList。发出十张卡片后,它告诉ToList“我完成了”。

OrderByDescending是做什么的?第一次戳时,它戳GroupBy。 GroupBy递给它一张卡片。它一直在戳GroupBy,直到GroupBy说“我完成了”。然后,OrderByDescending对卡片进行排序,然后将第一个卡片拿走。以后每次戳戳时,都会将新卡交给Take,直到Take停止询问。

GetLines,拆分,Where,GroupBy,OrderByDescending,Take,ToList和ForEach

等等。您会看到这种情况。查询运算符GetLines,Split,Where,GroupBy,OrderByDescending,Take都是惰性的,因为它们直到被戳才起作用。其中的一些命令(OrderByDescending,ToList,GroupBy)需要多次拨通其卡提供商,然后他们才能响应向其戳戳的人。他们中的某些人(GetLines,Split,Where,Take)在自己戳戳时仅戳一次其提供者。

完成ToList后,ForEach戳ToList。 ToList将ForEach移出列表。 Foreach会对单词进行计数,然后在白板上写一个单词和一个计数。 ForEach不断戳ToList,直到ToList说“没有更多”为止。

(请注意,ToList在您的查询中完全没有必要;它所做的只是将前十名的结果累加到一个列表中。ForEach可以直接与Take对话。)

现在,关于您是否可以进一步减少内存占用的问题:是的,可以。假设文件是​​“ foo bar foo blah”。您的代码建立了一组组:

{ 
{ key: foo, contents: { foo, foo } },
{ key: bar, contents: { bar } },
{ key: blah, contents: { blah } }
}


然后按内容列表的长度排序,然后排在前十位。您不必在内容列表中存储那么多的内容即可计算所需的答案。您真正想要存储的是:

{ 
{ key: foo, value: 2 },
{ key: bar, value: 1 },
{ key: blah, value: 1 }
}


然后按值对它进行排序。

或者,您也可以建立向后映射

{ 
{ key: 2, value: { foo } },
{ key: 1, value: { bar, blah }}
}


按键排序,然后在列表上进行多次选择,直到提取出前十个单词。

您想要查看以完成上述任一操作的概念是“累加器”。累加器是在迭代数据结构时有效地“累积”有关数据结构的信息的对象。 “ Sum”是一个数字序列的累加器。 “ StringBuilder”通常用作一系列字符串的累加器。您可以编写一个累加器,该累加器在遍历单词列表时累加单词数。

您想学习以了解如何执行此功能的函数是Aggregate:

http://msdn.microsoft.com/en-us/library/system.linq.enumerable.aggregate.aspx

祝好运!

关于c# - C#-fu-以功能样式查找常用单词,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2136653/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com