Creating a dynamic sort for a data class - c#

I am attempting to create a dynamic sort for my basic data class (just a bunch of properties).
The following code demonstrates what I am trying to do. It almost works. The only thing I don't understand is why ThenBy seems to completely resort the list. Can anyone explain why?
private void SortList(string[] sortCols)
{
bool first = true;
IOrderedEnumerable<Data> returnVal = null;
Console.WriteLine("Sorting test data");
if (sortCols.Length < 1)
return;
foreach (string col in sortCols)
{
if (first)
{
returnVal = _testData.OrderBy(p => typeof(Data).GetProperty(col).GetValue(p, null));
//Or OrderByDescending
first = false;
}
else
{
returnVal = returnVal.ThenBy(p => typeof(Data).GetProperty(col).GetValue(p, null));
//Or ThenByDescending
}
}
_testData = new List<Data>(returnVal);
}
_testData is just a List
The Data class looks like this:
public class Data
{
public string Name { get; set; }
public int Age { get; set; }
public double Income { get; set; }
public bool Married { get; set; }
public override string ToString()
{
return Name + "; age=" + Age.ToString() + "; income=" + Income.ToString() + "; married=" + Married.ToString();
}
}

Unable to duplicate your problem. Everything seems to sort properly:
public static void Main()
{
var data1 = new Data() { Name = "Albert", Age = 99 };
var data2 = new Data() { Name = "Zebra", Age = 1};
var data3 = new Data() { Name = "Zebra", Age = 99};
var data4 = new Data() { Name = "Albert", Age = 1 };
_testData.Add(data1);
_testData.Add(data2);
_testData.Add(data3);
_testData.Add(data4);
SortList(new string[] { "Name", "Age" });
foreach(var data in _testData)
{
Console.WriteLine(data.ToString());
}
Console.WriteLine(string.Empty);
SortList(new string[] { "Age", "Name" });
foreach(var data in _testData)
{
Console.WriteLine(data.ToString());
}
}
Results:
Sorting test data
Albert; age=1; income=0; married=False
Albert; age=99; income=0; married=False
Zebra; age=1; income=0; married=False
Zebra; age=99; income=0; married=False
.
Sorting test data
Albert; age=1; income=0; married=False
Zebra; age=1; income=0; married=False
Albert; age=99; income=0; married=False
Zebra; age=99; income=0; married=False
Update (For .Net 4.0)
Changing your code slightly solves the issue (example):
private static void SortList(string[] sortCols)
{
Console.WriteLine("Sorting test data");
if (sortCols.Length < 1)
return;
IOrderedEnumerable<Data> returnVal = _testData.OrderBy(p => typeof(Data).GetProperty(sortCols[0]).GetValue(p, null));
foreach (string col in sortCols.Skip(1))
{
returnVal = returnVal.ThenBy(p => typeof(Data).GetProperty(col).GetValue(p, null));
//Or ThenByDescending
}
_testData = returnVal.ToList();
}

You're better off creating an IComparer that explicitly does a comparison for each property (so you could have a public property on it that specifies the property order). You could then just sort once.
Something like so
public class DataComparer : Comparer<Data>
{
private readonly IList<string> _sortedProperties;
public DataComparer(IEnumerable<string> sortedProperties)
{
_sortedProperties = new List<string>(sortedProperties);
}
public override int Compare(Data x, Data y)
{
int result = 0;
foreach (var property in _sortedProperties)
{
if (property == "Name")
{
result = String.Compare(x.Name, y.Name, StringComparison.Ordinal);
}
else if (property == "Age")
{
result = x.Age.CompareTo(y.Age);
}
// Do other comparisons here
if (result != 0)
return result;
}
return 0;
}
}
This implementation will create a DataComparer with the given collection of properties that act as the properties that need to be compared. Then the Compare() will loop through the properties in the desired sort order until it finds a non-matching comparison and return.
The end result will be a faster sort operation that outputs something like
// "Jim Frost" 13
// "Jim Frost" 24
// "Jim Smith" 11
Where we want to sort on the Name property first, then the Age etc.
Edit:
You can sort it like so,
_testData.Sort(new DataComparer(sortCols));

Related

Efficient way to Sort CSV raw string data

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

Custom File Parser

