提问者:小点点

利用C#中的字典(或其他数据结构)降低嵌套循环复杂度


请您给出一个例子,在字典(可能是其他数据结构)的帮助下,降低了下面这段代码的时间复杂度?

据我所知,我的蛮力解决方案的时间复杂度为O(n^2),然而,可能在O(n)中完成(在非嵌套循环的n次中)。

任务是打印每一天和每一个地点,在这一天和每一个地点观察到的哺乳动物的百分比。

foreach (var day in EachDay(GetDateTimeForFirstObservation(animalObservations),
GetDateTimeForLastObservation(animalObservations)))
{
    Console.WriteLine("Day: {0}", day.ToString("dd/MM/yyyy"));

    foreach (var location in EachLocation(animalObservations
        .Where(ao => ao.Datetime.Day == day.Day).ToList()))
    {
        Console.WriteLine("Location: {0}", location);

        numOfAllAnimalsInLocationAndDay = animalObservations
            .Where(aob => aob.Location == location &&
                aob.Datetime.Date == day).Count();

        numOfMammalsAnimalsInLocationAndDay = animalObservations
            .Where(aob => aob.Location == location &&
            aob.Datetime.Date == day && aob.Animal.IsMammal).Count();

        Console.WriteLine("Percentage of Mammals in location and day: {0:N2}%",
            numOfMammalsAnimalsInLocationAndDay/numOfAllAnimalsInLocationAndDay * 100);
    }
}

输入如下所示:

[
{"DateTime":"2020-02-22 10:10:15", "Location":"Backyard", "Animal": {"Species":"Camel", "IsMammal": "TRUE"}},
{"DateTime":"2020-02-22 11:10:15", "Location":"Backyard", "Animal": {"Species":"Camel", "IsMammal": "TRUE"}},
{"DateTime":"2020-02-22 12:10:15", "Location":"Backyard", "Animal": {"Species":"Ant", "IsMammal": "FALSE"}},
{"DateTime":"2020-02-22 22:10:15", "Location":"Sky", "Animal": {"Species":"Flamingo", "IsMammal": "TRUE"}},
{"DateTime":"2020-02-22 23:10:15", "Location":"Sky", "Animal": {"Species":"Bee", "IsMammal": "FALSE"}},
{"DateTime":"2020-02-23 13:11:15", "Location":"City", "Animal": {"Species":"Racoon", "IsMammal": "TRUE"}},
{"DateTime":"2020-02-24 15:10:00", "Location":"City", "Animal": {"Species":"Dog", "IsMammal": "TRUE"}},
{"DateTime":"2020-02-24 19:10:00", "Location":"City", "Animal": {"Species":"Fly", "IsMammal": "FALSE"}},
{"DateTime":"2020-02-24 19:10:15", "Location":"City", "Animal": {"Species":"Butterfly", "IsMammal": "FALSE"}},
{"DateTime":"2020-02-24 19:10:20", "Location":"City", "Animal": {"Species":"Cat", "IsMammal": "TRUE"}},
{"DateTime":"2020-02-24 19:10:30", "Location":"City", "Animal": {"Species":"Flee", "IsMammal": "FALSE"}},
{"DateTime":"2020-02-25 21:10:15", "Location":"Village", "Animal": {"Species":"Horse", "IsMammal": "TRUE"}},
{"DateTime":"2020-02-25 22:10:15", "Location":"Village", "Animal": {"Species":"Fly", "IsMammal": "FALSE"}},
{"DateTime":"2020-02-25 23:10:15", "Location":"Village", "Animal": {"Species":"Bee", "IsMammal": "FALSE"}},
{"DateTime":"2020-02-25 10:10:15", "Location":"Home", "Animal": {"Species":"Iguana", "IsMammal": "FALSE"}}
]

和期望的输出:

Day: 22.02.2020
Location: Backyard
Percentage of Mammals in location and day: 66,67%
Location: Sky
Percentage of Mammals in location and day: 50,00%
Day: 23.02.2020
Location: City
Percentage of Mammals in location and day: 100,00%
Day: 24.02.2020
Location: City
Percentage of Mammals in location and day: 40,00%
Day: 25.02.2020
Location: Village
Percentage of Mammals in location and day: 33,33%
Location: Home
Percentage of Mammals in location and day: 0,00%

