简体   繁体   中英

dictionary or list c#

i got a strange C# programming problem, there is a data retrieval in groups of random lengths of number groups. The numbers should be all unique like :

group[1]{1,2,15}; 
group[2]{3,4,7,33,22,100};
group[3]{11,12,9};

//now there is a routine that adds number to a group
// for the eample just imagine the active group looks like:
// group[active]=(10,5,0)
group[active].add(new_number);

//now if 100 where to be added to the active group
//then active group should be merged to group[2] 
//(as that one allready contained 100)
//And then as a result it would like

group[1]{1,2,15}; 
group[2]{3,4,7,33,22,100,10,5,0};  //10 5 0 added to group[2]
group[3]{11,12,9};

// 100 wasnt added to group[2] since it was allready in there.

If the number to be added is all ready used (not unique) in a previous group. Then i should merge all numbers in the active group towards that previous group, so i dont get double numbers. So in above example if number 100 was added to the active group. Then all numbers in the group[active] should be merged into group[2] And then the group[active] should start clean fresh again with no items. And since 100 was already in group[2] it should not be added double.

I am not entirely sure on how to deal with this in a proper way.

As an important criteria here is that it has to work fast. i will have around minimal 30 groups (upper-bound unknown might be 2000 or more), and their length on average contains 5 integer numbers, but could be much longer or only 1 number

I kinda feel that I am reinventing the wheel here.

  • I wonder how this problem is called. (does it go by a name, some sorting, or grouping math problem) ?, with a name i might find some articles related to such problems.

  • But maybe its indeed something new, then what would you recommend me? Should I use list of lists or a dictionary of lists.. or something else? Somehow the checking if the number is allready present should be done fast.

Update i'm thinking along this path now, not sure if its best instead of a single number i use a struct now, it wasnt written in the original question as i was afraid, explaining that would make it too complex

struct data{int ID; int additionalNumber}

Dictionary <int,List<data>> group =new Dictionary<int, List<data>>(); 

update 2 i can step aside from using a struct in here, a lookup list could connect the other data to the proper index. So this makes it again more close to the original description.

on a side note great answers are given, So far i dont know yet what would work best for me in my situation.

note on selected answer Several answers where given here, I went for the pure dictionary solution. Just as a note for people in similar problem scenario's i'd still recommend testing, and maybe the others work better for you. Its just that in my case currently it worked best, the code was also quite short which i liked, and dictionary adds also other handy options for my future coding on this.

I would go with Dictionary<int, HashSet<int>> , since you want to avoid duplicates and want a fast way to check if given number already exists:

Usage example:

var groups = new Dictionary<int, HashSet<int>>();

// populate the groups
groups[1] = new HashSet<int>(new[] { 1,2,15 });
groups[2] = new HashSet<int>(new[] { 3,4,7,33,22,100 });

int number = 5;
int groupId = 4;

bool numberExists = groups.Values.Any(x => x.Contains(number));

// if there is already a group that contains the number
// merge it with the current group and add the new number
if (numberExists)
{
     var group = groups.First(kvp => kvp.Value.Contains(number));
     groups[group.Key].UnionWith(groups[groupId]);
     groups[groupId] = new HashSet<int>();
}
// otherwise just add the new number
else
{
    groups[groupId].Add(number);
}

I have kept it as easy to follow as I can, trying not to impact the speed or deviate from the spec.

在此输入图像描述

Create a class called Groups.cs and copy and paste this code into it:

using System;
using System.Collections.Generic;

namespace XXXNAMESPACEXXX
{
    public static class Groups
    {
        public static List<List<int>> group { get; set; }

        public static int active { get; set; }