I am building a parser for a custom pipe delimited file format and I am finding my code to be very bulky, could someone suggest better methods of parsing this data?
The file's data is broken down by a line delimited by a pipe (|), each line starts with a record type, followed by an ID, followed by different number of columns after.
Ex:
CDI|11111|OTHERDATA|somemore|other
CEX001|123131|DATA|data
CCC|123131|DATA|data1|data2|data3|data4|data5|data6
. I am splitting by pipe, then grabbing the first two columns, and then using a switch checking the first line and calling a function that will parse the remaining into an object purpose built for that record type. I would really like a more elegant method.
public Dictionary<string, DataRecord> Parse()
{
var data = new Dictionary<string, DataRecord>();
var rawDataDict = new Dictionary<string, List<List<string>>>();
foreach (var line in File.ReadLines(_path))
{
var split = line.Split('|');
var Id = split[1];
if (!rawDataDict.ContainsKey(Id))
{
rawDataDict.Add(Id, new List<List<string>> {split.ToList()});
}
else
{
rawDataDict[Id].Add(split.ToList());
}
}
rawDataDict.ToList().ForEach(pair =>
{
var key = pair.Key.ToString();
var values = pair.Value;
foreach (var value in values)
{
var recordType = value[0];
switch (recordType)
{
case "CDI":
var cdiRecord = ParseCdi(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key, CdiRecords = new List<CdiRecord>() { cdiRecord }
});
}
else
{
data[key].CdiRecords.Add(cdiRecord);
}
break;
case "CEX015":
var cexRecord = ParseCex(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CexRecords = new List<Cex015Record>() { cexRecord }
});
}
else
{
data[key].CexRecords.Add(cexRecord);
}
break;
case "CPH":
CphRecord cphRecord = ParseCph(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CphRecords = new List<CphRecord>() { cphRecord }
});
}
else
{
data[key].CphRecords.Add(cphRecord);
}
break;
}
}
});
return data;
}
Try out FileHelper, here is your exact example - http://www.filehelpers.net/example/QuickStart/ReadFileDelimited/
Given you're data of
CDI|11111|OTHERDATA|Datas
CEX001|123131|DATA
CCC|123131
You could create a class to model this to allow FileHelpers to parse the delimited file:
[DelimitedRecord("|")]
public class Record
{
public string Type { get; set; }
public string[] Fields { get; set; }
}
Then we could allow FileHelpers to parse in to this object type:
var engine = new FileHelperEngine<Record>();
var records = engine.ReadFile("Input.txt");
After we've got all the records loaded in to Record objects we can use a bit of linq to pull them in to their given types
var cdis = records.Where(x => x.Type == "CDI")
.Select(x => new Cdi(x.Fields[0], x.Fields[1], x.Fields[2])
.ToArray();
var cexs = records.Where(x => x.Type == "CEX001")
.Select(x => new Cex(x.Fields[0], x.Fields[1)
.ToArray();
var cccs = records.Where(x => x.Type == "CCC")
.Select(x => new Ccc(x.Fields[0])
.ToArray();
You could also simplify the above using something like AutoMapper - http://automapper.org/
Alternatively you could use ConditionalRecord attributes which will only parse certain lines if they match a given criteria. This will however be slower the more record types you have but you're code will be cleaner and FileHelpers will be doing most of the heavy lifting:
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CDI")]
public class Cdi
{
public string Type { get; set; }
public int Number { get; set; }
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
}
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CEX001")]
public class Cex001
{
public string Type { get; set; }
public int Number { get; set; }
public string Data1 { get; set; }
}
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CCC")]
public class Ccc
{
public string Type { get; set; }
public int Number { get; set; }
}
var input =
#"CDI|11111|Data1|Data2|Data3
CEX001|123131|Data1
CCC|123131";
var CdiEngine = new FileHelperEngine<Cdi>();
var cdis = CdiEngine.ReadString(input);
var cexEngine = new FileHelperEngine<Cex001>();
var cexs = cexEngine.ReadString(input);
var cccEngine = new FileHelperEngine<Ccc>();
var cccs = cccEngine.ReadString(input);
Your first loop isn't really doing anything other than organizing your data differently. You should be able to eliminate it and use the data as it is from the file. Something like this should give you what you want:
foreach (var line in File.ReadLines(_path))
{
var split = line.Split('|');
var key = split[1];
var value = split;
var recordType = value[0];
switch (recordType)
{
case "CDI":
var cdiRecord = ParseCdi(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key, CdiRecords = new List<CdiRecord>() { cdiRecord }
});
}
else
{
data[key].CdiRecords.Add(cdiRecord);
}
break;
case "CEX015":
var cexRecord = ParseCex(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CexRecords = new List<Cex015Record>() { cexRecord }
});
}
else
{
data[key].CexRecords.Add(cexRecord);
}
break;
case "CPH":
CphRecord cphRecord = ParseCph(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CphRecords = new List<CphRecord>() { cphRecord }
});
}
else
{
data[key].CphRecords.Add(cphRecord);
}
break;
}
};
Caveat: This is just put together here and hasn't been properly checked for syntax.

