Extracting a key-value pair from inside a string - c#

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.

Related

How do I combine objects in a generic list into generic class?

So I have a generic list of objects, and I need to combine their properties to get list of sums.
public class PickupReport
{
public int Units { get; set; }
public int Revenue { get; set; }
public int Accs { get; set; }
}
public class ReportBase<TReport>
{
public List<TReport> Reports { get; set; }
public List<int> Sum
{
get
{
return new List<int>{1,2,3}
}
}
}
So, if I have
var reportBase = new ReportBase<PickupReport>();
reportBase.Reports = new List<PickupReport>{
new PickupReport{Units = 1, Revenue = 2, Accs = 3},
new PickupReport{Units = 4, Revenue = 5, Accs = 6},
new PickupReport{Units = 2, Revenue = 3, Accs = 4}
}
I need to get and calculate reportBase.Sum and it should be List<int> with {7, 10, 13}
Like {the sum of property #1 for all objects, the sum of property #2 for all objects, and so on.. }
Is it possible to do this somehow, given that the number of objects in the list is dynamic? Well, the number of object properties, too, but within the class.
#MongZhu only int
Then I would suggest to use reflection for this approach. Make it a method rather then a property:
public class ReportBase<TReport>
{
public List<TReport> Reports { get; set; }
public IEnumerable<int> CalculateSum()
{
foreach (var element in typeof(TReport).GetProperties())
{
if (element.PropertyType == typeof(int))
{
yield return Reports.Sum(x => (int)element.GetValue(x));
}
}
}
}
Explanation:
This method iterates over all properties that are in the type and if it finds a property of type int it will calculate the sum of it from the entire list.
Here is a testprogramm and the result dump from LINQPad:
void Main()
{
var reportBase = new ReportBase<PickupReport>();
reportBase.Reports = new List<PickupReport>{
new PickupReport(1,2,3),
new PickupReport(4,5,6),
new PickupReport(2,3,4)
};
reportBase.CalculateSum().Dump();
}
EDIT:
just noticed, that objects in my project can have two types of properties, not one
Here is a version where you can add types to a list of allowed sum up types:
public class ReportBase<TReport>
{
public List<TReport> Reports { get; set; }
List<Type> possibleTypes = new List<Type> {typeof(int), typeof(double)};
public IEnumerable<double> CalculateSum()
{
foreach (PropertyInfo element in typeof(TReport).GetProperties())
{
if (possibleTypes.Contains(element.PropertyType))
{
yield return Reports.Sum(x => Convert.ToDouble(element.GetValue(x)));
}
}
}
}
Now you can also handle double
This should get you a List<int> with the sums of all int properties of TReport:
var properties = typeof(PickupReport).GetProperties();
List<int> sum = new List<int>();
for (int i = 0; i<properties.Length; ++i)
{
sum.Add(0);
if (properties[i].PropertyType == typeof(int))
{
foreach (var report in reportBase.Reports)
{
sum[i] += (int) properties[i].GetValue(report);
}
}
}

Issues with creating a JSON string

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),
};
});

Using an Object as Key for a SortedList<Object,Value> in c#

