Storing 2D Array with Protobuf (C#) - c#

I have a large multi-dimensional array that needs to be stored with protobuf. The array could have up to 5120*5120 = 26,214,400 items in it. Protobuf does not support storing multi-dimensional arrays, unfortunately.
As a test, I wrote two functions and an extra class. The class stores and x,y which points to the location inside of the array (array[x, y]). The class has a "value" that is the data from the array[x,y]. I use a List to store this data.
When I generate a fairly small array (1024*1024) I get an output file that is over 169MB. From my testing, it loads and generates the file extremely fast so there's no issue there. However, the file size is huge - I definitely need to cut down on size.
Is this a normal file size, or do I to rethink my entire process? Should I compress the data before saving it (zipping the file takes it from 169MB to 6MB)? If so, what's the fastest/easiest way to zip a file in C#?
This is pseudo code that is based on my real code.
[ProtoContract]
public class Example
{
[ProtoIgnore]
public string[,] MyArray { get; set; }
[ProtoMember(0)]
private List<MultiArray> Storage { get; set; }
public void MoveToList()
{
for (int x = 0; x < MyArray.GetLength(0); x++)
{
for (int y = 0; y < MyArray.GetLength(1); y++)
{
Storage.Add(new MultiArray
{
_x = x,
_y = y,
value = MyArray[x, y]
});
}
}
}
public void MoveToArray()
{
MyArray = new string[1024, 1024];
for (int i = 0; i < Storage.Count; i++)
{
MyArray[Storage[i].X, Storage[i].Y] = Storage[i]._value;
}
}
}
[ProtoContract]
public class MultiArray
{
[ProtoMember(0)]
public int _y { get; set; }
[ProtoMember(1)]
public int _x { get; set; }
[ProtoMember(2)]
public string _value { get; set; }
}
Notes: The value must be the correct x/y of the array.
I appreciate any suggestions.

I don't know about the storage but this is probably not the right way to do it.
The way you are doing it, you are creating a MultiArray object for every cell of your array.
A simplier and more efficient solution would be to do that:
String[] Storage = new String[1024*1024];
int width = 1024
int height = 1024;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Storage[x*width+y]=MyArray[x,y];
}
}

Ultimately, the protobuf format doesn't have a concept of arrays of higher dimension than one.
At the library level since you're using protobuf-net we could have the library do some magic here, essentially treating it as;
message Array_T {
repeated int32 dimensions;
repeated T items; // packed when possible
}
(noting that .proto doesn't actually support generics, but that doesn't really matter at the library level)
However, this would be a little awkward from a x-plat perspective.
But to test whether this would help, you could linearize your 2D array, and see what space it takes.
In your case, I suspect the real problem (re the size) is the quantity of strings. Protobuf writes string contents every time, without any attempt at lookup tables. It may also be worth checking what the sunlm total of string lengths (in UTF-8 bytes) is for your array contents.

Related

Read coordinates from txt file in Unity3D

i've just started to learn unity and i'm having some troubles to go ahead with my exercises.
I need to move 2 cars using coordinates stored in a .txt file called "positions.txt".
The cars have to move at the same time.
Inside the "cartesian" list there are numbers which have to be splitted in groups of 4 (time, x, y, z).
I'd like to create 2 arrays for each car: a time array and a Vector3 array.
First of all, i need to read the file and store the numbers in the arrays. I found several options and i tried this one without success: https://allison-liem.medium.com/unity-reading-external-json-files-878ed0978977
Which approach should i follow?
Thank you.
File "positions.txt"
[
{
"id":"car_1",
"position":{
"cartesian":[
1,0,0,0,
2,0,0,2,
3,1,0,3,
4,1,0,6,
5,2,1,9,
6,2,0,11,
7,3,1,13,
8,3,0,15,
9,3,1,18,
10,4,1,20]
}
},
{
"id":"car_2",
"position":{
"cartesian":[
1,0,0,0,
2,0,0,2,
3,1,0,3,
4,1,0,6,
5,2,1,9,
6,2,0,11,
7,3,1,13,
8,3,0,15,
9,3,1,18,
10,4,1,20]
}
}
]
So first of all you have a JSON so see Serialize and Deserialize Json and Json Array in Unity
I would recommend the Newtonsoft Json.NET package and do e.g.
[Serializable]
public class Position
{
// you can also go for array if you liked
// in JSON it is the same
public List<int> cartesian;
}
[Serializable]
public class Car
{
public string id;
public Position position;
}
and then
var json = File.RealAllText(FILEPATH);
// again could also go for array here
List<Car> cars = JsonConvert.DeserializeObject<List<Car>>(json);
However, the cartesian arrays are a bit "stupid" - have in mind that once this is handled as a list/array there are no line breaks and no good meaning for your redundant indices (1, 2, 3, 4, ...) at the beginning of each set.
I'd like to create 2 arrays for each car: a time array and a Vector3 array.
his wouldn't make sense in my eyes - if something that information clearly belongs coupled together so splitting it into individual arrays would be bad.
Either way you will have to manually extract your values when iterating over the list/array
Go in sets of 4 items
Ignore the first - it seems to be a redundant index
take the last three items and fill them into x,y,z of a vector
like e.g.
[Serializable]
public class Position
{
// you can also go for array if you liked
// in JSON it is the same
public List<int> cartesian;
public List<Vector3> GetPositions()
{
var count = cartesian.Count / 4;
var result = new List<Vector3>(count);
for(var resultIndex = 0; resultIndex < count; resultIndex++)
{
var cartesianIndex = resultIndex * 4;
var position = new Vector3(cartesian[cartesianIndex + 1], cartesian[cartesianIndex + 2], cartesian[cartesianIndex + 3]);
result.Add(position);
}
}
}
or if you actually need also that first value I would use a custom type like e.g.
public readonly struct TimeFrame
{
public int Time { get; }
public Vector3 Position { get; }
public TimeFrame(int time, Vector position)
{
Time = time;
Position = position;
}
}
and then adjust accordingly
[Serializable]
public class Position
{
// you can also go for array if you liked
// in JSON it is the same
public List<int> cartesian;
public List<TimeFrame> GetPositions()
{
var count = cartesian.Count / 4;
var result = new List<TimeFrame>(count);
for(var resultIndex = 0; resultIndex < count; resultIndex++)
{
var cartesianIndex = resultIndex * 4;
var time = cartesian[cartesianIndex];
var position = new Vector3(cartesian[cartesianIndex + 1], cartesian[cartesianIndex + 2], cartesian[cartesianIndex + 3]);
result.Add(new TimeFrame(time, position));
}
}
}
I saw that the medium article you're referring to uses Unity's built in JSON parsing library which does not support array parsing, as pointed out by this answer here https://stackoverflow.com/a/36244111/13489126.
That is the reason why your approach was not working.
I would recommend you to use Newtonsoft Json Parser instead of Unity's.

Can't figure out how to correct my array?