CRUD over two different collections of POCOs

I have an interesting question, one I'm having difficulty searching for an answer on.
I have two IEnumerable collections of objects. The underlying objects are completely separate, BUT I can identify a shared key that should match. The collections are important, in that my "left" object is the "system of record", and the "right" object is representing a system I need to ensure matches the system of record.
Once they are matched, I need to perform CRUD operations on one side to bring the right side in line with the left side. For example, it would create a new item on the right side if one didn't exist, or update values, or delete if the item was missing on the left, but not the right.
The catch is, I have hundreds of these collections to match up, and the actual CRUD code is different.
I'd like to introduce some shared code where I can pass in both collections, the collection types (as probably generics), some kind of comparer, and some delegates of what operation to perform for CRUD.
If this code actually existed, it may look something like this
class Stuff
{
string Id {get; set;}
string Name {get; set;}
}
class Junk
{
string Id {get; set;}
string ShortName {get; set;}
}
IEnumerable<Stuff> myStuff = GetStuff();
IEnumerable<Junk> myJunk = GetJunk();
CrudComparer cc = new CrudComparer<Stuff, Junk>(myStuff, myJunk);
cc.Comparer = (leftObject, rightObject) => {
leftObject.Name == rightObject.Name
}
cc.CreateOperation = (newObject, rightCollection) => {
Junk j = new Junk();
j.Shortname = newObject.Name;
rightCollection.Add(j);
}
cc.UpdateOperation = (leftObject, rightObject) => {
rightObject.Shortname = leftObject.Name;
}
cc.DeleteOperation = (rightCollection, rightObject) => {
rightCollection.Remove(rightObject);
}
cc.Compare();
Has anyone ever seen code that does something like this? I'd hate to reinvent the wheel if I can grab something already done.
Thanks for any help!
--Michael
I got to thinking more about this, and realized what I knew about delgates and generics should be sufficient to solve this problem, so I got in LinqPad and had some fun. I haven't written any unit tests around this yet, so use at your own risk, but hopefully if you want to use this you understand the underlying concepts.
class Blah
{
public int ID { get; set; }
public string BlahName { get; set;}
}
class Bleh
{
public int ID { get; set; }
public string BlehName { get; set;}
}
class CrudComparer<TLeft, TRight>
{
private readonly ICollection<TLeft> _leftCollection;
private readonly ICollection<TRight> _rightCollection;
private readonly Comparer _compareOperation;
private readonly CreateOperation _createOperation;
private readonly UpdateOperation _updateOperation;
private readonly DeleteOperation _deleteOperation;
public delegate bool Comparer(TLeft leftItem, TRight rightItem);
public delegate void CreateOperation(TLeft leftItem, ICollection<TRight> rightCollection);
public delegate void UpdateOperation(TLeft leftItem, TRight rightItem);
public delegate void DeleteOperation(TRight rightItem, ICollection<TRight> rightCollection);
public CrudComparer(ICollection<TLeft> leftCollection, ICollection<TRight> rightCollection, Comparer compareOperation, CreateOperation createOperation, UpdateOperation updateOperation, DeleteOperation deleteOperation)
{
_leftCollection = leftCollection;
_rightCollection = rightCollection;
_compareOperation = compareOperation;
_createOperation = createOperation;
_updateOperation = updateOperation;
_deleteOperation = deleteOperation;
}
public void Compare()
{
foreach (TLeft leftItem in _leftCollection)
{
bool foundItem = false;
foreach (TRight rightItem in _rightCollection)
{
if (_compareOperation(leftItem, rightItem))
{
//these equal
foundItem = true;
}
}
if (foundItem == false)
{
_createOperation(leftItem, _rightCollection);
}
}
List<TRight> itemsToDelete = new List<TRight>();
foreach (TRight rightItem in _rightCollection)
{
bool foundItem = false;
foreach (TLeft leftItem in _leftCollection)
{
if (_compareOperation(leftItem, rightItem))
{
foundItem = true;
_updateOperation(leftItem, rightItem);
break;
}
}
if (!foundItem)
{
itemsToDelete.Add(rightItem);
}
}
foreach (TRight itemToDelete in itemsToDelete)
{
_deleteOperation(itemToDelete, _rightCollection);
}
}
}
void Main()
{
List<Blah> blahItems = new List<Blah>();
blahItems.Add(new Blah() { ID = 1, BlahName = "Blah" });
blahItems.Add(new Blah() { ID = 2, BlahName = "ABC" });
blahItems.Add(new Blah() { ID = 34, BlahName = "XYZ" });
blahItems.Add(new Blah() { ID = 6442, BlahName = "123" });
List<Bleh> blehItems = new List<Bleh>();
blehItems.Add(new Bleh() { ID = 2, BlehName = "12345"});
blehItems.Add(new Bleh() { ID = 6, BlehName = "43232"});
blehItems.Add(new Bleh() { ID = 77, BlehName = "BlahBlah"});
blehItems.Add(new Bleh() { ID = 2334, BlehName = "ZYX"});
CrudComparer<Blah, Bleh>.Comparer compareOperation = (leftObject, rightObject) =>
{
return leftObject.ID == rightObject.ID;
};
CrudComparer<Blah, Bleh>.CreateOperation createOperation = (leftObject, rightCollection) =>
{
rightCollection.Add(new Bleh() { ID = leftObject.ID });
};
CrudComparer<Blah, Bleh>.UpdateOperation updateOperation = (leftObject, rightObject) =>
{
rightObject.BlehName = leftObject.BlahName;
};
CrudComparer<Blah, Bleh>.DeleteOperation deleteOperation = (rightObject, rightCollection) =>
{
rightCollection.Remove(rightObject);
};
CrudComparer<Blah, Bleh> cc = new CrudComparer<Blah, Bleh>(blahItems, blehItems, compareOperation, createOperation, updateOperation, deleteOperation);
cc.Compare();
}

