Merge 2 Object Array Data Series That Share the Same [0] Index - c#

So let's say that you have 2 series of data. (Both object arrays, in your choice of a serialized JSON string, or the actual objects).
For Instance:
string str1 = #"{""datapoints"":[[""02/28/2019"",146800.0],[""02/27/2019"",147700.0],[""02/26/2019"",153900.0]]}";
Then, you have a second series that is very similar...
string str2 = #"{""datapoints"":[[""02/28/2019"",145600.0],[""02/27/2019"",143600.0],[""02/26/2019"",152200.0]]}";
Note: the object arrays inside are both a length of "2", and both contain the same "date" as the [0] index.
How does one merge the 2 object arrays into 1, to yield the following output...
string str3 = #"{""datapoints"":[[""02/28/2019"",145600.0,145600.0],[""02/27/2019"",143600.0,143600.0],[""02/26/2019"",152200.0,152200.0]]}";
For clarity, I'm interested in using the [0] index once, and merging the [1] indexes together. (the number values)
Extra credit if this can be a loop, or can be done with any number of series.

Using json.net, you can deserialize each JSON sample to an object that contains a datapoints property that is an enumerable of object arrays, then merge them using the LINQ methods GroupBy() and Aggregate().
Say the JSON samples to be merged are in a string [][] jsonSeriesList like so:
string str1 = #"{""datapoints"":[[""02/28/2019"",146800.0],[""02/27/2019"",147700.0],[""02/26/2019"",153900.0]]}";
string str2 = #"{""datapoints"":[[""02/28/2019"",145600.0],[""02/27/2019"",143600.0],[""02/26/2019"",152200.0]]}";
var jsonSeriesList = new[] { str1, str2 }; // Add others as required
Then you can create a combined series as follows:
var merged = jsonSeriesList.Aggregate(
new { datapoints = Enumerable.Empty<object[]>() },
(m, j) => new
{
datapoints = m.datapoints.Concat(JsonConvert.DeserializeAnonymousType(j, m).datapoints)
// Group them by the first array item.
// This will throw an exception if any of the arrays are empty.
.GroupBy(i => i[0])
// And create a combined array consisting of the key (the first item from all the grouped arrays)
// concatenated with all subsequent items in the grouped arrays.
.Select(g => new[] { g.Key }.Concat(g.SelectMany(i => i.Skip(1))).ToArray())
});
var mergedJson = JsonConvert.SerializeObject(merged);
Notes:
I am deserializing to an anonymous type for brevity. You could create an explicit data model if you prefer.
I am assuming the individual datapoint arrays all have at least one item.
There is no attempt to sort the resulting merged JSON by series date. You could do that if necessary.
The solution assumes that you will never have multiple component arrays in the same series with the same first item, e.g. "02/28/2019" repeated twice. If so, they will get merged also.
Sample .Net fiddle here.

Here is a simplified example (simplified as validations might be required, but hope it given you a place to start):
Convert it into dot net object
Then go through each date point - for each date point go through all the series, adding the values.
//container object for the json series
public class Container
{
public List<List<object>> datapoints;
}
//Input series in JSON
string[] inputSeries = new string[]
{
"{\"datapoints\": [[\"02/28/2019\", 146800.0],[\"02/27/2019\", 147700.0],[\"02/26/2019\", 153900.0]]}",
"{\"datapoints\": [[\"02/28/2019\", 145600.0],[\"02/27/2019\", 143600.0],[\"02/26/2019\", 152200.0]]}"
};
//Container for input series in dot net object
List<Container> con = new List<Container>();
foreach (var series in inputSeries)
{
con.Add(JsonConvert.DeserializeObject<Container>(series));
}
// output container
Container output = new Container();
output.datapoints = new List<List<object>>();
// assuming all series have equal number of data points.....might not be so
for (int i = 0; i < con[0].datapoints.Count; i++)
{
output.datapoints.Add(new List<object>());
// inner loop is to go across series for the same datapoint....
for (int j = 0; j < con.Count; j++)
{
// add the date if this is the first series....after that only add the values
// right now the assumption is that the dates are in order and match....validation logic might be required
if (j == 0)
{
output.datapoints[i].Add(con[j].datapoints[i][0]);
output.datapoints[i].Add(con[j].datapoints[i][1]);
}
else
{
output.datapoints[i].Add(con[j].datapoints[i][1]);
}
}
}