        public static void AddNumberToGroup(int numberToAdd, int groupToAddItTo)
        {
            try
            {
                if (group == null)
                {
                    group = new List<List<int>>();

                }

                while (group.Count < groupToAddItTo)
                {
                    group.Add(new List<int>());

                }

                int IndexOfListToRefresh = -1;

                List<int> NumbersToMove = new List<int>();

                foreach (List<int> Numbers in group)
                {
                    if (Numbers.Contains(numberToAdd) && (group.IndexOf(Numbers) + 1) != groupToAddItTo)
                    {
                        active = group.IndexOf(Numbers) + 1;

                        IndexOfListToRefresh = group.IndexOf(Numbers);

                        foreach (int Number in Numbers)
                        {
                            NumbersToMove.Add(Number);

                        }

                    }

                }

                foreach (int Number in NumbersToMove)
                {
                    if (!group[groupToAddItTo - 1].Contains(Number))
                    {
                        group[groupToAddItTo - 1].Add(Number);

                    }

                }

                if (!group[groupToAddItTo - 1].Contains(numberToAdd))
                {
                    group[groupToAddItTo - 1].Add(numberToAdd);

                }

                if (IndexOfListToRefresh != -1)
                {
                    group[IndexOfListToRefresh] = new List<int>();

                }

            }
            catch//(Exception ex)
            {
                //Exception handling here
            }
        }

        public static string GetString()
        {
            string MethodResult = "";
            try
            {
                string Working = "";

                bool FirstPass = true;

                foreach (List<int> Numbers in group)
                {
                    if (!FirstPass)
                    {
                        Working += "\r\n";

                    }
                    else
                    {
                        FirstPass = false;

                    }

                    Working += "group[" + (group.IndexOf(Numbers) + 1) + "]{";

                    bool InnerFirstPass = true;

                    foreach (int Number in Numbers)
                    {
                        if (!InnerFirstPass)
                        {
                            Working += ", ";

                        }
                        else
                        {
                            InnerFirstPass = false;

                        }

                        Working += Number.ToString();

                    }

                    Working += "};";

                    if ((active - 1) == group.IndexOf(Numbers))
                    {
                        Working += " //<active>";

                    }

                }

                MethodResult = Working;

            }
            catch//(Exception ex)
            {
                //Exception handling here
            }
            return MethodResult;
        }


    }

}

I don't know if foreach is more or less efficient than standard for loops, so I have made an alternative version that uses standard for loops:

using System;
using System.Collections.Generic;

namespace XXXNAMESPACEXXX
{
    public static class Groups
    {
        public static List<List<int>> group { get; set; }

        public static int active { get; set; }


        public static void AddNumberToGroup(int numberToAdd, int groupToAddItTo)
        {
            try
            {
                if (group == null)
                {
                    group = new List<List<int>>();

                }

                while (group.Count < groupToAddItTo)
                {
                    group.Add(new List<int>());

                }

                int IndexOfListToRefresh = -1;

                List<int> NumbersToMove = new List<int>();

                for(int i = 0; i < group.Count; i++)
                {
                    List<int> Numbers = group[i];

                    int IndexOfNumbers = group.IndexOf(Numbers) + 1;

                    if (Numbers.Contains(numberToAdd) && IndexOfNumbers != groupToAddItTo)
                    {
                        active = IndexOfNumbers;

                        IndexOfListToRefresh = IndexOfNumbers - 1;

                        for (int j = 0; j < Numbers.Count; j++)
                        {
                            int Number = NumbersToMove[j];

                            NumbersToMove.Add(Number);

                        }

                    }

                }

                for(int i = 0; i < NumbersToMove.Count; i++)
                {
                    int Number = NumbersToMove[i];

                    if (!group[groupToAddItTo - 1].Contains(Number))
                    {
                        group[groupToAddItTo - 1].Add(Number);

                    }

                }

                if (!group[groupToAddItTo - 1].Contains(numberToAdd))
                {
                    group[groupToAddItTo - 1].Add(numberToAdd);

                }

                if (IndexOfListToRefresh != -1)
                {
                    group[IndexOfListToRefresh] = new List<int>();

                }

            }
            catch//(Exception ex)
            {
                //Exception handling here
            }
        }

        public static string GetString()
        {
            string MethodResult = "";
            try
            {
                string Working = "";

                bool FirstPass = true;

                for(int i = 0; i < group.Count; i++)
                {
                    List<int> Numbers = group[i];

                    if (!FirstPass)
                    {
                        Working += "\r\n";

                    }
                    else
                    {
                        FirstPass = false;

                    }

                    Working += "group[" + (group.IndexOf(Numbers) + 1) + "]{";

                    bool InnerFirstPass = true;

                    for(int j = 0; j < Numbers.Count; j++)
                    {
                        int Number = Numbers[j];

                        if (!InnerFirstPass)
                        {
                            Working += ", ";

                        }
                        else
                        {
                            InnerFirstPass = false;

                        }

                        Working += Number.ToString();

                    }

                    Working += "};";

                    if ((active - 1) == group.IndexOf(Numbers))
                    {
                        Working += " //<active>";

                    }

                }

                MethodResult = Working;

            }
            catch//(Exception ex)
            {
                //Exception handling here
            }
            return MethodResult;
        }


    }

}