How would you define an Object as Key to a SortedList in c#.
Here I would like to definte a Key Object like this
MyKey key = new MyKey();
key.type = 3; // can be 3,2 or 1
key.time = 2014-05-03 // DateTime in c# with timestamp
key.sequence = 5567 // a number unique to the combination above
I would like to sort this sorted list on priority type, time and sequence. How do i achieve this?
The SortedList in C# uses the IComparable interface for sorting the list. So for you to achieve this u must implement the IComparable interface. See: https://msdn.microsoft.com/en-us/library/system.icomparable.compareto(v=vs.110).aspx
A example:
public class Key : IComparable
{
public int Type {get; set; }
public DateTime Time { get; set; }
public int Sequence { get; set; }
int IComparable.CompareTo(object obj)
{
Key otherKey = obj as Key;
if (otherKey == null) throw new ArgumentException("Object is not a Key!");
if (Type > otherKey.Type)
return 1;
return -1;
}
}
Use the sorted list:
SortedList<Key,string> collection = new SortedList<Key, string>();
collection.Add(new Key { Type = 2 }, "alpha");
collection.Add(new Key { Type = 1 }, "beta");
collection.Add(new Key { Type = 3 }, "delta");
foreach (string str in collection.Values)
{
Console.WriteLine(str);
}
This writes:
beta
alpha
delta
Create a custom Comparer<myKey> and pass that to the SortedList constructor:
public class TypeComparer : Comparer<MyKey>
{
public override int Compare(MyKey x, MyKey y)
{
if (ReferenceEquals(x, y)) return 0;
int typeX = int.MinValue;
int typeY = int.MinValue;
if (x != null) typeX = x.type;
if (y != null) typeY = y.type;
return typeX.CompareTo(typeY);
}
}
Now you can use this constructor:
var sl = new SortedList<MyKey, string>(new TypeComparer());
If I understand correctly:
static void Main(string[] args)
{
Dictionary<MyKey, string> fooDictionary = new Dictionary<MyKey, string>();
fooDictionary.Add(new MyKey() {FooNumber=1, Sequence=50 }, "1");
fooDictionary.Add(new MyKey() { FooNumber = 2, Sequence = 40 }, "2");
fooDictionary.Add(new MyKey() { FooNumber = 3, Sequence = 30 }, "3");
fooDictionary.Add(new MyKey() { FooNumber = 4, Sequence = 20 }, "4");
fooDictionary.Add(new MyKey() { FooNumber = 5, Sequence = 10 }, "5");
var result = from c in fooDictionary orderby c.Key.Sequence select c;
Console.WriteLine("");
}
class MyKey
{
public int FooNumber { get; set; }
public DateTime MyProperty { get; set; }
public int Sequence { get; set; }
}

How to transform list of hierarchyid into a binary tree