Related

Distinct Values in hashsets and insert in DataGridView

I want to display a hashset with all the different values ​​between two DataGridView, but I have not been successful in displaying the strings, I attach images.
var dataA = new HashSet<string>();
var dataB = new HashSet<string>();
for (int i = 1; i < dgv_A.Rows.Count; i++)
{
dataA.Add(dgv_A[8, i].Value.ToString());
}
for (int i = 1; i < dgv_B.Rows.Count; i++)
{
dataB.Add(dgv_B[8, i].Value.ToString());
}
if (dataA == dataB)
{
lbl_resultado.Text = "Las certificaciones estan correctas";
}
else
{
var error = dataA.Except(dataB).Concat(dataB.Except(dataA));
var container = new HashSet<string>(error);
dgv_B.DataSource = container.ToList();
}
Theres the values that are needed:
The result of the code:
NOTE: the if (dataA == dataB) part, it's not the problem i need answer but thanks. The part needed its:
else
{
var error = dataA.Except(dataB).Concat(dataB.Except(dataA));
var container = new HashSet<string>(error);
dgv_B.DataSource = container.ToList();
}
NOTE 2: The main operation of the project is the verification of documents (for example, that no user has been modified)
If any value is changed you need to know which values ​​were changed.
With
var error = dataA.Except(dataB).Concat(dataB.Except(dataA));
var container = new HashSet<string>(error);
dgv_B.DataSource = container.ToList();
I get the distinct values ​​fron dataA and dataB, but I can't get it to show me the texts that appear.
NOTE:
Here's your problem:
if (dataA == dataB)
{
}
The HashSet<T> type does not override the == operator.
Consequently dataA == dataB performs only a reference equality comparison, which means the same thing as Object.ReferenceEquals( dataA, dataB ), which will always be false, as dataA and dataB are references to separate GC objects.
Instead, to compare both HashSets to see if they are both equivalent use (the confusingly named) SetEquals() method:
if( dataA.SetEquals( dataB ) )
{
lbl_resultado.Text = "Las certificaciones estan correctas";
}
Do not use Enumerable.SequenceEquals because that evaluates the HashSet as an ordered sequence of values, but a HashSet is a Mathematical Set, which is unordered and (AFAIK) its HashSet<T>.Enumerator iterator returns its elements in undefined order, so attempting to check two sets' set-equality by comparing sequences is incorrect.

How to turn a string into a 2d string array

