Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do a Sort in an Dictionary object

Tags:

c#

.net

I'm working with a Dictionary object in C#. I have got all kinds of data in there, but now I would like to sort it ascending given a certain value (double).

How Dictionary is defined:

Dictionary<int, Dictionary<string, string>> myDict = getDictData(nr);

This is what the Dictionary looks like:

myDict[0]{"name", "name1"}
myDict[0]{"number", 0.0158}

myDict[1]{"name", "name2"}
myDict[1]{"number", 0.0038}

myDict[2]{"name", "name3"}
myDict[2]{"number", 0.0148}

I don't know if I am wrting this down well, to clearify I am calling the name2 name like this:

myDict[1]["name"]

So what I want to do is to sort this object so the first (myDict[0]) contains the object with the lowsest "number" value.

I tried looking online but all I can find is how to order this by first key/value which is of course the zero myDict[0].

As you can understand I didn't get far. So maybe you can help me out a bit.

This is what I tried so far:

foreach (var item in myDict.OrderBy(i => i.Key))
{
      Console.WriteLine(item);
}

or this:

var items = from pair in myDict orderby pair.Value ascending select pair;

or this: myDict .Sort((x, y) => x.waterDepth.CompareTo(y.waterDepth));

nothing worked...

Any help would be much appreciated

like image 423
BonifatiusK Avatar asked Jan 08 '23 11:01

BonifatiusK


2 Answers

A Dictionary<TKey, TValue> is not kept in sorted order, and it has no defined order. This is because it is implemented using a hashing algorithm.

Although there is a SortedDictionary available, it doesn't look suitable for what you want.

Instead it looks like you should use a list of dictionaries, and use a custom comparer to sort it:

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    using StrStrDict = Dictionary<string, string>;

    public sealed class CustomComparer: IComparer<StrStrDict>
    {
        public int Compare(StrStrDict lhs, StrStrDict rhs)
        {
            double x = Double.Parse(lhs["number"]);
            double y = Double.Parse(rhs["number"]);

            return x.CompareTo(y);
        }
    }

    internal class Program
    {
        private static void Main()
        {
            var dict1 = new StrStrDict
            {
                {"name",   "name1"},
                {"number", "0.0158"}
            };

            var dict2 = new StrStrDict
            {
                {"name",   "name2"},
                {"number", "0.0038"}
            };

            var dict3 = new StrStrDict
            {
                {"name",   "name3"},
                {"number", "0.0148"}
            };

            var list = new List<StrStrDict> {dict1, dict2, dict3};

            list.Sort(new CustomComparer());

            foreach (var element in list)
            {
                Console.WriteLine("Number = " + element["number"]);
            }
        }
    }
}

Note that this isn't quite like your example, because you seem to be storing actual doubles rather than string representations of doubles, but that doesn't match with your use of Dictionary<string, string>. Therefore I have stored them as strings and parse them into doubles on demand. That isn't very efficient, and the alternative is to use a Dictionary<string, object> and cast the values in the comparer - see the example later.

Also note that I haven't added any error handling at all!

Here's an example using a Dictionary<string, object> instead:

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    using StrObjDict = Dictionary<string, object>;

    public sealed class CustomComparer: IComparer<StrObjDict>
    {
        public int Compare(StrObjDict lhs, StrObjDict rhs)
        {
            double x = (double)lhs["number"];
            double y = (double)rhs["number"];

            return x.CompareTo(y);
        }
    }

    internal class Program
    {
        private static void Main()
        {
            var dict1 = new StrObjDict
            {
                {"name",   "name1"},
                {"number", 0.0158}
            };

            var dict2 = new StrObjDict
            {
                {"name",   "name2"},
                {"number", 0.0038}
            };

            var dict3 = new StrObjDict
            {
                {"name",   "name3"},
                {"number", 0.0148}
            };

            var list = new List<StrObjDict> {dict1, dict2, dict3};

            list.Sort(new CustomComparer());

            foreach (var element in list)
            {
                Console.WriteLine("Name = {0}, Number = {1}", element["name"], element["number"]);
            }
        }
    }
}

Finally, note that this approach only makes sense if you want to dynamically store other things in the dictionaries. If you are only ever storing "name" and "number" in each dictionary then you should just create a specific class to store that data rather than using a dictionary!

That approach might look like this - note how much simpler it is (and typesafe too!):

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    public sealed class Entry
    {
        public readonly string Name;
        public readonly double Number;

        public Entry(string name, double number)
        {
            Name   = name;
            Number = number;
        }
    }

    internal class Program
    {
        private static void Main()
        {
            var list = new List<Entry>
            {
                new Entry("name1", 0.0158),
                new Entry("name2", 0.0038),
                new Entry("name3", 0.0148)
            };

            list.Sort((lhs, rhs) => lhs.Number.CompareTo(rhs.Number));

            // Alternatively if you don't want an in-place sort and you
            // want to keep the original unsorted list, you can create
            // a separate sorted list using Linq like so:
            //
            // var sortedList = list.OrderBy(x => x.Number).ToList();

            foreach (var element in list)
            {
                Console.WriteLine("Name = {0}, Number = {1}", element.Name, element.Number);
            }
        }
    }
}
like image 53
Matthew Watson Avatar answered Jan 11 '23 22:01

Matthew Watson


As Matthew Watson said Dictionaries are not sequential collections so I suggest to use List<Dictionary<string, string>>.
I suggest List because it seems you use the int key as an index for locating proper Dictionary<string, string> and List "Represents a strongly typed list of objects that can be accessed by index."
And for ordering your list this can solve the problem:

        List<Dictionary<string, string>> myDict = new List<Dictionary<string, string>>();
        myDict.Add(new Dictionary<string, string>());
        myDict.Add(new Dictionary<string, string>());
        myDict.Add(new Dictionary<string, string>());
        myDict[0].Add("name", "name1");
        myDict[0].Add("number", "0.0158");
        myDict[1].Add("name", "name2");
        myDict[1].Add("number", "0.0038");
        myDict[2].Add("name", "name3");
        myDict[2].Add("number", "0.0148");

        var result = myDict.SelectMany(x => x.Where(d => d.Key == "number")).OrderBy(x => x.Value);

But I strongly recommend to use object-oriented data structures. For example consider this code as an alternative way:

class Foo
{
    public string Name { get; set; }
    public double Number { get; set; }
}

List<Foo> myDict = new List<Foo>();
myDict.Add(new Foo() { Name = "name1", Number = 0.0158 });
myDict.Add(new Foo() { Name = "name2", Number = 0.0038 });
myDict.Add(new Foo() { Name = "name3", Number = 0.0148 });
var result = myDict.OrderBy(x => x.Number);
like image 42
Ham3d Avatar answered Jan 11 '23 22:01

Ham3d