I am working on a multi-level marketing (binary) which looks like this:
(but the binary tree is not required to be perfect. A node can have 0-2 child)
My problem is the data that I fetch from the database is flat list.
Notice that I am using hierarchyid (sql server 2014)
Basically the TextNode column is like a breadcrumb.
every slash / represents a level.
If I have TextNode of /1/ as root. then every node that starts with /1/ belongs to that root which are /1/, /1/1/ and /1/1/1/ (the root node is included which will be the level 0)
I've tried the accepted answer in this question but its not working.
How can I transform the flatlist to a Binary Tree so that I can easily traverse and display it on a screen?
Im using C#, ASP MVC 5, SQL Server 2014 if it matters.
I implement exactly this code According to Alex implementation but as is mentioned in some case it didn't work correctly .. have a look to my Image and my code (which copied from Alex post) [data in the database are correct but in tree view seems some problems ]
public class Row : IRow<string>
{
public string TextNode { get; }
public string Value { get; }
public long Id { get; }
public string FIN { get; }
public Row(string textNode, string userName, long id, string fin)
{
FIN = fin;
Id = id;
TextNode = textNode;
Value = userName;
}
}
public interface IRow<out T>
{
string TextNode { get; }
long Id { get; }
string FIN { get; }
T Value { get; }
}
public class TreeNode<T>
{
private struct NodeDescriptor
{
public int Level { get; }
public int ParentIndex { get; }
public NodeDescriptor(IRow<T> row)
{
var split = row.TextNode.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
Level = split.Length;
ParentIndex = split.Length > 1 ? int.Parse(split[split.Length - 2]) - 1 : 0;
}
}
public T title { get; }
public long Id { get; }
public string FIN { get; }
public List<TreeNode<T>> children { get; }
private TreeNode(T value, long id, string fin)
{
Id = id;
FIN = fin;
title = value;
children = new List<TreeNode<T>>();
}
public static TreeNode<T> Parse(IReadOnlyList<IRow<T>> rows)
{
if (rows.Count == 0)
return null;
var result = new TreeNode<T>(rows[0].Value, rows[0].Id, rows[0].FIN);
FillParents(new[] { result }, rows, 1, 1);
return result;
}
private static void FillParents(IList<TreeNode<T>> parents, IReadOnlyList<IRow<T>> rows, int index, int currentLevel)
{
var result = new List<TreeNode<T>>();
for (int i = index; i < rows.Count; i++)
{
var descriptor = new NodeDescriptor(rows[i]);
if (descriptor.Level != currentLevel)
{
FillParents(result, rows, i, descriptor.Level);
return;
}
var treeNode = new TreeNode<T>(rows[i].Value, rows[i].Id, rows[i].FIN);
parents[descriptor.ParentIndex].children.Add(treeNode);
result.Add(treeNode);
}
}
}
g
this is also my JSON output for more information :
{"title":"Earth","Id":32,"FIN":"FIN","children":[{"title":"Europe","Id":33,"FIN":"FIN001","children":[{"title":"France","Id":35,"FIN":"FIN001001","children":[{"title":"Paris","Id":36,"FIN":"FIN001001001","children":[]},{"title":"Brasilia","Id":41,"FIN":"FIN002001001","children":[]},{"title":"Bahia","Id":42,"FIN":"FIN002001002","children":[]}]},{"title":"Spain","Id":38,"FIN":"FIN001002","children":[{"title":"Madrid","Id":37,"FIN":"FIN001002001","children":[{"title":"Salvador","Id":43,"FIN":"FIN002001002001","children":[]}]}]},{"title":"Italy","Id":45,"FIN":"FIN001003","children":[]},{"title":"Germany","Id":48,"FIN":"FIN001004","children":[]},{"title":"test","Id":10049,"FIN":"FIN001005","children":[]}]},{"title":"South America","Id":34,"FIN":"FIN002","children":[{"title":"Brazil","Id":40,"FIN":"FIN002001","children":[{"title":"Morano","Id":47,"FIN":"FIN001003001","children":[]}]}]},{"title":"Antarctica","Id":39,"FIN":"FIN003","children":[{"title":"McMurdo Station","Id":44,"FIN":"FIN003001","children":[]}]}]}
Here is a very simple implementation (assuming that Nodes are in the right order), which may be enhanced in multiple ways
public interface IRow<out T>
{
string TextNode { get; }
T Value { get; }
}
public class TreeNode<T>
{
private struct NodeDescriptor
{
public int Level { get; }
public int ParentIndex { get; }
public NodeDescriptor(IRow<T> row)
{
var split = row.TextNode.Split(new [] {"/"}, StringSplitOptions.RemoveEmptyEntries);
Level = split.Length;
ParentIndex = split.Length > 1 ? int.Parse(split[split.Length - 2]) - 1 : 0;
}
}
public T Value { get; }
public List<TreeNode<T>> Descendants { get; }
private TreeNode(T value)
{
Value = value;
Descendants = new List<TreeNode<T>>();
}
public static TreeNode<T> Parse(IReadOnlyList<IRow<T>> rows)
{
if (rows.Count == 0)
return null;
var result = new TreeNode<T>(rows[0].Value);
FillParents(new[] {result}, rows, 1, 1);
return result;
}
private static void FillParents(IList<TreeNode<T>> parents, IReadOnlyList<IRow<T>> rows, int index, int currentLevel)
{
var result = new List<TreeNode<T>>();
for (int i = index; i < rows.Count; i++)
{
var descriptor = new NodeDescriptor(rows[i]);
if (descriptor.Level != currentLevel)
{
FillParents(result, rows, i, descriptor.Level);
return;
}
var treeNode = new TreeNode<T>(rows[i].Value);
parents[descriptor.ParentIndex].Descendants.Add(treeNode);
result.Add(treeNode);
}
}
}
Sample usage:
public class Row : IRow<string>
{
public string TextNode { get; }
public string Value { get; }
public Row(string textNode, string userName)
{
TextNode = textNode;
Value = userName;
}
}
class Program
{
static void Main(string[] args)
{
IRow<string>[] rows =
{
new Row("/", "Ahmed"),
new Row("/1/", "Saeed"),
new Row("/2/", "Amjid"),
new Row("/1/1/", "Noura"),
new Row("/2/1/", "Noura01"),
new Row("/2/2/", "Reem01"),
new Row("/1/1/1", "Under_noura")
};
var tree = TreeNode<string>.Parse(rows);
PrintTree(tree);
}
private static void PrintTree<T>(TreeNode<T> tree, int level = 0)
{
string prefix = new string('-', level*2);
Console.WriteLine("{0}{1}", prefix, tree.Value);
foreach (var node in tree.Descendants)
{
PrintTree(node, level + 1);
}
}
}