共3个答案

匿名用户

我认为您的解决方案实际上复杂度为O(n^3),因为您正在进行3个嵌套迭代:

  1. 每隔一天
  2. 当天的每个不同位置
  3. 计数日-位置对的动物和哺乳动物的数量--这是在Linq表达式中给出的,因此不是很明显

假设您具有以下类结构:

public class AnimalObservation {
    public DateTime DateTime { get; set; }
    public string Location { get; set; }
    public Animal Animal { get; set; }
}

public class Animal {
    public string Species { get; set; }
    public bool IsMammal { get; set; }
}

您可以在O(n)中使用两个字典--一个用于动物,一个用于哺乳动物--来实现这一点,这两个字典将一个日期-位置对作为键,将一个计数器作为值

    IDictionary<ValueTuple<DateTime, string>, int> animals = new Dictionary<ValueTuple<DateTime, string>, int>(new DayLocationComparer());
    IDictionary<ValueTuple<DateTime, string>, int> mammals = new Dictionary<ValueTuple<DateTime, string>, int>(new DayLocationComparer());
    foreach (AnimalObservation ao in aos) {
        ValueTuple<DateTime, string> dayLocation = new ValueTuple<DateTime, string>(ao.DateTime, ao.Location);

        if (!animals.ContainsKey(dayLocation)) {
            animals.Add(dayLocation, 1);
        } else {
            animals[dayLocation] = animals[dayLocation] + 1;
        }


        if (!mammals.ContainsKey(dayLocation) && ao.Animal.IsMammal) {
            mammals.Add(dayLocation, 1);
        } else if (!mammals.ContainsKey(dayLocation) && !ao.Animal.IsMammal) {
            mammals.Add(dayLocation, 0);
        } else if (mammals.ContainsKey(dayLocation) && ao.Animal.IsMammal) {
            animals[dayLocation] = animals[dayLocation] + 1;
        }
    }

    foreach (ValueTuple<DateTime, string> dayLocation in animals.Keys) {
        int nrOfAnimals = animals[dayLocation];
        int nrOfMammals = mammals[dayLocation];
        Console.WriteLine((double)nrOfMammals / nrOfAnimals * 100);
    }

其中DayLocationComparer是忽略DateTime的时间部分的比较器

public class DayLocationComparer : EqualityComparer<ValueTuple<DateTime, string>> {
    public override bool Equals(ValueTuple<DateTime, string> x, ValueTuple<DateTime, string> y) => x.Item1.Date == y.Item1.Date && x.Item2 == y.Item2;
    public override int GetHashCode(ValueTuple<DateTime, string> x) => x.Item1.GetHashCode();
}

当然,我建议使用一个用于日位置对的类,以获得更多可读性更强的代码。

匿名用户

有很多真正疯狂的技巧来降低时间复杂度,但大多数时候处理数据时,您必须依赖于数据的初始排序方式。 如果它没有被排序,我们可以通过某个复合键来排序。 在您的示例中,关键字是tuple,其中Item1是日期的日期,Item2是位置。 这将需要n*log(n),然后使用线性时间遍历数据,使用线性时间生成结果。

所以看着你的数据,它已经被分类了。 所以我们可以跳过那部分直接走过去。 基本思想,我们初始化一些状态,如果它改变了,我们就会产生结果。 在我们的案例中,状态是当前的日子,当前的位置,我们跟踪两个变量的信息总的动物,总的哺乳动物。

    public static void PrintPopulation(List<AnimalObservations> animalObservations)
    {
        if (animalObservations.Count == 0)
            return;
        var item = animalObservations[0];
        string currentLocation = item.Location;
        DateTime currentDate = item.DateTime.Date;
        int totalAnimals = 1;
        int totalMammals = item.Animal.IsMammal ? 1 : 0;
        for (int i = 1; i < animalObservations.Count; i++)
        {
            item = animalObservations[i];
            if (currentLocation != item.Location ||
                currentDate != item.DateTime.Date)
            {
                PrintResult(currentDate, currentLocation, totalAnimals, totalMammals);
                totalMammals = 0;
                totalAnimals = 0;
                currentLocation = item.Location;
                currentDate = item.DateTime.Date;
            }

            totalAnimals++;
            totalMammals += item.Animal.IsMammal ? 1 : 0;
        }
        PrintResult(currentDate, currentLocation, totalAnimals, totalMammals);
    }

    public static void PrintResult(DateTime date, string location, int totalAnimals, int totalMammals)
    {
        Console.WriteLine($"{date} {location} {(double) totalMammals / totalAnimals}");
    }

