Author Topic: Finding the index of an item by value in a dictionary<string, string>  (Read 4079 times)

0 Members and 1 Guest are viewing this topic.

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
How can this be done better? It seems so inefficient.

Code - C#: [Select]
  1. //Find index of a given value in a Dictionary<string, string>
  2. //-1 if not found
  3. public int getFirstIndexOf(string search, Dictionary<string, string> mydict)
  4. {
  5.     int i = 0;
  6.     foreach (KeyValuePair<string, string> item in mydict)
  7.     {
  8.         if (item.Value == search)
  9.         {
  10.             return i;
  11.         }
  12.         i++;
  13.     }
  14.     return -1;
  15. }
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #1 on: August 25, 2018, 09:59:02 PM »
A Dictionary (i.e. hash table) is not guaranteed to maintain an ordered index and "The order in which the items are returned is undefined."

Maybe a custom List would be a better container?

We need a bit more to go on, what is this index used for?
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #2 on: August 26, 2018, 12:52:32 AM »
The order of the items in the dictionary are unimportant provided it doesn't change immediately after its creation. In fact, in practice, the order of layouts returned by the third party tool seldom match the actual order of the layouts in the drawing except that item[0] is always "Model". Its merely a selection tool to pair up the layout handle with the layout name.

The project called for storing xRecord data uniquely identifiable per layout but not attached to the layout so it will always be available even if the layout is subsequently deleted.

In comes the entity handle ... each layout has one that is unique and is mostly stable .. unless the user wblocks the drawing or does a couple of other things that aren't typically done on a regular basis.