Extracting a key-value pair from inside a string

I have the following string:
string myString = "{'gridObject':'[1,2,3,4],[5,6,7,8]'}";
How do I process this into an object so that I can do this:
charts[0] //=> [1,2,3,4]
charts[0][1] //=> 2
If I can convert it to this object, even better:
public class gridObject {
public int datarow {get; set;}
public int datacol {get; set;}
public int datasizex {get; set;}
public int datasizey {get; set;}
}
This is what I would do.
Create your classes first,
public class GridObject
{
public int datarow { get; set; }
public int datacol { get; set; }
public int datasizex { get; set; }
public int datasizey { get; set; }
}
public class GridObjectCollection
{
public GridObject[] GridObjects { get; set; }
}
Then, to see what JSON you need, serialize it once: (JsonConvert is part of Json.NET, you can get it with NuGet)
GridObjectCollection gridObjects = new GridObjectCollection();
gridObjects.GridObjects = new GridObject[]
{
new GridObject() { datacol = 1, datarow = 2, datasizex = 3, datasizey = 4 },
new GridObject() { datacol = 5, datarow = 6, datasizex = 7, datasizey = 8 }
};
Console.WriteLine
(
JsonConvert.SerializeObject
(
gridObjects,
new JsonSerializerSettings() { Formatting = Formatting.Indented }
)
);
Here you can see that the valid JSON content which will produce these classes when deserialized is like:
{
"GridObjects": [
{
"datarow": 2,
"datacol": 1,
"datasizex": 3,
"datasizey": 4
},
{
"datarow": 6,
"datacol": 5,
"datasizex": 7,
"datasizey": 8
}
]
}
Then, just try a deserialization just to make sure:
var f = JsonConvert.DeserializeObject<GridObjectCollection>
(
"{'GridObjects':[{'datarow':2,'datacol':1,'datasizex':3,'datasizey':4},{'datarow':6,'datacol':5,'datasizex':7,'datasizey':8}]}"
);
Here is one way to do it:
public static gridObject[] Parse(string str)
{
int first = str.IndexOf("[");
int last = str.LastIndexOf("]");
str = str.Substring(first, last - first + 1);
string[] big_parts = str.Split(new string[] {"[", "],[", "]"} , StringSplitOptions.RemoveEmptyEntries);
return big_parts.Select(x =>
{
string[] small_parts = x.Split(',');
return new gridObject()
{
datarow = Convert.ToInt32(small_parts[0]),
datacol = Convert.ToInt32(small_parts[1]),
datasizex = Convert.ToInt32(small_parts[2]),
datasizey = Convert.ToInt32(small_parts[3]),
};
}).ToArray();
}
It first searches for the the first [ and the last ] and trims anything before [ and after ].
Then, it splits the string based on [, ],[, ].
This will give us 1,2,3,4 and 5,6,7,8 for your example.
Then for each one of them, we split based on , and convert the results into a gridObject object.
Done with custom parsing:
public class GridObject
{
public int datarow { get; set; }
public int datacol { get; set; }
public int datasizex { get; set; }
public int datasizey { get; set; }
}
/// <summary>
/// MySuperObject class which holds a reference to inner array of integers
/// </summary>
public class MySuperObject
{
public List<int> Items { get; set; } // Inner array of list of integers
public MySuperObject()
{
Items = new List<int>();
}
public override string ToString()
{
// Need to override ToString to return something like "[1,2,3,4]"
var result = "";
foreach (var item in Items)
{
if (result.Length > 0)
result += ",";
result += item.ToString();
}
return string.Format("[{0}]", result);
}
/// <summary>
/// Function to generate GridObject from existing set of integers
/// </summary>
public GridObject GetGridObject()
{
var result = new GridObject();
if (Items.Count >= 1) result.datarow = Items[0];
if (Items.Count >= 2) result.datacol = Items[1];
if (Items.Count >= 3) result.datasizex = Items[2];
if (Items.Count >= 4) result.datasizey = Items[3];
return result;
}
}
// Parse functions
public List<MySuperObject> Parse(string value)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("value cannot be null or empty!", "value");
var result = new List<MySuperObject>();
// First get the indexes of first [ and last ]
var idxStart = value.IndexOf("[");
var idxEnd = value.LastIndexOf("]");
// Check validity
if (idxStart < 0 || idxEnd < 0 || idxEnd <= idxStart)
return result; // Return empty list
value = value.Substring(idxStart, idxEnd - idxStart + 1).Trim();
// Split by [] after replacing spaces with empty strings (and removing first and last [ and ])
var arr = value.Replace(" ", "").Trim('[',']').Split(new[] { "],[" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var str in arr)
{
// Construct list of integers with a help of LINQ
var nums = str.Split(',').Select(t => Convert.ToInt32(t)).ToList();
// Create and add MySuperObject to existing list which will be returned
result.Add(new MySuperObject
{
Items = new List<int>(nums),
});
}
return result;
}
And here is the usage of such parsing:
var myString = "{'gridObject':'[1,2,3,4],[5,6,7,8]'}";
var value = Parse(myString);
// Get all grid objects
var allGridObjects = value.Select(t => t.GetGridObject()).ToList();
Of course, this could need a little bit more of error checking but basically, this MySuperObject is used to use any number of integers you desire, while providing you with a helper method of "GetGridObject" to fill you grid object with appropriate numbers from array of numbers.