Here is my code. I get a red line under StockPrices saying that it can not implicitly convert type decimal to int. Which I understand since StockPrices Array is set as a decimal. I can't figure out how to convert it. (If you find any other issues, please call it out. I'm still learning!)
public int FindNumTimesNegativePriceChange()
{
int difference = 0;
decimal[] negativeChange = new decimal[StockPrices];
for (int i = 0; i < StockPrices.Length - 1; ++i)
{
difference = (int)(StockPrices[i + 1] - StockPrices[i]);
if (difference < 0)
{
negativeChange++;
}
}
return negativeChange;
Currently no result is returned.
If you want a new array with the same length as an existing array, use the Length property for the source array, not the array itself:
new decimal[StockPrices.Length];
But I'm not sure that is what you are looking for at all.
You want a counter, so difference only needs to be an int in this case.
The next issue is that you are explicitly casting decimal values to an int which means you will lose precision. Other data types would throw an exception in this case but decimal allows it and will truncate the values, not round them.
For stock prices, commonly the changes are less than 1, so in this business domain precision is usually important.
If it is your intention to only count whole integer losses then you should include a comment in the code, mainly because explicit casting like this is a common code mistake, comments are a great way to prevent future code reviewers from editing your logic to correct what looks like a mistake.
Depending on your source code management practises, it can be a good idea to include a reference to the documentation / task / change request that is the authority for this logic.
public int FindNumTimesNegativePriceChange()
{
int difference = 0;
int negativeChange = 0;
for (int i = 0; i < StockPrices.Length - 1; ++i)
{
// #11032: Only counting whole dollar changes
difference = (int)(StockPrices[i + 1] - StockPrices[i]);
if (difference < 0)
{
negativeChange++;
}
}
return negativeChange;
}
A final peer review item, this method processes a single input, but currently that input needs to be managed outside of the scope of this method. In this case StockPrices must be declared at the member level, but this logic is easier to isolate and test if you refactor it to pass through the source array:
public int FindNumTimesNegativePriceChange(decimal[] stockPrices)
{
decimal difference = 0;
int negativeChange = 0;
for (int i = 0; i < stockPrices.Length - 1; ++i)
{
difference = stockPrices[i + 1] - stockPrices[i];
if (difference < 0)
{
negativeChange++;
}
}
return negativeChange;
}
This version also computes the difference (delta) as a decimal
Fiddle: https://dotnetfiddle.net/XAyFnm

C# Memory Concerns

I work with cellular automata. My repo for my work is here. The basic structure is
1) A grid of
2) cells, which may have
3) agents.
The agents act according to a set of rules, and typically one designates "states" for the agents (agents of different states have different rules). One (relatively) well-known CA is the game of life.
I'm trying to expand things a bit more and incorporate other types of "properties" in my CAs, mainly to simulate various phenomena (imagine an animal agent that consumes a plant agent, with the plant's "biomass" or what have you decreasing).
To do this I'm incorporating a normal dictionary, with strings as keys and a struct called CAProperty as the value. The struct is as follows:
public struct CAProperty
{
public readonly string name;
public readonly dynamic value;
//public readonly Type type;
public CAProperty(string name, dynamic value)
{
this.name = name;
this.value = value;
}
}
(note: previously I had the "type" variable to enable accurate typing at runtime...but in attempts to solve the issue in this post I removed it. Fact is, it'll need to be added back in)
This is well and good. However, I'm trying to do some work with large grid sizes. 100x100. 1000x1000. 5000x5000, or 25 million cells (and agents). That would be 25 million dictionaries.
See the image: a memory snapshot from Visual Studio for a 4000x4000 grid, or 16 million agents (I tried 5000x5000, but Visual Studio wouldn't let me take a snapshot).
On the right, one can clearly see that the debugger is reading 8 GB memory usage (and I tried this in a release version to see 6875 MB usage). However, when I count up everything in the third column of the snapshot, I arrive at less than 4 GB.
Why is there such a dramatic discrepancy between the total memory usage and the size of objects stored in memory?
Additionally: how might I optimize memory usage (mainly the dictionaries - is there another collection with similar behavior but lower memory usage)?
Edit: For each of the three "components" (Grid, Cell, Agent) I have a class. They all inherit from an original CAEntity class. All are shown below.
public abstract class CAEntity
{
public CAEntityType Type { get; }
public Dictionary<string, dynamic> Properties { get; private set; }
public CAEntity(CAEntityType type)
{
this.Type = type;
}
public CAEntity(CAEntityType type, Dictionary<string, dynamic> properties)
{
this.Type = type;
if(properties != null)
{
this.Properties = new Dictionary<string, dynamic>(properties);
}
}
}
public class CAGraph:CAEntity
{
public ValueTuple<ushort, ushort, ushort> Dimensions { get; }
public CAGraphCell[,,] Cells { get;}
List<ValueTuple<ushort, ushort, ushort>> AgentCells { get; set; }
List<ValueTuple<ushort, ushort, ushort>> Updates { get; set; }
public CA Parent { get; private set; }
public GridShape Shape { get; }
//List<double> times = new List<double>();
//System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
public CAGraph (CA parent, ValueTuple<ushort, ushort, ushort> size, GridShape shape):base(CAEntityType.Graph)
{
this.Parent = parent;
this.Shape = shape;
AgentCells = new List<ValueTuple<ushort, ushort, ushort>>();
Updates = new List<ValueTuple<ushort, ushort, ushort>>();
Dimensions = new ValueTuple<ushort, ushort, ushort>(size.Item1, size.Item2, size.Item3);
Cells = new CAGraphCell[size.Item1, size.Item2, size.Item3];
for (ushort i = 0; i < Cells.GetLength(0); i++)
{
for (ushort j = 0; j < Cells.GetLength(1); j++)
{
for (ushort k = 0; k < Cells.GetLength(2); k++)
{
Cells[i, j, k] = new CAGraphCell(this, new ValueTuple<ushort, ushort, ushort>(i, j, k));
}
}
}
}
public CAGraph(CA parent, ValueTuple<ushort, ushort, ushort> size, GridShape shape, List<ValueTuple<ushort, ushort, ushort>> agents, CAGraphCell[,,] cells, Dictionary<string, dynamic> properties) : base(CAEntityType.Graph, properties)
{
Parent = parent;
Shape = shape;
AgentCells = agents.ConvertAll(x => new ValueTuple<ushort, ushort, ushort>(x.Item1, x.Item2, x.Item3));
Updates = new List<ValueTuple<ushort, ushort, ushort>>();
Dimensions = new ValueTuple<ushort, ushort, ushort>(size.Item1, size.Item2, size.Item3);
Cells = new CAGraphCell[size.Item1, size.Item2, size.Item3];
for (ushort i = 0; i < size.Item1; i++)
{
for (ushort j = 0; j < size.Item2; j++)
{
for (ushort k = 0; k < size.Item3; k++)
{
//if(i == 500 && j == 500)
//{
// Console.WriteLine();
//}
Cells[i, j, k] = cells[i, j, k].Copy(this);
}
}
}
}
}
public class CAGraphCell:CAEntity
{
public CAGraph Parent { get; set; }
public CAGraphCellAgent Agent { get; private set; }
public ValueTuple<ushort, ushort, ushort> Position { get; private set; }
//private Tuple<ushort, ushort, ushort>[][] Neighbors { get; set; }
//System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
public CAGraphCell(CAGraph parent, ValueTuple<ushort, ushort, ushort> position):base(CAEntityType.Cell)
{
this.Parent = parent;
this.Position = position;
//this.Neighbors = new Tuple<ushort, ushort, ushort>[Enum.GetNames(typeof(CANeighborhoodType)).Count()][];
}
public CAGraphCell(CAGraph parent, ValueTuple<ushort, ushort, ushort> position, Dictionary<string, dynamic> properties, CAGraphCellAgent agent) :base(CAEntityType.Cell, properties)
{
this.Parent = parent;
this.Position = position;
if(agent != null)
{
this.Agent = agent.Copy(this);
}
}
}
public class CAGraphCellAgent:CAEntity
{
// have to change...this has to be a property? Or no, it's a CAEntity which has a list of CAProperties.
//public int State { get; set; }
public CAGraphCell Parent { get; set; }
//System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
public CAGraphCellAgent(CAGraphCell parent, ushort state):base(CAEntityType.Agent)
{
this.Parent = parent;
AddProperty(("state", state));
}
public CAGraphCellAgent(CAGraphCell parent, Dictionary<string, dynamic> properties) :base(CAEntityType.Agent, properties)
{
this.Parent = parent;
}
}
It sounds like your problem is that your representation of your agents (using dictionaries) consumes too much memory. If so, the solution is to find a more compact representation.
Since you're working in an object-oriented language, the typical solution would be to define an Agent class, possibly with subclasses for different types of agents, and use instance variables to store the state of each agent. Then your CA grid will be an array of Agent instances (or possibly nulls for vacant cells). This will be a lot more compact than using dictionaries with string keys.
Also, I would recommend not storing the position of the agent on the grid as part of the agent's state, but passing it as a parameter to any methods that need it. Not only does this save a bit of memory just by itself, but it also allows you to place references to the same Agent instance at multiple cells on the grid to represent multiple identical agents. Depending on how often such identical agents occur in your CA, this may save a huge amount of memory.
Note that, if you modify the state of such a reused agent instance, the modification will obviously affect all agents on the grid represented by that instance. For that reason, it may be a good idea to make your Agent objects immutable and just create a new one whenever the agent's state changes.
You might also want to consider maintaining a cache (e.g. a set) of Agent instances already on the grid so that you can easily check if a new agent might be identical with an existing one. Whether this will actually do any good depends on your specific CA model — with some CA you might be able to handle de-duplication sufficiently well even without such a cache (it's perfectly OK to have some duplicate Agent objects), while for others there might simply not be enough identical agents to make it worthwhile. Also, if you do try this, note that you'll need to either design the cache to use weak references (which can be tricky to get right) or periodically purge and rebuild it to avoid old Agent objects lingering in the cache even after they've been removed from the grid.
Addendum based on your comment below, which I'll quote here:
Imagine an environment where the temperature varies seasonally (so a property on the graph). There are land and water cells (so properties on cells), and in low enough temperatures the water cells become frozen so animal agents can use them to cross over between land locations. Imagine those animal agents hunt other animal agents to eat them (so properties on the agents). Imagine the animal agents that get eaten eat trees (so other agents with properties), and tend to eat young saplings (limiting tree growth), thereby limiting their own growth (and that of the carnivore agents).
OK, so let's sketch out the classes you'd need. (Please excuse any syntax errors; I'm not really a C# programmer and I haven't actually tested this code. Just think of it as C#-like pseudocode or something.)
First of all, you will obviously need a bunch of agents. Let's define an abstract superclass for them:
public abstract class Agent {
public abstract void Act(Grid grid, int x, int y, float time);
}
Our CA simulation (which, for simplicity, I'm going to assume to be stochastic, i.e. one where the agents act one at a time in a random order, like in the Gillespie algorithm) is basically going to involve repeatedly picking a random cell (x, y) on the grid, checking if that cell contains an agent, and if so, calling Act() on that agent. (We'll also need to update any time-dependent global state while we're doing that, but let's leave that for later.)
The Act() methods for the agents will receive a reference to the grid object, and can call its methods to make changes to the state of nearby cells (or even get a reference to the agents in those cells and call their methods directly). This could involve e.g. removing another agent from the grid (because it just got eaten), adding a new agent (reproduction), changing the acting agent's location (movement) or even removing that agent from the grid (e.g. because it starved or died of old age). For illustration, let's sketch a few agent classes:
public class Sapling : Agent {
private static readonly double MATURATION_TIME = 10; // arbitrary time delay
private double birthTime; // could make this a float to save memory
public Sapling(double time) => birthTime = time;
public override void Act(Grid grid, int x, int y, double time) {
// if the sapling is old enough, it replaces itself with a tree
if (time >= birthTime + MATURATION_TIME) {
grid.SetAgentAt(x, y, Tree.INSTANCE);
}
}
}
public class Tree : Agent {
public static readonly Tree INSTANCE = new Tree();
public override void Act(Grid grid, int x, int y, double time) {
// trees create saplings in nearby land cells
(int x2, int y2) = grid.RandomNeighborOf(x, y);
if (grid.GetAgentAt(x2, y2) == null && grid.CellTypeAt(x2, y2) == CellType.Land) {
grid.SetAgentAt(x2, y2, new Sapling(time));
}
}
}
For the sake of brevity, I'll leave the implementation of the animal agents as an exercise. Also, the Tree and Sapling implementations above are kind of crude and could be improved in various ways, but they should at least illustrate the concept.
One thing worth noting is that, to minimize memory usage, the agent classes above have as little internal state as possible. In particular, the agents don't store their own location on the grid, but will receive it as arguments to the act() method. Since omitting the location in fact made my Tree class completely stateless, I went ahead and used the same global Tree instance for all trees on the grid! While this won't always be possible, when it is, it can save a lot of memory.
Now, what about the grid? A basic implementation (ignoring the different cell types for a moment) would look something like this:
public class Grid {
private readonly int width, height;
private readonly Agent?[,] agents;
public Grid(int w, int h) {
width = w;
height = h;
agents = new Agent?[w, h];
}
// TODO: handle grid edges
public Agent? GetAgentAt(int x, int y) => agents[x, y];
public void SetAgentAt(int x, int y, Agent? agent) => agents[x, y] = agent;
}
Now, what about the cell types? You have a couple of ways to handle those.
One way would be to make the grid store an array of Cell objects instead of agents, and have each cell store its state and (possibly) an agent. But for optimizing memory use it's probably better to just have a separate 2D array storing the cell types, something like this:
public enum CellType : byte { Land, Water, Ice }
public class Grid {
private readonly Random rng = new Random();
private readonly int width, height;
private readonly Agent?[,] agents;
private readonly CellType[,] cells; // TODO: init in constructor?
private float temperature = 20; // global temperature in Celsius
// ...
public CellType CellTypeAt(int x, int y) {
CellType type = cells[x,y];
if (type == CellType.Water && temperature < 0) return CellType.Ice;
else return type;
}
}
Note how the CellType enum is byte-based, which should keep the array storing them a bit more compact than if they were int-based.
Now, let's finally look at the main CA simulation loop. At its most basic, it could look like this:
Grid grid = new Grid(width, height);
grid.SetAgentAt(width / 2, height / 2, Tree.INSTANCE);
// average simulation time per loop iteration, assuming that each
// actor on the grid acts once per unit time step on average
double dt = 1 / (width * height);
for (double t = 0; t < maxTime; t += dt) {
(int x, int y) = grid.GetRandomLocation();
Agent? agent = grid.GetAgentAt(x, y);
if (agent != null) agent.Act(grid, x, y, t);
// TODO: update temperature here?
}
(Technically, to correctly implement the Gillespie algorithm, the simulation time increment between iterations should be an exponentially distributed random number with mean dt, not a constant increment. However, since only the actor on one of the width * height cells is chosen in each iteration, the number of iterations between actions by the same actor is geometrically distributed with mean width * height, and multiplying this by dt = 1 / (width * height) gives an excellent approximation for an exponential distribution with mean 1. Which is a long-winded way of saying that in practice using a constant time step is perfectly fine.)
Since this is getting long enough, I'll let you continue from here. I'll just note that there are plenty of ways to further expand and/or optimize the algorithm I've sketched above.
For example, you could speed up the simulation by maintaining a list of all grid locations that contain a live actor and randomly sampling actors from that list (but then you'd also need to scale the time step by the inverse of the length of the list). Also, you may decide that you want some actors to get more frequent chances to act than others; while the simple way to do that is just to use rejection sampling (i.e. have the actor only do something if rng.Sample() < prob for some prob between 0 and 1), a more efficient way would be to maintain multiple lists of locations depending on the type of the actor there.

Populate Scriptable Objects Automatically

A few of my Scriptable Objects(used for seeding my initial game data) contain large 2 dimensional arrays (like 10x10), the data of which i am generating with an external Python script. I do use the Odin inspector plugin as well, to serialize the 2d array for me and provide me with a nice representation of that array inside the Unity editor.
I am simply doing it like this :
[TableMatrix()]
public int[,] table = new int[10, 10];
and this is just an Odin SerializedScriptableObject class.
The problem is, I really want to avoid having to add the 10x10 elements by hand using the Unity editor and also I want my objects to have variable 2d array sizes, one could beb (10,10), another could be (5,5). Is there a way to populate my scriptable objects programmatically to achieve that ? (Or does the Odin inspector plugin support something like that if anyone knows ?)
Thanks !
Sorry for the late response #Spyros.
My approach will be similar to #D Manokhin approach, but instead of using jagged array I'll use a multidimensional array (cause you can build a custom editor script to visualize them, I'm pretty sure you can visualize jagged arrays on editor without plugins, I've never used Odin plugin).
So I delcare one class who will store the structs called TileData:
using UnityEngine;
using System.Collections;
[System.Serializable]
public class TileData
{
/*
* rows
[rowData][rowData][rowData]
[cell] ->value
->type
[cell] ->value
->type
[cell] ->value
->type
*/
[System.Serializable]
public struct cell
{
public float value;
public string type;
}
[System.Serializable]
public struct rowData
{
public cell[] row;
}
public rowData[] rows;
}
I used value and type as an "examples", it's absolutly up to you, you can also store Vector3 if you want!
Then, how to call and use this kind of structure? Let's see:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Matrix : MonoBehaviour {
//declare the structure that will store the data
public TileData tileData = new TileData();
//those are public so you could make the matrix with the size you want..
//..ideally dynamically call it from your python file requisits
public int horizontalMatrixSize = 10;
public int verticalMatrixSize = 10;
// Use this for initialization
void Start()
{
//Create the matrix structure and fill it
tileData.rows = new TileData.rowData[horizontalMatrixSize];
for (int i = 0; i < tileData.rows.Length; i++)
{
tileData.rows[i].row = new TileData.cell[verticalMatrixSize];
for (int u = 0; u < tileData.rows[i].row.Length; u++)
{
tileData.rows[i].row[u].value = GetValuesFromPythonFileOrWhatever();
tileData.rows[i].row[u].type = GetValuesFromPythonFileOrWhatever();
}
}
}
}
But if you really like the jagged structure, you can still use it (but remember that will not be represented on editor) like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Matrix : MonoBehaviour {
//declare the structure that will store the data
[SerializeField] private float[,] grayscaleBidimensional = null;
//those are public so you could make the matrix with the size you want..
//..ideally dynamically call it from your python file requisits
public int horizontalMatrixSize = 10;
public int verticalMatrixSize = 10;
// Use this for initialization
void Start()
{
//Create the matrix structure and fill it
tileData.rows = new TileData.rowData[horizontalMatrixSize];
grayscaleBidimensional = new float[horizontalMatrixSize, verticalMatrixSize];
for (int x = 0; x < horizontalMatrixSize; x++)
{
for (int y = 0; y < verticalMatrixSize; y++)
{
grayscaleBidimensional[x, y] = GetValuesFromPythonFileOrWhatever();
}
}
}
}
Remember that you can make the values of the objects as you want, so they don't need to have an static size!
To change an individual value, you can do table[x,y] = (your value). Lets say you want to fill every value with the number one, you could do:
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 10; x++)
{
table[x, y] = 1;
}
}
Hope that answers your question and sorry if the code is wrong - I don't have access to Unity at the moment, so you may need to fiddle around with it.
You should serialize the arrays out to files in python, and then use a Scripted Importer to import them into Unity, construct the arrays in the ScriptableObject, and fill in their values.
https://docs.unity3d.com/Manual/ScriptedImporters.html

Filling in a matrix(2D array) using functions from a class

I've learned C++ and am currently taking C#. Just to clarify, I am providing all the instructions but if I can get help with parts (b) and (c) I think I can do the rest.
This is my assignment:
Matrix is a 2 dimensional array of row size r and column size c. There are specific rules of adding or subtracting matrices A and B (of same row size m and column size n). The product of matrix A of dimension m by n (m is the row size or row dimension, n is the column dimension) and another matrix B of dimension p by q is meaningful only if p = n and in this case AB is a matrix of dimension m by q.
(a) Define class Matrix with at least private variables depending on row size, column size etc.
(b) Provide constructor when row size and column size are specified. You will set all the arguments to 0.0.
(c) Provide method to set the entries of a matrix
(d) Provide Add method of matrices
(e) Provide Subtract method.
(f) Provide scalar multiplication which will multiply all the elements of matrix A by the value x.
(g) Provide multiplication method mul so that A.mul(B) is equal to AB if A’s column dimension c is equal to B’s row dimension. Generate error message if the product AB is meaningless.
{
{
private int r=10;//row size
private int c=10;//column size
int[,] array=new int [10,10];
public Matrix()
{
for (int i = 0; i < this.r; i++)
{
for (int j = 0; j < this.c; j++)
{
}
}
}
public Matrix(int rowSize, int columnSize)
{
this.r = rowSize;
this.c = columnSize;
}
public void SetMatrix()
{
for (int i=0; i<this.r; i++)
{
for(int j=0; j<this.c; j++)
{
}
}
}
}
}
Without a finite size, I'm not sure how to proceed with creating the array (the 10,10 was just so the compiler would stop complaining). Secondly, when that's established, I'm not sure on how to fill in the array (most likely from the console). Hopefully my question doesn't sound too much like I'm asking for someone to do my homework for me. XD
First - you don't need a loop to initialize your matrix. Just declaring an int[r,c] will give you a 2d array filled with zeros. Your description doesn't say a zero argument constructor is required, so I wouldn't provide one, it's not clear how it should behave anyway. So just create one constructor like this:
public Matrix(int rows, int cols)
{
r = rows;
c = cols;
array = new int[r,c]; // Note: this will be filled with zeros!
}
For setting entries you can just provide a method like this:
public void SetCell(int row, int col, int val)
{
array[row,col] = val;
}
Or you could even do something like this using an indexer:
public int this[int row, int col]
{
get { return array[row,col]; }
set { array[row,col] = value; } // You didn't ask for a setter, but I assume you'll need one
}
if I can get help with parts (b) and (c) I think I can do the rest.
Ok, good luck then
EDIT:
So to clarify, when you declare an array of int it will be initially filled with zeros. This is in contrast to c++ where it would be a random block of memory that will be filled with random junk and you would need to set to zero. The use of unassigned variables is one of the "problems" with c++ that c# aimed to fix. For example, if you run this code:
int[] arr = new int[10];
Console.WriteLine(string.Join(",",arr));
It will write 0,0,0,0,0,0,0,0,0,0 every single time.
For more info read here. In particular, this part:
Note If you do not initialize an array at the time of declaration, the array members are automatically initialized to the default initial value for the array type. Also, if you declare the array as a field of a type, it will be set to the default value null when you instantiate the type.
But note, if you do this:
int a;
Console.WriteLine(a);
You will actually get a compile time error complaining about the usage of an unassigned variable.

Categories