sorting List<string[]> by many columns - c#

I have List which I would like to sort by many columns. For example, string[] has 5 elements (5 columns) and List has 10 elements (10 rows). For example I would like to start sorting by 1st column, then by 3rd and then by 4th.
How could it be done in the easiest way with C#?
I thought about such algorithm:
Delete values corresponding to those columns that I don't want to use for sorting
Find for each of columns that are left, the longest string that can be used to store their value
Change each row to string, where each cell occupies as many characters as there is maximum number of characters for the value for the given column
Assign int with index for each of those string values
Sort these string values
Sort the real data, with help of already sorted indices
But I think this algorithm is very bad. Could you suggest me any better way, if possible, that uses already existing features of C# and .NET?

List<string[]> list = .....
var newList = list.OrderBy(x => x[1]).ThenBy(x => x[3]).ThenBy(x => x[4]).ToList();

Something like this:
var rows = new List<string[]>();
var sortColumnIndex = 2;
rows.Sort((a, b) => return a[sortColumnIndex].CompareTo(b[sortColumnIndex]));
This will perform an in-place sort -- that is, it will sort the contents of the list.
Sorting on multiple columns is possible, but requires more logic in your comparer delegate.
If you're happy to create another collection, you can use the Linq approach given in another answer.
EDIT here's the multi-column, in-place sorting example:
var rows = new List<string[]>();
var sortColumnIndices = new[] { 1, 3, 4 };
rows.Sort((a, b) => {
for (var index in sortColumnIndices)
{
var result = a[index].CompareTo(b[index]);
if (result != 0)
return result;
}
return 0;
});

Related

Combine values from two columns in a data table to a single list

