List sorting by multiple parameters - c#

I have a .csv with the following headers and an example line from the file.
AgentID,Profile,Avatar,In_Time,Out_Time,In_Location,Out_Location,Target_Speed(m/s),Distance_Traveled(m),Congested_Duration(s),Total_Duration(s),LOS_A_Duration(s),LOS_B_Duration(s),LOS_C_Duration(s),LOS_D_Duration(s),LOS_E_Duration(s),LOS_F_Duration(s)
2177,DefaultProfile,DarkGreen_LowPoly,08:00:00,08:00:53,East12SubwayportalActor,EWConcourseportalActor,1.39653,60.2243,5.4,52.8,26.4,23,3.4,0,0,0
I need to sort this .csv by the 4th column (In_time) by increasing time ( 08:00:00, 08:00:01) and the 6th (In_Location) by alphabetical direction (e.g. East, North, etc).
So far my code looks like this:
List<string> list = new List<string>();
using (StreamReader reader = new StreamReader("JourneyTimes.csv"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
line.Split(',');
list.Add(line);
}
I read in the .csv and split it using a comma (there are no other commas so this is not a concern). I then add each line to a list. My issue is how do I sort the list on two parameters and by the headers of the .csv.
I have been looking all day at this, I am relatively new to programming, this is my first program so I apologize for my lack of knowledge.

You can use LINQ OrderBy/ThenBy:
e.g.
listOfObjects.OrderBy (c => c.LastName).ThenBy (c => c.FirstName)
But first off, you should map your CSV line to some object.
To map CSV line to object you can predefine some type or create it dynamically
from line in File.ReadLines(fileName).Skip(1) //header
let columns = line.Split(',') //really basic CSV parsing, consider removing empty entries and supporting quotes
select new
{
AgentID = columns[0],
Profile = int.Parse(columns[1]),
Avatar = float.Parse(columns[2])
//other properties
}
And be aware that like many other LINQ methods, these two use deferred execution

You are dealing with two distinct problems.
First, ordering two columns in C# can be achieved with OrderBy, ThenBy
public class SpreadsheetExample
{
public DateTime InTime { get; set; }
public string InLocation { get; set; }
public SpreadsheetExample(DateTime inTime, string inLocation)
{
InTime = inTime;
InLocation = inLocation;
}
public static List<SpreadsheetExample> LoadMockData()
{
int maxMock = 10;
Random random = new Random();
var result = new List<SpreadsheetExample>();
for (int mockCount = 0; mockCount < maxMock; mockCount++)
{
var genNumber = random.Next(1, maxMock);
var genDate = DateTime.Now.AddDays(genNumber);
result.Add(new SpreadsheetExample(genDate, "Location" + mockCount));
}
return result;
}
}
internal class Class1
{
private static void Main()
{
var mockData = SpreadsheetExample.LoadMockData();
var orderedResult = mockData.OrderBy(m => m.InTime).ThenBy(m => m.InLocation);//Order, ThenBy can be used to perform ordering of two columns
foreach (var item in orderedResult)
{
Console.WriteLine("{0} : {1}", item.InTime, item.InLocation);
}
}
}
Now you can tackle the second issue of moving data into a class from Excel. VSTO is what you are looking for. There are lots of examples online. Follow the example I posted above. Replace your custom class in place of SpreadSheetExample.

You may use a DataTable:
var lines = File.ReadAllLines("test.csv");
DataTable dt = new DataTable();
var columNames = lines[0].Split(new char[] { ',' });
for (int i = 0; i < columNames.Length; i++)
{
dt.Columns.Add(columNames[i]);
}
for (int i = 1; i < lines.Length; i++)
{
dt.Rows.Add(lines[i].Split(new char[] { ',' }));
}
var rows = dt.Rows.Cast<DataRow>();
var result = rows.OrderBy(i => i["In_time"])
.ThenBy(i => i["In_Location"]);
// sum
var sum = rows.Sum(i => Int32.Parse(i["AgentID"].ToString()));

Related

What will be the linq query for below code?

class Program
{
public static bool Like(string toSearch,string toFind)
{
if (toSearch.Contains(toFind))
return true;
else
return false;
}
static void Main(string[] args)
{
List<string> str = new List<string>();
List<string> strNew = new List<string>();
str.Add("abdecacd");
str.Add("facdgh");
str.Add("iabcacdjk");
str.Add("lmn");
str.Add("opqe");
str.Add("acbd");
str.Add("efgh");
string strToSearch= "acd,abc,abcacd,al";
string[] desc = strToSearch.Split(',');
for(int i = 0; i < str.Count; i++)
{
for(int j = 0; j < desc.Length; j++)
{
if(Like(str[i].ToString(),desc[j].ToString()))
{
strNew.Add(str[i].ToString());
break;
}
}
}
if(strNew != null)
{
foreach(string strPrint in strNew)
{
Console.WriteLine(strPrint);
}
}
}
}
How to write a linq query for above code,in this strToSearch variable value will be dynamic,user will enter comma separated values,user may enter as many comma separated values as user wants,I want to write a linq query which will find all the values in List which will contain the value entered by user.
Reason I need linq query,because linq is used in my application. Kindly help me out on this.
The LINQ expression is:
List<string> strNew = str.Where(x => desc.Any(y => x.Contains(y))).ToList();
that can even be simplified (simplified for the .NET runtime, not for the programmer) to:
List<string> strNew = str.Where(x => desc.Any(x.Contains)).ToList();
by removing an intermediate lambda function.
In general there is no "speed" difference between what you wrote and what I wrote. Both expressions are O(m*n), with m = str.Length and n = desc.Length, so O(x^2). You aren't doing an exact search, so you can't use the usual trick of creating an HashSet<string> (or doing str.Intersect(desc).ToList() that internally does the same thing).

How to add existing string array value with this code?

Variable.cs
public string[] CcEmails { get; set; }
Mail.cs
EDTO.CcEmails = dr["rsh_ccmail"].ToString().Split(';');
here i got two strings eg. xxxx#gmail.com ; yyy#gmail.com
MailProcess.cs
dataRPT1=get data from sql
EDTO.CcEmails = new string[dataRPT1.Rows.Count];
for (int i = 0; i < dataRPT1.Rows.Count; i++)
{
EDTO.CcEmails[i] = dataRPT1.Rows[i]["email_addr"].ToString();
}
Here i got list of string eg.aaa#gmail.com ......
I am try to add with existing but it add only new values..Anyone could help me..
I tend to use union, although that will remove duplicate entries. But to keep all entries you can use Concat on the array.
var emailString = "me#test.com;you#test.com";
string[] emails = emailString.Split(';');
string[] emailsFromSQL = new string[3];
emailsFromSQL[0] = "everyone#test.com";
emailsFromSQL[1] = "everyone2#test.com";
emailsFromSQL[2] = "everyone2#test.com";
//No Duplicates
var combined = emails.Union(emailsFromSQL).ToArray();
//Duplicates
var allCombined = emails.Concat(emailsFromSQL).ToArray();
Thanks
I find the easiest way of doing this is to create a list, add items to the list, then use string.Join to create the new string.
var items = new List<string>();
for (int i = 0; i < dataRPT1.Rows.Count; i++)
{
items.Add(dataRPT1.Rows[i]["email_addr"].ToString());
}
EDTO.CcEmails = string.Join(";", items);
Update after changed question:
If the type of the CcEmails is an array, the last line could be:
EDTO.CcEmails = items.ToArray();

How To Read Data Into A List Structure in C#?

I am writing a program that uses a StudentStruct (structure) that consists of: studentID, studentName, and grades (which is a separate List). The program is supposed to read in a sequential text file and contains methods that allow the user to: Add Student, Add Grades (to a particular student), Change Student Name, and Delete Student. At the end of the program it is supposed to overwrite the previous file with the new changes made from the current program session.
My question is how do I read in data from the text file into the separate structure variables within the lists?
My text file looks like this:
00000,Mimzi Dagger,100,50,75,70,45,10,98,83
00001,Alexander Druaga,89,45,80,90,15,73,99,100,61
00002,Nicholas Zarcoffsky,100,50,80,50,75,100,100
00003,Kantmiss Evershot,50,100
Once the structure variables within the list are filled, how do I overwrite the file with the contents of the List structure in the same format as the above file?
Since I have multiple and varying amounts of grades, how would I achieve looping through and adding each grade to the grades list?
As you can maybe already tell, I am very new to c# and this is my first project. Any help would be greatly appreciated!
class Program
{
static string pathSource = #"C:\schoolfiles\StudentGrades.txt";
struct StudentStruct
{
public string studentID;
public string studentName;
public List<string> grades;
}
static void Main(string[] args)
{
StudentStruct student = new StudentStruct();
List<StudentStruct> myList = new List<StudentStruct>();
student.grades = new List<string>();
}
UPDATE: Here is what I have come up with so far:
for (int i = 0; i < fileLines.Length; i++)
{
output = fileLines[i];
string[] outputArray = output.Split(',');
student.grades = new List<string>();
student.studentID = outputArray[0];
student.studentName = outputArray[1];
for (int j = 2; j < outputArray.Length; j++)
{
student.grades.Add(outputArray[j]);
}
myList.Add(student);
UPDATE: The code above worked out wonderfully. Here is the top part of my code involving this question:
static void Main(string[] args)
{
//Declare variables
string output;
//Read the File into an array
string[] fileLines = File.ReadAllLines(PathSource);
StudentStruct student = new StudentStruct();
List<StudentStruct> myList = new List<StudentStruct>();
for (int i = 0; i < fileLines.Length; i++)
{
output = fileLines[i];
string[] outputArray = output.Split(',');
student.grades = new List<string>();
student.studentID = outputArray[0];
student.studentName = outputArray[1];
for (int j = 2; j < outputArray.Length; j++)
{
student.grades.Add(outputArray[j]);
}
myList.Add(student);
}
MainMenu(myList);
}
And then to add the list back to the file I did this:
static void ExitModule(List<StudentStruct> myList)
{
//Declare variables
string inputChoice = null;
string output = null;
System.IO.StreamWriter file = new System.IO.StreamWriter(PathSource);
Console.Clear();
Console.WriteLine("Are You Sure You Want To Exit The Program? Y/N");
inputChoice = Console.ReadLine();
if (inputChoice == "Y" || inputChoice == "y")
{
for (int i = 0; i < myList.Count; i++)
{
output = (myList[i].studentID + "," + myList[i].studentName);
for (int j = 0; j < myList[i].grades.Count; j++)
{
output += ("," + myList[i].grades[j]);
}
file.WriteLine(output);
}
file.Close();
Environment.Exit(0);
Since this is clearly homework, it may be a good time for you to learn about data structures. This will help you store the information you've read in from the text file efficiently, allowing you to write it out much more easily after modifying the records.
Here are a couple of other random pointers:
The System.IO.File.ReadLines(pathSource) method would be a good place to start reading each line of a text file
You should almost never be using a struct in C# (especially one that is mutable like in your example) unless you are well-versed in its semantics and purpose. Use a class instead
try this code. it's working for me
class Program
{
static string pathSource = #"C:\schoolfiles\StudentGrades.txt";
struct StudentStruct
{
public string studentID;
public string studentName;
public List<string> grades;
}
static void Main(string[] args)
{
StudentStruct student = new StudentStruct();
List<StudentStruct> myList = new List<StudentStruct>();
student.grades = new List<string>();
// get all lines from text file
string[] allLinesFromFile = File.ReadAllLines(pathSource);
// iterate through each line and process it
foreach(string line in allLinesFromFile)
{
// split each line on ','
string[] sections = line.Split(',');
student.studentID = sections[0];
student.studentName = sections[1];
// use this loop to add numbers to list of grades
for(int i =2; i < sections.Length; i++)
{
student.grades.Add(sections[i]);
}
// add this student to list
myList.Add(student);
// create new object of student
student = new StudentStruct();
student.grades = new List<string>();
}
}
A few things.
(1) I am not sure why your teacher/professor would have you use a struct here. A class is much more appropriate. I actually cringed when I first saw the code
(2) having something as a const also implies that it is static "pathSource"
(3) Properties should be capitalized "pathSource" -> "PathSource"
(4) You should separate responsibilities... Try not to put everything in Main
(5) You shouldn't have to create an instance of grades outside of StudentStruct, this can lead to errors. i.e. NullPointerException... basically the struct should be self containing.
(6) Everything should compile. I haven't tested it. If you have any issues. let me know.
Program {
private const string PathSource = #"C:\schoolfiles\StudentGrades.txt";
private struct StudentStruct
{
public string StudentID;
public string StudentName;
private List<string> _gradeList;
public List<string> GradeList
{
// if _gradeList is null create a new instance
get { return _gradeList ?? (_gradeList = new List<string>()); }
private set { _gradeList = value; }
}
}
private static void Main( string[] args )
{
var studentFile = File.ReadAllLines( PathSource );
// If you haven't learned about delegates or extensions methods...
// You can just change it to a foreach statement
var myList = studentFile.Select( GetStudent ).ToList();
// make sure we have everything
foreach (var studentStruct in myList)
{
Console.WriteLine( "{0} {1}", studentStruct.studentName, studentStruct.studentID );
foreach (var grade in studentStruct.GradeList) { Console.Write( grade + " " ); }
Console.WriteLine();
}
}
private static StudentStruct GetStudent( string line )
{
var student = new StudentStruct();
var studentDelimited = line.Split( ',' );
student.studentID = studentDelimited[ 0 ];
student.studentName = studentDelimited[ 1 ];
for ( int i = 2; i < studentDelimited.Length; i++ ) { student.GradeList.Add( studentDelimited[ i ] ); }
return student;
}
}

Get first value in CSV column without duplicates

I am getting a list of items from a csv file via a Web Api using this code:
private List<Item> items = new List<Item>();
public ItemRepository()
{
string filename = HttpRuntime.AppDomainAppPath + "App_Data\\items.csv";
var lines = File.ReadAllLines(filename).Skip(1).ToList();
for (int i = 0; i < lines.Count; i++)
{
var line = lines[i];
var columns = line.Split('$');
//get rid of newline characters in the middle of data lines
while (columns.Length < 9)
{
i += 1;
line = line.Replace("\n", " ") + lines[i];
columns = line.Split('$');
}
//Remove Starting and Trailing open quotes from fields
columns = columns.Select(c => { if (string.IsNullOrEmpty(c) == false) { return c.Substring(1, c.Length - 2); } return string.Empty; }).ToArray();
var temp = columns[5].Split('|', '>');
items.Add(new Item()
{
Id = int.Parse(columns[0]),
Name = temp[0],
Description = columns[2],
Photo = columns[7]
});
}
}
The Name attribute of the item list must come from column whose structure is as follows:
Groups>Subgroup>item
Therefore I use var temp = columns[5].Split('|', '>'); in my code to get the first element of the column before the ">", which in the above case is Groups. And this works fine.
However, I a getting many duplicates in the result. This is because other items in the column may be:
(These are some of the entries in my csv column 9)
Groups>Subgroup2>item2, Groups>Subgroup3>item4, Groups>Subgroup4>item9
All start with Groups, but I only want to get Groups once.
As it is I get a long list of Groups. How do I stop the duplicates?
I want that if an Item in the list is returned with the Name "Groups", that no other item with that name would be returned. How do I make this check and implement it?
If you are successfully getting the list of groups, take that list of groups and use LINQ:
var undupedList = dupedList
.Distinct();
Update: The reason distinct did not work is because your code is requesting not just Name, but also, Description, etc...If you only ask for Name, Distinct() will work.
Update 2: Try this:
//Check whether already exists
if((var match = items.Where(q=>q.Name == temp[0])).Count==0)
{
items.add(...);
}
How about using a List to store Item.Name?
Then check List.Contains() before calling items.Add()
Simple, only 3 lines of code, and it works.
IList<string> listNames = new List();
//
for (int i = 0; i < lines.Count; i++)
{
//
var temp = columns[5].Split('|', '>');
if (!listNames.Contains(temp[0]))
{
listNames.Add(temp[0]);
items.Add(new Item()
{
//
});
}
}

C# How do I parse a text file of data of unknown row size to a 2D array?

I can easily pass a data file to a 2D array if I know the size of the data file, but thats pretty pointless when you want to add and remove data.
How can I instantiate a 2D array of that Data rows length that is global and not out of scope to the rest of the program?
Here is the usual code, for an array of 4 rows and 6 columns, but I want to add/remove data to the data file making it of unknown length in rows.
string[,] allStudentData = new string[4, 6];
string[] allStudents = File.ReadAllLines("data.txt");
for (int i = 0; i < allStudents.Count(); i++)
{
for (int j = 0; j < 6; j++)
{
string[] DataIn = allStudents[i].Split(',');
allStudentData[i, j] = DataIn[j];
}
}
Thanks for your ideas :-)
Why not use something mutable, like a List ?
You can add columns using the .Add() command, , and you can collapse it into an array when you're done with it (.ToArray())
you can do as below
var result = File.ReadAllLines("data.txt").Select(x=>x.Split(',')).ToArray();
You can use a List that holds a custom object (it could be an array) instead of Array and then at the end convert the list to an array with the ToArray extension:
var list = new List<string[]>();
var data1 = new string[2] {"1", "2" };
var data2 = new string[3] {"1", "2", "3" };
list.Add(data1);
list.Add(data2);
list.ToArray(); // converts the list to string[][]
After you are done reading the file you could add easily more items to the list and then at the end write all modifications to the file. If you think, that in future you will need more than the two dimensional array, than it's worth it to create a custom object like:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
and use this instead of the string[]:
var studentData = new List<Student>();
The maintenance later will be much more easier (adding address, classes, and so on). I admit, you will have a little more work to do when reading and writing, but if the project grows it will pay off.
You can still use LINQ to read the data on one line:
// creates a List<Student>
var studentData = File.ReadAllLines("data.txt")
.Select(row => row.Split(','))
.Select(elements =>
new Student
{
Name = elements[0],
Grade = int.Parse(elements[1])
}).ToList();
If the number of columns is always 6 and only the number of rows is unknown. Just change the first 2 rows of your code to:
string[] allStudents = File.ReadAllLines("data.txt");
string[,] allStudentData = new string[allStudents.Count(), 6];
If the number of columns is unknown you can do this to get a 2D result:
var not2DResult = File.ReadAllLines("data.txt").Select(x => x.Split(',')).ToArray();
var maxRow = not2DResult.Count()-1;
int maxColumn = Enumerable.Select(not2DResult, c => c.Count()).Max()-1;
var result2D = new string[maxRow, maxColumn];
for (int rowNumber = 0; rowNumber < maxRow; rowNumber++)
{
var row = not2DResult[rowNumber];
for (int columnNumber = 0; columnNumber < row.Count(); columnNumber++)
{
result2D[rowNumber, columnNumber] = row[columnNumber];
}
}
Try using a Lists to hold the file information.
var myList = new List<string>();
myList = File.ReadLines("data.txt");
var my2DList = new List<List<string>>();
foreach(string line in myList)
my2DList.Add(line.Split(','));
If you want to know the number of lines, just use:
int numberOfLines = my2DList.Count;
For the number of items in an individual line:
int lengthOfLine3 = my2DList[3].Length;

Categories

Resources