从日志文件中获取前100个URL

时间:2012-05-24 08:42:12

标签: algorithm data-structures

我的一位朋友在接受采访时被问到以下问题。谁能告诉我如何解决它?

我们有一个相当大的日志文件,大约5GB。日志文件的每一行都包含用户在我们网站上访问过的URL。我们想弄清楚用户访问过的最受欢迎的100个网址是什么。怎么做?

3 个答案:

答案 0 :(得分:7)

如果我们有超过10GB的RAM,只需使用hashmap直接进行。

否则,使用哈希函数将其分成几个文件。然后处理每个文件并获得前5名。每个文件的“前5名”,将很容易获得前5名。

另一种解决方案是使用任何外部排序方法对其进行排序。然后扫描文件一次以计算每次出现的次数。在此过程中,您无需跟踪计数。你可以安全地扔掉任何没有进入top5的东西。

答案 1 :(得分:2)

只需根据URL对日志文件进行排序(如果选择堆排序或快速排序等算法,则需要常量空间),然后计算每个URL显示的次数(简单,具有相同URL的行是下一个彼此)。

总体复杂度为O(n * Log(n))。

为什么要拆分多个文件并且只为每个文件保留前3个(或前5个或前N个)是错误的:

     File1 File2 File3
url1   5     0     5
url2   0     5     5
url3   5     5     0
url4   5     0     0
url5   0     5     0
url6   0     0     5
url7   4     4     4

url7从未进入单个文件中的前3名,但总体上是最好的。

答案 2 :(得分:1)

由于日志文件相当大,您应该使用流阅读器读取日志文件。不要在内存中全部阅读。 我希望在我们处理日志文件时,在内存中包含可能不同的链接数是可行的。

// Pseudo
Hashmap map<url,count>
while(log file has nextline){
    url = nextline in logfile
    add url to map and update count
}

List list 
foreach(m in map){
    add m to list         
}

sort the list by count value
take top n from the list

运行时为O(n)+ O(m * log(m)),其中n是行中日志文件的大小,其中m是不同找到链接的数量。

这是伪代码的C#实现。未提供实际的文件读取器和日志文件。 提供了使用存储器中的列表读取日志文件的简单模拟。

该算法使用散列映射来存储找到的链接。排序算法之后创建了前100个链接。一个简单的数据容器数据结构用于排序算法。

内存复杂性取决于预期的不同链接。 hashmap必须能够包含找到的不同链接, 否则这个算法将不起作用。

// Implementation
using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main(string[] args)
    {
        RunLinkCount();
        Console.WriteLine("press a key to exit"); 
        Console.ReadKey();
    }

    class LinkData : IComparable
    {
        public string Url { get; set; }
        public int Count { get; set; }
        public int CompareTo(object obj)
        {
            var other = obj as LinkData;
            int i = other == null ? 0 : other.Count;
            return i.CompareTo(this.Count);
        }
    }

    static void RunLinkCount()
    {
        // Data setup
        var urls = new List<string>();
        var rand = new Random();
        const int loglength = 500000;
        // Emulate the log-file
        for (int i = 0; i < loglength; i++)
        {
            urls.Add(string.Format("http://{0}.com", rand.Next(1000)
                 .ToString("x")));
        }

        // Hashmap memory must be allocated 
        // to contain distinct number of urls
        var lookup = new Dictionary<string, int>();

        var stopwatch = new System.Diagnostics.Stopwatch();
        stopwatch.Start();

        // Algo-time
        // O(n) where n is log line count
        foreach (var url in urls) // Emulate stream reader, readline
        {
            if (lookup.ContainsKey(url))
            {
                int i = lookup[url];
                lookup[url] = i + 1;
            }
            else
            {
                lookup.Add(url, 1);
            }
        }

        // O(m) where m is number of distinct urls
        var list = lookup.Select(i => new LinkData 
             { Url = i.Key, Count = i.Value }).ToList();
        // O(mlogm)
        list.Sort();
        // O(m)
        var top = list.Take(100).ToList(); // top urls

        stopwatch.Stop();
        // End Algo-time

        // Show result
        // O(1)
        foreach (var i in top)
        {
            Console.WriteLine("Url: {0}, Count: {1}", i.Url, i.Count);
        }

        Console.WriteLine(string.Format("Time elapsed msec: {0}",
           stopwatch.ElapsedMilliseconds));
    }
}

修改:此答案已根据评论更新

  • 补充:运行时间和内存复杂性分析
  • 添加:伪代码
  • 补充说明:解释我们如何管理相当大的日志文件