as the title suggests, I am looking for guidance in how to turn a string (csvData) into a 2D string array by splitting it two times with ';' and ',' respectivly.
Currently I am at the stage where I am able to split it once into rows and turn it into an array, but I cannot figure out how to instead create a 2D array where the columns divided by ',' are also separate.
string[] Sep = csvData.Split(';').Select(csvData => csvData.Replace(" ","")).Where(csvData => !string.IsNullOrEmpty(csvData)).ToArray();
I have tried various things like :
string[,] Sep = csvData.Split(';',',').Select(csvData => csvData.Replace(" ","")).Where(csvData => !string.IsNullOrEmpty(csvData)).ToArray();
naivly thinking that c# would understand what I tried to achieve, but since I am here it's obvious that I got the error that "cannot implicitly convert type string[] to string [*,*]"
Note that I have not coded for a while, so if my thinking is completely wrong and you do not understand what I am trying to convey with this question, I apologize in advance.
Thanks!
In a strongly-typed language like C#, the compiler makes no assumptions about what you intend to do with your data. You must make your intent explicit through your code. Something like this should work:
string csvData = "A,B;C,D";
string[][] sep = csvData.Split(';') // Returns string[] {"A,B","C,D"}
.Select(str => str.Split(',')) // Returns IEnumerable<string[]> {{"A","B"},{"C","D"}}
.ToArray(); // Returns string[][] {{"A","B"},{"C","D"}}
Rows are separated by semicolon, columns by comma?
Splitting by ';' gives you an array of rows. Split a row by ',' gives you an array of values.
If your data has a consistent schema, as in each csv you process has the same columns, you could define a class to represent the entity to make the data easier to with with.
Let's say it's customer data:
John,Smith,8675309,johnsmith#gmail.com;
You could make a class with those properties:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}
Then:
var rows = csvdata.Split(';');
List<Customer> customers = new();
foreach(var row in rows)
{
var customer = row.Split(',');
customers.Add(new()
{
FirstName = row[0],
LastName = row[1],
Phone = row[2],
Email = row[3]
});
}
Now you have a list of customers to do whatever it is you do with customers.
Here is an answer to present a few alternative ideas and things you can do with C# - more for educational/academic purposes than anything else. These days to consume a CSV we'd use a CSV library
If your data is definitely regularly formed you can get away with just one Split. The following code splits on either char to make one long array. It then stands to reason that every 4 elements is a new customer, the data of the customer being given by n+0, n+1, n+2 and n+3. Because we know how many data items we will consume, dividing it by 4 gives us the number of customers so we can presize our 2D array
var bits = data.Split(';',',');
var twoD = new string[bits.Length/4,4];
for(int x = 0; x < bits.Length; x+=4){
twoD[x/4,0] = bits[x+0];
twoD[x/4,1] = bits[x+1];
twoD[x/4,2] = bits[x+2];
twoD[x/4,3] = bits[x+3];
}
I don't think I'd use 2D arrays though - and I commend the other answer advising to create a class to hold the related data; you can use this same technique
var custs = new List<Customer>();
for(int x = 0; x < bits.Length;){
custs.Add(new()
{
FirstName = bits[x++],
LastName = bits[x++],
Phone = bits[x++],
Email = bits[x++]
});
}
Here we aren't incrementing x in the loop header; every time a bit of info is assigned x is bumped up by 1 in the loop body. We could have kept the same approach as before, jumping it by 4 - just demoing another approach that lends itself well here.
I mentioned that these days we probably wouldn't really read a csv manually and split ourselves - what if the data contains a comma, or a semicolon - it wrecks the file structure
There are a boatload of libraries that read CSV files, CsvHelper is a popular one, and you'd use it like:
using var reader = new StreamReader("path\\to\\file.csv");
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture)
var custs = csv.GetRecords<Customer>().ToList();
...
Your file would have a header line with column names that match your property names in c#. If it doesn't then you can use attributes on the properties to tell CsvH what column should be mapped to what property - https://joshclose.github.io/CsvHelper/getting-started/
Here's the simplest way I know to produce a 2d array by splitting a string.
string csvData = "A,B,C;D,E,F,G";
var temporary =
csvData
.Split(';')
.SelectMany((xs, i) => xs.Split(',').Select((x, j) => new { x, i, j }))
.ToArray();
int max_i = temporary.Max(x => x.i);
int max_j = temporary.Max(x => x.j);
string[,] array = new string[max_i + 1, max_j + 1];
foreach (var t in temporary)
{
array[t.i, t.j] = t.x;
}
I purposely chose csvData to be missing a value.
temporary is this:
And the final array is this:

C# Split - Split on list dont return all the wanted strings

I'm fairly new to C#, and i've come across a problem trying to split on list elements.
I have a resource file containing string properties as such:
ResourceFile
ResourceFile
I've collected them in a List as:
public List<String> RawNewsList1 = new List<String>()
{
{Resource.NewsContentAndroid1},
{Resource.NewsMetaAndroid1},
};
I'm trying to split on the semicolons but only get results from my second list item.
My split look like this:
public void FilterRawNews()
{
String[] seperator = { ";;;" };
String[] filteredList1 = { "" };
for (int i = 0; i < RawNewsList1.Count; i++) {
filteredList1 = RawNewsList1[i].Split(seperator, 5,
StringSplitOptions.RemoveEmptyEntries);
}
foreach (String s in filteredList1)
{
Console.WriteLine(s);
}
}
Its only prints:
110
2.8
02-07-2020
What am i doing wrong?
Thanks in advance!
The filteredList1 variable is first filled with data from your the first resource, then at the next loop the variable's content is replaced with the data coming from the second resource.
You can use a List<string> instead that has the AddRange method to continuosly add elements to the list
List<string> filteredList1 = new List<string>();
for (int i = 0; i < RawNewsList1.Count; i++) {
filteredList1.AddRange(RawNewsList1[i].Split(seperator, 5,StringSplitOptions.RemoveEmptyEntries));
}
From this we could simplify the code to one-liner with
filteredList = RawNewsList1.SelectMany(a => a.Split(seperator,5, StringSplitOptions.RemoveEmptyEntries)).ToList();
So, what's happen in that single line? That syntax is used when you work with objects that can be treated as a sequence of data. In this context your array RawNewsList1 is a sequence of data and we can use the IEnumerable extensions brought to us by using the Linq namespace. The SelectMany extension requires a lambda expression ( a => ....) that is used to produce the instructions where each element of the sequence (a) is passed to an expression that returns another sequence of data (the array returned by Split). The sequence returned is accumulated to the sequence produced by the next elements from the original RasNewsList1. Finally the accumulated sequence is materialized with the call to ToList()
You are overwriting filteredList1 in each iteration.
That is why you only get the last result.
Just declare filteredList1 as a list and and use AddRange().
Edit: or use LINQ:
var raw = new List<string>() { "111;;;222", "333;;;444" };
String[] seperator = { ";;;" };
var filterlist1 = raw.SelectMany(r => r.Split(seperator, 5, StringSplitOptions.RemoveEmptyEntries)).ToList();

Search a List of string array to find a value in matching element and return another element in same array

So I have
List<string[]> listy = new List<string[]>();
listy.add('a','1','blue');
listy.add('b','2','yellow');
And i want to search through all of the list ti find the index where the array containing 'yellow' is, and return the first element value, in this case 'b'.
Is there a way to do this with built in functions or am i going to need to write my own search here?
Relatively new to c# and not aware of good practice or all the built in functions. Lists and arrays im ok with but lists of arrays baffles me somewhat.
Thanks in advance.
As others have already suggested, the easiest way to do this involves a very powerful C# feature called LINQ ("Language INtegrated Queries). It gives you a SQL-like syntax for querying collections of objects (or databases, or XML documents, or JSON documents).
To make LINQ work, you will need to add this at the top of your source code file:
using System.Linq;
Then you can write:
IEnumerable<string> yellowThings =
from stringArray in listy
where stringArray.Contains("yellow")
select stringArray[0];
Or equivalently:
IEnumerable<string> yellowThings =
listy.Where(strings => strings.Contains("yellow"))
.Select(strings => strings[0]);
At this point, yellowThings is an object containing a description of the query that you want to run. You can write other LINQ queries on top of it if you want, and it won't actually perform the search until you ask to see the results.
You now have several options...
Loop over the yellow things:
foreach(string thing in yellowThings)
{
// do something with thing...
}
(Don't do this more than once, otherwise the query will be evaluated repeatedly.)
Get a list or array :
List<string> listOfYellowThings = yellowThings.ToList();
string[] arrayOfYellowThings = yellowThings.ToArray();
If you expect to have exactly one yellow thing:
string result = yellowThings.Single();
// Will throw an exception if the number of matches is zero or greater than 1
If you expect to have either zero or one yellow things:
string result = yellowThings.SingleOrDefault();
// result will be null if there are no matches.
// An exception will be thrown if there is more than one match.
If you expect to have one or more yellow things, but only want the first one:
string result = yellowThings.First();
// Will throw an exception if there are no yellow things
If you expect to have zero or more yellow things, but only want the first one if it exists:
string result = yellowThings.FirstOrDefault();
// result will be null if there are no yellow things.
Based on the problem explanation provided by you following is the solution I can suggest.
List<string[]> listy = new List<string[]>();
listy.Add(new string[] { "a", "1", "blue"});
listy.Add(new string[] { "b", "2", "yellow"});
var target = listy.FirstOrDefault(item => item.Contains("yellow"));
if (target != null)
{
Console.WriteLine(target[0]);
}
This should solve your issue. Let me know if I am missing any use case here.
You might consider changing the data structure,
Have a class for your data as follows,
public class Myclas
{
public string name { get; set; }
public int id { get; set; }
public string color { get; set; }
}
And then,
static void Main(string[] args)
{
List<Myclas> listy = new List<Myclas>();
listy.Add(new Myclas { name = "a", id = 1, color = "blue" });
listy.Add(new Myclas { name = "b", id = 1, color = "yellow" });
var result = listy.FirstOrDefault(t => t.color == "yellow");
}
Your current situation is
List<string[]> listy = new List<string[]>();
listy.Add(new string[]{"a","1","blue"});
listy.Add(new string[]{"b","2","yellow"});
Now there are Linq methods, so this is what you're trying to do
var result = listy.FirstOrDefault(x => x.Contains("yellow"))?[0];

Create objects within a foreach to push to an array in C#

What I am trying to achieve is to split a string into multiple adresses like "NL,VENLO,5928PN" which getLocation will return a "POINT( x y)" string value.
This works. Next I need to create a WayPointDesc object for each location. And each of these objects has to be pushed into the WayPointDesc[]. I have tried various methods but I cannot find a feasable option so far. My last resort is to hardcode a maximum amount of waypoints but I would rather avoid such a thing.
Using a list is unfortunately not an option... I think.
This is the function:
/* tour()
* Input: string route
* Output: string[] [0] DISTANCE [1] TIME [2] MAP
* Edited 21/12/12 - Davide Nguyen
*/
public string[] tour(string route)
{
// EXAMPLE INPUT FROM QUERY
route = "NL,HELMOND,5709EM+NL,BREDA,8249EN+NL,VENLO,5928PN";
string[] waypoints = route.Split('+');
// Do something completly incomprehensible
foreach (string point in waypoints)
{
xRoute.WaypointDesc wpdStart = new xRoute.WaypointDesc();
wpdStart.wrappedCoords = new xRoute.Point[] { new xRoute.Point() };
wpdStart.wrappedCoords[0].wkt = getLocation(point);
}
// Put the strange result in here somehow
xRoute.WaypointDesc[] waypointDesc = new xRoute.WaypointDesc[] { wpdStart };
// Calculate the route information
xRoute.Route route = calculateRoute(waypointDesc);
// Generate the map, travel distance and travel time using the route information
string[] result = createMap(route);
// Return the result
return result;
//WEEKEND?
}
Arrays are fixed-length, if you want to dynamically add elements, you need to use some type of linked list structure. Also, your wpdStart variable was out of scope when you were adding it originally.
List<xRoute.WaypointDesc> waypointDesc = new List<xRoute.WaypointDesc>();
// Do something completly incomprehensible
foreach (string point in waypoints)
{
xRoute.WaypointDesc wpdStart = new xRoute.WaypointDesc();
wpdStart.wrappedCoords = new xRoute.Point[] { new xRoute.Point() };
wpdStart.wrappedCoords[0].wkt = getLocation(point);
// Put the strange result in here somehow
waypointDesc.add(wpdStart);
}
If you really want the list as an array later, use: waypointDesc.ToArray()

Categories