Both implimentations contain the group variable and two methods, which are; AddNumberToGroup and GetString, where GetString is used to check the current status of the group variable.

Note: You'll need to replace XXXNAMESPACEXXX with the Namespace of your project. Hint: Take this from another class.

When adding an item to your List, do this:

int NumberToAdd = 10;

int GroupToAddItTo = 2;

AddNumberToGroup(NumberToAdd, GroupToAddItTo);

...or...

AddNumberToGroup(10, 2);

In the example above, I am adding the number 10 to group 2.

Test the speed with the following:

DateTime StartTime = DateTime.Now;

int NumberOfTimesToRepeatTest = 1000;

for (int i = 0; i < NumberOfTimesToRepeatTest; i++)
{
    Groups.AddNumberToGroup(4, 1);
    Groups.AddNumberToGroup(3, 1);
    Groups.AddNumberToGroup(8, 2);
    Groups.AddNumberToGroup(5, 2);
    Groups.AddNumberToGroup(7, 3);
    Groups.AddNumberToGroup(3, 3);
    Groups.AddNumberToGroup(8, 4);
    Groups.AddNumberToGroup(43, 4);
    Groups.AddNumberToGroup(100, 5);
    Groups.AddNumberToGroup(1, 5);
    Groups.AddNumberToGroup(5, 6);
    Groups.AddNumberToGroup(78, 6);
    Groups.AddNumberToGroup(34, 7);
    Groups.AddNumberToGroup(456, 7);
    Groups.AddNumberToGroup(456, 8);
    Groups.AddNumberToGroup(7, 8);
    Groups.AddNumberToGroup(7, 9);

}

long MillisecondsTaken = DateTime.Now.Ticks - StartTime.Ticks;

Console.WriteLine(Groups.GetString());

Console.WriteLine("Process took: " + MillisecondsTaken);

I think this is what you need. Let me know if I misunderstood anything in the question.

As far as I can tell it's brilliant, it's fast and it's tested.

Enjoy!

...and one more thing:

For the little windows interface app, I just created a simple winforms app with three textboxes (one set to multiline) and a button.

Then, after adding the Groups class above, in the button-click event I wrote the following:

private void BtnAdd_Click(object sender, EventArgs e)
{
    try
    {
        int Group = int.Parse(TxtGroup.Text);

        int Number = int.Parse(TxtNumber.Text);

        Groups.AddNumberToGroup(Number, Group);

        TxtOutput.Text = Groups.GetString();

    }
    catch//(Exception ex)
    {
        //Exception handling here
    }
}

From what I gather you want to iteratively assign numbers to groups satisfying these conditions:

  1. Each number can be contained in only one of the groups
  2. Groups are sets (numbers can occur only once in given group)
  3. If number n exists in group g and we try to add it to group g' , all numbers from g' should be transferred to g instead (avoiding repetitions in g )

Although approaches utilizing Dictionary<int, HashSet<int>> are correct, here's another one (more mathematically based).

You could simply maintain a Dictionary<int, int> , in which the key would be the number, and the corresponding value would indicate the group, to which that number belongs (this stems from condition 1.). And here's the add routine:

//let's assume dict is a reference to the dictionary
//k is a number, and g is a group
void AddNumber(int k, int g)
{
    //if k already has assigned a group, we assign all numbers from g
    //to k's group (which should be O(n))
    if(dict.ContainsKey(k) && dict[k] != g)
    {
        foreach(var keyValuePair in dict.Where(kvp => kvp.Value == g).ToList())
            dict[keyValuePair.Key] = dict[k];
    }
    //otherwise simply assign number k to group g (which should be O(1))
    else
    {
        dict[k] = g;
    }
}

Notice that from a mathematical point of view what you want to model is a function from a set of numbers to a set of groups.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM