-2

I have a List of Dictionary<String, String> data structure. I want to filter it to get only those Dictionary entries that match some key-value pairs (input to the filtering method). Number of these key-value pairs vary from one call to another.

I wrote following code to achieve what I wanted to do. If I use the GetPlanningDataMatching method, it works perfect without any issues.

However, if I use GetPlanningDataMatching_alt method, I get index out of bounds error at (row[planningDataKeys[inx]] == planningDataValues[inx]). inx is equal to the planningDataKeys.Count.

What am I doing wrong?

My question is different than What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?, otherwise both of my methods - GetPlanningDataMatching and GetPlanningDataMatching_alt would have failed.

// Build this data structure in the class constructor (not shown here)
List<Dictionary<String, String>> planningData = null;

// planningDataKeys.Count is always same as planningDataValues.Count
public List<Dictionary<String, String>> GetPlanningDataMatching_alt(List<String> planningDataKeys, List<String> planningDataValues)
{
    IEnumerable<Dictionary<String, String>> matchingPlanningData = null;
    for (int inx = 0; inx < planningDataKeys.Count; ++inx)
        matchingPlanningData = (inx == 0 ? planningData : matchingPlanningData)
                                        .Where(row => row[planningDataKeys[inx]] == planningDataValues[inx]);
    return matchingPlanningData.ToList();
}

// planningDataKeys.Count is always same as planningDataValues.Count
public List<Dictionary<String, String>> GetPlanningDataMatching(List<String> planningDataKeys, List<String> planningDataValues)
{
    List<Dictionary<String, String>> matchingPlanningData = null;
    for (int inx = 0; inx < planningDataKeys.Count; ++inx)
        matchingPlanningData = (inx == 0 ? planningData : matchingPlanningData)
                                        .Where(row => row[planningDataKeys[inx]] == planningDataValues[inx])
                                        .ToList();
    return matchingPlanningData;
}
YogiWatcher
  • 165
  • 1
  • 8
  • Possible duplicate of [What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?](https://stackoverflow.com/questions/20940979/what-is-an-indexoutofrangeexception-argumentoutofrangeexception-and-how-do-i-f) – Ňɏssa Pøngjǣrdenlarp Jul 21 '19 at 16:05
  • Does planningDataKeys.Count equal the number of items in the dictionary? – jdweng Jul 21 '19 at 16:09
  • 2
    I wonder what is the purpose of abbreviating `index` to `inx` ;) – Peter Wolf Jul 21 '19 at 16:10
  • See also [Captured Closure (Loop Variable) in C# 5.0](https://stackoverflow.com/questions/16264289/captured-closure-loop-variable-in-c-sharp-5-0). – Peter Wolf Jul 21 '19 at 16:23
  • @ŇɏssaPøngjǣrdenlarp: My question is different that that question, otherwise both of my methods - `GetPlanningDataMatching` and `GetPlanningDataMatching_alt` would have failed with index out of bounds error. – YogiWatcher Jul 21 '19 at 17:14
  • @PeterWolf: I prefer inx because then in nested loops I can use `inx`, `jnx`, `knx`, etc.... – YogiWatcher Jul 21 '19 at 17:16

1 Answers1

0

My gut feeling is your problem is in not materialising the collection within the for loop.

E.g. in the second method, you do the .ToList() each iteration, which is why your inx is captured correctly.

But in the _alt method, you capture a reference to inx, each iteration INCREASES the inx, and then you use the backwards ++inx rather than the typical inx++ meaning on the LAST iteration it WILL be equal to the count.

And finally, you call the .tolist() on your IEnumerable which causes the code to look at the indexes beyond what is in the array.

So moving .ToList() would fix it.

As a side note, this whole thing seem really strange from the readability perspective.

How about a

public List<Dictionary<String, String>> GetPlanningDataMatching(Func<KeyValuePair, Bool> predicate)
{
    return this.planningData.Where(predicate).ToList();
}

And then to call, do something like

var stuff = new Dictionary<String, String>(){{"a", "b"}};
WhateverClass()
.GetPlanningDataMatching(kp => stuff.ContainsKey(kp.Key) && stuff[kp.Key] == kp.Value);
zaitsman
  • 8,984
  • 6
  • 47
  • 79
  • Post increment operator `inx++` have to store the value prior to increment in case it's used. In my case I am not using the value of `inx` prior to increment. Hence `inx++` and `++inx` has same effect, except `++inx` is bit more efficient. When you say moving `.ToList()` would fix it - are you suggesting that the `_alt` method is not going to work. – YogiWatcher Jul 21 '19 at 17:35
  • You maybe onto something... I was reading up on LINQ. It appears that LINQ methods are implemented using deferred execution and LINQ queries are not executed until results are enumerated. Could this be the reason why `GetPlanningDataMatching` works because results are enumerated in each loop because of `ToList()` call. But the `GetPlanningDataMatching_alt` method does not enumerate results until all loop iterations are over and it's time to return from the method? – YogiWatcher Jul 21 '19 at 17:40
  • @YogiWatcher yes, that is exactly what i was saying in my answer – zaitsman Jul 21 '19 at 21:03