I am trying to build a list of integers from a data table by combining the data in two of the columns in the data table and getting a distinct list of values.
To get the data table.. i am calling a stored procedure (which i don't have the ability to edit)
After I call my method to return the data table that's returned by the stored procedure..
I want to look at two of the columns in the data table and get a list of values that are in these columns.
The data table returned by the stored procedure looks like this:
I want to get a list of the values in columns MailerKey and BillToKey.
What is the best way to do this? Can I use Linq to do this?
So far I've tried doing:
using(DataTable dt = getCustomers())
{
List<int> MailerKeys = dt.AsEnumerable().Select(x => x.Field<int>("MailerKey")).Distinct().ToList();
List<int> BillToKeys = dt.AsEnumerable().Select(x => x.Field<int>("BillToKey")).Distinct().ToList();
}
But how can I now combine the values?
By combine I mean, to get a list that contains the distinct values from both columns.. so eg:
I should get back:
275
58
250
50
59
99
55
You could:
create a HashSet<int> and add both lists to it;
add one list to the other and use Distinct from System.Linq.
For example:
var a = new[] { 1, 2 }; // Could be any IEnumerale<int>
var b = new[] { 2, 3 };
var hash = new HashSet<int>(a);
hash.UnionWith(b);
Console.WriteLine(string.Join(",", hash)); // Prints 1,2,3
You can use Linq to achieve the "combine" step.
First concatenate your lists using Concat, then keep only unique values using Distinct.
using(DataTable dt = getCustomers())
{
var mailerKeys = dt.AsEnumerable().Select(x => x.Field<int>("MailerKey")).Distinct().ToArray();
var billToKeys = dt.AsEnumerable().Select(x => x.Field<int>("BillToKey")).Distinct().ToArray();
var combinedDistinctKeys = mailerKeys.Concat(billToKeys).Distinct();
}
Notes:
'combinedDistinctKeys' is not yet enumerated, this would occur using ToArray or ToList for example;
I preferred to use ToArray for the source lists, since I know I will not need List<> features.
For more information about the Concat method from Linq, see: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.concat?view=net-5.0

Best way to merge in list<object> where object contains list of values

I have an IList<Row> where Row contains a list of Cells IList<Cell>. These cells have a ToString, ToDouble etc.
I want to loop through this list of rows and check if there are rows with the same value for cell[index]. Let's say for cell 3.
If there are rows with the same value, I should merge these rows into one row. It is certain that all cells are - in case of the same key - the same except for the cell with an amount, let's say that this is cell 4. So this should be merged (so 1 deleted) with the only difference that the value is the sum of both.
I have tried to create a Dictionary<string, double>. I looped through all rows, check whether map contains key, if not -> merge (also did this with an extension method Merge, but the same idea).
After this loopthrough, I created a new list, placed the dictionary in there and looped through the old list for the other information.
Well I think that my way is way too long, that there should be a way to do this much easier, maybe by LINQ or whatsoever. Any idea's on how to do this properly? Or do you guys think that my approach isn't that bad?
Try:
var mergedRows = rows.GroupBy(x => x.Cells[0].Value.ToString())
.Select(x => new Row() { Cells = new List<Cell>
{
new Cell() { Value = x.Key },
new Cell() { Value = x.Sum(y => int.Parse(y.Cells[1].Value.ToString())) }
}
});

Splitting an ordered list [duplicate]

This question already has answers here:
Group by in LINQ
(11 answers)
Closed 4 years ago.
I am trying to create a sorted list of lists. Where the outer "layer" is sorted by the field BU. and the inner layer is sorted by JobNum. I have this method, and variables.
public List<string> ListSorter<T>(IEnumerable<T> records)where T :
IMapToCSVSource, new()
List<IEnumerable<T>> organizedRecords = new List<IEnumerable<T>>();
List<T> shortenedRecord = new List<T>();
Here I am trying to create a more concise object with only 3 specific fields instead of the possible 8 that comes with the records object. BU and JobNum are set within this method.
foreach (var record in records)
{
string businessUnit = "research"; //This would vary each loop, not constant as displayed
string JobNum = "test" //this would vary each loop, not constant as displayed
shortenedRecord.Add(new T()
{
Cost = record.Cost,
BU = businessUnit,
JobNum = jobNum
});
}
shortenedRecord.OrderBy(o => o.BU).ThenBy(n=>n.JobNum);
here, to my knowledge, now has all the records shortened and ordered by BU, then JobNum.
Now I want to Split this ordered list into sections of BU specific records and add it to organizedRecords. Such that each element of organizedRecords is a specific BU. How would I do this?
For example, say shortenedRecord is a list of 30 elements, but there is only a total of 5 unique BU values. I would like to order and SPLIT the list into their 5 respective BU values, and add it to organizedRecords.
such that:
organizedRecords[0] should be a list of 'corporate' records
organizedRecords[1] should be a list of 'research' records
where corporate and research are BU values.
I tried to explain as best I could. Thank you for any suggestions.
Thanks for suggestions, I toyed around a bit, and I found this to work as I needed it.
var query = shortenedRecord.GroupBy(p => p.BU).ToList();
foreach (var group in query)
{
var ordered = group.OrderBy(x => x.JobNum);
organizedRecords.Add(ordered);
}

Populate ArrayList 2 columns and keep a count in second column of each occurrence c#

I'm new to ASP.NET C#. Trying to create an ArrayList with 2 columns one for the value (string) and one for counting how many of each. While adding values I need to search the ArrayList to find if the value already exist, if so add 1, if not, add it to the array and set count column to 1. Can someone provide a bit of code sample? If there is a better approach then I'd like to hear it.
private static Dictionary<string, int> values = new Dictionary<string, int>();
private static void Add(string newValue)
{
if(values.ContainsKey(newValue))
{
values[newValue]++; // Increment count of existing item
}
else
{
values.Add(newValue, 1); // Add new item with count 1
}
}
If you're just starting with a list of strings, there are plenty of simpler ways to do this.
I'd probably use the GroupBy extension here
List<string> items = GetItems(); // from somewhere
var groups = items.GroupBy(i => i);
var countedItems = groups.Select(g => new
{ Value = g.First(), HowMany = g.Count() });
Then putting into an ArrayList, if you want:
var arrayList = new ArrayList();
foreach (var thing in countedItems)
{
arrayList.Add(thing.Value + " " thing.HowMany);
}
But I'd probably prefer to put this into a Dictionary, because you know that each word will map to just one value - the number of times it appears.
var result = countedItems.ToDictionary(i => i.Value, i => i.HowMany);

split SortedList to multiple lists or arrays [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to split an array into a group of n elements each?
I believe I oversimplified this question so I am editing it a bit. From within a .NET 3.5 console application I have a SortedList string,string that will contain an unknown number of key/value pairs. I will get this collection by reading in rows from a table within a Microsoft Word document. The user will then be able to add additional items into this collection. Once the user has finished adding to the collection I then need to write the collection back to a new Microsoft Word document. The difficulty is that the items must be written back to the document in alphabetical order to a multicolumn table, first down the left side of the table and then down the right side of the table and since the output will likely be spread across multiple pages I need to also keep the order across multiple pages. So the first table on the first page may contain A through C on the left side of the table and C through F on the right side of the table then if the table exceeds the page a new table is needed. The new table may contain F through I and the right side L through O.Since the table will likely span multiple pages and I know the maximum number of rows per table per page I can do the math to determine how many tables I will need overall. This image is representative of the output:
For the sake of brevity if a output table can contain a maximum of 7 rows per page and 2 items per row and I have 28 items then I will need to write the output to 2 tables but of course I won't really know how many tables I will need until I read in the data so I can't simply hardcode the number of output tables.
What is the best way to take my SortedList and split it out into n collections in order to create the table structure described?
It is not necessary to split the list (if the only purpose is to write items in a table).
You can just iterate through the list and write row breaks in appropriate places.
for (int i = 0; i < sortedList.Count; i++)
{
if (i % 3 == 0)
{
Console.Write("|"); // write beginning of the row
}
Console.Write(sortedList[i].ToString().PadRight(10)); // write cell
Console.Write("|"); // write cell divider
if (i % 3 == 2)
{
Console.WriteLine() // write end of the row
}
}
// optional: write empty cells if sortedList.Count % 3 != 0
// optional: write end of the row if sortedList.Count % 3 != 2
You should extend your question by specifying what is the output of your script. If you want to write a table to the console, the above solution is probably the best. However, if you are using rich user interface (such as WinForms or ASP.NET), you should use built-in tools and controls to display data in table.
I played with LINQ a little bit and came up with this solution. It creates some kind of tree structure based on the "input parameters" (rowsPerPage and columnsPerPage). The columns on the last page could not have the same size (the code can be easily fixed if it is a problem).
SortedList<string, string> sortedList ... // input sortedList
int rowsPerPage = 7;
int columnsPerPage = 2;
var result = from col in
(from i in sortedList.Select((item, index) => new { Item = item, Index = index })
group i by (i.Index / rowsPerPage) into g
select new { ColumnNumber = g.Key, Items = g })
group col by (col.ColumnNumber / columnsPerPage) into page
select new { PageNumber = page.Key, Columns = page };
foreach (var page in result)
{
Console.WriteLine("Page no. {0}", page.PageNumber);
foreach (var col in page.Columns)
{
Console.WriteLine("\tColumn no. {0}", col.ColumnNumber);
foreach (var item in col.Items)
{
Console.WriteLine("\t\tItem key: {0}, value: {1}", item.Item.Key, item.Item.Value);
}
}
}

Categories