The program captures the entity handle (key) and name (value) of each layout and populates a combobox with that information.
The form can be instantiated with no specific layout as the selected item in which case it always defaults to "Model" or the user can open the form and pass a layout name (because that's the only way a user can identify a layout) … this is where the issue comes in.


The combobox datasource is a dictionary <string, string> where the key is the layout handle and the value is the layout name. It is created on the fly every time the form is shown and is never modified. When the user passes a layout name, during initialization, the SelectedIndex of the combobox needs to be set so that the correct information can be populated in the remaining controls. Now, the user has no way of knowing that the data is keyed under H3A or B94 or whatever the handle happens to be, but they do know that the layout is called "Model" or "Populated evidence v2 2021" or whatever the case may be.

Because the layout name is the Value and not the Key, nothing out of the box allows me to figure out that "Populated evidence v2 2021" is index 4 so I have to iterate over the collection and find the first instance (also layout names must be unique too so it will actually be the only one found).


When the form is disposed, the dictionary goes with it. The index is never used again except when the combobox SelectedItem is changed.

Code - C#: [Select]
  1. private void Form_Load()
  2. {
  3.     _loading = true;
  4.     try
  5.     {
  6.         //external library LayoutHelper
  7.         LayoutHelper helper = new LayoutHelper();
  8.         string caption = helper.getDWGName(FilenameStyle.Elipsed);
  9.         Dictionary<string, string> cs = helper.getLayouts().ToDictionary();
  10.         //Bind layout info to combobox
  11.         cbxLayouts.ComboBox.DataSource = new BindingSource(cs, null);
  12.         cbxLayouts.ComboBox.DisplayMember = "Value";
  13.         cbxLayouts.ComboBox.ValueMember = "Key";
  14.         //_initialLayout is "Model" or a string passed in the constructor -1 returned if no matching layout found
  15.         cbxLayouts.SelectedIndex = getFirstIndexOf(_initialLayout, cs);
  16.         if (cbxLayouts.Text != "")
  17.         {
  18.             //Get the xRecords with the matching layout handle (primary) or name (secondary)
  19.             _data = helper.getXrecords(((KeyValuePair<string, string>)cbxLayouts.SelectedItem()).Key, ((KeyValuePair<string, string>)cbxLayouts.SelectedItem()).Value);
  20.         }
  21.     }
  22.     catch{}
  23.     _loading = false;
  24. }
  25.  
  26. private void cbxLayouts_SelectedIndexChanged(object sender, EventArgs e)
  27. {
  28.     if (!_loading)
  29.     {
  30.         try
  31.         {
  32.             _data = helper.getXrecords(((KeyValuePair<string, string>)cbxLayouts.SelectedItem()).Key, ((KeyValuePair<string, string>)cbxLayouts.SelectedItem()).Value);
  33.         }
  34.         catch{}
  35.     }
  36. }
  37.  
  38. //Find index of a given value in a Dictionary<string, string>
  39. //-1 if not found
  40. public int getFirstIndexOf(string search, Dictionary<string, string> mydict)
  41. {
  42.     int i = 0;
  43.     foreach (KeyValuePair<string, string> item in mydict)
  44.     {
  45.         if (item.Value == search)
  46.         {
  47.             return i;
  48.         }
  49.         i++;
  50.     }
  51.     return -1;
  52. }


This itself may not be the best way to handle the task, but its what I have to work with. If I could tell the function to return the data in a different manner (say swapping Value and Key) I could make Key the DisplayMember and then get the Value member of layout handle using OOB functions.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #3 on: August 26, 2018, 05:58:38 PM »
How about just iterating the dictionary to a list to get the layout names to populate the combobox, you can then grab the combobox selected 'value' and use that to search the original dictionary by using ContainsValue/ContainsKey or similar?
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #4 on: August 26, 2018, 06:32:37 PM »
Not sure that will do anything for me. I will still have to iterate something whether its the dictionary or the combobox values. Iterating the dictionary and populating the combobox only assure that I'll need to iterate the list a second time, now I could iterate the dictionary and compare _initalLayout to item.Value and grab an index at that time but no matter, I'm still iterating the dictionary and quite frankly, I can't see the dictionary changing ordering that quickly because IndexOf(key) would be useless as well.


Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #5 on: August 27, 2018, 07:22:39 AM »
Code - C#: [Select]
  1. return mydict.Select((d, i) => new {d.Value, Index = i})
  2.                 .FirstOrDefault(e => e.Value == search)?.Index ?? -1;
Revit 2019, AMEP 2019 64bit Win 10

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #6 on: August 27, 2018, 07:30:58 AM »
This is a perfect example of how WPF and the MVVM pattern are superior to anything else for .NET UI.  You would have the Dictionary<string, string> as a property of your ViewModel that would be bound to the ComboBox and you would not have to write any code to iterate over the Dictionary.

Keith I know its to late for your project but if someone else is reading this thread now would be a good time to point them away from antiquated forms and get them going in the right direction.
Revit 2019, AMEP 2019 64bit Win 10

n.yuan

  • Bull Frog
  • Posts: 348
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #7 on: August 27, 2018, 09:16:45 AM »
Keith, It seems you are dealing with Layouts (the order of layout IDs stored in LayoutDictionary vs actual Latout Tab order). As Mike D pointed out, "a Dictionary is not guaranteed to maintain an ordered index". That is why Layout class has a property "TabOrder". So, if you need your UI to present layout information in the same order as the layout tab order, only dealing LayoutDictionary is not enough, you also need to open each Layout object to get its "TabOrder" property.

I am also with MexicanCustard: if the UI is modeless form, especially PaletteSet, taking advantage of WPF's binding should always be the first choice whenever it is possible.

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #8 on: August 27, 2018, 03:48:48 PM »
It doesn't matter if the dictionary changes its order at some time in the future. I just need to know where item <string> is in the dictionary *now* when it is created. Because the dictionary is populating a combobox and the order of the items in the combobox is inconsequential.

Everyone seems to be fixated on the fact that a dictionary can change its order .. that's fine, but if its that big of a deal, how is searching by value and getting an index inherently different than searching by key and getting an index? If you think about it its completely irrelevant.

Consider this dictionary of <string><string> organized as such when created:
ndx Value    Key
0    <ABC><123>
1    <DEF><456>
2    <GHI><789>

If I want to know what is in position 1, I can ask for Item[1]
If I want to know the index of the Key 456 I can ask IndexOf(456) and it will return 1
So why is it that getting the index from the value i.e. IndexOf(DEF) is subject to be wrong? Wouldn't IndexOf(456) be wrong as well?


This produces the desired results in a much more concise manner. I'll be using it. Thanks!

Code - C#: [Select]
  1. return mydict.Select((d, i) => new {d.Value, Index = i})
  2.                 .FirstOrDefault(e => e.Value == search)?.Index ?? -1;
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #9 on: August 29, 2018, 10:43:40 AM »
How can this be done better? It seems so inefficient.

only if there's a "lot" entries
System.Collections.Specialized.OrderedDictionary iterates, see function IndexOfKey
https://referencesource.microsoft.com/#system/compmod/system/collections/specialized/ordereddictionary.cs

so you could roll something like
Code: [Select]
class CoolDictionary : Dictionary<string,string>
    {
        public int IndexOfKey(string key)
        {
            int nitem = 0;
            foreach(var entry in this)
            {
                if (entry.Key == key)
                    return nitem;
                nitem++;
            }
            return -1;
        }
   }
« Last Edit: August 29, 2018, 07:50:27 PM by nullptr »

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Re: Finding the index of an item by value in a dictionary<string, string>
« Reply #10 on: August 29, 2018, 06:28:48 PM »
AutoCAD layouts used to be limited to 256, not sure if that's still the case or not .. I've not checked it lately.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie