Given a datatable that does not have a total column or row. How can I use LINQ to add a Total Column and Total row to create the table below?
TextColumn, NumberColumn0, NumberColumn1 Total
A 1 4 5
B 2 55 47
C 2.3 DBNULL 2.3
D 4 3 7
Total 9.3 62 61.3
Thanks!
Um, for those who need code. I'm using this currently for total column:
public static void CreateTotalColumnByType(DataTable table, Type valueType)
{
// create expression
string expression = string.Empty;
// do not count the first column (used for string field)
for (int columnIndex = 1; columnIndex < table.Columns.Count; ++columnIndex)
{
if (columnIndex != table.Columns.Count - 1)
{
// add +
expression += String.Format("[{0}] + ", table.Columns[columnIndex].ColumnName);
}
else
{
// last column so don't add plus
expression += String.Format("[{0}]", table.Columns[columnIndex].ColumnName);
}
}
// add total column
DataColumn totalColumn = new DataColumn("Total", valueType, expression);
table.Columns.Add(totalColumn);
}
but I'd like to replace it with LINQ.
//adds Total column
var q1 = src.Select(x=>new { x.TextColumn
,x.NumberColumn0
,x.NumberColumn1
,Total=x.NumberColumn0+x.NumberColumn1});
//adds Totals row
var q2 = q1.Concat(new[]{new{ TextColumn="Total"
,NumberColumn0 = src.Sum(_=>_.NumberColumn0)
,NumberColumn1 = src.Sum(_=>_.NumberColumn1)
,Total=q1.Sum(_=>_.Total)}});
There are a number of ways of doing this, depending on how you are getting this back you wouldn't even need to use LINQ.
You could do the following if you are using standard classes for data transport.
public class MyDataClass
{
public string TextColumn {get; set;}
public int NumberColumn0 {get; set;}
public int NumberColumn1 {get; set;}
public int Total
{
get { return NumberColumn0 + NumberColumn1; }
}
}
This way you have no need for LINQ. If you REALLY want to use LINQ you could use
var results = from x in MyDataList
select new { TextColumn = x.TextColumn, NumberColumn0 = x.NumberColumn0,
NumberColumn1 = x.NumberColumn1,
Total = x.NumberColumn1 + x.NumberColumn0};
If you have a variable number of columns or something similar, you might not have as much luck using LINQ, we would need to see more to get a true idea of what you are doing.
You could try like this one.
For each col As DataGridViewColumn in grid.Columns
'' You could try adding if here if there are exception columns
gRID.Item(col.Index, 0).Value = gRID.Rows.Cast(Of DataGridViewRow)().AsEnumerable().Sum(Function(c) Convert.ToDecimal(c.Cells(col.Index).Value)).ToString()
Next
I just enhance p.cambell's solution a bit
Related
Hello I am a newbie programmer, I am trying to format arrays in a way where there is a line break at the end of a specific set of arrays. I currently have 4 separate arrays in which I want to arrange each item in the array to a specific pattern. I have accomplished this task but now I am stumped because they are all in one line. Let me give you an example: (I am doing this on a datagridview by the way)
(This is what I want to happen)
Binder Clips Small pc 12 1260
Selleys All Clear pc 12 2400
(This is what I am getting)
Binder Clips Small pc 12 1260 Selleys All Clear pc 12 2400
This is my code:
//I get these from a datagridview
var items = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[1].Value.ToString().Trim())
.ToArray();
var units = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[2].Value.ToString().Trim())
.ToArray();
var quantity = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[6].Value.ToString().Trim())
.ToArray();
var prices = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[8].Value.ToString().Trim())
.ToArray();
//this is what I use to sort out the pattern that I want the arrays to be in
string[] concat = new string[items.Length * 4];
int index = 0;
for (int i = 0; i < items.Length; i++)
{
concat[index++] = items[i];
concat[index++] = units[i];
concat[index++] = quantity[i];
concat[index++] = prices[i];
}
// and this is where I am stuck because I can just put \n, it would ruin the format even more
cartitems.Text = string.Join(" ", concat);
I also tried doing something like this:
int j = 0;
string str = "";
foreach (var item in concat)
{
str += item;
if (j <= concat.Length - 1)
{
if (j % 3 == 0)
str += " ";
else
str += "\n";
}
j++;
}
It kinda gets the job done but the line breaks are all over the place.
This is what my projects look like so you can get a better gist on where am I getting the data from the 4 arrays:
basically the product name, unit, quantity and line total
and lastly I am storing in on a label so I can see how it the formatting looks like:
that about sums up my problem, I really hope you can help a newbie like me out, I have a feeling the answer is quite simple and I am just un-experienced.
As a general rule, you should keep your data structures (how the data is stored, implemented here as an array of values) separate from the data representation (in this case, written to a list box).
C# is an object-oriented language, so we might as well take advantage of that, right?
Create a class for your items.
class Item
{
public string Name { get; set; }
public string Unit { get; set; }
public string Quantity { get; set; }
public string Price { get; set; }
override public string ToString() {
return $"{Name} {Unit} {Quantity} {Price}";
}
}
This is how you load your array.
Item[] concat = new Item[items.Length];
int index = 0;
for (int i = 0; i < items.Length; i++) {
concat[index++] = new Item {
Name = items[i],
Unit = units[i],
Quantity = quantity[i],
Price = prices[i]
};
}
and this is how you can add the list of items to a listbox.
foreach(Item item in concat) {
listBox.Items.Add(item);
}
I have a datagrid with many columns.
Below are two of those columns.
I need to add up the count values for P and F values of P/F column seperately and compare them.for P the sum is 3 and for F it is 7. I need to display the sum with greater value. Is there any way i can achieve dis.
P/F | Count
------------------------
P | 2
P | 1
F | 5
F | 2
Using Linq
var p_sum = from p_col in dataGridView1 //--> am getting error here(group by not found)
group p_col by p_col.Status into g
select g.Sum(p => p.weightagepercent) ;
You could use linq to do something like:
var p_sum =
from p_col in datagrid
group p_col by p_col.datagrid_p_f_column into g
select g.Sum(p => p.datagrid_value_column) };
Do the same for F, and then just show the bigger with a simple smaller, bigger, or max on both variables
Firs't learn linq. It'll make your life easier :) Here's the official 101 linq samples.
Let's assume I have an class called ExtraStringDataPoint, defined like this:
public class ExtraStringDataPoint : IDataPoint
{
public ExtraStringDataPoint(double x, double y , string s)
{
X = x;
Y = y;
Extra = s;
}
public double X { get; set; }
public double Y { get; set; }
public string Extra { get; set; }
}
Now I have a collection of those points:
List<ExtraStringDataPoint> MyList = new List<ExtraStringDataPoint>()
{
new ExtraStringDataPoint(10,10,"ten"),
new ExtraStringDataPoint(20, 20, "twenty"),
new ExtraStringDataPoint(30,30, "thirty")
};
You could use linq to sum all the point's value of Y, where the X value is bigger than 15 like this for example:
var bigger_than_15 = from point in MyList
where point.X > 15
select point;
var total_y = bigger_than_15.Sum(point => point.Y);
So, back to your case. Linq will group the item's according to your datagrid by the column that hold's the name (P or F) into a group. p_col is a temp variable, datagrid_p_f_column should be the column name (or property in an object, it's the same). Once the grouping is done, it will sum all the values. In your case for each p, it will sum p.datagrid_value_column which should be the name of the column holding your numeric value. In the end, p_sum will have the sum of the values of the row's where the name has p. Rinse and repeat for f. compare, and that's it.
int countF=0;
int countP=0;
foreach(DataRow row in DataTable.rows)
{
if(row.itemArray[0].tostring().equals("F")
{
countf++;
}
`if(row.itemArray[0].tostring().equals("P")`
{
countP++;
}
}
if(countF>countP)
{
Display
}
else
Display
//for dataGrid View
dataGridView1.Rows.Add("P","1");
dataGridView1.Rows.Add("F","2");
int countF = 0;
int countP = 0;
for (int i = 0; i < dataGridView1.Rows.Count-1; i++)
{
string a= dataGridView1[0, i].Value.ToString();
string b = dataGridView1[1, i].Value.ToString();
if (a == "F")
{
countF= Convert.ToInt32(b);
countF++;
}
if (a == "P")
{
countF = Convert.ToInt32(b);
countP++;
}
}
if (countF > countP)
{
//display
}
else
{
//display
}
I don't use Linq.
Below is a simple working code
double sumP = 0;
double sumF = 0;
for (int i = 6; i < dataGridView1.Rows.Count-1; ++i)
{
if (dataGridView1.Rows[i].Cells[6].Value.Equals("P"))
{
sumP += Convert.ToDouble(dataGridView1.Rows[i].Cells[9].Value);
}
else if (dataGridView1.Rows[i].Cells[6].Value.Equals("F"))
{
sumF += Convert.ToDouble(dataGridView1.Rows[i].Cells[9].Value);
}
}
If(sumF>sumP)
{
Label2.text="Fail";
}
else
{
label2.text="Pass";
}
I have a DataTable which has a column "amount" for each rows and I'd like to have the total sum of all the rows. And also, I'd like to get total number of rows in the DataTable. Could anyone teach me how to have it done with LINQ instead of ordinary way?
Number of rows:
DataTable dt; // ... populate DataTable
var count = dt.Rows.Count;
Sum of the "amount" column:
DataTable dt; // ... populate DataTable
var sum = dt.AsEnumerable().Sum(dr => dr.Field<int>("amount"));
Aggregate allows you to avoid enumerating the rows twice (you could get the row count from the rows collection but this is more to show how to extract multiple aggregates in 1 pass):
var sumAndCount = table.AsEnumerable().Aggregate(new { Sum = 0d, Count = 0},
(data, row) => new { Sum = data.Sum + row.Field<double>("amount"), Count = data.Count + 1});
double sum = sumAndCount.Sum;
int count = sumAndCount.Count;
decimal[] Amount = {2,3,5 };
var sum = Amount.Sum();
var count = Amount.Count();
Based on Roy Goode's Answer you could also create an Extension
public static int Sum(this DataTable table, string Column)
{
return table.AsEnumerable().Sum(dr => dr.Field<int>(Column));
}
Unfortunately you can't be more generic her because there is no where T : numeric
I need to be binding a collection to a GridView depending on the report type selected by the user.
Each report varies slightly but uses the same basic result set which has many columns. Before I bind I want to loop through the result set and copy to a simpler collection (3 string variables called 'column1', 'column2', 'column3').
Code:
namespace etc.etc.etc
{
public class ReportEntity
{
public string column1 { get; set; }
public string column2 { get; set; }
public string column3 { get; set; }
}
}
List<ReportEntity> a = new List<ReportEntity>();
ReportEntity[] b = new ReportEntity[results.Length];
for (int i = 0; i < results.Length; i++)
{
//a[i].column1 = results[i].class.desc;
//a[i].column2 = results[i].student.firstname;
//a[i].column3 = results[i].timescanned.ToString();
//b[i].column1 = results[i].class.desc;
//b[i].column2 = results[i].student.firstname;
//b[i].column3 = results[i].timescanned.ToString();
}
Uncommenting where I set values for a gives Index was out of range. Must be non-negative and less than the size of the collection..
Uncommenting where i set values for b gives Object reference not set to an instance of an object..
results definitely has many records. What could I be doing wrong?
You get IndexOutRangeException in the 1st case because you just created an instance of list but this list doesn't contain any elements.
You get NullReferenceException in the 2nd case because you just filled array with results.Length of nulls.
What you should do is to explicitly create instance of ReportEntity and put in the underlying data structure.
List<ReportEntity> a = new List<ReportEntity>();
ReportEntity[] b = new ReportEntity[results.Length];
for (int i = 0; i < results.Length; i++)
{
a.Add(new ReportEntity() {column1 = results[i].class.desc,
column2 = results[i].student.firstname,
column3 = results[i].student.firstname }
b[i] = new ReportEntity() {column1 = results[i].class.desc,
column2 = results[i].student.firstname,
column3 = results[i].student.firstname }
}
Or you can you LINQ with Select extenssion method to like it's mentioned in another answer.
To add value to a List use the Add method.
Alternatively, use the select from LINQ:
var a = results.Select(r => new ReportEntity {
column1 = r.class.desc,
column2 = r.student.firstname,
column3 = r.timescanned.ToString()
}).ToList();
I observe a very strange behavior, maybe could you help me to see what happen.
Here the class:
public sealed class Sudoku
{
private SudokuCell[] _grid = new SudokuCell[81];
// ctor {}
private IEnumerable<SudokuCell> Grid
{
get { return _grid; }
}
private SudokuRow[] _rows;
public IEnumerable<SudokuRow> Rows
{
get
{
if (_rows == null)
{
_rows = new SudokuRow[9];
for (int i = 0, length = 9; i < length; i++)
{
_rows[i] = new SudokuRow(from cell in Grid
where cell.Row == i
select cell);
// Always print 9 (GOOD)
Trace.WriteLine("First Loop " + i + " : " + _rows[i].Cells.Count());
}
}
for (int i = 0; i < 9; i++)
{
// Always print 0 ! Huh !?
Trace.WriteLine("Second Loop " + i + " : " + _rows[i].Cells.Count());
}
return _rows;
}
}
}
public abstract class SudokuPart
{
public SudokuPart(IEnumerable<SudokuCell> cells)
{
Cells = cells;
}
public int Index
{ get; protected set; }
public IEnumerable<SudokuCell> Cells
{ get; protected set; }
}
public sealed class SudokuRow : SudokuPart
{
public SudokuRow(IEnumerable<SudokuCell> cells)
: base(cells)
{
base.Index = cells.First().Row;
}
}
Could anyone tell me why in the second loop it trace 0 instead of 9 !? I changed nothing between both loops !!!
Thanks...
This is the problem:
_rows[i] = new SudokuRow(from cell in Grid
where cell.Row == i
select cell);
That's capturing the loop variable (i)... within the loop, it has a sensible value, which is why you're seeing 9 matches.
However, when you count the matching values in the second loop, that single captured variable will have the value 9. Now no cell.Row has a value of 9, so you're not getting any matches. For more information on this, see Eric Lippert's great blog post, "Closing over the loop variable considered harmful."
Three fixes:
Capture a copy of the loop variable:
int copy = i;
_rows[i] = new SudokuRow(from cell in Grid
where cell.Row == copy
select cell);
Each iteration of the loop will get a separate copy.
Materialize the query in the loop:
_rows[i] = new SudokuRow((from cell in Grid
where cell.Row == i
select cell).ToList());
Or even:
_rows[i] = new SudokuRow(Grid.Where(cell => cell.Row == i).ToList());
Don't use LINQ at all! Why not just have an array of arrays to represent the grid? That's a much more natural approach, IMO.
I think Jon Skeet answer is great, but I just wanted to add a bit to it with an example of Deferred LINQ Queries. Once I saw this in action, it helped me understand a bit more about some of the nuances of this kind of code problem you ran into.
Try this code.
var numbers = new List<int> {1, 2, 3, 4, 5};
//Lets create an IEnumerable<int> with the values in our numbers list greater then 3.
var bignumbers = numbers.Where(n => n > 3);
//You may assume our variable bignumbers now contains the numbers 4 and 5
//now lets add another number to our original list that fits the criteria of our LINQ Where statement
numbers.Add(6);
foreach (var big in bignumbers) {
Console.WriteLine(big.ToString());
}
Our output from our foreach loop is going to be 4,5,6! This is because our query doesn't run until the foreach causes the enumeration of the items in our bignumbers variable.
Just something else to consider when your building lists within loops and your querying those lists outside of the loops. Your often going to get something other than what your expecting.