I have a list of numbers on a textbox like so (the numbers used are just examples):
1 1 1
2 2 2
...
So I want to convert that into a 2d array. I know to use .ToArray() or Regex.Split() for 1d lists but am not sure how to use that for 2d. I've also tried to use those functions on a string[] array to make it 2d but there was an error.
Also, the array is supposed to be an int[,] so that the values in the array can be compared. Any help would be appreciated, thanks!
Here you go, if you don't understand any part please ask in the comments:
// assuming the numbers are in perfect 2D format in textBox (only 1 newline separates the lines, only 1 space separates numbers in each line and all lines have the same amount of numbers)
string textWithNumbers = textBox.Text;
// first put all lines into an string array
string[] allLines = textWithNumbers.Split(new string[]{Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries);
// calculate 2D array's dimension lengths, and initialize the 2Darray
int rowCount = allLines.Length;
int columnCount = ((allLines[0].Length + 1) / 2);
int[,] twoDArray = new int[rowCount, columnCount];
// we then iterate through the 2D array
for (int row = 0; row < rowCount; row++)
{
// parse each number from string format to integer format & assign it to the corresponding location in our 2D array
string[] line = allLines[row].Split(' ');
for (int column = 0; column < columnCount; column++)
{
twoDArray[row, column] = int.Parse(line[column]);
}
}
This will give you a nice jagged 2D array that doesn't depend on all the text boxes having the same length. If you need them to all be the same length, it's trivial to check.
string[] data = // text input from all the text boxes
var result = data.Select(x => x.Split(' ')
.Select(y => int.Parse(y)).ToArray())
.ToArray();
Result is not quite an int[,] but an int[int[]], which is practically the same thing.
Of course, you need to deal with input validation or error handling.
Let me first start with the most naive solution, this makes very few assumptions about the data entered by the user. For example, it does not assume that every row has the same number of entries etc. So this could be optimized for those special conditions that you might know hold true or can enforce before running this routine.
// This is the data from the textbox
// hardcoded here for demonstration
string data = "1 1 1" + Environment.NewLine
+ "2 2 2" + Environment.NewLine
+ "12 12 12";
// First we need to determine the size of array dimension
// How many rows and columns do we need
int columnCount;
int rowCount;
// We get the rows by splitting on the new lines
string[] rows = data.Split(new string[]{Environment.NewLine},
StringSplitOptions.RemoveEmptyEntries);
rowCount = rows.Length;
// We iterate through each row to find the max number of items
columnCount = 0;
foreach (string row in rows)
{
int length = row.Split(' ').Length;
if (length > columnCount) columnCount = length;
}
// Allocate our 2D array
int[,] myArray = new int[rowCount, columnCount];
// Populate the array with the data
for (int i = 0; i < rowCount; ++i)
{
// Get each row of data and split the string into the
// separate components
string[] rowData = rows[i].Split(' ');
for (int j = 0; j < rowData.length; ++j)
{
// Convert each component to an integer value and
// enter it into the 2D array
int value;
if (int.TryParse(rowData[j], out value))
{
myArray[i, j] = value;
}
}
}
Given the above where we are considering the possibility of each row not having the same number of elements, you might consider using a sparse array int[][] which coincidentally also yield better performance in .NET.
Related
I'm kind of new to C# and I'm trying to make a sudoku game. I'm using a for loop to loop through the column arrays and add a random number to the spots but I'm using Random and Next() so it allows the numbers to repeat. But because I can't have more than one of a number in each column (array) for it to function, how can I replace the repeated number with another number that isn't repeated? I don't know how to do this.
Also the columns are stored in other arrays that I've called rows just so that I can say Row1[0][2] to access position 3 in column 1 for example.
Here's the method & for loop I'm using to replace add the numbers to the arrays:
void populateColumns(int[][] arr) // goes through column arrays and replaces the numbers with random ones from Num()
{
int i;
int j = 0;
for (int l = 0; l <= 8; l++)
{
for (i = 0; i <= 8; i++)
{
arr[j][i] = Num();
int currentPos = arr[j][i];
Console.Write(currentPos);
}
j = l;
}
}
string[] n = Console.ReadLine().Split();
for (int i = 1; i <6; i++)
{
int[] a = long.Parse(n[i]);
}
If each number in a row separated with whitespace - you can use your Split more efficiently:
int[] array = Console.ReadLine().Split().Select(int.Parse).ToArray(); // Improved according to #Caius Jard comment
If need array of longs - replace int.Parse with long.Parse and declare variable as long[] array.
You need to add using System.Linq to get access to ToArray extension method.
EDIT.
Insprired by #Caius Jard, non-LINQ version:
// Read input line and split it by whitespace (default)
string[] values = Console.ReadLine().Split();
// Declare arrays for Int or Long values.
// Arrays sizes equals to size of array of input values
int[] arrayOfInts = new int[values.Length];
long[] arrayOfLongs = new long[values.Length];
// Iterate with for loop over amount of input values.
for (int i = 0; i < values.Length; i++)
{
// Convert trimmed input value to Int32
arrayOfInts[i] = int.Parse(values[i].Trim());
// Or to Int64
arrayOfLongs[i] = long.Parse(values[i].Trim());
}
int.Parse and long.Parse may be replaced with Convert.ToInt32 and Convert.ToInt64 if needed.
Please, don't use magic constants: i < 6. Here 6 doesn't necessary equal to n.Length.
You can put it as
string[] n = Console.ReadLine().Split();
List<int> list = new List<int>();
for (int i = 0; i < n.Length; ++i) {
if (int.TryParse(n[i], out int value))
list.Add(value);
else {
// Invalid item within user input, say "bla-bla-bla"
//TODO: either reject the input or ignore the item (here we ignore)
}
}
if (list.Count == a.Length) {
// We have exactly a.Length - 6 valid integer items
for (int i = 0; i < a.Length; ++i)
a[i] = list[i];
}
else {
//TODO: erroneous input: we have too few or too many items
}
I want a 2D array generated from a CSV file with unknown number of rows/columns. The column count is fixed based on the header data. I need to be able to process it as a grid going both across rows and down columns hence needing array.
At the moment, I can split the data into rows, then split each row into components. I then add each row to a list. This all seems to work fine.
What I cant do is convert a list of string arrays into a 2d array.
It currently is failing on the line string[,] newCSV = csvFile.ToArray(); with error Cannot implicitly convert type 'string[][]' to 'string[ * , * ]' so I'm obviously not declaring something properly - I've just no idea what!
List<string[]> csvFile = new List<string[]>();
void Start()
{
// TODO: file picker
TextAsset sourceData = Resources.Load<TextAsset>("CSVData");
if (sourceData != null)
{
// Each piece of data in a CSV has a newline at the end
// Split the base data into an array each time the newline char is found
string[] data = sourceData.text.Split(new char[] {'\n'} );
for (int i = 0; i < data.Length; i ++)
{
string[] row = data[i].Split(new char[] {','} );
Debug.Log(row[0] + " " + row[1]);
csvFile.Add(row);
}
string[,] newCSV = csvFile.ToArray();
} else {
Debug.Log("Can't open source file");
}
Since your data is in the form of a table, I highly suggest using a DataTable instead of a 2d array like you're currently using to model/hold the data from your csv.
There's a ton of pre baked functionality that comes with this data structure that will make working with your data much easier.
If you take this route, you could then also use this which will copy CSV data into a DataTable using the structure of your CSV data to create the DataTable.
It's very easy to configure and use.
Just a small tip, you should always try to use data structures that best fit your task, whenever possible. Think of the data structures and algorithms you use as tools used to build a house, while you could certainly use a screw driver to pound in a nail, it's much easier and more efficient to use a hammer.
You can use this function to get 2d array.
static public string[,] SplitCsvGrid(string csvText)
{
string[] lines = csvText.Split("\n"[0]);
// finds the max width of row
int width = 0;
for (int i = 0; i < lines.Length; i++)
{
string[] row = SplitCsvLine(lines[i]);
width = Mathf.Max(width, row.Length);
}
// creates new 2D string grid to output to
string[,] outputGrid = new string[width + 1, lines.Length + 1];
for (int y = 0; y < lines.Length; y++)
{
string[] row = SplitCsvLine(lines[y]);
for (int x = 0; x < row.Length; x++)
{
outputGrid[x, y] = row[x];
// This line was to replace "" with " in my output.
// Include or edit it as you wish.
outputGrid[x, y] = outputGrid[x, y].Replace("\"\"", "\"");
}
}
return outputGrid;
}
I'm trying to store a board made up of 1's and 0's in a two-dimensional array. I'm trying to return 3 sets of 3 values into the array but it says a value is expected in csvArray[][].
I've already created a string of 1's and 0's and split them into substrings separated by "\n"
int[][] loadBoardfromString(string Data)
{
string csvBoard = "0,1,0\n2,0,1\n0,0,1";
string[] csvArray = csvBoard.Split('\n');
return csvArray[][];
}
Here's what you need:
string csvBoard = "0,1,0\n2,0,1\n0,0,1";
int[][] csvArray =
csvBoard
.Split('\n') // { "0,1,0", "2,0,1", "0,0,1" }
.Select(x =>
x
.Split(',') // { "X", "Y", "Z" }
.Select(y => int.Parse(y)) // { X, Y, Z }
.ToArray())
.ToArray();
My guess this is some sort of homework so I will try use the most basic solution so the teacher doesn't know :).
string csvBoard = "0,1,0\n2,0,1\n0,0,1";
// This splits the csv text into rows and each is a string
string[] rows = csvBoard.Split('\n');
// Need to alocate a array of the same size as your csv table
int[,] table = new int[3, 3];
// It will go over each row
for (int i = 0; i < rows.Length; i++)
{
// This will split the row on , and you will get string of columns
string[] columns = rows[i].Split(',');
for (int j = 0; j < columns.Length; j++)
{
//all is left is to set the value to it's location since the column contains string need to parse the values to integers
table[i, j] = int.Parse(columns[j]);
}
}
// For jagged array and some linq
var tableJagged = csvBoard.Split('\n')
.Select(row => row.Split(',')
.Select(column => int.Parse(column))
.ToArray())
.ToArray();
Here is my suggestion on how you should improve this so you learn the concepts. Make a more applicable method that can spilt any random csv no matter the size and return a two dimensional array not a jagged array. Also try to handle the case when someone doesn't put a valid csv text as a parametar into your method.
I was having this discussion with my friend who had this question asked to him in the Interview. The Question goes like this. Write a Function which takes in a byte array(2 dimensional) as input along with an Integer n, The initial assumption is all the elements of M*N byte array is zero and the problem is to fill 'n' Byte array elements with value 1, For instance if M=5 and N=5 and the n value is 10 the Byte array should've 10/25 elements to be 1 and rest of the 15 values to be 0. The values filled should be random and one cell in byte array should be filled only once. I was fascinated to try solving this on my own. I've attached the code I've come up with so far.
public Boolean ByteArrayFiller(int a,int b, int n)
{
int count = n;
int iLocalCount = 0;
byte[,] bArray= new byte[a,b];
for (int i = 0; i <a; i++)
for (int j = 1; j <b; j++)
bArray[i, j] = 0;
Random randa= new Random();
int iRandA = randa.Next(a);
int iRandB = randa.Next(b);
while (iLocalCount < n)
{
if (bArray[iRandA, iRandB] == 0)
{
bArray[iRandA, iRandB] = 1;
iLocalCount++;
}
iRandA = randa.Next(a);
iRandB = randa.Next(b);
continue;
}
//do
//{
// //iRandA = randa.Next(a);
// //iRandB = randa.Next(b);
// bArray[iRandA,iRandB]=1;
// iLocalCount++;
//} while (iLocalCount<=count && bArray[iRandA,iRandB]==0);
return true;
}
The code i wrote is in C# but it's straight forward to understand. It's able to do the purpose of the question( I did some trials runs and results came out correctly) perfectly but I have used Random object in C#(Equivalent to Math.Rand in Java) to fill up the byte array and I keep thinking if Rand returns the same values for a and b. There is a good chance for this to go indefinitely. Is that the purpose of the question? or Does the solution that i came up for this question is good enough!
I am curious to see how experts here solve this problem? I am just looking for new ideas to expand my horizon. Any pointers would be greatly appreciated. Thanks for taking the time to read this post!
A while loop trying random locations until it finds a good one is generally a very bad approach. If n = M*N, then the last one will have a probability of 1/(M*N) of finding a match. If M*N are sufficiently large, this can be extremely inefficient.
If M*N is not too large, I would create a temporary array of M*N size, fill it with the numbers 0 through (M*N)-1, and then permutate it - i.e. you walk through it and swap the current value with that of a random other value.
Then you go to the first n elements in your array and set the appropriate cell. (row = value / columns, col = value % columns).
I would treat the array, logically, as a one-dimensional array. Fill the first n positions with the prescribed value, and then shuffle the array.
Given a byte array, and the number of rows and columns in the array, and assuming that the array is already filled with 0:
int NumElements = NumRows * NumCols;
for (int i = 0; i < NumElementsToFill; ++i)
{
int row = i / NumRows;
int col = i % NumCols;
array[row, col] = 1;
}
// Now shuffle the array
Random rnd = new Random();
for (int i = 0; i < NumElements; ++i)
{
int irow = i / NumRows;
int icol = i % NumCols;
int swapWith = rnd.Next(i+1);
int swapRow = swapWith / NumRows;
int swapCol = swapWith % NumCols;
byte temp = array[irow, icol];
array[irow, icol] = array[swapRow, swapCol];
array[swapRow, swapCol] = temp;
}
The key here is converting the one-dimensional index into row/col values. I used / and %. You could also use Math.DivRem. Or create Action methods that do the get and set for you.
Choose a number, which is larger than both N and M and is prime (or co-prime to both N and M). Let's call this number p.
Loop until you've set x numbers:
Generate a random number less than N*M. Call this number `l`.
Then the next place to put the number will be `p*l%(N*M)`, if that position hasn't been set.
A downside to this approach is that if the array is filling up, you'll have more collisions.
Bascially, you need to choose n unique random numbers from range [0, p) (where p = M * N), and map them to positions of 2-dimensional array.
Naive approaches are 1) generate non-unique numbers with retry 2) fill an array with numbers from 0 to p-1, shuffle it and take first n numbers (takes O(p) time, O(p) memory).
Another approach is to choose them with the following algorithm (O(n2) time, O(n) memory, code in Java):
public Set<Integer> chooseUniqueRandomNumbers(int n, int p) {
Set<Integer> choosen = new TreeSet<Integer>();
Random rnd = new Random();
for (int i = 0; i < n; i++) {
// Generate random number from range [0, p - i)
int c = rnd.nextInt(p - i);
// Adjust it as it was choosen from range [0, p) excluding already choosen numbers
Iterator<Integer> it = choosen.iterator();
while (it.hasNext() && it.next() <= c) c++;
choosen.add(c);
}
return choosen;
}
Mapping of generated numbers to positions of 2-dimensional array is trivial.