|
I was simply referring to the fact that a DateTime is a value type, whereas String is a reference type. Allocation of value types tends to be "cheaper" than reference types.
Jon's workaround is good, but could be improved in .NET Core 2.1, or even in .NET Framework 4.7.2+ with a reference to the System.Memory package and a couple of helpers defined.
#if NETSTANDARD2_0
namespace System.Buffers
{
public delegate void SpanAction<T, in TArg>(Span<T> span, TArg arg);
}
#endif
static class StringHelpers
{
public static string Create<TArg>(int length, TArg state, SpanAction<char, TArg> action)
{
if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
if (action is null) throw new ArgumentNullException(nameof(action));
if (length == 0) return string.Empty;
#if NETSTANDARD2_0
unsafe
{
var str = new string('\0', length);
fixed (char* chars = str)
{
var span = new Span<char>(chars, length);
action(span, state);
}
return str;
}
#else
return string.Create(length, state, action);
#endif
}
}
public static string FormatDateTime(DateTime dt) => StringHelpers.Create(21, dt, static (buffer, date) =>
{
int value = date.Day;
buffer[0] = (char)('0' + (value / 10));
buffer[1] = (char)('0' + (value % 10));
buffer[2] = '.';
value = date.Month;
buffer[3] = (char)('0' + (value / 10));
buffer[4] = (char)('0' + (value % 10));
buffer[5] = '.';
value = date.Year;
buffer[6] = (char)('0' + ((value / 10) % 10));
buffer[7] = (char)('0' + (value % 10));
buffer[8] = ' ';
value = date.Hour;
buffer[9] = (char)('0' + (value / 10));
buffer[10] = (char)('0' + (value % 10));
buffer[11] = ':';
value = date.Minute;
buffer[12] = (char)('0' + (value / 10));
buffer[13] = (char)('0' + (value % 10));
buffer[14] = ':';
value = date.Second;
buffer[15] = (char)('0' + (value / 10));
buffer[16] = (char)('0' + (value % 10));
buffer[17] = ':';
value = date.Millisecond;
buffer[18] = (char)('0' + ((value / 100) % 10));
buffer[19] = (char)('0' + ((value / 10) % 10));
buffer[20] = (char)('0' + (value % 10));
}); Rather than allocating a new 21-character char array on every call, and then having the string constructor copy that array to its internal storage, this new version writes the values directly to the string 's internal storage. It does have to allocate a delegate for the SpanAction , but since it's a static lambda, that only happens once per AppDomain.
If you're using .NET Core 2.1 or later, or .NET 5, you can drop the helpers and just use string.Create directly.
Creating Strings with No Allocation Overhead Using String.Create - Steve Gordon - Code with Steve[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
modified 22-Sep-21 5:23am.
|
|
|
|
|
Excellent ! I hope you'll publish this in an article, or a Tip/Trick.
«The mind is not a vessel to be filled but a fire to be kindled» Plutarch
|
|
|
|
|
I think your code is missing the 'state' parameter.
«The mind is not a vessel to be filled but a fire to be kindled» Plutarch
|
|
|
|
|
No - dt is the state parameter, and is referenced as date in the lambda method.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Hi, in the scope of your class StringHelpers: return string.Create(length, state, action); 'state is referenced, but is never defined. In the code in Steve Gordon's article, 'state is defined.
public static string Create<TState> (int length, TState state, System.Buffers.SpanAction<char,TState> action); My eyesight is terrible; as the doofus detective says in police procedural movies: "what am i missing ?"
«The mind is not a vessel to be filled but a fire to be kindled» Plutarch
|
|
|
|
|
Whoops! The parameter arg should have been called state .
That should be fixed now.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Knowing you are not quite perfect makes me feel a little more secure, but, in no way diminishes my respect for your technical excellence
«The mind is not a vessel to be filled but a fire to be kindled» Plutarch
|
|
|
|
|
Alex Dunlop wrote: would it consume more memory due to the instantiation? Hi, I think to answer that clearly one would need to know the database, its internal format, etc., and the nature of your query that returns (I assume) some subset of rows. I don't have the expertise in this area to be more helpful.
But, yes, creating strings has a cost; here's an idea from uber-guru Jon Skeet you might try: [^]
«The mind is not a vessel to be filled but a fire to be kindled» Plutarch
|
|
|
|
|
This is my data on which a LINQ query executed. i have bit confusion about LINQ grouping used there. here is sample code.
List<Data> _data = new List<Data>
{
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "ZB",
BrokerName = "B Securities",
Period = "2012 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2013 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "1Q 2014A",
PeriodValue = "204.45"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2Q 2014A",
PeriodValue = "205.00"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2012 FYA",
PeriodValue = "101.33"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2013 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "1Q 2014A",
PeriodValue = "204.45"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2Q 2014A",
PeriodValue = "201.00"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2012 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2013 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "1Q 2014A",
PeriodValue = "204.45"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2Q 2014A",
PeriodValue = "201.00"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2012 FYA",
PeriodValue = "101.33"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2013 FYA",
PeriodValue = "222.30"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "1Q 2014A",
PeriodValue = "784.45"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2Q 2014A",
PeriodValue = "555.00"
},
};
var periods = _data.Select(y => y.Period).Distinct().OrderBy(y => y).ToArray();
var results =
_data
.GroupBy(
x => new { x.Section, x.Lineitem, x.BrokerCode, x.BrokerName },
x => new { x.Period, x.PeriodValue })
.Select(x => new
{
x.Key,
Lookup = x.ToLookup(y => y.Period, y => y.PeriodValue),
})
.Select(x => new
{
x.Key.Section,
x.Key.Lineitem,
x.Key.BrokerCode,
x.Key.BrokerName,
Map = periods.ToDictionary(y => y, y => String.Join("|", x.Lookup[y])),
})
.ToArray();
var dt = new DataTable();
dt.Columns.Add("Section");
dt.Columns.Add("Lineitem");
dt.Columns.Add("BrokerCode");
dt.Columns.Add("BrokerName");
foreach (var period in periods)
{
dt.Columns.Add(period);
}
foreach (var result in results)
{
string[] key_values = new string[]
{
result.Section,
result.Lineitem,
result.BrokerCode,
result.BrokerName,
};
string[] period_values = periods.Select(p => result.Map[p]).ToArray()
dt.Rows.Add(key_values.Concat(period_values).ToArray());
}
i have to group on few fields and those are Section, LineItem, BrokerCode and Period but a guy does the grouping like this way
var results =
_data
.GroupBy(
x => new { x.Section, x.Lineitem, x.BrokerCode, x.BrokerName },
x => new { x.Period, x.PeriodValue })
.Select(x => new
{
x.Key,
Lookup = x.ToLookup(y => y.Period, y => y.PeriodValue),
})
.Select(x => new
{
x.Key.Section,
x.Key.Lineitem,
x.Key.BrokerCode,
x.Key.BrokerName,
Map = periods.ToDictionary(y => y, y => String.Join("|", x.Lookup[y])),
})
.ToArray();
So my question is what kind of grouping is it.... see the below grouping code.
.GroupBy(
x => new { x.Section, x.Lineitem, x.BrokerCode, x.BrokerName },
x => new { x.Period, x.PeriodValue })
Is it two set of grouping ? one set is { x.Section, x.Lineitem, x.BrokerCode, x.BrokerName }
and another set is { x.Period, x.PeriodValue } ?
please guide me how the above grouping will be working ?
Thanks
|
|
|
|
|
|
Sir this kind of grouping still not clear.
GroupBy(
x => new { x.Section, x.Lineitem, x.BrokerCode, x.BrokerName },
x => new { x.Period, x.PeriodValue })
if it is one group then why there two x=> new{}
in first set they mention four fields name and in second set they mention two fields name.
still not clear the syntax how it is working. is it grouping on six fields ? if yes then developer could mention 6 fields name with in one
x=> new{x.Section, x.Lineitem, x.BrokerCode, x.BrokerName,x.Period, x.PeriodValue} ?
please help me to understand how these grouping is working. if possible please attach small sample code which explain how this grouping is working.
Thanks
|
|
|
|
|
Read the documentation again. The first parameter defines the object by which the list is grouped - the group's Key . The second parameter defines the type of objects within the group.
That GroupBy call will produce:
Group:
Key: { Section, Lineitem, BrokerCode, BrokerName }
Enumerable items:
{ Period, PeriodValue }
It is grouping on four fields, and projecting two fields for the items within each group.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Sir can you please provide me any good example which use the same kind of grouping. i search google but found not any similar LINQ grouping example code.
Thanks
|
|
|
|
|
Read the documentation!
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Stop trying to get other people to write your code for you, and start studying and experimenting.
«The mind is not a vessel to be filled but a fire to be kindled» Plutarch
|
|
|
|
|
What is the meaning of forum....it is place where people discuss their problem and other give suggestion & answer if possible.
when a person do not know the answer or have negative mind or sluggish then those people come with excuse like this one.... "Stop trying to get other people to write your code for you, and start studying and experimenting."
Cheers.
|
|
|
|
|
It is for discussion, but what you're doing is NOT discussion. You keep saying "write more code for me."
|
|
|
|
|
Sorry sir if my approach was wrong. cheers!
|
|
|
|
|
i am not forcing anyone with gun to write down code for me. so anyone can overlook any post....am i right ?
cheers.
|
|
|
|
|
I like to know how to convert List<t> to Pivot datatable.
this is my sample data populated in List.
List<Data> _data = new List<Data>
{
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "ZB",
BrokerName = "B Securities",
Period = "2012 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2013 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "1Q 2014A",
PeriodValue = "204.45"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2Q 2014A",
PeriodValue = "205.00"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2012 FYA",
PeriodValue = "101.33"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2013 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "1Q 2014A",
PeriodValue = "204.45"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Net Revenue",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2Q 2014A",
PeriodValue = "201.00"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2012 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2013 FYA",
PeriodValue = ""
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "1Q 2014A",
PeriodValue = "204.45"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "ZB",
BrokerName = "B. Riley Securities",
Period = "2Q 2014A",
PeriodValue = "201.00"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2012 FYA",
PeriodValue = "101.33"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2013 FYA",
PeriodValue = "222.30"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "1Q 2014A",
PeriodValue = "784.45"
},
new Data
{
Section = "Consensus Model",
Lineitem = "Cost of Goods Sold",
BrokerCode = "TU",
BrokerName = "Cantor Fitzgerald & Co",
Period = "2Q 2014A",
PeriodValue = "555.00"
},
};
Data grouping should be like Section, Lineitem, BrokerCode & Period
sample periods are like 2012 FYA or 2013 FYA...2Q 2014A
Desired output would be like below example
+-----------------+--------------------+-------------+-------------------+----------+----------+-----------+----------+
| Section | LineItem | Broker Code | Broker Name | 2012 FYA | 2013 FYA | 1Q 2014A | 2Q 2014A |
+-----------------+--------------------+-------------+-------------------+----------+----------+-----------+----------+
| Consensus Model | Net Revenue | ZB | B Securities | | | 204.45 | 205.00 |
| Consensus Model | Net Revenue | TU | Cantor Fitzgerald | 101.33 | | 204.45 | 201.00 |
| Consensus Model | Cost of Goods Sold | ZB | B Securities | | | 204.45 | 205.00 |
| Consensus Model | Cost of Goods Sold | TU | Cantor Fitzgerald | 101.33 | | 204.45 | 201.00 |
+-----------------+--------------------+-------------+-------------------+----------+----------+-----------+----------+
ToPivotTable() function sample code
public static DataTable ToPivotTable<T, TColumn, TRow, TData>(
this IEnumerable<T> source,
Func<T, TColumn> columnSelector,
Expression<Func<T, TRow>> rowSelector,
Func<IEnumerable<T>, TData> dataSelector)
{
DataTable table = new DataTable();
var rowName = ((MemberExpression)rowSelector.Body).Member.Name;
table.Columns.Add(new DataColumn(rowName));
var columns = source.Select(columnSelector).Distinct();
foreach (var column in columns)
table.Columns.Add(new DataColumn(column.ToString()));
var rows = source.GroupBy(rowSelector.Compile())
.Select(rowGroup => new
{
Key = rowGroup.Key,
Values = columns.GroupJoin(
rowGroup,
c => c,
r => columnSelector(r),
(c, columnGroup) => dataSelector(columnGroup))
});
foreach (var row in rows)
{
var dataRow = table.NewRow();
var items = row.Values.Cast<object>().ToList();
items.Insert(0, row.Key);
dataRow.ItemArray = items.ToArray();
table.Rows.Add(dataRow);
}
return table;
}
Please guide me how to customize the code in the function ToPivotTable()
As a result i can use that function to get desired output.
if ToPivotTable() function code customization not possible then also tell me how to group data using LINQ to display data as i mention in the post.
Thanks
|
|
|
|
|
 That method only supports grouping on a single column. Dynamic grouping on multiple columns is not trivial.
