Matching property/value data between two arrays - c#

A certain API call is returning two arrays.
One array contains Property names, e.g
Properties[0] = "Heartbeat"
Properties[1] = "Heartbeat 2"
Properties[2] = "Some Other discovery method"
Another array contains values for the Properties array, e.g
Values[0] = "22/01/2007"
Values[1] = "23/02/2007"
Values[2] = "5/06/2008"
The values and properties array elements match up, e.g Values[0] is always the value of Properties[0], etc.
My aim is to get the most recent "Heartbeat*" value. Note that the Heartbeat properties and values are not always in elements 1 and 2 of the array, so I need to search through them.
Code looks like this:
static DateTime GetLatestHeartBeat(string[] Properties, string[] Values)
{
DateTime latestDateTime = new DateTime();
for(int i = 0; i < Properties.Length; i++)
{
if(Properties[i].Contains("Heart"))
{
DateTime currentDateTime;
DateTime.TryParse(Values[i],out currentDateTime);
if (currentDateTime > LatestDateTime)
latestDateTime = currentDateTime;
}
}
return latestDateTime
}
The above code gives me the desired result, only issue being the loop continues after there are no more Heartbeat values to find. Is there a more efficient way of performing the above?

While this doesnt address performance, I would optimize the query like this:
var latestDateTime = Properties.Select((p, index) =>
new {p, v = DateTime.Parse(Values[index])})
.Where(e => e.p.Contains("Heart"))
.OrderByDescending(e => e.v).First();
Perhaps moving the parse after the where clause would limit the times that it casts.
var latestDateTime = Properties.Select((p, index) =>
new {p, v = Values[index]})
.Where(e => e.p.Contains("Heart"))
.Select(e => DateTime.Parse(e.v))
.Max();
EDIT: Per #dbc's comments, changed .OrderByDescending(e => e).First(); to Max();

I'd find the indices that contain "Heart" (or other key word) in a parallel for loop to speed it up. Then iterate over those indices to find the latest one.
static DateTime GetLatestHeartBeat(string[] props, string[] vals)
{
ConcurrentBag<int> heartIndxs = new ConcurrentBag<int>();
// find the indices of "Heart" in parallel
Parallel.For(0, props.Length,
index =>
{
if (props[index].Contains("Heart"))
{
heartIndxs.Add(index);
}
});
// loop over each heart index to find the latest one
DateTime latestDateTime = new DateTime();
foreach (int i in heartIndxs)
{
DateTime currentDateTime;
if (DateTime.TryParse(vals[i], out currentDateTime) && (currentDateTime > latestDateTime))
latestDateTime = currentDateTime;
}
return latestDateTime;
}
If using DateTime.TryParse is really too slow you could use RegEx to parse the date string and do your own comparison. Honestly I'm not sure that is faster than just using the DateTime.TryParse. Here is a discussion of that topic: Which is Quicker: DateTime.TryParse or Regex

Related

Comparing Names in array to themselves

I have an Array of names I sorted alphabetically. Many names repeat themselves and I track each occurrence as Popularity I have been trying to figure out how I can compare each Index and the one next to it to see if its the same name or not. Each time the same name appears I have a counter that ticks up, when it reaches a different name it checks its occurrence vs "foundNamePop" it stores the counter in a separate variable and resets. The problem is that some Arrays as input have the same name repeating at the end of the array (i.e. Lane, Lane, Lane \0) it leaves out of my IF LOOP and doesn't store it because I just have only the "nameCounter++". I just can't seem to find the solution to making sure it reads every name and store it all no matter if there are multiple names at the end or single names that are different i.e.(Lane, Dane, Bane \0).
Let me also add these .txt files can contain ~50 thousand names and I have no idea what names are in there.
Why does that ending If statement not work it just enters like normal. I ran with debugging and i watched it just slip right into the function even when .ElementsAt(i).Value > (5 for this instance)
var dict = new ConcurrentDictionary<string,int>(StringComparer.OrdinalIgnoreCase);
foreach (var name in updatedName)
{
dict.AddOrUpdate(name, 1, (_, count) => ++count);
}
for (int i = 0; i < dict.Count; i++)
{
if (dict.ElementsAt(i).Value <= foundNamePop);
{
lessPopNameSum += dict.ElementAt(i).Value;
}
}
The simple solution is to add a check after the loop
if (foundNamePop >= nameCounter)
{
lessPopNameSum += nameCounter;
}
But it is not clear to me what you are actually computing, it looks like you are summing the duplicate names that have more duplicates than foundNamePop, but it is not clear what value this has, nor what actual meaning the result will have.
You should be able to use LINQ to get something similar with less code:
var lessPopNameSum = sameLengthName
.GroupBy(n => n)
.Select(group => group.Count())
.Where(c => c >= foundNamePop)
.Sum();
Although I like the elegance of the other posted solution another alternative could be to use a Dictionary to store a count of each of the names.
const int FoundNamePop = 2;
var names = new string[] { "Bill", "Jane", "Jeff", "Rebecca", "Bill" };
var count = FindPopularNames(names)
.Where(kvp => kvp.Value < FoundNamePop)
.Sum(kvp => kvp.Value);
// With 'FoundNamePop' set to two, the below line will print '3'.
Console.WriteLine($"Count: {count}");
static IDictionary<string, int> FindPopularNames(IEnumerable<string> names)
{
var dict = new ConcurrentDictionary<string, int>
(StringComparer.OrdinalIgnoreCase);
foreach (var name in names)
{
dict.AddOrUpdate(name, 1, (_, count) => ++count);
}
return dict;
}