我假设

public class AnimalObservations
{
    public DateTime DateTime { get; set; }
    public string Location { get; set; }
    public Animal Animal { get; set; }
}

public class Animal
{
    public bool IsMammal { get; set; }
}

匿名用户

一种解决方案是使用字典,其中键类型包括日期和位置,值类型保存哺乳动物/非哺乳动物计数。

请尝试下面的代码。 您将需要将靠近顶部的字符串常量指向JSON文件的位置。 您还需要将NewtonSoft JSON库添加到项目中。 注意,我重写了用作字典键类型的类中的Equals和GetHashCode方法。

namespace Mammals
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;

    using Newtonsoft.Json.Linq;

    class Program
    {
        static void Main(string[] args)
        {
            const string filePath = "C:\\temp\\mammals.json";

            // Read input from JSON:
            string jsonInput;
            using (var file = new StreamReader(new FileStream(filePath, FileMode.Open)))
            {
                jsonInput = file.ReadToEnd();
            }

            var json = JArray.Parse(jsonInput);
            var sightings = json.Select(j => j.ToObject<Sighting>());

            // Set up dictionary, using day/location as key:
            var sightingDictionary = new Dictionary<SightingDayAndPlace, SightingCounter>();

            // Loop through sightings in O(n):
            foreach (var sighting in sightings)
            {
                var sightingTimeAndPlace = sighting.GetSightingDayAndPlace;
                if (!sightingDictionary.ContainsKey(sightingTimeAndPlace))
                {
                    sightingDictionary.Add(sightingTimeAndPlace, new SightingCounter());
                }

                if (sighting.IsMammal)
                {
                    sightingDictionary[sightingTimeAndPlace].MammalCount++;
                }
                else
                {
                    sightingDictionary[sightingTimeAndPlace].NonMammalCount++;
                }
            }

            // Print output:
            var currentDay = default(DateTime);
            foreach (var item in sightingDictionary)
            {
                var key = item.Key;
                if (key.Day != currentDay)
                {
                    Console.WriteLine($"Day: {key.Day:dd.MM.yyyy}");
                }

                Console.WriteLine($"Location: {key.Location}");
                Console.WriteLine($"Percentage of Mammals in location and day: {item.Value.MammalPercentage:F}%");
            }

            Console.ReadKey();
        }

        private class SightingDayAndPlace
        {
            public SightingDayAndPlace(DateTime day, string location)
            {
                this.Day = day;
                this.Location = location;
            }

            public DateTime Day { get; }

            public string Location { get; }

            public override bool Equals(object obj)
            {
                if (obj == null || !(obj is SightingDayAndPlace that))
                {
                    return false;
                }

                return this.Day == that.Day
                       && this.Location == that.Location;
            }

            public override int GetHashCode()
            {
                // Consider a different implementation if memory or performance is relevant.
                return new { this.Day, this.Location }.GetHashCode();
            }
        }

        private class Sighting
        {
            public DateTime DateTime { get; set; }
            public string Location { get; set; }
            public Animal Animal { get; set; }
            public string Species => Animal.Species;
            public bool IsMammal => Animal.IsMammal;
            public DateTime Day => DateTime.Date;

            public SightingDayAndPlace GetSightingDayAndPlace => new SightingDayAndPlace(this.Day, this.Location);
        }

        private class Animal
        {
            public string Species { get; set; }
            public bool IsMammal { get; set; }
        }

        private class SightingCounter
        {
            public int MammalCount { get; set; }
            public int NonMammalCount { get; set; }

            public double MammalPercentage => (MammalCount / ((double)MammalCount + NonMammalCount)) * 100;
        }
    }
}