Search Unity

What type of array do i need for each of these situations? And why are array lists "obsoleted"

Discussion in 'Scripting' started by Nanako, Nov 22, 2014.

  1. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    Hi all. I'm poresently looking at this page; http://wiki.unity3d.com/index.php?title=Which_Kind_Of_Array_Or_Collection_Should_I_Use?


    Firstly, i note with concern, this:

    So i've searched through the other collection types there, and i'm just thinking wtf? They're clearly not obsolete at all. Generic Lists are strictly typed, and are therefore NOT a valid replacement. I'm concerned at this attitude, especially the implication that UT only bbrought them in as a legacy compatibility thing. How else are you supposed to do mixed lists in C#?


    Okay, anyways,, my question.

    I have need for two different arrays.

    1. An array where performance is not important. Values will be read from it once, and cached. This array must be two dimensional, and must contain both floats and strings. All the values in it will be filled manually at authortime so it will be a set size.


    2. An array where performance is utterly critical, it will be used and processed constantly. Must also be two dimensional. each record needs to contain exactly one reference to an object, and one integer. nothing else is needed.

    The second one is really the most critical. I'll be recalculating the value of that integer in each record frequently, and sorting the list in descending order of that integer (maybe once per second, generally as often as i can afford to do it). The list may contain hundreds, maybe even a few thousand elements at a time, and will be dynamic in size.



    Since both of these are mixed lists, i'm given to understand that i'd need an ArrayList for each of them. Which lends farther disbelief to the statement that they are obsolete. But since that IS stated, i've got to wonder if i'm missing something here, hence that is the purpose for this thread.


    I think i've stated my requirements for the second list pretty well, i'd like any advice on optimisation tricks to make that sort of data structure work well.
     
  2. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Use a generic List of type object. You will have to cast to the correct type when retrieving values, but that's true of ArrayList too. There is literally zero use for ArrayList, and yes it's totally obsolete; it's only there so old code wouldn't break.

    As for the questions:

    1) 2D array.
    2) 1D array, where basic math is used to simulate a 2D array. Allocate the max size, and use an index to determine where the "end" is.

    I doubt you really need to use an array of mixed types; that's pretty rare. Use a custom class instead, and the arrays will use that class as the type.

    --Eric
     
    Tomnnn, Nanako and Stoven like this.
  3. Stoven

    Stoven

    Joined:
    Jul 28, 2014
    Posts:
    171
    When you say mixed lists, you mean a list that has two types in it?

    If that's the case, why not just use a class that holds these two types and flatten the arrays to 1 dimension and store that type instead? You can even make a custom sorting object to sort the type, or simply make the type inherit from IComparable and define the CompareTo method for the type?

    Also, the sorting done on List types should be in O(NlogN) for large amounts of data, which is pretty much the fastest running time a sort can be unless you're sorting extremely specialized values based on their values and using a dynamic method.
     
  4. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    I think
    Do you mean a sort of jumping list, where i store reference,int, reference, int, reference, int, etc. And then increment the index by 2 instead of 1 each time? I'm thinking that would be complex to sort.
     
  5. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    This sounds interesting and efficient. i'll try to find more information, anything you could point me to?

    I honestly have no idea what this means, a little over my head, but i'll attempt to take it and learn. if you feel like simplifying that a bit, i wouldn't object.
     
  6. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    No, just an index that says where the end is. So if you had an array of 500 elements, but were only using 150, then the end index is 150. But as discussed above, it's unlikely it should contain mixed elements. Use a custom class.

    Actually, on second thought, just use a generic List; that would make things easier. An array is faster than a List, but wait until you're done and profile to make sure that's actually really a bottleneck that would be improved by using an array instead of a List.

    --Eric
     
    Nanako likes this.
  7. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    Oh another question;

    I would like, if possible, for my first list (the one where performance doesn't matter) to be a constant/static variable, so that i don't have to instance the class and can read it from anywhere. As mentioned all the data in it will just be entered manually at authortime, and it will only be read from, not written to. It's really just a data storage

    best way to accomplish that?

    is it still possible to do with the custom class idea?
     
  8. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    You can use a static variable, yes.

    --Eric
     
  9. Stoven

    Stoven

    Joined:
    Jul 28, 2014
    Posts:
    171
    Forgive me for this toy example. I'm not completely used to C# Generics so I wasn't completely sure how to make a Generic version of say MixedType<T, U> where T contains the interface IComparable<T> and U contains the interface IComparable<U>

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class __TestScript300 : MonoBehaviour
    7. {
    8.  
    9.     public int itemCount = 15;
    10.     List<MixedData> myMixedData;
    11.  
    12.     class MixedData : IComparable<MixedData>
    13.     {
    14.         public string someString;
    15.         public int someInt;
    16.  
    17.         public MixedData(string someString, int someInt)
    18.         {
    19.             this.someInt = someInt;
    20.             this.someString = someString;
    21.         }
    22.  
    23.         public int CompareTo(MixedData other)
    24.         {
    25.             return someInt.CompareTo(other.someInt);
    26.         }
    27.  
    28.         public override string ToString()
    29.         {
    30.             return someInt + ": " + someString;
    31.         }
    32.     }
    33.  
    34.     class SortMixedDataByString : IComparer<MixedData>
    35.     {
    36.         public int Compare(MixedData left, MixedData right)
    37.         {
    38.             return left.someString.CompareTo(right.someString);
    39.         }
    40.     }
    41.  
    42.  
    43.     void Start()
    44.     {
    45.  
    46.         myMixedData = new List<MixedData>(itemCount);
    47.  
    48.         Debug.Log("-------------\nBefore sorting our custom type...");
    49.  
    50.         for (int i = 0; i < itemCount; i++)
    51.         {
    52.             float _a = UnityEngine.Random.Range(101f, 400f);
    53.             float _b = UnityEngine.Random.Range(101f, 400f);
    54.             float _c = UnityEngine.Random.Range(101f, 400f);
    55.             myMixedData.Add(new MixedData("<" +_a + ", " + _b + ", " + _c + ">", (int)UnityEngine.Random.Range(1f, 100f)));
    56.             Debug.Log(myMixedData[i]);
    57.         }
    58.  
    59.         myMixedData.Sort();
    60.  
    61.         Debug.Log("-------------\nAfter sorting our custom type based on its default sorting method...");
    62.  
    63.         for (int i = 0; i < myMixedData.Count; i++)
    64.             Debug.Log(myMixedData[i]);
    65.  
    66.         myMixedData.Sort(new SortMixedDataByString());
    67.  
    68.         Debug.Log("-------------\nAfter sorting our custom type based on its other parameter");
    69.  
    70.         for (int i = 0; i < myMixedData.Count; i++)
    71.             Debug.Log(myMixedData[i]);
    72.     }
    73.  
    74. }
    The custom class inherits from IComparable<MixedData> so we can properly compare against our custom type when performing a List.Sort operation. If the custom type doesn't inherit from IComparable, I don't know if C# will sort the values (I doubt it, but I could be wrong). By explicitly defining how the sort should be done, you have more control over the ordering of your custom class.

    The other method of sorting in this scenario is with an object instance that inherits from ICompararer<MixedData>. This method allows you to override the default sorting behavior for your custom type, giving you even more options.


    Edit: It doesn't look like your custom type needs to inherit from IComparable at all, as long as you make an IComparer for your type and supply it to the List at the call of List.Sort

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public class __TestScript301 : MonoBehaviour
    8. {
    9.  
    10.     public int itemCount = 15;
    11.     List<DoesntInheritIComparable> otherList;
    12.  
    13.     class DoesntInheritIComparable
    14.     {
    15.  
    16.         public string someString;
    17.         public int someInt;
    18.  
    19.         public DoesntInheritIComparable(string someString, int someInt)
    20.         {
    21.             this.someInt = someInt;
    22.             this.someString = someString;
    23.         }
    24.  
    25.         public override string ToString()
    26.         {
    27.             return someInt + ": " + someString;
    28.         }
    29.     }
    30.  
    31.     class SortOtherDataByInt : IComparer<DoesntInheritIComparable>
    32.     {
    33.         public int Compare(DoesntInheritIComparable left, DoesntInheritIComparable right)
    34.         {
    35.             return left.someInt.CompareTo(right.someInt);
    36.         }
    37.     }
    38.  
    39.  
    40.     void Start()
    41.     {
    42.  
    43.         otherList = new List<DoesntInheritIComparable>();
    44.  
    45.         Debug.Log("-------------\nBefore sorting our custom type...");
    46.  
    47.         for (int i = 0; i < itemCount; i++)
    48.         {
    49.             float _a = UnityEngine.Random.Range(101f, 400f);
    50.             float _b = UnityEngine.Random.Range(101f, 400f);
    51.             float _c = UnityEngine.Random.Range(101f, 400f);
    52.             otherList.Add(new DoesntInheritIComparable("<" + _a + ", " + _b + ", " + _c + ">", (int)UnityEngine.Random.Range(1f, 100f)));
    53.             Debug.Log(otherList[i]);
    54.         }
    55.  
    56.         Debug.Log("-----------\nSorting our type that doesn't inherit from IComparable by using an IComparer");
    57.  
    58.         otherList.Sort(new SortOtherDataByInt());
    59.  
    60.         for (int i = 0; i < otherList.Count; i++)
    61.             Debug.Log(otherList[i]);
    62.     }
    63.  
    64. }
    65.  
    66.  
    67.  
    If you're never planning on sorting by more than one type (for example, you only plan to sort by integers) then you only need one class that follows the IComparer interface for your custom type.
     
    Last edited: Nov 22, 2014
    Nanako likes this.
  10. Fraconte

    Fraconte

    Joined:
    Dec 6, 2013
    Posts:
    327
    Consider a Dictionary too for seek performance.
     
  11. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    regarding my first requirement, the non intensive data list;

    Here's what i'm attempting to do
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PMats : MonoBehaviour
    5. {
    6.     public const PMaterialData[] data = {new PMaterialData("Steel", 7.75f, 671f, 240f)};
    7. }
    this refuses to compile with the error: Assets/Scripts/Dictionaries/Static/PMats.cs(7,34): error CS0134: A constant `PMats.data' of reference type `PMaterialData[]' can only be initialized with null


    Here's the PMaterialData class by the way

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PMaterialData : MonoBehaviour
    5. {
    6.     public string physicMaterialName;//The name of the physic material to which this data corresponds
    7.     public float density;//Expressed as Kilograms per Cubic Metre (Kg/m^3)
    8.     public float tensileStrength;//Expressed as pascals required to break a chunk off an object
    9.     public float compressiveStrength;//Expressed as Pascals required to fracture an object and make it "give*
    10.  
    11.     public PMaterialData(string name, float den, float ten, float comp)
    12.     {
    13.         physicMaterialName = name;
    14.         density = den;
    15.         tensileStrength = ten;
    16.         compressiveStrength = comp;
    17.     }
    18. }

    what am i doing wrong?
     
  12. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    That shouldn't be using const. Const means constant; i.e. no change. You would use that for things that are set once and never again, like "const int meaningOfLife = 42;".

    --Eric
     
  13. Stoven

    Stoven

    Joined:
    Jul 28, 2014
    Posts:
    171
    I don't know what the rules with const in C# are, but more importantly, your Monobehaviour has a Constructor which isn't good. You should always do initialization of a Monobehaviour in the Start method.

    Do you want to generate your PMaterialData dynamically? If so, you should consider using a new GameObject, set its position, then add a PMaterialData component to it, assigning the values to the component after applying it to the GameObject. The downside to doing this is that you'll have to manually set the size of the PMaterialData[] array in PMats and also manage which index you need to assign a new reference of a PMaterialData to the PMaterialData[] array in PMats. You can probably make a helper method in PMats to do that for you, though so it's not really that bad.

    Otherwise you can setup your Scene and assign each GameObject's PMaterialData individually. There's a pretty big benefit of doing things this way, as you don't have to instantiate the PMaterialData array with a size and you can simply assign references of each GameObject's PMaterialData directly to the PMats data array [via the Inspector]
     
    Last edited: Nov 23, 2014
  14. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Right, I didn't see the MonoBehaviour; that should likely be removed since I can't see any reason for the PMaterialData class to extend MonoBehaviour.

    --Eric
     
    Stoven likes this.
  15. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    I know what constant means. that IS EXACTLY what i want.

    This is data i'm entering at authortime. I want the variables to be tied to the class, not an instance of the class, so that i can read them without instancing it. i want to never ever have to instance PMats at all.I'll read data from it something like this:

    density = PMats.data[PMats.STEEL].density;

    PMats.STEEL will be from a set of integers corresponding to the appropriate array index
     
    Last edited: Nov 23, 2014
  16. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    But PMats isn't going to be called, so how can i be running any methods? i'm a bit confused as to how this works

    No, nothing dynamic. This is entirely just a storage method for data entered at authortime

    This method honestly seems cumbersome. wouldn't that make it annoyingly difficult to view/edit the material parameters? I'd really rather have them all in plaintext, in the PMats file, like the sample record shown above for steel. i'll keep this in mind as an alternate/last resort.
     
  17. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    No, you want static, not constant.

    --Eric
     
  18. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    don't i want both? constant means it can't change at runtime doesn't it?, and that is what i want
     
  19. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Just use static. As far as I know you can't use const with arrays, since they are initialized at runtime, and const is only for compile-time variables.

    --Eric
     
  20. Kirk Clawson

    Kirk Clawson

    Joined:
    Nov 4, 2014
    Posts:
    65
    const only works with numbers, string, bool, and null. No classes, dates, structs, etc... are allowed to be const by the C# compiler. Arrays are classes, even if it's an array of bool.

    If you want "const" type behavior from a class, use static readonly. Though it does have some subtle differences.
     
  21. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    nah, problem solved. Eric's suggestion of using static instead of constant did the trick, my code works with only that change. Scenario 1 is sorted.

    now i have to think on how to accomplish the second one. i'll read back over the posts in this thread
     
  22. Stoven

    Stoven

    Joined:
    Jul 28, 2014
    Posts:
    171
    I misinterpreted the issue. You just wanted to have the data available from the class without needing to instantiate the class that the data originates from which is exactly what static is for. I mixed that up with a personal preference to easily see and modify the values from the array with references to that Monobehaviour likely because I was under the assumption that you want to have PMaterialData as a Monobehaviour and not just a class with information.

    I deleted the post to prevent further confusion XD
     
    Last edited: Nov 24, 2014
    Nanako likes this.
  23. Dabeh

    Dabeh

    Joined:
    Oct 26, 2011
    Posts:
    1,614
    A 1D array where you can access it like this:
    Code (csharp):
    1.  
    2. int index = (y * maxX) + x;
    3.  
    Sorting is relatively easy, there are lots of ways to go about it based on what your bottleneck is.

    Is there a reason the reference needs to be in the same array? If they're related, why not just store them in a class together?


    Note: The reason why I suggest 1D is because a 2D array is actually really performance intensive relatively speaking.
     
    Stoven likes this.
  24. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,528
    relatively speaking, it depends on what you're doing.

    To get a specific row/col in a 2d array or in a 1d array indexed, either one ends up doing roughly the same arithmetic, and actually is comparable in speed.

    It's really only if enumerating over all entries with a for loop that the multi-dimensional array is slower. You can enumerate a 1d array easily, it's pointer + index, as index increases by 1. Where as the MD has to perform the same arithmetic as it would if accessing a single entry.

    Enumerating as an IEnumerator, that gets into some other issues, especially since the mono framework being used in Unity is bit out of date and poorly treats these kind of enumerations (primarily seen with foreach loops). Not exactly sure how it impacts the multi-dimensional array.

    If you're just directly accessing though, the speeds are pretty close. And unless you're doing some intense seeking over the arrays, or using very large arrays, even the 'relative' speed differences are a bit negligible. I'd go with whatever is easier for you to write.
     
    Stoven likes this.
  25. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Just to finalize this thought - constant only works with primitive types (and null, but that's ultimately useless). You can do this though
    Code (csharp):
    1.  
    2. static readonly int[] Numbers = new int[4] { 1, 2, 3, 4 }
    3.  
     
    Nanako and Stoven like this.
  26. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    You would expect so, but no, getting a specific row/col in a 2D array is about twice as slow compared to a 1D array.

    Normally it's best to use whatever's simpler and profile later to see if using a 1D array would actually improve things, but a 1D array is so clearly faster, and it takes little enough effort to use one, that I generally gravitate toward that to begin with. However, jagged arrays are a different matter, since they are very very close to 1D arrays in speed. (They're just a bit of a pain to initialize.)

    --Eric
     
    Stoven and Dabeh like this.
  27. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    If it is performance-critical, I wouldn't use custom classes like
    Code (csharp):
    1. class Item { public int x; public object reference; }
    2. var myArray = new Item[...];
    because instead of an array of items I'll get an array of references to items (more things to compute + extra load on GC). Structures are preferable over the classes in this case.

    And if it is really performance-critical, I would consider using two separate arrays: int[] and object[] (or whatever type it be), but it depends.

    Two bound checks instead of one or zero. Sometimes it's even faster to use pointers, but only sometimes.
     
    Nanako likes this.
  28. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047
    Structs are very interesting to me, first i've heard of the concept. it sounds more useful than objects.

    How would i sort a list/array of structs?
     
  29. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    The same way you do with a list/array of class instances. If your class/struct implements IComparable interface then you just call list.Sort() or array.Sort()
     
  30. Nanako

    Nanako

    Joined:
    Sep 24, 2014
    Posts:
    1,047

    I've ended up using this method, thank you very much <3
     
  31. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Another way to sort lists:
    Code (csharp):
    1. list.Sort((a, b) => a.someInt.Compare(b.someInt));