Combining numbers and names collections

I have 2 List collections. One contains numbers, the other names. There are twice as many numbers as names(always). I want to take the first name from the collection and the first two numbers from the other collection then put them together in a 3rd user collection of (VentriloUser). Then the second name needs to be matched with the 3rd and 4th numbers and so on.
I was thinking something with a for or foreach loop, but I can't wrap my head around it right now.
public class VentriloUser
{
public VentriloUser(string name, int seconds, int ping)
{
Name = name; Seconds = seconds; Ping = ping;
}
public string Name { get; set; }
public int Ping { get; set; }
public int Seconds { get; set; }
}
public class Ventrilo
{
public Ventrilo(string statusurl)
{
StatusURL = statusurl;
}
public string StatusURL { get; set; }
public string HTML { get; set; }
public List<VentriloUser> Users { get; set; }
private Regex findNumbers = new Regex("\\<td width=\"10%\" bgcolor=\"#\\w{6}\"\\>\\<font color=\"#000000\">\\<div style=\"overflow:hidden;text-overflow:ellipsis\"\\>-?\\d+\\<");
private Regex findNames = new Regex("\\<td width=\"20%\" bgcolor=\"#\\w{6}\"\\>\\<font color=\"#000000\">\\<div style=\"overflow:hidden;text-overflow:ellipsis\"\\>\\b\\w+\\<");
private WebClient wClient = new WebClient();
public void DownloadHTML()
{
HTML = wClient.DownloadString(StatusURL);
}
public List<int> GetNumbers()
{
var rawnumbers = findNumbers.Matches(HTML);
var numbers = new List<int>();
foreach (var rawnumber in rawnumbers)
{
var match = Regex.Match(rawnumber.ToString(), "\\>\\-?\\d+\\<");
string number = Regex.Replace(match.ToString(), "\\<|\\>", "");
numbers.Add(Convert.ToInt32(number));
}
return numbers;
}
public List<string> GetNames()
{
var rawnames = findNames.Matches(HTML);
var names = new List<string>();
foreach (var rawname in rawnames)
{
var match = Regex.Match(rawname.ToString(), "\\>\\w+<");
string name = Regex.Replace(match.ToString(), "\\<|\\>", "");
names.Add(name);
}
return names;
}
public List<VentriloUser> GenerateUsers()
{
var numbers = GetNumbers();
var names = GetNames();
var users = new List<VentriloUser>();
}
}
I am a hobbyist, but hope to pursue a career one day. Any help is much appreciated. Thank you for your time.
Using LINQ:
var users = names.Select((name,idx) => new VentriloUser(name, numbers[idx*2], numbers[idx*2+1]))
.ToList();
Using loops:
var users = new List<VentriloUser>();
for (int i = 0; i < names.Count; i++)
{
var name = names[i];
int j = i * 2;
if (j >= numbers.Count - 1)
break; // to be safe...
users.Add(new VentriloUser(name, numbers[j], numbers[j + 1]));
}

Categories