c# bindingsource and linq

I have a bindingsource which has been filled out by
tableadapter.fill(DS, param1);
lets say that this bindingsource has :
char num
A 1
B 2
C 3
how do I get num value with given char 'A' using linq?
I could literate using
foreach (DataRowView data in this.p_EM_Select_Event_TypeBindingSource)
but I would like to know how to do this in linq
Thanks,
This should help:
http://msdn.microsoft.com/en-us/library/bb399401.aspx
cheers
First, you need to specify what your data type is.
Since you did not, I will create one here as a placeholder:
public class Data1
{
public Data1()
{
Character = String.Empty;
Number = -1;
Other = null;
}
public String Character { get; set; }
public int Number { get; set; }
public object Other { get; set; }
}
Now that you have a data type, we can assume that your BindingSource is populated with this type of data:
Somewhere in your code, you have to fill your BindingList:
p_EM_Select_Event_TypeBindingSource.DataSource = new List<Data1>();
Methods like below make nice wrappers for these types of tasks:
public int GetIndex(String value)
{
var result = -1;
if (!String.IsNullOrEmpty(value))
{
var list = (IList<Data1>)p_EM_Select_Event_TypeBindingSource.List;
if (list.Any(x => x.Character == value))
{
result = list.IndexOf(list.First(x => x.Character == value));
}
}
return result;
}
public int GetNumber(String value)
{
var result = -1;
if (!String.IsNullOrEmpty(value))
{
var item = list.IndexOf(list.FirstOrDefault(x => x.Character == value));
if (item != null)
{
result = item.Number;
}
}
return result;
}
To set the BindingSource to your data item with given Character "A", you can call your method:
p_EM_Select_Event_TypeBindingSource.Position = GetIndex("A");
Or, to get the Number to your data item with the given Character, you can call the similar method:
var number = GetNumber("A");

Categories