如何在C#中高效地搜索泛型集合
例如,假设我们有两个内存集合,一个集合包含City 模型,另一个集合包含Restaurant 模型。我们的系统需要按City 组织 Restaurant :
这是我们的模型,其中Restaurant的CityId属性用于标识其居住的City:
1 public class City 2 { 3 public Guid Id { get; set; } 4 public string Name { get; set; } 5 } 6 7 public class Restaurant 8 { 9 public Guid Id { get; set; } 10 public Guid CityId { get; set; } 11 public string Name { get; set; } 12 }
我们的目标是优化组织集合的操作。为了做到这一点,我们必须确保对内存中的集合进行尽可能少的迭代。首先让我们看一下效率低下的解决方案,并讨论如何对其进行改进。
1 public IDictionary<City, IEnumerable<Restaurant>> OrganizeRestaurantsByCity(IEnumerable<City> cities, IEnumerable<Restaurant> restaurants) 2 { 3 IDictionary<City, IEnumerable<Restaurant>> restaurantsByCity = new Dictionary<City, IEnumerable<Restaurant>>(); 4 5 foreach (var city in cities) 6 { 7 var restaurantsInCity = restaurants 8 .Where(restaurant => restaurant.CityId.Equals(city.Id)); 9 10 restaurantsByCity.Add(city, restaurantsInCity); 11 } 12 13 return restaurantsByCity; 14 }
首先,我们实例化一个以City为关键字的字典,每个关键字都指向一组Restaurant 。然后,我们遍历City ,以根据CityId查找匹配的Restaurant 。找到特定City 的 Restaurants 后,便将其添加到字典中。
该解决方案似乎完全合理吧?我相信您在职业生涯中也见过类似的经历。LINQ的缺点之一是它的便利性。这听起来可能违反直觉,但它使开发人员不得不编写代码来手动执行这些操作的日子变得抽象了。在那些日子里,您可能不得不考虑它们的效率,并立即发现嵌套循环。
这里的问题隐藏在for每个循环中。对于每个City,我们都在迭代整个Restaurant 集合以寻找匹配项。鉴于有大量的City 和 Restaurant ,这种解决方案远非理想。最好,我们的代码应保证两个集合仅被迭代一次。索引就是答案……
通过按CityId索引Restaurant ,我们可以直接查找它们。因此,无需搜索所有的对象。LINQ为此目的设计了一个很棒的扩展方法,称为ToLookup()。
1 public IDictionary<City, IEnumerable<Restaurant>> OrganizeRestaurantsByCity(IEnumerable<City> cities, IEnumerable<Restaurant> restaurants) 2 { 3 IDictionary<City, IEnumerable<Restaurant>> restaurantsByCity = new Dictionary<City, IEnumerable<Restaurant>>(); 4 5 var restaurantsLookup = restaurants 6 .ToLookup(restaurant => restaurant.CityId); 7 8 foreach (var city in cities) 9 { 10 var restaurantsInCity = restaurantsLookup[city.Id]; 11 restaurantsByCity.Add(city, restaurantsInCity); 12 } 13 14 return restaurantsByCity; 15 }
您所要做的就是定义ToLookup()方法用于索引集合的键。在上面的代码中,我们正在使用CityId。假设您要查找CityId为12的每家餐厅,只需 RestaurantsLookup [12]。然后,我们可以通过使用restaurantLookup [city.Id]遍历City 来使用变量索引。
现在,我们有了一个有效的解决方案,可以通过确保各自的集合重复一次,根据Restaurant 所在的City 来组织 Restaurant 。我们可以将代码进一步简化为几行:
1 public IDictionary<City, IEnumerable<Restaurant>> OrganizeRestaurantsByCity(IEnumerable<City> cities, IEnumerable<Restaurant> restaurants) 2 { 3 var restaurantsLookup = restaurants 4 .ToLookup(restaurant => restaurant.CityId); 5 6 return cities 7 .ToDictionary(city => city, city => restaurantsLookup[city.Id]); 8 }