Parsing multiple string lines to numbers

I have such code:
string[] list_lines = System.IO.File.ReadAllLines(#"F:\VS\WriteLines.xls");
System.Console.WriteLine("Contents of Your Database = ");
foreach (var line in list_lines.OrderBy(line => line.Split(';')[3]))
{
Console.WriteLine("\t" + line);
}
I would like to TryParse the list_lines so they are numbers, not strings.
Is it possible to 'bulk' it somehow?
Each line consists of 5 strings after they are Split.
EDIT
I wrote this:
string[] list_lines = System.IO.File.ReadAllLines(#"F:\VS\WriteLines.xls");
int[] newList;
// Display the file contents by using a foreach loop.
System.Console.WriteLine("Contents of Your Database = ");
int.TryParse(list_lines[], out newList);
foreach (var line in newList.OrderBy(line => line.Split(';')[3]))
{
// Use a tab to indent each line of the file.
Console.WriteLine("\t" + line);
}
But I get error on list_lines[] , it says that there must be a value.
Based on your previous question, it seems that you want to order the lines by the 3rd split result as int, then you can do this way :
foreach (var line in list_lines.OrderBy(line =>
{
int lineNo;
var success = int.TryParse(line.Split(';')[3], out lineNo);
if(success) return lineNo;
return int.MaxValue;
}))
{
Console.WriteLine("\t" + line);
}
I'm using int.MaxValue as default for when TryParse fails. This way, failed lines will come last. You can change the default to int.MinValue instead, if you want the failed lines to come first.
By the way, C# naming convention uses camel-case for variables, like lineNo and listLines instead of line_no and list_lines.
To get int[] that corresponds to each line, you can use similar logic, but now in a Select() method instead of OrderBy() :
int[] newList = list_lines.Select(line =>
{
int lineNo;
var success = int.TryParse(line.Split(';')[3], out lineNo);
if(success) return lineNo;
return int.MaxValue; //or whatever default value appropriate
})
.ToArray();
You can use SelectMany to flatten the list.
list_lines.SelectMany(line => line.Split(';')).Select(cell => int.Parse(cell));
If there can be non-number cells and you are looking for positive numbers you can add a Where clause
list_lines.SelectMany(line => line.Split(';')).Where(cell => cell.All(#char => char.IsDigit(#char))).Select(cell => int.Parse(cell));
One way of doing it:
int number;
var intList = list_lines.Select(s => s.Split(';')
.Where(p => Int32.TryParse(p, out number))
.Select(y => Int32.Parse(y)))
.SelectMany(d=>d).ToList();

Assigning a 'day counter' for objects with DateTime?

I have a list of Order objects with a property OrderDate of type DateTime and I am trying to assign a 'DayCounter' for these objects that represents what order of the day it was, for example one day, I have 5 orders so every order gets a counter from 1 up to 5.
Here is what I tried:
orders.GroupBy(order => order.OrderDate.DayOfYear + order.OrderDate.Year)
.SelectMany(group =>
{
var count = 1;
group.Select(order =>
{
order.DayCounter = count;
count++;
return order;
});
return group;
});
the Order objects I get from this code all have DayCounter of 0
Any help would be appreciated
LINQ is not for modifying data, it is for selecting and projecting data. Your Select never gets run because Select is a lazy method and you never iterate over it. Use a normal foreach instead.
var groups = orders.GroupBy(order => order.OrderDate.Date);
foreach (var grouping in groups)
{
int orderCount = 1;
foreach (var order in grouping)
{
order.DayCounter = orderCount;
orderCount++;
}
}
I also changed your grouping key to be a more reliable seperator OrderDate.Date, your old method would consider days that are a year minus one day apart to be the same day.
Try using the overload of .Select which takes the index as a second parameter.
orders.GroupBy(order => order.OrderDate.DayOfYear + order.OrderDate.Year)
.SelectMany(group =>
{
group.Select((order,idx) =>
{
order.DayCounter = idx + 1;
return order;
});
return group;
});

LINQ: Collapsing a series of strings into a set of "ranges"

I have an array of strings similar to this (shown on separate lines to illustrate the pattern):
{ "aa002","aa003","aa004","aa005","aa006","aa007", // note that aa008 is missing
"aa009"
"ba023","ba024","ba025"
"bb025",
"ca002","ca003",
"cb004",
...}
...and the goal is to collapse those strings into this comma-separated string of "ranges":
"aa002-aa007,aa009,ba023-ba025,bb025,ca002-ca003,cb004, ... "
I want to collapse them so I can construct a URL. There are hundreds of elements, but I can still convey all the information if I collapse them this way - putting them all into a URL "longhand" (it has to be a GET, not a POST) isn't feasible.
I've had the idea to separate them into groups using the first two characters as the key - but does anyone have any clever ideas for collapsing those sequences (without gaps) into ranges? I'm struggling with it, and everything I've come up with looks like spaghetti.
So the first thing that you need to do is parse the strings. It's important to have the alphabetic prefix and the integer value separately.
Next you want to group the items on the prefix.
For each of the items in that group, you want to order them by number, and then group items while the previous value's number is one less than the current item's number. (Or, put another way, while the previous item plus one is equal to the current item.)
Once you've grouped all of those items you want to project that group out to a value based on that range's prefix, as well as the first and last number. No other information from these groups is needed.
We then flatten the list of strings for each group into just a regular list of strings, since once we're all done there is no need to separate out ranges from different groups. This is done using SelectMany.
When that's all said and done, that, translated into code, is this:
public static IEnumerable<string> Foo(IEnumerable<string> data)
{
return data.Select(item => new
{
Prefix = item.Substring(0, 2),
Number = int.Parse(item.Substring(2))
})
.GroupBy(item => item.Prefix)
.SelectMany(group => group.OrderBy(item => item.Number)
.GroupWhile((prev, current) =>
prev.Number + 1 == current.Number)
.Select(range =>
RangeAsString(group.Key,
range.First().Number,
range.Last().Number)));
}
The GroupWhile method can be implemented like so:
public static IEnumerable<IEnumerable<T>> GroupWhile<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
List<T> list = new List<T>() { iterator.Current };
T previous = iterator.Current;
while (iterator.MoveNext())
{
if (!predicate(previous, iterator.Current))
{
yield return list;
list = new List<T>();
}
list.Add(iterator.Current);
previous = iterator.Current;
}
yield return list;
}
}
And then the simple helper method to convert each range into a string:
private static string RangeAsString(string prefix, int start, int end)
{
if (start == end)
return prefix + start;
else
return string.Format("{0}{1}-{0}{2}", prefix, start, end);
}
Here's a LINQ version without the need to add new extension methods:
var data2 = data.Skip(1).Zip(data, (d1, d0) => new
{
value = d1,
jump = d1.Substring(0, 2) == d0.Substring(0, 2)
? int.Parse(d1.Substring(2)) - int.Parse(d0.Substring(2))
: -1,
});
var agg = new { f = data.First(), t = data.First(), };
var query2 =
data2
.Aggregate(new [] { agg }.ToList(), (a, x) =>
{
var last = a.Last();
if (x.jump == 1)
{
a.RemoveAt(a.Count() - 1);
a.Add(new { f = last.f, t = x.value, });
}
else
{
a.Add(new { f = x.value, t = x.value, });
}
return a;
});
var query3 =
from q in query2
select (q.f) + (q.f == q.t ? "" : "-" + q.t);
I get these results:

Get Count in List of instances contained in a string

I have a string containing up to 9 unique numbers from 1 to 9 (myString) e.g. "12345"
I have a list of strings {"1"}, {"4"} (myList) .. and so on.
I would like to know how many instances in the string (myString) are contained within the list (myList), in the above example this would return 2.
so something like
count = myList.Count(myList.Contains(myString));
I could change myString to a list if required.
Thanks very much
Joe
I would try the following:
count = mylist.Count(s => myString.Contains(s));
It is not perfectly clear what you need, but these are some options that could help:
myList.Where(s => s == myString).Count()
or
myList.Where(s => s.Contains(myString)).Count()
the first would return the number of strings in the list that are the same as yours, the second would return the number of strings that contain yours. If neither works, please make your question more clear.
If myList is just List<string>, then this should work:
int count = myList.Count(x => myString.Contains(x));
If myList is List<List<string>>:
int count = myList.SelectMany(x => x).Count(s => myString.Contains(s));
Try
count = myList.Count(s => s==myString);
This is one approach, but it's limited to 1 character matches. For your described scenario of numbers from 1-9 this works fine. Notice the s[0] usage which refers to the list items as a character. For example, if you had "12" in your list, it wouldn't work correctly.
string input = "123456123";
var list = new List<string> { "1", "4" };
var query = list.Select(s => new
{
Value = s,
Count = input.Count(c => c == s[0])
});
foreach (var item in query)
{
Console.WriteLine("{0} occurred {1} time(s)", item.Value, item.Count);
}
For multiple character matches, which would correctly count the occurrences of "12", the Regex class comes in handy:
var query = list.Select(s => new
{
Value = s,
Count = Regex.Matches(input, s).Count
});
try
var count = myList.Count(x => myString.ToCharArray().Contains(x[0]));
this will only work if the item in myList is a single digit
Edit: as you probably noticed this will convert myString to a char array multiple times so it would be better to have
var myStringArray = myString.ToCharArray();
var count = myList.Count(x => myStringArray.Contains(x[0]));

Categories