Sort a 2D array and String array together - c#

So I have a particular issue.
I have a standard 2D array (non-jagged) and a string array. They are linked lets just say via the 1st column of the 2D array.
So that means I would like to sort them together (its not a key based system so I can't use the columns as keys into the string array, just when ever a row in the 2D array moves so too shall the value in equivalent row of the string array move).
Not sure what is the best solution here. The dirty method that I have tried and works is standard nested loops to sort via the first column and move everything accordingly.
I just want to know if there is a better solution than this, perhaps using Linq and things like that??

TLDR:
private (int[,], string[]) SortByColumnNames(int[,] array, string[] columnNames)
{
var columnNameToIndex = columnNames.Select((c, index) => Tuple.Create(c, index));
var sortedColumns = columnNameToIndex.OrderBy(ci => ci.Item1, StringComparer.OrdinalIgnoreCase).ToList();
var sortedArray = new int[2, 3];
for (var newColumnIndex = 0; newColumnIndex < sortedColumns.Count; newColumnIndex++)
{
var oldColumnIndex = sortedColumns[newColumnIndex].Item2;
for (var rowIndex = 0; rowIndex < array.GetLength(1); rowIndex++)
{
sortedArray[newColumnIndex, rowIndex] = array[oldColumnIndex, rowIndex];
}
}
var resultColumnNames = sortedColumns.Select(ci => ci.Item1).ToArray();
return (sortedArray, resultColumnNames);
}
Idea:
Create list of structures: column name + column index
Order this array by column name
New list will have ordered column names and old column indexes
Create new structure and fill it with the following logic: row should be the same, however column should be new.
In the other words:
We remember column indexes
Sort columns (and don't forget indexes)
Create map: from old index to new
Create new dictionary where sortedArray[newColumnIndex, rowIndex] = array[oldColumnIndex, rowIndex]

Related

Count the frequency of each elements in a DataGridView in .Net Windows Application Form

I have some datasets that I read from csv files into DataGridViews and I need to plot some charts where I count the frequency of each different variable on each column.
I found a code that count the frequency of array elements and then print them, I tried using the same logic on my datagrid.
I started with a column ("SEX" column) that has only 2 values (2.0 or 1.0), but when I try to view the count result I always get 1 (which means it didn't count a thing). It's supposed to show the count of the last different value found in the column (in my case 2.0 which has 140ish occurence).
Edit: I tried appending the count result to the textbox and I see that I end up with 1 for each loop, while I am supposed to get only 2 values (which are the count 2.0and then the count of 1.0)
I also need to plot the output, so I am guessing I could use a dictionary where I store the variable name + the frequency.
public void countFreq() //function to count the frequency of each var in a column -not working yet-
{
var n = dataGridView1.RowCount;
var visited = new bool[n];
// Traverse through array elements and
// count frequencies
for (var i = 0; i < n; i++)
{
// Skip this element if already processed
if (visited[i] || dataGridView1.Rows[i].Cells["SEX"].Value
== null)
continue;
// Count frequency
var count = 1;
for (var j = i + 1; j < n; j++)
if (dataGridView1.Rows[i].Cells["SEX"].Value == dataGridView1.Rows[j].Cells["SEX"].Value)
{
visited[j] = true;
count++;
}
textFile.Text += count.ToString(); //for testing purposes I used a textfield to print the last count value
}
}
I know that for a column where the values are explicit I can just loop on my datagrid rows and use the count method (which I did) but for most my data I don't explicitly know the values in each row so I needed to find a way to do that.
I am not sure if this is what you are looking for. In addition, the code below loops through the rows in the grid, however, if the grid has a data source I suggest looping though that collection instead of the grid rows.
Below is a method that takes a column index and returns a Dictionary<string, int>. A simple loop through each cell in the given column and if the cell's Value is not in the dictionary, we will add it. If the cells value is already in the dictionary will simply increment its int Value. After the loop finishes, the dictionary is returned. Something like…
private Dictionary<string, int> GetCountOfValues(string columnName) {
string curKey = "";
Dictionary<string, int> valuesAndCounts = new Dictionary<string, int>();
foreach (DataGridViewRow row in dataGridView1.Rows) {
if (!row.IsNewRow) {
if (row.Cells[columnName].Value != null) {
curKey = row.Cells[columnName].Value.ToString();
if (valuesAndCounts.ContainsKey(curKey)) {
valuesAndCounts[curKey]++;
}
else {
valuesAndCounts.Add(curKey, 1);
}
}
}
}
return valuesAndCounts;
}
Usage may look something like…
Dictionary<string, int> column0Counts = GetCountOfValues("Col0");
Dictionary<string, int> column1Counts = GetCountOfValues("Date");
You should really load your CSV data into a datatable and then query that
var dt = SomeFunctionThatReadsCsvIntoDatatable(..);
yourDataGridView.DataSource = dt;
Your query is then answered, simply, by grouping the datatable using linq;
(yourDataGridView.DataSource as DataTable).Rows
.Cast<DataRow>()
.GroupBy(r => r["someColumn"])
.Select(g => new { K = g.Key, C = g.Count() });
"someColumn" would be "SEX"
K would end up as an object holding whatever Type the data is ` - it's hard to tell from the information posted whether you've just got your csv as strings or whether they're eg doubles, dates etc
If you want to do it for all columns, it would probably be easiest to do it in a loop on the datatable Columns collection. DataColumn.ColumnName provides the "someColumn"

I have a list of index to mark which position to insert to DataTable, how to iterate while insert without messing the position?

I have a List<Tuple<String, Index>> that I need to insert to DataTable
A,5
B,8
meaning I will insert A to position 5 in DataTable, B to position 8, etc.
how do you insert to DataTable in proper position because when you do something like
foreach list in lists
{
//do something
dt.Rows.InsertAt(dr, position); //insert to DataTable
}
it will mess the index because every iteration, the index will not be valid anymore. I tried foreach list in lists.OrderByDescending but in some cases, it will also break.
Here's one case where OrderByDescending will break position.
A
B
C
F
G
I have a list with (D, 3) and (E, 4). Here's what happened if I OrderByDescending / Insert data in reverse order.
First Iteration will insert the E
A
B
C
F
E //WRONG PLACE
G
Second Iteration will insert the D
A
B
C
D
F
E //WRONG PLACE
G
You should first ensure that the list is sorted by the index ascending, for example:
lists.Sort((x1, x2) => x1.Item2.CompareTo(x2.Item2)); // omit this if the list is already sorted
Now, before you can move them to the correct postion, add the rows to the end of the DataTable with Add, for example with List.ForEach:
lists.ForEach(x => dt.Rows.Add(x.Item1)); // added to the end
Now they are at the end of the table in the correct order but at the wrong position.
int newRowsBeginAt = dt.Rows.Count - lists.Count; // where are they, at the end
Now you can iterate this list (in ascending index order) and move them to their position:
for (int i = 0; i < lists.Count; i++)
{
int currentIndex = newRowsBeginAt + i;
int newIndex = lists[i].Item2;
if (currentIndex != newIndex)
{
DataRow row = dt.Rows[currentIndex];
object[] data = row.ItemArray; // need to clone it otherwise RemoveAt will delete data
dt.Rows.RemoveAt(currentIndex); // remove it from the table
row.ItemArray = data; // needed to restore data
dt.Rows.InsertAt(row, newIndex);// insert to correct position
}
}
That works because you don't change the indexes of earlier moved rows later if you start with the lower-indexes and then move to the higher indexes.
Tested with your sample data:
DataTable dt = new DataTable();
dt.Columns.Add("Col");
dt.Rows.Add("A");
dt.Rows.Add("B");
dt.Rows.Add("C");
dt.Rows.Add("F");
dt.Rows.Add("G");
var lists = new List<Tuple<string, int>>()
{
Tuple.Create("D", 3), Tuple.Create("E", 4)
};
If you reverse the list so that you add the ones at the end first, you should not get any issues using InsertAt.
So it appears that for the list coming in, the position values of each tuple is the "absolute" position, so-to-speak, of where that entry needs to go. If that is the case, sort the incoming tuple list by ascending position value, and then insert them in order.
The values already in the list that you are adding to appear not to matter as much, it's the incoming values that are important. With the sorted list to add, this should ensure everything ends up where they need to be.

Insert datagridview column values into an Array c#

In my form I have this DataGridView (grid1), filled from a sqllite database.
I want select the column with the name "cap" and insert the column's values into an array.
How can I do?
I don't find a way to select the column cap by name, so I decided to indicate it with the index, but I don't like this way..
I don't know ho to insert these values into an array. there are a lot of columns/cell method but I don't figured out which one can help me!
I tryed to do this way (from an old answer here in the site) but it gives me error of "out of bounded matrix"
int[] wareCap = new int[grid1.Rows.Count];
...
//How I filled my dgv, if this can help
var context = new ForliCesenaEntities2();
BindingSource bi1 = new BindingSource();
bi1.DataSource = context.warehouses.ToList();
grid1.DataSource = bi1;
grid1.Refresh();
//How I try to insert the values into int[] wareCap
for (int i = 0; i<(grid1.Rows.Count-1); i++)
wareCap[i] = Convert.ToInt16(grid1.Rows[i].Cells[1].Value);
Thanks in advice
First you gridview has to be filled with values, then:
List<int> intValues = new List<int>();
foreach (DataGridViewRow row in grid.Rows)
{
intValues.Add(int.Parse(row.Cells["cap"].Value.ToString()));
}
int[] array = intValues.ToArray();
should do the trick.
Alternatively, you could use LINQ.
int[] intArray = dataGridView1.Rows
.Cast<DataGridViewRow>()
.Select(row => int.Parse(row.Cells["cap"].Value.ToString())).ToArray();
UPDATE:
The above solution might crash if the DataGridView's AllowUserToAddRows property is set to true. Because in that case your last row is the row for entering a new record, the Value is null, so invoking the ToString method on it causes a NullReferenceException.
I see two possibilities:
set your datagridview's AllowUserToAddRows property to false (but the user won't be able to add new rows),
use the following solution
Check if the row is the row for entering a new record:
int[] intArray = dataGridView1.Rows
.Cast<DataGridViewRow>()
.Where(row => !row.IsNewRow)
.Select(row => Convert.ToInt32(row.Cells["cap"].Value.ToString())).ToArray();
You already got all your values in a List, because you do sth. like this:
bi1.DataSource = context.warehouses.ToList();
You can easily store this in a reference:
var values = context.warehouses.ToList();
bi1.DataSource = values;
Your Grid is build based on Objekts in this List. So this should build your array:
int[] array = new int[values.Count];
int counter = 0;
foreach (var object in values)
{
array[counter++] = object.CAP;
}
int[] wareCap = new int[grid1.Rows.Count];
foreach(DataGridViewRow row in grid1.Rows)
{
wareCap.Add(Convert.ToInt32(cell["cap"].Value.ToString()));
}

Changing an FSharpList in C#

FSharpList<FSharpList<int>> newImageList;
FSharpList<int> row;
for(int i = 0; i < CurrentImage.Header.Height)
{
row = PPMImageLibrary.GrayscaleImage(CurrentImage.ImageListData);
newImageList.Head = row;
}
Above I'm trying to take a int list list and set each index to row which is a int list. Obviously I can't do it with .Head, and if I could it would only change the first index. I'm wondering how I could possibly make this work, I'm having a hard time getting any index of newImageList in the first place.
FSharpList is an immutable list. Therefore you cannot assign its Head and Tail properties something. However, you can try adding your FSharp list into a generic C# List or any collection that inherits an IEnumerable. For example from your code:
List<int> newImageList = new List<int>();
for(int i = 0; i < CurrentImage.Header.Height)
{
newImageList.AddRange(PPMImageLibrary.GrayscaleImage(CurrentImage.ImageListData)); // I am assuming your GrayscaleImage method might return multiple records.
}
I hope this helps.

Linq: select where in List<string>

Struggeling with some LinqToExcel filtering here...
Ive got a List<string> columnsToFilter that contains 9 strings, and with that i want to filter out the data for certain columns in a List<Row>, where Row contains the properties
IEnumerable<string> ColumnNames
Cell this[string columnName]
So: List<Row> has say 30 rows, each having 12 ColumnNames. Now i want to filter that List<Row> using List<string> columnsToFilter so that i end up with a List<Row> of 30 rows and 9 ColumnNames.
I can select the data for one column by quering the columnname:
var result = content.Select(m => m["Column1"]).ToList();
Now i want to filter the data based on a List of strings List<string> columnsToFilter. Whats the best way to achieve that?
Is this what you are looking for?
var colnames = new List<string>();
var rows = new Dictionary<string, object>();
var result = rows.Where(kv => colnames.Contains(kv.Key)).Select(kv => kv.Value);
Define an object called MyObject which has the property names corresponding to the 9 columns that you want to select.
var excel = new ExcelQueryFactory("excelFileName");
var myObjects = from c in excel.Worksheet<MyObject>()
select c;
Bingo. You can now iterate through the 30 objects with the 9 columns as properties.
Remember that LinqToExcel will happily fill objects even if they don't have all the columns represented.
You could even have a property or method as part of MyObject called "row" that would be a Dictionary() object so you could say myObject.row["ColumnName"] to reference a value if you preferred that syntax to just saying myObject.ColumnName to get the value. Personally I would rather deal with actual properties than to use the dictionary convolution.
I ended up doing this in two steps:
foreach (var column in columnNumbers)
{
yield return data.Select(m => m[column].Value.ToString()).ToList();
}
Now I have the data I need, but with the rows and columns swapped, so i had to swap rows for columns and vice versa:
for (int i = 1; i < rowCount; i++)
{
var newRow = new List<string>();
foreach (var cell in list)
{
newRow.Add(cell[i]);
}
yield return newRow;
}

Categories

Resources