This works for me given you sample input data:
public static DataTable ToPivotTable<T, TPivot>(
this IEnumerable<T> source,
IReadOnlyList<Expression<Func<T, object>>> groupColumns,
Func<T, string> pivotColumn, Func<T, TPivot> pivotValue)
{
var result = new DataTable();
var groupColumnMembers = groupColumns.Select(c => ((MemberExpression)c.Body).Member).Cast<PropertyInfo>().ToList();
foreach (var groupColumn in groupColumnMembers)
{
result.Columns.Add(new DataColumn(groupColumn.Name, groupColumn.PropertyType));
}
var p = Expression.Parameter(typeof(T), "p");
var tupleType = Type.GetType($"System.Tuple`{groupColumns.Count}");
var groupColumnTypes = groupColumnMembers.Select(x => x.PropertyType).ToArray();
var tupleConstructor = tupleType.MakeGenericType(groupColumnTypes).GetConstructor(groupColumnTypes);
var args = groupColumnMembers.Select(c => Expression.Property(p, c));
var body = Expression.New(tupleConstructor, args);
var groupByExpression = Expression.Lambda<Func<T, System.Runtime.CompilerServices.ITuple>>(body, p);
foreach (var group in source.GroupBy(groupByExpression.Compile()))
{
foreach (T element in group)
{
string pivotColumnName = pivotColumn(element);
if (!result.Columns.Contains(pivotColumnName))
{
result.Columns.Add(new DataColumn(pivotColumnName, typeof(TPivot)));
}
}
var row = result.NewRow();
for (int index = 0; index < group.Key.Length; index++)
{
row[index] = group.Key[index];
}
foreach (T element in group)
{
string pivotColumnName = pivotColumn(element);
TPivot pivotColumnValue = pivotValue(element);
row[pivotColumnName] = pivotColumnValue;
}
result.Rows.Add(row);
}
return result;
}
DataTable pivotedData = _data.ToPivotTable(
new List<Expression<Func<Data, object>>>
{
d => d.Section,
d => d.Lineitem,
d => d.BrokerCode,
d => d.BrokerName
},
d => d.Period,
d => d.PeriodValue); Output:
Section | Lineitem | BrokerCode | BrokerName | 2012 FYA | 2013 FYA | 1Q 2014A | 2Q 2014A
----------------------------------------------------------------------------------------------------------------------
Consensus Model | Net Revenue | ZB | B Securities | | null | null | null
Consensus Model | Net Revenue | ZB | B. Riley Securities | null | | 204.45 | 205.00
Consensus Model | Net Revenue | TU | Cantor Fitzgerald & Co | 101.33 | | 204.45 | 201.00
Consensus Model | Cost of Goods Sold | ZB | B. Riley Securities | | | 204.45 | 201.00
Consensus Model | Cost of Goods Sold | TU | Cantor Fitzgerald & Co | 101.33 | 222.30 | 784.45 | 555.00
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Wow !
«The mind is not a vessel to be filled but a fire to be kindled» Plutarch
|
|
|
|
|
Sir thank you so much. i will test your code and let you know if i face any problem.
|
|
|
|
|
Sir i am using VS2013 and .net version 4.5.2.
so when i copy your code in your project then i am getting error for these below line which may work in .net upper version.
var tupleType = Type.GetType($"System.Tuple`{groupColumns.Count}");
var groupByExpression = Expression.Lambda<Func<T, System.Runtime.CompilerServices.ITuple>>(body, p);
foreach (var group in source.GroupBy(groupByExpression.Compile()))
{
}
sir can you please provide a code which may compile and work in .net version 4.5.2 using VS2013.
thank you.
|
|
|
|
|
ITuple was added in .NET 4.7.1; you will need to upgrade your project to at least that version to use it.
NB: .NET Framework 4.5.2 will reach end of support next April:
.NET Framework 4.5.2, 4.6, 4.6.1 will reach End of Support on April 26, 2022[^]
If you want a solution for an earlier version of .NET, then you'll need to write a specific method for your data - eg:
public static DataTable DataToPivotTable(this IEnumerable<Data> source)
{
var result = new DataTable();
result.Columns.Add(new DataColumn("Section", typeof(string)));
result.Columns.Add(new DataColumn("Lineitem", typeof(string)));
result.Columns.Add(new DataColumn("BrokerCode", typeof(string)));
result.Columns.Add(new DataColumn("BrokerName", typeof(string)));
var sourceList = source.ToList();
var periods = sourceList.Select(d => d.Period).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
foreach (string period in periods)
{
result.Columns.Add(new DataColumn(period, typeof(string)));
}
var groupedData = source.GroupBy(d => new
{
d.Section,
d.Lineitem,
d.BrokerCode,
d.BrokerName
});
foreach (var group in groupedData)
{
var row = result.NewRow();
row["Section"] = group.Key.Section;
row["Lineitem"] = group.Key.Lineitem;
row["BrokerCode"] = group.Key.BrokerCode;
row["BrokerName"] = group.Key.BrokerName;
foreach (var item in group)
{
row[item.Period] = item.PeriodValue;
}
result.Rows.Add(row);
}
return result;
}
DataTable pivotedData = _data.DataToPivotTable(); Output is identical to the previous method, but you won't be able to reuse this method for other input types or other sets of columns.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|