2

I am working on a program where each item can hold an array of items (i'm making a menu, which has a tree-like structure)

currently i have the items as a list, instead of an array, but I don't feel like I'm using it to its full potential to simplify code. I chose a list over a standard array because the interface (.add, .remove, etc...) makes a lot of sense.

I have code to search through the structure and return the path of the name (i.e. Item.subitem.subsubitem.subsubsubitem). Below is my code:

public class Item
{
                                                                //public Item[] subitem; <-- Array of Items
    public List<Item> subitem;                                  // <-- List of Items

    public Color itemColor = Color.FromArgb(50,50,200);
    public Rectangle itemSize = new Rectangle(0,0,64,64);
    public Bitmap itemBitmap = null;
    public string itemName;


    public string LocateItem(string searchName)
    {
        string tItemName = null;

        //if the item name matches the search parameter, send it up)
        if (itemName == searchName)
        {
            return itemName;
        }

        if (subitem != null)
        {

            //spiral down a level
            foreach (Item tSearchItem in subitem)
            {
                tItemName = tSearchItem.LocateItem(searchName);

                if (tItemName != null)
                    break;  //exit for if item was found
            }
        }


        //do name logic (use index numbers)
        //if LocateItem of the subitems returned nothing and the current item is not a match, return null (not found)
        if (tItemName == null && itemName != searchName)
        {
            return null;
        }

        //if it's not the item being searched for and the search item was found, change the string and return it up
        if (tItemName != null && itemName != searchName)
        {
            tItemName.Insert(0, itemName + ".");  //insert the parent name on the left -->  TopItem.SubItem.SubSubItem.SubSubSubItem
            return tItemName;
        }

        //default not found
        return null;
    }


}

My question is if there is an easier way to do this with lists? I've been going back and forth in my head as to whether I should use lists or just an array. The only reason I have a list is so that I don't have to make code to resize the array each time I add or remove an item.

David Torrey
  • 1,335
  • 3
  • 20
  • 43

3 Answers3

2

Using a list in this case is perfectly acceptable. An array would make a better choice if performance was an issue - if it is, arrays are slightly faster but much less flexible as you've discovered.

One thing that people don't talk about enough is that simplicity is a great basis for structuring code. If it's simpler to write and maintain using lists than arrays, then (all else being equal) using lists is perfectly correct.

Winfield Trail
  • 5,535
  • 2
  • 27
  • 43
2

Lists sound great. I'd suggest a variation on your definition though. Try creating your class like this:

public class Item : List<Item>
{
    public string Name;
}

If you make Item inherit from List<Item> you automatically make it a tree without requiring the subitem field.

Here's my full version of your class:

public class Item : List<Item>
{
    public string Name;

    private List<Item> LocateItems(string searchName)
    {
        if (this.Name == searchName)
            return (new [] { this }).ToList();

        var result =
            this
                .Select(s => s.LocateItems(searchName))
                .Where(x => x !=null && x.Count > 0)
                .FirstOrDefault();

        if (result != null)
            result.Add(this);

        return result;
    }

    public string LocateItem(string searchName)
    {
        var items = this.LocateItems(searchName);
        if (items == null)
            return null;
        else
            return String.Join(".", items.Select(i => i.Name).Reverse());
    }
}

The method LocateItems returns the list of Item starting with the Item that matched and followed by all of the parent Item instances up to and including the root.

I tested with this code:

var foos = new Item() { Name = "Foo" };
var bars = new Item() { Name = "Bar" };
var qazs = new Item() { Name = "Qaz" };
var wees = new Item() { Name = "Wee" };

foos.Add(bars);
bars.Add(qazs);
foos.Add(wees);

Console.WriteLine(foos.LocateItem("Wee"));
Console.WriteLine(foos.LocateItem("Qaz"));
Console.WriteLine(foos.LocateItem("Bar"));
Console.WriteLine(foos.LocateItem("Foo"));

And I got these results:

Foo.Wee
Foo.Bar.Qaz
Foo.Bar
Foo
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • there are a couple lines i'm not familiar with (I'm just getting into LINQ stuff). What does the FirstOrDefault do? also, what is the .Count counting? Also, I want to make sure I have this straight, it adds each level to a list, which is then joined ath the end with dots in reverse order? Finally, does the .Select .Where line work like an SQL database? – David Torrey Sep 03 '12 at 05:54
  • 1
    The `FirstOrDefault` extension method returns the first value in the sequence, ignoring the rest, but will return `null` if the sequence doesn't contain any values. Is this case it says "give me the first match on the search, or null if there are no matches". The `LocateItems` method was described in my answer. Because the first item in the list is the leaf node it has to reverse them before joining. The `Select` performs a recursive search on all sub-items & the `Where` filters our all non-matches - very much like a database query, but in memory. – Enigmativity Sep 03 '12 at 08:58
  • I'm adopting your type of approach because I think it seems easier, but I am curious about one thing. You said that .Select performs a recursive search on all sub-items. With my original code, would it include searching all items in the 'subitem' object, or would it not because it's the object directly included in the list, not the subitem itself? – David Torrey Sep 03 '12 at 09:39
  • Sorry, I'm not sure what you're asking. Can you explain your question more clearly? – Enigmativity Sep 03 '12 at 12:03
  • well for instance, you use .select, which you said does a recursive search, which I take to mean it can do down an 'infinite' number of levels to find a match. the example also shows that, where you were able to find "Wee" using a single LocateItem, even though it's not within the top list, but in a list containing a list, containing a list, etc.... The difference between your code and my code, however, is that your class inherited from List, and mine created a whole separate list Object within the class. So my question is if the '.Select' would work with my original code recursively – David Torrey Sep 03 '12 at 12:42
  • even though my list is a whole separate object within an object... whereas yours seems more integrated. I hope that makes sense – David Torrey Sep 03 '12 at 12:42
  • You could easily use my code in place of yours. Just replace the `this` in the `Select` statement with `this.subitem`. Oh, and I renamed `itemName` to `Name` - keep that in mind too. – Enigmativity Sep 03 '12 at 23:00
1

I would suggest lists. Since adding/removing items to an array reallocates memory, for a dynamic collection of items (which I assume is your case) lists usually have better performance overall. You might want to take a look at:

Array versus List<T>: When to use which?

Community
  • 1
  • 1
RollingCog
  • 294
  • 1
  • 5