views:

382

answers:

9

List(Of T) stores data indexed by integer Dictionary(Of String, T) stores data indexed via string

Is there a type or generic or specialized something that would let me access an array of T by either an index or name?

A: 

I think you are looking for System.Collections.Hashtable :)

Aistina
No. HashTable is basically the pre-generic form of a Dictionary. Maslow (I believe) is looked for an indexable dictionary, which doesn't exist in the core framework.
Reed Copsey
Beat me to it. That's what I was going to say.
ryanulit
I don't see a way to index that by integer, and it's not generic. I thought from my understanding of hashtables that a hashtable should be exactly what I want, but so far what I've explored of System.Collections.Hashtable it's missing indexing and type safety.
Maslow
+7  A: 

I think System.Collections.Specialized.OrderedDictionary is what you're looking for.

Mehrdad Afshari
This is a sort of special-case multimap; too bad the BCL doesn't have a general-purpose one.
Charlie
OrderedDictionary will let you index by integer or Object it appears? I don't like to deal with boxing/unboxing of constant casting, but I suppose it could work in some cases.
Maslow
OrderedDictionary lets you use an integer index or an *object* key. It's older than generics came around so both key and value are object types. You could use a couple dictionaries (one Dictionary<int,...> for indexes and a Dictionary<string,...> for keys) and manually keep them in sync. I'd settle with OrderedDictionary and boxing though ;)
Mehrdad Afshari
That's also it's weakness: it's not strongly-typed. If you can easily determine your key based on your payload, a KeyedCollection might work a little better.
Joel Coehoorn
If this doesn't work, I've accomplished such by building a custom collection that inherits from CollectionBase and then just overload the "this[int index]" and "this[string key]" properties for the logic to pull the item from the baseclass list. It's not generic but would be strongly typed for you if OrderedDictionary doesn't work.
JamesEggers
A: 

Is accessing a list of T by index (List<List<T>>) good enough?

List<List<foo>> list = new List<List<foo>>();
List<foo> firstList = list[0];
lance
that doesn't appear to let me reference a foo by name/string.
Maslow
Perhaps I answered too literally? It stores lists (not arrays, hence my asking if that was sufficient) and lets you get a list at a specified (numerical) "index". When you said "index or name", I interpreted that as "int or string". I'm guessing that was a mistake?
lance
no, I do want a list of T referenced by int or string. but a list of list of T would not be indexable by string, that's a 2 level int?
Maslow
Ah. I get it now. I *did* read your question too literally. It says "something that would let me access an array of T". You don't want to "access an array". You want to "access an element" *of* an array. I gave you a solution that lets you "access an array" -- an *entire* array (list) -- by an index. My mistake (clearly, given how many people correctly interpreted your question, I should have also).
lance
A: 

It sounds like you need a multimap, but unfortunately there is no general-purpose implementation of this in the BCL. As mentioned in another answer, System.Collections.Specialized.OrderedDictionary is a specific implementation that might cover your needs, although it doesn't use generics.

Charlie
+1  A: 

The Specialized version of OrderedDictionary is not generic.

You could implement a Generic Dictionary interface with a custom GenericOrderedDictionary class.

Have a private

List<TKey>
and private
List<TValue>
.

Visual Studio can stub the interface methods for you.

The start of it would look like:


