1

The idea is that I have an object GrossPanel that has a property GrossPanel.PanelListthat contains a list of Panelobjects ;List<Panel>

Each Panelobject has the property of type double Panel.Prod_Width

What I want to do is to match each Panel.Prod_Widthin the inputto each Panel.Prod_Widthin the template.PanelList

When a match is found, the Panelfrom the inputlist is put into a new GrossPanelobject and removed frominput. If a complete matching set is found the resulting GrossPanelis added to the _Returnlist and everyting is repeated until the input list is exhausted.

Example:

Lets say the inputcontains 9 elements (Panel0-Panel8) and templatecontains 2 elements (temp0-temp1)

  • Panel0-Panel3 has Prod_Width = 200
  • Panel4-Panel7 has Prod_Width = 300
  • Panel8 has Prod_Width = 400
  • temp0 has Prod_Width = 200 and temp1 has Prod_Width = 300

This should create 4 GrossPanelobjects, GP0-GP3

  • GP0 should contain Panel0 and Panel4
  • GP1 should contain Panel1 and Panel5
  • GP2 should contain Panel2 and Panel6
  • GP3 should contain Panel3 and Panel7

Panel8 can't be used

This is the code I have written to do this. It works, but it is very slow. Is there a way to do this more efficient? I tried to do a foreach loop and removing elements from inputalong the way, but it din't work. Instead I use Indexand _index to skip the "used" elements in input

private static List<GrossPanel> Match (List<Panel> input, GrossPanel template)
    {
        List<Panel> _template = template.PanelList.OrderBy(panel => panel.Prod_Width).ToList();
        List<Panel> _input = input.OrderBy(panel => panel.Prod_Width).ToList();
        List<GrossPanel> _Returnlist = new List<GrossPanel>();
        List<int> indexlist = new List<int>(); // list of used indexes
        int Index = 0; //counting the panels you have checked
        while (Index < _input.Count)
        {
            int _index = 0;
            GrossPanel Grosspanel = new GrossPanel();
            for (int templateindex = 0; templateindex < _template.Count(); templateindex++)
            {
                for (int inputindex = _index; inputindex < _input.Count(); inputindex++)
                {
                    if ((!indexlist.Contains(inputindex)) && (_template.ElementAt(templateindex).Prod_Width == _input.ElementAt(inputindex).Prod_Width))
                    {
                        indexlist.Add(inputindex);
                        Grosspanel.AddNetPanel(input.ElementAt(inputindex));
                        _index = indexlist.Last(); // 
                        Index++;
                        break;
                    }
                }
            }
            if (Grosspanel.NetPanelCount == _template.Count()) _Returnlist.Add(Grosspanel);
            else if (Grosspanel.NetPanelCount != _template.Count()) Index = _input.Count;
        }
        return _Returnlist;
    }

OK...

I tried to use IEnuberable and yield return to make this quicker. My problem now is that when I find a match in input I can't seem to remove it from input in the next iteration.

here is the code

private static IEnumerable<GrossSteniPanel> Match (IEnumerable<Panel> input, GrossPanel template, List<Panel> usedpanels, int index)
    {
        GrossPanel Grosspanel;
        List<Panel> _usedpanels = new List<Panel>();
        IEnumerable<Panel> _input = input;
        _input = _input.Except(usedpanels);
        if (index < 0 | (_input.Count() == 0)) yield return Grosspanel = new GrossPanel();
        else
        { 
            foreach (Panel p in _input)
            {
                if (p.Prod_Width == template.NetPanelList.ElementAt(index).Prod_Width)
                {
                    _usedpanels.Add(p);
                    _input = _input.Except(_usedpanels);
                    foreach (GrossPanel panel in Match (_input, template, usedpanels, index - 1))
                    {
                        Grosspanel = panel;
                        Grosspanel.AddNetPanel(p);
                        yield return Grosspanel;
                    }
                }
            }
        }
    } 

