I have a 2D integer array to store x,y coordinates. I checked out a few functions to write 2D array into a file but cannot find anything that is able to read that binary file on load and push it into a new 2 dimensional integer array.
This is my world generator function which saves it to the file:
public WorldGenerator()
{
int worldSizeX = 100;
int worldSizeY = 100;
int[,] world = new int[worldSizeX*worldSizeY, 2];
Logger.log("Generating world...");
for(int x = 0; x < worldSizeX; x++)
{
for(int y = 0; y < 2; y++)
{
System.Random random = new System.Random();
int itemID = random.Next(0, 1);
world[x, y] = itemID;
}
}
FileStream fs = new FileStream(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/ConsoleGame/world/default.wd", FileMode.OpenOrCreate, FileAccess.Write);
BinaryWriter bw = new BinaryWriter(fs);
for (int x = 0; x < worldSizeX; x++)
{
for (int y = 0; y < 2; y++)
{
bw.Write(world[x, y]);
}
}
bw.Close();
fs.Close();
Logger.log("World generated.");
}
Any good idea that could work for reading this file in? I should get back a 2D integer array and world[0,0] should get me the itemid. I am new to c# and this would be just a basic console application.
I have also seen others answering similar questions but none of them are worked for me yet. Maybe because this save function is wrong or something else.
EDIT:
Here is how I load the file:
using (var filestream = File.Open(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/ConsoleGame/world/default.wd", FileMode.Open))
using (var binaryStream = new BinaryReader(filestream))
{
while (binaryStream.PeekChar() != -1)
{
Console.WriteLine(binaryStream.ReadInt32());
}
}
need Newtonsoft.Json
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ConsoleApp18
{
class Program
{
static void Main(string[] args)
{
int worldSizeX = 100;
int worldSizeY = 100;
int[,] world = new int[worldSizeX * worldSizeY, 2];
System.Random random = new System.Random();
for (int x = 0; x < worldSizeX; x++)
{
for (int y = 0; y < 2; y++)
{
int itemID = random.Next(0, 2);
world[x, y] = itemID;
}
}
string json = JsonConvert.SerializeObject(world, Formatting.Indented);
System.IO.File.WriteAllText("WriteText.txt", json);
string text = System.IO.File.ReadAllText("WriteText.txt");
int[,] deserialized = JsonConvert.DeserializeObject<int[,]>(text);
//use "deserialized"
}
}
}
What you need is called "Serialization".
Start with the simple builtin binary serializer.
Serializable attribute does the magic here.
By the moment you'll realize it is not the best option, you'll be able to use something more suiting your needs, like proto-buf.
I've also changed ints to shorts in your example. I doubt you need 32 bits for each world cell, so we can save a bit of hard drive space.
[Serializable]
public class WorldState
{
public short[,] Items { get; set; }
public void Save(string filename)
{
if (filename == null) throw new ArgumentNullException(nameof(filename));
using (var file = File.Create(filename))
{
var serializer = new BinaryFormatter();
serializer.Serialize(file, this);
}
}
public static WorldState Load(string filename)
{
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (!File.Exists(filename)) throw new FileNotFoundException("File not found", filename);
using (var file = File.OpenRead(filename))
{
var serializer = new BinaryFormatter();
return serializer.Deserialize(file) as WorldState;
}
}
}
public class WorldStateTests
{
[Fact]
public void CanSaveAndLoad()
{
var ws = new WorldState
{
Items = new short[,]
{
{ 1, 2, 3, 4 },
{ 1, 2, 3, 4 },
{ 1, 2, 3, 4 },
{ 1, 2, 3, 4 }
}
};
// save the world state to file. Find it and see what's inside
ws.Save("./ws.bin");
// load the world back
var loaded = WorldState.Load("./ws.bin");
// check a new world state got loaded
Assert.NotNull(loaded);
// and it still has items
Assert.NotEmpty(loaded.Items);
// and the items are the same as we saved
Assert.Equal(ws.Items, loaded.Items);
}
}
Related
I have an array of objects which I want to write to a json file and recover it later
Point - Visual Basic class:
<Serializable> Public Class Point
Implements IComparable
Public Property X As Integer
Public Property Y As Integer
Public Sub New()
X = 0
Y = 0
End Sub
Public Sub New(ByVal x As Integer, ByVal y As Integer)
Me.X = x
Me.Y = y
End Sub
End Class
Point3D - C# class
[Serializable]
public class Point3D : Point
{
public int Z { get; set; }
public Point3D() : base()
{
var rnd = new Random();
Z = rnd.Next(10);
}
public Point3D(int x, int y, int z) : base(x, y)
{
this.Z = z;
}
}
Serialization works fine but when I try to deserialize json from file, all objects presented as Point3D (even the ones with only X and Y variables).
Before deserialization:
enter image description here
After:
enter image description here
Serialization code block:
using (var fs = new FileStream(dlg.FileName, FileMode.Create, FileAccess.Write))
{
switch (Path.GetExtension(dlg.FileName))
{
...
case ".json":
var jf = new JsonSerializer();
jf.TypeNameHandling= TypeNameHandling.Auto;
using (var w = new StreamWriter(fs))
jf.Serialize(w, points);
break;
}
}
Deserialization code block:
using (var fs = new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read))
{
switch (Path.GetExtension(dlg.FileName))
{
...
case ".json":
var jf = new JsonSerializer();
using (var r = new StreamReader(fs))
points = (Point[])jf.Deserialize(r, typeof(Point3D[]));
break;
}
}
Code I use to initialize array of objects (Point and Point3D):
private void buttonCreate_Click(object sender, EventArgs e)
{
points = new Point[5];
var rnd = new Random();
for (int i = 0; i < points.Length; i++)
{
points[i] = rnd.Next(3) % 2 == 0 ? new Point() : new Point3D();
}
listBox.DataSource = points;
}
And json string
{"$type":"PointLib.Point[], PointLib","$values":[{"$type":"LabOneFormsApp.Point3D, LabOneFormsApp","Z":6,"X":0,"Y":0},{"$type":"LabOneFormsApp.Point3D, LabOneFormsApp","Z":1,"X":0,"Y":0},{"$type":"PointLib.Point, PointLib","X":0,"Y":0},{"$type":"PointLib.Point, PointLib","X":0,"Y":0},{"$type":"PointLib.Point, PointLib","X":0,"Y":0}]}
I tried adding
jf.TypeNameHandling= TypeNameHandling.Auto;
to my code but it does not seem to work for me. Any ideas?
I've decided to do it simple way
case ".json":
var reader = new StreamReader(fs).ReadToEnd();
var jsonFile = JsonConvert.DeserializeObject<List<Dictionary<string, int>>>(reader);
if (jsonFile != null)
{
points = new Point[jsonFile.Count];
for (int i = 0; i < jsonFile.Count; i++)
{
var pointDict = jsonFile[i];
switch (pointDict.Count)
{
case 2:
points[i] = new Point(pointDict["X"], pointDict["Y"]);
break;
case 3:
points[i] = new Point3D(pointDict["X"], pointDict["Y"], pointDict["Z"]);
break;
}
}
}
else
{
points = null;
}
Thanks everyone for the answers!
I am learning C# and want to created a weighted job scheduler.
The max profit is working fine. But I need to be able to get the JobIDs associated with it so I am trying to save it in an array jobsId, similar to this implementation in C++: https://onlinegdb.com/a9wx3nHoN.
But then in this snippet, I am getting an error: Wrong number of indices inside []; expected 2
if (profitSum > dp[i-1]) {
dp[i] = profitSum;
jobsId[i,0] = jobsId[task]; // Problem is here
jobsId[i].Append(jobs[i].id).ToArray(); //And here
}
What am I doing wrong? Please help, thanks!
Codes can be accessed here:
https://rextester.com/NXM85235
Alternatively, here is my entire snippet:
using System.IO;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
class Program
{
public int JobScheduling(int[] startTime, int[] endTime, int[] profit) {
var jobs = startTime
.Select((_, i) => new // create custom obj
{
id = i,
s = startTime[i],
e = endTime[i],
p = profit[i],
}
)
.OrderBy(x => x.e) // sort by end-time
.ToArray();
int[] dp = new int[jobs.Length];
int [,] jobsId = new int[,] {{jobs[0].id}};
int profitSum = jobs[0].p;
int task = -1;
dp[0] = jobs[0].p;
for (var i = 1; i < jobs.Length; i++) {
for (var j = i-1; j >= 0; j--) {
if (jobs[j].e <= jobs[i].s) {
task = j;
break;
}
}
if (task != -1) {
profitSum += dp[task];
}
if (profitSum > dp[i-1]) {
dp[i] = profitSum;
jobsId[i,0] = jobsId[task]; // Problem is here
jobsId[i].Append(jobs[i].id).ToArray();
}
}
// Need to implement this
for (var iter = 0; iter < jobsId.Length; iter++) {
Console.WriteLine(jobsId[iter,0]);
}
return dp[jobs.Length-1];
}
public static void Main(string[] args)
{
int[] startTime = { 1,3,6,2 };
int[] endTime = { 2,5,7,8 };
int[] profit = { 50,20,100,200 };
// Creating object
Program job = new Program();
Console.WriteLine(job.JobScheduling(startTime, endTime, profit));
}
}
int [,] jobsId = new int[,] {{jobs[0].id}};
The multi-dimensional 3d array that you are using has a fixed size .i.e. only one. You can't add any more elements to that array. Instead, try using List< List < int > >.
jobsId[i,0] = jobsId[task]; // problem here
Here jobsId is a 2d-array. You can't access the individual rows. You can only access elements within. For this too you need to create an array of array .i.e List< List < int > >.
And I could not figure out why are trying to get an array here.
jobsId[i].Append(jobs[i].id).ToArray();
Random.Next() randomness failures are almost always caused by creating and then using multiple instances of System.Random with the same seed, either with a time seed or a manual one. However, this is the only instance creation code in my class:
System.Random rNG;
if (string.IsNullOrEmpty(Map.Seed))
{
rNG = new System.Random();
}
else
{
rNG = new System.Random(Map.Seed.GetHashCode());
}
Looping through this second attempt code correctly creates random numbers:
var resourceRoll = rNG.Next(0, this.ResourceByRoll.Count);
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
.Where(row => row["Resource"].Equals(
this.ResourceByRoll[resourceRoll]
))
Looping through this original attempt code often creates the same number twice in a row:
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
.Where(row => row["Resource"].Equals(
this.ResourceByRoll[rNG.Next(0, this.ResourceByRoll.Count)]
))
Am I somehow silently creating a new instance of System.Random when using a Random.Next call as a dictionary index? Why does my original code often return the same number twice in a row?
If it matters:
This class is a Unity script
I am using System.Random, not UnityEngine.Random
My complete class is below:
using Assets.Code.Tools;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using UnityEngine;
public class Map : MonoBehaviour
{
public static int Length { get; set; }
public static int Width { get; set; }
public static int ResourceChanceDenominator { get; set; }
public static string Seed { get; set; }
private static int[,] objectGrid;
private DataTable ProcGenResourceTable { get; set; }
private Dictionary<int, string> ResourceByRoll { get; set; }
private List<GameObject> prefabTrees;
private List<GameObject> prefabStones;
private void Start()
{
this.prefabTrees = GeneralTools.GetPrefabsWithTag("Tree");
this.prefabStones = GeneralTools.GetPrefabsWithTag("Stone");
GenerateMap();
}
public void GenerateMap()
{
var procGenResourceTable = Resources.Load("ProcGenResourceTable") as TextAsset;
if (procGenResourceTable != null)
{
this.ProcGenResourceTable = GeneralTools.GetDataTableFromCSV(procGenResourceTable, "|", true, false);
}
else
{
Console.WriteLine("ProcGenResourceTable could not be found");
return;
}
Map.objectGrid = new int[Map.Width, Map.Length];
this.ResourceByRoll = GetPopulatedResourceByRollDictionary();
System.Random rNG;
if (string.IsNullOrEmpty(Map.Seed))
{
rNG = new System.Random();
}
else
{
rNG = new System.Random(Map.Seed.GetHashCode());
}
for (var i = 0; i < Map.Length; i++)
{
for (var j = 0; j < Map.Width; j++)
{
var roll = rNG.Next(Map.ResourceChanceDenominator);
if (roll == 1)
{
// var resourceRoll = rNG.Next(0, this.ResourceByRoll.Count);
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
.Where(row => row["Resource"].Equals(
this.ResourceByRoll[rNG.Next(0, this.ResourceByRoll.Count)]
))
select new
{
ModelFamily = row["Model Family"],
Tags = row["Tags"]
};
foreach (var row in resourceRow)
{
GameObject resource = null;
switch (row.ModelFamily)
{
case "Tree":
resource = Instantiate(this.prefabTrees[rNG.Next(this.prefabTrees.Count - 1)], new Vector3(i, 0, j), new Quaternion());
break;
case "Stone":
resource = Instantiate(this.prefabStones[rNG.Next(this.prefabStones.Count - 1)], new Vector3(i, 0, j), new Quaternion());
break;
default:
resource = Instantiate(this.prefabTrees[rNG.Next(this.prefabTrees.Count - 1)], new Vector3(i, 0, j), new Quaternion());
break;
}
var tagsListForResource = row.Tags.ToString().Split(new char[] { '|' }).ToList();
if (tagsListForResource.Contains("Resource"))
{
resource.tag = "Resource";
}
}
}
}
}
}
private Dictionary<int, string> GetPopulatedResourceByRollDictionary()
{
var resourceByRoll = new Dictionary<int, string>();
foreach (DataRow row in this.ProcGenResourceTable.Rows)
{
if (!string.IsNullOrEmpty(row["Weight"].ToString()))
{
for (var i = 0; i < Convert.ToInt32(row["Weight"]); i++)
{
resourceByRoll.Add(resourceByRoll.Count, row["Resource"].ToString());
}
}
}
return resourceByRoll;
}
}
I misunderstood what was going on - this is not related to repeated random numbers.
The answer to Michael Welch's question is that I am seeing trees and rocks generate on the same coordinates.
I expected this code to only generate one row of results:
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
.Where(row => row["Resource"].Equals(
this.ResourceByRoll[rNG.Next(0, this.ResourceByRoll.Count)]
))
However, it is sometimes returns more, because .Where() does some looping of its own that re-rolls the number each time.
Having multiple rows returned causes the foreach block to generate multiple prefabs at the coordinate instead of just one.
I have an array consisting of 1 string value and 2 int values, which I would like to write to a binary file.
It consists of name, index and score.
I have attached the array code below, how could I write this to a file?
Player[] playerArr = new Player[10];
int index = 0;
index = index + 1; // when a new player is added the index is increased by one
Player p = new Player(txtName3.Text, index, Convert.ToInt16(txtScore.Text)); // set the values of the object p
p.refName = txtName3.Text; // set refName to be the string value that is entered in txtName
p.refTotalScore = Convert.ToInt16(txtScore.Text);
playerArr[index] = p; // set the p object to be equal to a position inside the array
I would also like to sort each instantiation of the array to be output in descending order of score. How could this be done?
The file handling code I have so far is:
private static void WriteToFile(Player[] playerArr, int size)
{
Stream sw;
BinaryFormatter bf = new BinaryFormatter();
try
{
sw = File.Open("Players.bin", FileMode.Create);
bf.Serialize(sw, playerArr[0]);
sw.Close();
sw = File.Open("Players.bin", FileMode.Append);
for (int x = 1; x < size; x++)
{
bf.Serialize(sw, playerArr[x]);
}
sw.Close();
}
catch (IOException e)
{
MessageBox.Show("" + e.Message);
}
}
private int ReadFromFile(Player[] playerArr)
{
int size = 0;
Stream sr;
try
{
sr = File.OpenRead("Players.bin");
BinaryFormatter bf = new BinaryFormatter();
try
{
while (sr.Position < sr.Length)
{
playerArr[size] = (Player)bf.Deserialize(sr);
size++;
}
sr.Close();
}
catch (SerializationException e)
{
sr.Close();
return size;
}
return size;
}
catch (IOException e)
{
MessageBox.Show("\n\n\tFile not found" + e.Message);
}
finally
{
lstLeaderboard2.Items.Add("");
}
return size;
}
For the first part, you need to mark your class as Serializable, like this:
[Serializable]
public class Player
It's fine to Append to a new file, so you can change your code to this:
sw = File.Open(#"C:\Players.bin", FileMode.Append);
for (int x = 0; x < size; x++)
{
bf.Serialize(sw, playerArr[x]);
}
sw.Close();
(with the appropriate exception handling, and you'll obviously need to amend this if the file might already exist).
For the second part, you can sort an array like this using LINQ:
var sortedList = playerArr.OrderBy(p => p.Score);
If you require an array as output, do this:
var sortedArray = playerArr.OrderBy(p => p.Score).ToArray();
(Here, Score is the name of the property on the Player class by which you want to sort.)
If you'd like any more help, you'll need to be more specific about the problem!
I have a txt file that the format is:
0.32423 1.3453 3.23423
0.12332 3.1231 9.23432432
9.234324234 -1.23432 12.23432
...
Each line has three double value. There are more than 10000 lines in this file. I can use the ReadStream.ReadLine and use the String.Split, then convert it.
I want to know is there any faster method to do it.
Best Regards,
StreamReader.ReadLine, String.Split and Double.TryParse sounds like a good solution here.
No need for improvement.
There may be some little micro-optimisations you can perform, but the way you've suggested sounds about as simple as you'll get.
10000 lines shouldn't take very long - have you tried it and found you've actually got a performance problem? For example, here are two short programs - one creates a 10,000 line file and the other reads it:
CreateFile.cs:
using System;
using System.IO;
public class Test
{
static void Main()
{
Random rng = new Random();
using (TextWriter writer = File.CreateText("test.txt"))
{
for (int i = 0; i < 10000; i++)
{
writer.WriteLine("{0} {1} {2}", rng.NextDouble(),
rng.NextDouble(), rng.NextDouble());
}
}
}
}
ReadFile.cs:
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
public class Test
{
static void Main()
{
Stopwatch sw = Stopwatch.StartNew();
using (TextReader reader = File.OpenText("test.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
string[] bits = line.Split(' ');
foreach (string bit in bits)
{
double value;
if (!double.TryParse(bit, out value))
{
Console.WriteLine("Bad value");
}
}
}
}
sw.Stop();
Console.WriteLine("Total time: {0}ms",
sw.ElapsedMilliseconds);
}
}
On my netbook (which admittedly has an SSD in) it only takes 82ms to read the file. I would suggest that's probably not a problem :)
I would suggest reading all your lines at once with
string[] lines = System.IO.File.ReadAllLines(fileName);
This wold ensure that the I/O is done with the maximum efficiency. You woul have to measure (profile) but I would expect the conversions to take far less time.
your method is already good!
you can improve it by writing a readline function that returns an array of double and you reuse this function in other programs.
This solution is a little bit slower (see benchmarks at the end), but its nicer to read. It should also be more memory efficient because only the current character is buffered at the time (instead of the whole file or line).
Reading arrays is an additional feature in this reader which assumes that the size of the array always comes first as an int-value.
IParsable is another feature, that makes it easy to implement Parse methods for various types.
class StringSteamReader {
private StreamReader sr;
public StringSteamReader(StreamReader sr) {
this.sr = sr;
this.Separator = ' ';
}
private StringBuilder sb = new StringBuilder();
public string ReadWord() {
eol = false;
sb.Clear();
char c;
while (!sr.EndOfStream) {
c = (char)sr.Read();
if (c == Separator) break;
if (IsNewLine(c)) {
eol = true;
char nextch = (char)sr.Peek();
while (IsNewLine(nextch)) {
sr.Read(); // consume all newlines
nextch = (char)sr.Peek();
}
break;
}
sb.Append(c);
}
return sb.ToString();
}
private bool IsNewLine(char c) {
return c == '\r' || c == '\n';
}
public int ReadInt() {
return int.Parse(ReadWord());
}
public double ReadDouble() {
return double.Parse(ReadWord());
}
public bool EOF {
get { return sr.EndOfStream; }
}
public char Separator { get; set; }
bool eol;
public bool EOL {
get { return eol || sr.EndOfStream; }
}
public T ReadObject<T>() where T : IParsable, new() {
var obj = new T();
obj.Parse(this);
return obj;
}
public int[] ReadIntArray() {
int size = ReadInt();
var a = new int[size];
for (int i = 0; i < size; i++) {
a[i] = ReadInt();
}
return a;
}
public double[] ReadDoubleArray() {
int size = ReadInt();
var a = new double[size];
for (int i = 0; i < size; i++) {
a[i] = ReadDouble();
}
return a;
}
public T[] ReadObjectArray<T>() where T : IParsable, new() {
int size = ReadInt();
var a = new T[size];
for (int i = 0; i < size; i++) {
a[i] = ReadObject<T>();
}
return a;
}
internal void NextLine() {
eol = false;
}
}
interface IParsable {
void Parse(StringSteamReader r);
}
It can be used like this:
public void Parse(StringSteamReader r) {
double x = r.ReadDouble();
int y = r.ReadInt();
string z = r.ReadWord();
double[] arr = r.ReadDoubleArray();
MyParsableObject o = r.ReadObject<MyParsableObject>();
MyParsableObject [] oarr = r.ReadObjectArray<MyParsableObject>();
}
I did some benchmarking, comparing StringStreamReader with some other approaches, already proposed (StreamReader.ReadLine and File.ReadAllLines). Here are the methods I used for benchmarking:
private static void Test_StringStreamReader(string filename) {
var sw = new Stopwatch();
sw.Start();
using (var sr = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) {
var r = new StringSteamReader(sr);
r.Separator = ' ';
while (!r.EOF) {
var dbls = new List<double>();
while (!r.EOF) {
dbls.Add(r.ReadDouble());
}
}
}
sw.Stop();
Console.WriteLine("elapsed: {0}", sw.Elapsed);
}
private static void Test_ReadLine(string filename) {
var sw = new Stopwatch();
sw.Start();
using (var sr = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) {
var dbls = new List<double>();
while (!sr.EndOfStream) {
string line = sr.ReadLine();
string[] bits = line.Split(' ');
foreach(string bit in bits) {
dbls.Add(double.Parse(bit));
}
}
}
sw.Stop();
Console.WriteLine("elapsed: {0}", sw.Elapsed);
}
private static void Test_ReadAllLines(string filename) {
var sw = new Stopwatch();
sw.Start();
string[] lines = System.IO.File.ReadAllLines(filename);
var dbls = new List<double>();
foreach(var line in lines) {
string[] bits = line.Split(' ');
foreach (string bit in bits) {
dbls.Add(double.Parse(bit));
}
}
sw.Stop();
Console.WriteLine("Test_ReadAllLines: {0}", sw.Elapsed);
}
I used a file with 1.000.000 lines of double values (3 values each line). File is located on a SSD disk and each test was repeated multiple times in release-mode. These are the results (on average):
Test_StringStreamReader: 00:00:01.1980975
Test_ReadLine: 00:00:00.9117553
Test_ReadAllLines: 00:00:01.1362452
So, as mentioned StringStreamReader is a bit slower than the other approaches. For 10.000 lines, the performance is around (120ms / 95ms / 100ms).