I have a raw csv data as mentioned below
James,Mary,Patricia,Anthony,Donald\n
145,10,100,39,101\n
21,212,313,28,1
In above mentioned string, columns are comma , separated, first line is column and after each \n a new row where the data is for each person. What I am trying to achieve here is it should be sorted as mentioned below.
Anthony,Donald,James,Mary,Patricia\n
39,101, 145,10,100\n
28,1,21,212,313
What I have tried so far is, splitting based on \n, further splitting based on comma , for each value, but in this case there will be no proper reference to sort value.
What Part I am Struggling with
string data = "James,Mary,Patricia,Anthony,Donald\n145,10,100,39,101\n21,212,313,28,1";
var rows = data.Split('\n');
var unorderedNames = rows[0].Split(',');
Split Array based on \n
Split Names based on , comma -
Now, If I implement sorting, I believe I will loose all references because names will be sorted but below in row 2 and 3, mentioned payments will not.
In my code mentioned above, The first line split the array into three based on \n. Then when I soft first line, I beleive I don't have reference of other values in the same array.
I appreciate if you can assist me to find some efficient method to convert this raw data into sored alphabetically with values in efficient way.
public class StackDemo
{
private string source = "James,Mary,Patricia,Anthony,Donald\n145,10,100,39,101\n21,212,313,28,1";
public string ProcessString()
{
var rows = source.Split('\n');
var row1Values = rows[0].Split(',');
var row2Values = rows[1].Split(',');
var row3Values = rows[2].Split(',');
List<Person> people = new List<Person>();
for (int index = 0; index < 5; index++)
{
people.Add(new Person()
{
Name = row1Values[index],
SomeValue = row2Values[index],
OtherValue = row3Values[index]
});
}
people.Sort((x, y) => x.Name.CompareTo(y.Name));
List<string> names = new List<string>();
List<string> someValues = new List<string>();
List<string> otherValues = new List<string>();
foreach (Person p in people)
{
names.Add(p.Name);
someValues.Add(p.SomeValue);
otherValues.Add(p.OtherValue);
}
string result = "";
result = BuildString(names, result);
result = BuildString(someValues, result);
result = BuildString(otherValues, result);
result = result.Remove(result.Length - 1, 1);
return result;
}
private static string BuildString(List<string> names, string result)
{
foreach (string s in names)
{
result += s + ",";
}
result = result.Remove(result.Length - 1, 1);
result += "\n";
return result;
}
}
public class Person
{
public string Name { get; set; }
public string SomeValue { get; set; }
public string OtherValue { get; set; }
}
This code is extremely basic, (rude) but it does what I think you want?)
Also it returns the string in the same format as it was received.
EDIT: Expanded on comment question!
Added some unit tests to help validate how I understood your question:
public class UnitTest1
{
[Fact]
public void TestWith5()
{
string input = "James,Mary,Patricia,Anthony,Donald\n145,10,100,39,101\n21,212,313,28,1";
string expected = "Anthony,Donald,James,Mary,Patricia\n39,101,145,10,100\n28,1,21,212,313";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith4()
{
string input = "James,Mary,Patricia,Anthony,\n145,10,100,39,\n21,212,313,28,";
string expected = ",Anthony,James,Mary,Patricia\n,39,145,10,100\n,28,21,212,313";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith3()
{
string input = "James,Mary,Patricia,,\n145,10,100,,\n21,212,313,,";
string expected = ",,James,Mary,Patricia\n,,145,10,100\n,,21,212,313";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith2()
{
string input = ",,James,Mary,\n,,145,10,\n,,21,212,";
string expected = ",,,James,Mary\n,,,145,10\n,,,21,212";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith1()
{
string input = "James,,,,\n145,,,,\n21,,,,";
string expected = "James,,,,\n145,,,,\n21,,,,";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
[Fact]
public void TestWith0()
{
string input = ",,,,\n,,,,\n,,,,";
string expected = ",,,,\n,,,,\n,,,,";
// arrange
StackDemo3 subject = new StackDemo3();
// act
string actualResult = subject.ProcessString(input);
// assert
Assert.Equal(expected, actualResult);
}
}
Here is the actual implementation:
public interface IStringPeopleParser
{
List<Person> ConvertToPeople(string input);
}
public interface IPeopleStringParser
{
string ConvertPeopleToString(List<Person> people);
}
public class PeopleStringParser : IPeopleStringParser
{
public string ConvertPeopleToString(List<Person> people)
{
List<string> names = new List<string>();
List<string> someValues = new List<string>();
List<string> otherValues = new List<string>();
foreach (Person p in people)
{
names.Add(p.Name);
someValues.Add(p.SomeValue);
otherValues.Add(p.OtherValue);
}
string output = "";
output += string.Join(",", names);
output += "\n";
output += string.Join(",", someValues);
output += "\n";
output += string.Join(",", otherValues);
return output;
}
}
public class StringPeopleParser : IStringPeopleParser
{
public List<Person> ConvertToPeople(string source)
{
var rows = source.Split('\n');
string[] row1Values = rows[0].Split(',');
string[] row2Values = rows[1].Split(',');
string[] row3Values = rows[2].Split(',');
List<Person> people = new List<Person>();
for (int index = 0; index < row1Values.Length; index++)
{
people.Add(new Person()
{
Name = row1Values[index],
SomeValue = row2Values[index],
OtherValue = row3Values[index]
});
}
return people;
}
}
public class StackDemo3
{
IStringPeopleParser stringPeopleParser = new StringPeopleParser();
IPeopleStringParser peopleStringParser = new PeopleStringParser();
public string ProcessString(string s) {
List<Person> people = stringPeopleParser.ConvertToPeople(s);
int validCount = people.Where(x => x.IsValid()).Count();
switch (validCount)
{
case 0:
case 1:
{
return peopleStringParser.ConvertPeopleToString(people);
}
case 2:
case 3:
case 4:
case 5:
{
people = people.OrderBy(x => x.Name).ToList();
return peopleStringParser.ConvertPeopleToString(people);
}
default:
{
return "";//outside bounds of reality. Should never happen.
}
}
}
}
public class Person
{
public string Name { get; set; }
public string SomeValue { get; set; }
public string OtherValue { get; set; }
public bool IsValid() {
if (string.IsNullOrWhiteSpace(Name) || string.IsNullOrWhiteSpace(SomeValue) || string.IsNullOrWhiteSpace(OtherValue))
{
return false;
}
return true;
}
}
Also I don't really know why you don't want the person class?
You need to have a reference between the 3 values possible in each row (the index value is the key) by creating the Person class, the class instance becomes said reference.
I think the problem is you want to sort the headers of your CSV into some "whatever" order, and have the data "go with it"
Come up with some way to represent your data as a 2D array:
var lines = File.ReadAllLines("path");
var data = lines.Skip(1).Select(line => line.Split(',')).ToArray(); //nasty way of parsing a CSV but it's accessory to this discussion..
var head = lines[0]
.Split(',')
.Select((s,i) => new { Name = s, Index = i })
.OrderBy(at => at.Name)
.ToArray();
head is now the sorted headers, but it has an additional property that tells you what column in data holds that person's data. Anthony is first in heaD, but their Index is 3 so we should get Anthony's data from data[3]
foreach(var person in head){
Console.WriteLine($"Now printing {person.name} data from column {person.Index}");
foreach(var line in data){
Console.Writeline(line[person.Index]);
}
}
We didn't bother sorting the data (it's more efficient not to), we just stored what column it's in as part of the object that does get sorted, and then regardless of person sort order, we access the data via that column. Sorting head is very fast, because it's just a few names. It always maintains its map of "where is the data" because Index doesn't change regardless of the sort order of head
is there a way to make a comma separated from an object. Note its object not List of object
Ex:
public class EmployeeLogReportListViewModel
{
public DateTime Date { get; set; }
public int EmployeeID { get; set; }
public TimeSpan Time { get; set; }
public int Sort { get; set; }
public string Employer { get; set; }
}
With the following values
Date = "2018/02/03"
EmployeeID = 111
Time = 11:53 AM
Sort = 1
Employer = EMP
this should result into
2018/02/03,111,11:53 AM,1 EMP
What is the best way to make this. possible single line of code cause i dont want to use string builder and append all of it.
I think you are looking for Overridden .ToString() method. you have to modify the class like this:
public class EmployeeLogReportListViewModel
{
public DateTime Date { get; set; }
public int EmployeeID { get; set; }
public TimeSpan Time { get; set; }
public int Sort { get; set; }
public string Employer { get; set; }
public override string ToString()
{
return String.Format("{0},{1},{2},{3},{4}", this.Date, this.EmployeeID, this.Time, this.Sort, this.Employer);
}
}
Usage Example:
EmployeeLogReportListViewModel objVm = new EmployeeLogReportListViewModel();
// Assign values for the properties
objVm.ToString(); // This will give you the expected output
Challenge accepted
var o = new EmployeeLogReportListViewModel();
var text = string.Join
(
",",
typeof(EmployeeLogReportListViewModel)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select
(
prop => prop.GetValue(o).ToString()
)
);
Console.WriteLine(text);
Technically that is one line.
If you want the properties sorted alphabetically, you could use this:
var o = new EmployeeLogReportListViewModel();
var text = string.Join
(
",",
typeof(EmployeeLogReportListViewModel)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.OrderBy( prop => prop.Name )
.Select
(
prop => prop.GetValue(o).ToString()
)
);
Console.WriteLine(text);
It's a bit late to reply, but I can imagine you want to do that in order to have some kind of csv output style
a nice and generic way to do it is to create a extension method that transform any Enumerable into a csv string.
so borrowing #John Wu's solution, we come up with something like
public static class EnumerableToCsvExtension
{
public static string ToCSVString<TContent>(this IEnumerable<TContent> enumerable, char propertySeparator = ',', bool includeHeader = true)
{
var properties = typeof(TContent).GetProperties(BindingFlags.Instance | BindingFlags.Public);
var header = string.Join(propertySeparator, properties.Select(p => p.Name));
var rows = enumerable.ToList().ConvertAll(item => string.Join(propertySeparator, properties.Select(p => p.GetValue(item) ?? string.Empty )));
var csvArray = includeHeader ? rows.Prepend(header) : rows;
return string.Join(Environment.NewLine, csvArray);
}
}
then you use it like
var list = new List<EmployeeLogReportListViewModel> { new EmployeeLogReportListViewModel(), new EmployeeLogReportListViewModel() };
list.ToCSVString();
list.ToCSVString(propertySeparator: '|', includeHeader: false);
You can use like below:
public class bKashAccountInfo
{
public string bKashNumber { get; set; }
public string bKashAccountType { get; set; }
public override string ToString()
{
return String.Format($"bKash Number-{bKashNumber}, Account Type - {bKashAccountType}");
}
}
My brain is not working and I am trying to make something harder than I think it really should be and I need another set of eyes. I have the following in a text file
|TS|170702/2300|170703/0503|42.80 -102.64 39.76 -102.64 39.44 -99.37 42.48 -99.37
|TS|170703/0505|170703/0905|40.22 -97.30 38.63 -97.30 38.19 -101.03 39.78 -101.03
what the above means...(|watchtype|watchstart|watchend| lat/long pairs)
The problem I'm having is that I need to take EACH ROW (could be 0 or could be 100+) and create a polygon on a map to mark the location of these storm watches. I currently have the following.
MODEL
public class WatchPolygons
{
public string WatchType { get; set; }
public string WatchStart { get; set; }
public string WatchEnd { get; set; }
public List<lat_longPairs> Lat_Long_Pairs {get; set;}
}
public class lat_longPairs
{
public decimal latitude { get; set; }
public decimal longitude { get; set; }
}
CONTROLLER
public JsonResult GetWatchPath()
{
var watchFilePaths = ConfigurationManager.AppSettings["watchFilePath"];
return Json(Directory.GetFiles(Server.MapPath(watchFilePaths), "current*.txt"), JsonRequestBehavior.AllowGet);
}
[HttpGet]
public ActionResult GetWatchData(string watchPath)
{
var stringData = new List<string>();
using (var reader = new StreamReader(watchPath))
{
while (!reader.EndOfStream)
{
var data = reader.ReadLine().Trim();
if (!string.IsNullOrEmpty(data))
stringData.Add(data);
}
}
return Json((from item in stringData
select item.Split(new char [] { '|' }, StringSplitOptions.RemoveEmptyEntries)
into rawData
select new WatchPolygons
{
WatchType = rawData[0],
WatchStart = rawData[1],
WatchEnd = rawData[2]
}).ToList(), JsonRequestBehavior.AllowGet);
}
I know I am missing the latlong pairs = rawData. I don't have it in the code because in the model it's a list and I can't easily convert the list to string the way I need it.
What am I missing? I believe I need to read over each line then read over each group to get the lat/long pairs. Just not sure.
You just need to parse rawData[3], containing the string with space separated lat/lon pairs. This is a naive implementation that will break when the input string does not contain pairs of numbers or when the current locale doesn't use a dot as a decimal separator:
private static List<lat_longPairs> ParseLatLon(string input)
{
var numbers = input.Split(new [] { " " }, StringSplitOptions.RemoveEmptyEntries)
.Select(i => decimal.Parse(i))
.ToArray();
var latLonPairs = new List<lat_longPairs>();
for (int i = 0; i < numbers.Length; i += 2)
{
latLonPairs.Add(new lat_longPairs
{
latitude = numbers[i],
longitude = numbers[i + 1],
});
}
return latLonPairs;
}
Then call it from where you're projecting the polygons:
select new WatchPolygons
{
WatchType = rawData[0],
WatchStart = rawData[1],
WatchEnd = rawData[2],
Lat_Long_Pairs = ParseLatLon(rawData[3])
}
You may want to move the parsing code away from the controller as well, into its own class.
You can use GroupBy since two set make a coordinate.
var str = "|TS|170702/2300|170703/0503|42.80 -102.64 39.76 -102.64 39.44 -99.37 42.48 -99.37";
int itemsInGroup = 2;
var pairs = str.Split('|')[4].Split(' ').
// Give each set of coordinate a group number.
Select((n, i) => new { GroupNumber = i / itemsInGroup, Number = n }).
GroupBy(n => n.GroupNumber).
Select(g =>
{
var coordinate = g.Select(n => n.Number).ToList();
return new lat_longPairs
{
latitude = decimal.Parse(coordinate[0], NumberFormatInfo.InvariantInfo),
longitude = decimal.Parse(coordinate[1], NumberFormatInfo.InvariantInfo),
};
});
i have two list one is string list and other one is list of one class.that class contain some properties like soid,itemname,qty etc and its value also..string list and class list have some common property .so i wanted to check the string list with class list and add common values to one dictionary.
public class Input
{
public string soid { get; set; }
public string itemname { get; set; }
public int qty { get; set; }
}
list<Input> inputclass=new list<Input>();//inputclass list
List<string> metadata=new List<string>();//string list
metadata.Add("itemname");
metadata.Add("soid");
metadata.Add("qty");
so i wanted to compare the class member name with string list name
Not sure if I understood you 100% correctly, but given the following input:
var inputclass= new List<Input>() {
new Input(){ soid="123", itemname="Bar", qty=123 },
new Input(){ soid="777", itemname="Foo", qty=999 }
};
List<string> metadata=new List<string>() { "itemname", "soid", "qty" };
you can use the .ToDictionary() and .GetProperty() methods like this
Type t = typeof(Input);
var result = metadata.ToDictionary(i => i, i => inputclass.Select(c => t.GetProperty(i).GetValue(c)));
to create a dictionary that will look like
EDIT:
If metadata can contain values that are not properties of Input, the following would be save:
var result = metadata.Select(i => t.GetProperty(i))
.Where(i => i != null)
.ToDictionary(i => i.Name, i => inputclass.Select(c => i.GetValue(c)));
If I understood it right, you want to compare the type itself, not the instances inside that list.
You could do it this way:
List<string> metadata = new List<string>();//string list
metadata.Add("itemname");
metadata.Add("soid");
metadata.Add("qty");
metadata.Add("yada");
var result = from str in metadata
join prop in typeof(Input).GetProperties() on str equals prop.Name
select str;
foreach (string prop in result)
{
Console.WriteLine(prop);
}
Now, if you have a List of T unknown objects and want to match each with the metadata, tell me and I'll help you.
EDIT based on your comment: when we get the common name between list<input> and string how will get the value of corresponding member of the class.now you return only common names r8..?
You could do it like this. Suppose you have these two classes:
public class Input
{
public string soid { get; set; }
public string itemname { get; set; }
public int qty { get; set; }
}
public class Yada : Input
{
public string yada { get; set; }
}
So Input has 3 of the 4 properties, and Yada class has all 4.
Then suppose we have a list of objects:
List<Input> inputclass = new List<Input>();//inputclass list
inputclass.Add(new Input() { itemname = "test",soid="myId",qty=10 });
inputclass.Add(new Yada() { itemname = "test2",soid="myId2", yada = "woo",qty=20 });
You could get all the matching properties from the objects, including their current values, with this code:
var result = inputclass.Select(
input => (from str in metadata
join prop in input.GetType().GetProperties()
on str equals prop.Name
select new { Obj = input, Prop = str, Value = prop.GetValue(input, null) }))
.SelectMany(i => i)
.GroupBy(obj => obj.Obj);
foreach (var obj in result)
{
Console.WriteLine(obj.Key);
foreach (var prop in obj)
{
Console.WriteLine(prop.Prop + ":" + prop.Value);
}
Console.WriteLine();
}
Console.ReadKey();
Here is my input:
Just on a note, be careful when using GetValue: you'll have to do slight changes for it to work with Indexers, for example.
Using this ReflectionExt.GetAttr you can do something like this:
var dict = new Dictionary<string, List<string>>();
foreach(var listItem in inputclass)
{
foreach(var metaItem in metadata)
{
try
{
var value = ReflectionExt.GetAttr(listItem, metaItem);
if (dict[metaItem] == null)
dict[metaItem] = new List<string>();
if (!dict[metaItem].Contains(value)) // Add only if not exists
dict[metaItem].Add(value);
}
catch
{
;
}
}
}