Home > OS >  Sort list of objects based on the order of elements in an array
Sort list of objects based on the order of elements in an array

Time:01-30

I searched high and low on SO & Google, but couldn't find a similar question/answer.

So I have an object

class Animal
{
  int Id;
  string Name;
  string Country;
  string Notes;
}

Here are the inputs

var allAnimals = new Animal[]
{
    new Animal(1, "Monkey1", "Malaysia", "Some notes"),
    new Animal(2, "Monkey2", "South America", "Some notes"),
    new Animal(3, "Monkey3", "Singapore", "Some notes"),
    new Animal(4, "Monkey4", "Malaysia", "Some notes"),
    new Animal(5, "Monkey5", "China", "Some notes"),
    new Animal(5, "Monkey5", "Thailand", "Some notes"),
};

var sortByCountries = new string[] {"Malaysia", "Thailand", "Japan"};

Couldn't figure out how to sort allAnimals by passed in sortByCountries order.

So in the example above, here is what I am hoping for

animal object form malaysia
animal object form malaysia
animal object form thailand
animal object form japan

and if sortByCountries order of countries is changed, the result should also change.

Basically I couldn't figure out how to sort allAnimals by the order of sortByCountries passed in

One way I can think of is to create an empty array, and loop over sortByCountries, and then start pushing objects into the new array. Not sure if that's a good solution.

Can someone help out please. TIA

CodePudding user response:

You can achieve this by adding the animals that were selected in the filter, and then append the remaining items to the list like this:

var sortedAnimals = new List<Animal>();
foreach (var country in sortByCountries) {
    var sorted = allAnimals.Where(item => item.Country == country);
    sortedAnimals.AddRange(sorted);
}
        
// Appending the remaining animals that were not sorted
var excludedAnimals = allAnimals.Except(sortedAnimals);
sortedAnimals.AddRange(excludedAnimals);

And here's the full working example:

using System;
using System.Linq;
using System.Collections.Generic;
                    
public class Program
{
    public class Animal
    {
        public int Id {get; set;}
        public string Name {get; set;}
        public string Country {get; set;}
        public string Notes {get; set;}
        public Animal(int id, string name, string country, string notes) {
            this.Id = id;
            this.Name = name;
            this.Country = country;
            this.Notes = notes;
        }
    }
    
    public static void Main()
    {
        var allAnimals = new Animal[]
        {
            new Animal(1, "Monkey1", "Malaysia", "Some notes"),
            new Animal(2, "Monkey2", "South America", "Some notes"),
            new Animal(3, "Monkey3", "Singapore", "Some notes"),
            new Animal(4, "Monkey4", "Malaysia", "Some notes"),
            new Animal(5, "Monkey5", "China", "Some notes"),
            new Animal(5, "Monkey5", "Thailand", "Some notes"),
        };

        var sortByCountries = new string[] {"Malaysia", "Thailand", "Japan"};
        
        var sortedAnimals = new List<Animal>();
        foreach (var country in sortByCountries) {
            var sorted = allAnimals.Where(item => item.Country == country);
            sortedAnimals.AddRange(sorted);
        }
        
        // Appending the remaining animals that were not sorted
        var excludedAnimals = allAnimals.Except(sortedAnimals);
        sortedAnimals.AddRange(excludedAnimals);
        
        foreach (var animal in sortedAnimals)
        {
            Console.WriteLine($"{animal.Id} {animal.Country}");
        }
    }
}

CodePudding user response:

For potentially large collections, best performance might be achieved by using dictionary:

var sortByCountriesDictionary = sortByCountries
    .Select((country, index) => new {country, index})
    .ToDictionary(x => x.country, x => x.index);

var sortedAnimals = allAnimals
    .Where(animal => sortByCountriesDictionary.ContainsKey(animal.Country))
    .OrderBy(animal => sortByCountriesDictionary[animal.Country])
    .ToList();  // Or .ToArray()

The advantage of the dictionary is that it avoids many-to-many string compares, which could lead to poor performance with large collections. Under the covers, the dictionary uses a hash table lookup, which is generally very efficient.

The above is still sub-optimal, because it looks up the country in the dictionary twice per animal. The following more complicated code combines the lookup and exists filter into one dictionary reference per animal.

var sortedAnimals = allAnimals
    .Select(animal => {
         bool exists = sortByCountriesDictionary.TryGetValue(animal.Country, out int index);
         return new {animal, exists, index};
    })
    .Where(item => item.exists)
    .OrderBy(item => item.index)
    .Select(item => item.country)
    .ToList();  // Or .ToArray()

Whether or not this is more efficient depends on whether the cost of the temporary object creation exceeds the savings of one less dictionary reference.

  • Related