What am I missing??

  • Idea: create two look-up tables, one for width -> template and one for width -> panel. Then foreach panel in the input list, check which template aplies (second look-up) and then check which panel fits in the other slot (first look-up). Then remove the found panels form the first look-up and add the created gross-panel to the result list. That should be O(N) down from O(N^3), but you won't get the best "fill-rate", that is, if there are two templates that fit the panel and only one has a panel available for its second slot, it's luck if the right one is chosen. – Haukinger Feb 14 '16 at 15:17
  • Does your `template.PanelList` contain panels with duplicate `Prod_Width`? – Ivan Stoev Feb 14 '16 at 17:27
  • Yes, `template.PanelList`can contain panels with duplicate `Prod_Width`, but it will always be a combination of the panels in the `input` list. The end goal is to find every unique combination from `input`with a certain length.(this is the `template`, and I have the code to generate this ) and then find all the combinations of panels in `input` that maches the template (the code in the original post does this, but it's too slow...) I will then check which `template` is the most efficient way of stacking the panels in `input` The function will run several times, so I want to make it run faster – Jan Marius Kruse Feb 14 '16 at 19:54
  • @Haukinger - How do I remove a `Element` from a `Lookup` ? As I understand it, I have to remove the element from the input list and then create a new lookup based on the now smaller input list. – Jan Marius Kruse Feb 17 '16 at 08:24
  • What about a reverse lookup? Use that to get the key, delete from the regular lookup with it, then remove from the reverse lookup. You could create an `IDictionary` implementation that does this (constant time removal by value, not only by key) by wrapping two regular dictionaries. – Haukinger Feb 17 '16 at 08:55

1 Answers1

0

My recommendations:

  • Try to make your code easier to read and understand (in 99% of the cases this is more important than performance).
  • Use one convention when naming variables. I found 4 different naming conventions in first 5 lines of "Match" method.
  • Using underline before local variable name is not good.
  • try to write tests for "Match" method.

Refactor example (name "Match" changed to "BuildGrossPanelList"):

static IList<GrossPanel> BuildGrossPanelList(List<Panel> input, GrossPanel template)
{
    var templatePanels = template.PanelList
        .OrderBy(panel => panel.Width);

    var inputPanels = input
        .OrderBy(panel => panel.Width)
        .ThenBy(panel => panel.Id);        
    // If `input` can have elements with the same `panel.Width` 
    // and you want to always retun the same result then the sorting has to be extend.

    var templatesWithMatchingPanels = templatePanels
        .ToDictionary(
            tp => tp,
            tp => inputPanels.Where(p => p.Width == tp.Width).ToList());

    return GetGrossPanels(templatesWithMatchingPanels);
}

static IList<GrossPanel> GetGrossPanels(Dictionary<Panel, List<Panel>> templatesWithMatchingPanels)
{
    var result = new List<GrossPanel>();
    while(AnyNotUsedPanelExistsForEveryTemplate(templatesWithMatchingPanels))
    {
        result.Add(CreateNewGrossPanel(templatesWithMatchingPanels));
    }
    return result;
}

static bool AnyNotUsedPanelExistsForEveryTemplate(Dictionary<Panel, List<Panel>> templatesWithMatchingPanels)
{
    return templatesWithMatchingPanels.All(entry => entry.Value.Any());
}

static GrossPanel CreateNewGrossPanel(Dictionary<Panel, List<Panel>> templatesWithMatchingPanels)
{
    var result = new GrossPanel();
    foreach(var templatePanelEntry in templatesWithMatchingPanels)
    {
        var firstMatchingPanel = GetAndRemoveFirst(templatePanelEntry.Value);
        result.AddNetPanel(firstMatchingPanel);
    }
    return result;
}

static Panel GetAndRemoveFirst(IList<Panel> panels)
{
    var result = panels.First();
    panels.RemoveAt(0);
    return result;
}

If "Match" method is part of a large class at least put the above code in a nested class. Consider creating a new class in a separate file. Methods in this new class wouldn't have to be static.

r2-
  • 11
  • 3
  • I get the following error while declaring `var templatesWithMatchingPanels` An item with the same key has already been added. Is this because template contains panels with identical widths ? – Jan Marius Kruse Feb 18 '16 at 15:58
  • The exception tells you exactly what the problem is. Keys in Dictionary have to be unique. In this case keys are references to Panel objects. You will get this error when for example template.PanelList[0] and template.PanelList[1] refer to the same Panel object. – r2- Feb 19 '16 at 10:02
  • It is also possible that your Panel class implements System.IEquatable interface. If this is the case then method Equals(T) is used to determine if keys are equal. – r2- Feb 19 '16 at 10:18
  • The template is generated by first extracting every distinct panel in `input` (based on the `Prod_Width` property), and then ,from those distinct panels, I generate all combinations of a certain length (see http://stackoverflow.com/questions/25824376/combinations-with-repetitions-c-sharp) So, if I have two distinct widths and need a GrossPanel with 4 NetPanels, I'll get at least two identical Panels in the template. In most cases I'll end up with identical Panels in the template.. – Jan Marius Kruse Feb 20 '16 at 03:08