当前位置:  开发笔记 > 编程语言 > 正文

C#-fu - 在功能样式中查找最常用的单词

如何解决《C#-fu-在功能样式中查找最常用的单词》经验,为你挑选了1个好方法。

这个小程序找到文件中十大最常用的单词.您将如何通过逐行流式处理文件来优化此方法,还是将它保持为现在的功能样式?

    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 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支持).



1> Eric Lippert..:

所以你要说的是你想要的:

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 pokes Take.做一些事情然后用手来列出一张带有一系列单词的卡片.ToList继续戳戳直到Take说"我已经完成".此时,ToList会从已经交出的所有牌中列出一个列表,然后将第一张牌交给ForEach.下一次被戳,它会分发下一张牌.

Take做什么?每次被戳时,它会向另一张卡询问OrderByDescending,并立即将该卡交给ToList.发出十张牌后,它告诉ToList"我已经完成了".

OrderByDescending做什么?当它第一次被戳时,它捅了GroupBy.GroupBy递给他一张卡片.它继续戳GroupBy,直到GroupBy说"我已经完成".然后OrderByDescending对牌进行排序,并将第一张牌交给Take.每次接下来都会被戳,它会拿一张新牌拿走,直到Take停止询问.

GetLines,Split,Where,GroupBy,OrderByDescending,Take,ToList和ForEach

等等.你看这是怎么回事.查询运算符GetLines,Split,Where,GroupBy,OrderByDescending,Take是懒惰的,因为它们在戳之前不会执行.他们中的一些人(OrderByDescending,ToList,GroupBy)需要多次捅他们的卡提供商,然后才能回应那些戳他们的人.他们中的一些人(GetLines,Split,Where,Take)只在他们自己被戳时才戳他们的提供者.

完成ToList后,ForEach会调用ToList.ToList hands 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 }}
}

按键排序,然后在列表上执行select-many,直到您提取了前十个单词.

您要查看的任何一个概念都是"累加器".累加器是在迭代数据结构的同时有效地"累积"关于数据结构的信息的对象."Sum"是一系列数字的累加器."StringBuilder"通常用作字符串序列的累加器.您可以编写一个累加器,当单词列表被移过时累积单词的计数.

要了解如何执行此操作,您要学习的功能是Aggregate:

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

祝好运!

推荐阅读
wangtao
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有