public class GenericOrderedDictionary< TKey, TValue >
    : IDictionary<TKey, TValue>
{
    private List<TKey> keys;
    private List<TValue> values;

    #region IDictionary<TKey,TValue> Members

    void IDictionary<TKey, TValue>.Add( TKey key, TValue value )
    {
     keys.Add( key );
     values.Add( value );
    }

    bool IDictionary<TKey, TValue>.ContainsKey( TKey key )
    {
     return keys.Contains( key );
    }

    ICollection<TKey> IDictionary<TKey, TValue>.Keys
    {
     get
     {
      return new List<TKey>( keys );
     }
    }

    bool IDictionary<TKey, TValue>.Remove( TKey key )
    {
     int index = keys.IndexOf( key );
     if ( index >= 0 )
     {
      keys.Remove( key );
      values.RemoveAt( index );
     }
    }

maxwellb
A: 

If you have an array of T, you can generate multiple dictionaries from this array by calling ToDictionary and feeding in different properties of T.

Supposing T is customer:

Customer[] myCustomers = getArray();
Dictionary<int, Customer> byID = myCustomers
    .ToDictionary(c => c.ID);
Dictionary<string, Customer> byName = myCustomers
    .ToDictionary(c => c.Name);
Dictionary<int, Customer> byOriginalPosition = myCustomers
    .Select( (c, i) => new {c, i})
    .ToDictionary(x => x.i, x => x.c);
David B
+2  A: 

I think something like this is closest to what you want:

  class IndexDictionary<TKey, TValue> : Dictionary<TKey, TValue>
  {
    public TValue this[int i]
    {
      get { return this[Keys.ElementAt(i)]; }
      set { this[Keys.ElementAt(i)] = value; }
    }
  }

You're just taking a regular Dictionary<> and adding the ability to index by int as well.

Edit: Mehrdad raises a good point, that my IndexDictionary.Add(TKey, TValue) method may result in an insert rather than an append. If that will cause problems in your situation, then I would suggest something like this:

  class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
  {
    private OrderedDictionary data = new OrderedDictionary();

    public TValue this[int i]
    {
      get { return (TValue)data[i]; }
      set { data[i] = value; }
    }

    //Implement IDictionary<TKey, TValue> using the methods of the OrderedDictionary
  }

This gives you the order-preserving benefits of OrderedDictionary with the type safety of Dictionary<TKey, TValue>.

Iceman
The order of keys are not guaranteed, though. They might change as new elements are added or removed.
Mehrdad Afshari
+4  A: 

If your "names" are easily determined from your "T", I suggest KeyedCollection.

It works like a List, in that you can look up items by index. But it also works like a dictionary, in that it uses a Dictionary internally to map names (keys) to the appropriate index and provides an indexer for your key type.


You asked how it knows what to use for the key. KeyedCollection is an abstract class that you have to inherit. Fortunately, it's easy to do. The only method you need to overload is GetKeyForItem(). That method is the answer to your question. For example, take this simple class:

Public Class MyClass
    Public UniqueID As Guid
    Public OtherData As String
End Class

You could implement KeyedCollection like this:

Public Class MyClassCollection
    Inherits KeyedCollection(Of Guid, MyClass)

    Public Overrides Function GetKeyForItem(ByVal item As MyClass) As Guid
        Return item.UniqueID
    End Function
End Class

That's all there is to it. You now have a collection that will work like a dictionary or a list. It's even more powerful when you can use generics or other interfaces to avoid tying the class to a specific type.

Joel Coehoorn
how does it determine which property or field on the object would be the key?
Maslow
`KeyedCollection` is an abstract class that you have to inherit. Fortunately, it's easy to do. The only method you need to overload is `GetKeyForItem()`. That method is the answer to your question.
Joel Coehoorn
Awesome, I think this will work.
Maslow
A: 

This is what I'm testing now most of the functionality was autofilled in for me when I implemented IDictionary

Public Class bDictionary(Of TKey, TVAlue)
Implements IDictionary(Of TKey, TVAlue)

Private dictionary As New Dictionary(Of TKey, TVAlue)
Private list As List(Of TKey)

Default Public Property Item(ByVal which As TKey) As TVAlue Implements System.Collections.Generic.IDictionary(Of TKey, TVAlue).Item
    Get
        Return dictionary(which)
    End Get
    Set(ByVal value As TVAlue)
        dictionary(which) = value
    End Set
End Property

Default Public Property Item(ByVal index As Integer) As TVAlue
    Get
        Return dictionary(list(index))
    End Get
    Set(ByVal value As TVAlue)
        dictionary(list(index)) = value
    End Set
End Property

Public Sub Add(ByVal key As TKey, ByVal value As TVAlue) Implements System.Collections.Generic.IDictionary(Of TKey, TVAlue).Add
    dictionary.Add(key, value)
    list.Add(key)
End Sub

Public Sub Add(ByVal item As System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)) Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)).Add
    Add(item.Key, item.Value)
End Sub

Public Sub Clear() Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)).Clear
    dictionary.Clear()
    list.Clear()
End Sub

Public Function Contains(ByVal item As System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)) As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)).Contains
    If dictionary.ContainsKey(item.Key) AndAlso dictionary(item.Key).Equals(item.Value) Then
        Return True
    Else
        Return False
    End If

End Function


Public ReadOnly Property Count() As Integer Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)).Count
    Get
        Return list.Count
    End Get
End Property

Public ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)).IsReadOnly
    Get
        Return False
    End Get
End Property

Public Function Remove(ByVal item As System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)) As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)).Remove
    Return Remove(item.Key)
End Function

Public Function ContainsKey(ByVal key As TKey) As Boolean Implements System.Collections.Generic.IDictionary(Of TKey, TVAlue).ContainsKey
    Return list.Contains(key)
End Function

Public ReadOnly Property Keys() As System.Collections.Generic.ICollection(Of TKey) Implements System.Collections.Generic.IDictionary(Of TKey, TVAlue).Keys
    Get
        Return dictionary.Keys
    End Get
End Property

Public Function Remove(ByVal key As TKey) As Boolean Implements System.Collections.Generic.IDictionary(Of TKey, TVAlue).Remove
    If list.Contains(key) Then
        list.Remove(key)
        dictionary.Remove(key)
        Return True
    Else
        Return False
    End If
End Function

Public Function TryGetValue(ByVal key As TKey, ByRef value As TVAlue) As Boolean Implements System.Collections.Generic.IDictionary(Of TKey, TVAlue).TryGetValue
    Return dictionary.TryGetValue(key, value)
End Function

Public ReadOnly Property Values() As System.Collections.Generic.ICollection(Of TVAlue) Implements System.Collections.Generic.IDictionary(Of TKey, TVAlue).Values
    Get
        Return dictionary.Values
    End Get
End Property


Public Sub CopyTo(ByVal array() As System.Collections.Generic.KeyValuePair(Of TKey, TVAlue), ByVal arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)).CopyTo

    For Each Item As TKey In dictionary.Keys
        array.SetValue(New KeyValuePair(Of TKey, TVAlue)(Item, dictionary(Item)), arrayIndex)
        arrayIndex += 1
    Next

End Sub

Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
    Return dictionary.GetEnumerator()
End Function

Public Function GetEnumerator1() As System.Collections.Generic.IEnumerator(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)) Implements System.Collections.Generic.IEnumerable(Of System.Collections.Generic.KeyValuePair(Of TKey, TVAlue)).GetEnumerator
    Return dictionary.GetEnumerator
End Function

End Class

Maslow