Reputation: 11
I have two classes as follows:
public class SensorReading
{
public DateTime DatetimeStamp { get; set; }
public float Sound { get; set; }
public float Temperature { get; set; }
public float Humidity { get; set; }
public SensorReading()
{
}
}
public class API_Obj
{
public DateTime DateTimeStamp { get; set; }
public float Value { get; set; }
public API_Obj()
{
}
}
And I'm getting my sensor readings from a REST API; however, it separates the Sound, Temperature and Humidity readings into different API Endpoints. Hence I've made 3 separate API calls and stored each reading type into a list of Type List<API_Obj>. The code looks something this:
//set static objects
List<API_Obj> SoundReadings = GetReadings("sound");
List<API_Obj> TempReadings = GetReadings("temperature");
List<API_Obj> HumidReadings = GetReadings("humidity");
Now I would like to merge the 3 lists according to the DateTimeStamp class property. I've come up with this so far:
var AllReadings = SoundReadings.Union(TempReadings).Union(HumidReadings).GroupBy(obj => obj.DateTimeStamp)
.Select(newObj => new SensorReading()
{
DateTimeStamp = newObj.Key,
// Sound = ???
// Temperature = ???
// Humidity = ???
}).ToList();
This would return 0 for Sound, Temperature and Humidity, however. How do I continue from here?
Upvotes: 0
Views: 869
Reputation: 27282
LINQ is not performant for such task, I would suggest the following function:
public static void MergeList<TItem, TKey, TResult>(IEnumerable<TItem> items, Dictionary<TKey, TResult> result,
Func<TItem, TKey> keyFunc, Action<TItem, TResult> setterAction)
where TResult : new()
{
if (items == null) throw new ArgumentNullException(nameof(items));
if (keyFunc == null) throw new ArgumentNullException(nameof(keyFunc));
if (setterAction == null) throw new ArgumentNullException(nameof(setterAction));
if (result == null) throw new ArgumentNullException(nameof(result));
foreach (var item in items)
{
var key = keyFunc(item);
if (!result.TryGetValue(key, out var value))
{
value = new TResult();
result.Add(key, value);
}
setterAction(item, value);
}
}
And usage:
var result = new Dictionary<DateTime, SensorReading>();
MergeList(SoundReadings, result, x => x.DatetimeStamp, (a, r) => r.Sound = a.Value);
MergeList(TempReadings, result, x => x.DatetimeStamp, (a, r) => r.Temperature = a.Value);
MergeList(HumidReadings, result, x => x.DatetimeStamp, (a, r) => r.Humidity = a.Value);
Upvotes: 0
Reputation: 169200
You need to keep track of the type of value one way or another. Given your current type definitions, something like this should work:
var AllReadings = SoundReadings.Select(x => new { x.DatetimeStamp, x.Value, Type = "SoundReadings" })
.Union(TempReadings.Select(x => new { x.DatetimeStamp, x.Value, Type = "TempReadings" })
.Union(HumidReadings.Select(x => new { x.DatetimeStamp, x.Value, Type = "HumidReadings" })))
.GroupBy(obj => obj.DatetimeStamp)
.Select(newObj => new SensorReading()
{
DatetimeStamp = newObj.Key,
Sound = newObj.FirstOrDefault(x => x.Type == "SoundReadings")?.Value,
Temperature = newObj.FirstOrDefault(x => x.Type == "TempReadings")?.Value,
Humidity = newObj.FirstOrDefault(x => x.Type == "HumidReadings")?.Value
}).ToList();
Upvotes: 4
Reputation: 10765
As stated in the comments, you could modify your API_Obj
class to hold a new string property called APIType
and you can decorate the property with JsonIgnore
attribute.
public class API_Obj
{
public DateTime DatetimeStamp { get; set; }
public float Value { get; set; }
[JsonIgnore]
public string APIType { get; set; }
public API_Obj()
{
}
}
Then after you do your API Calls, use the List.ForEach
to set the new property on each object of the list:
List<API_Obj> SoundReadings = GetReadings("sound");
List<API_Obj> TempReadings = GetReadings("temperature");
List<API_Obj> HumidReadings = GetReadings("humidity");
//set the APIType property on each list object
SoundReadings.ForEach(z => z.APIType = "sound");
TempReadings.ForEach(z => z.APIType = "temperature");
HumidReadings.ForEach(z => z.APIType = "humidity");
Now you can use these to determine which property to set in your Linq Query
Upvotes: 1
Reputation: 33516
What you came up with is nice, and of course you've got the newObj.Key
there, but you didn't notice what newObj
is: it is the group itself. A group = a collections of items from under that common key.
That's why the 'grouping' newObj
implements not only the Key property, but also IEnumerable<API_Obj>
interface, so you can inspect the items that fell to that group.
That is, if you've originally had:
SoundReadings:
- date:0011, value: aa
- date:0022, value: bb
- date:0033, value: cc
TempReadings:
- date:0011, value: dd
- date:0055, value: ee
HumidReadings:
- date:0055, value: ff
- date:0066, value: gg
You will end up with these groups:
11:
- date:0011, value: aa (sound)
- date:0011, value: dd (temp)
22:
- date:0022, value: bb (sound)
33:
- date:0033, value: cc (sound)
44:
- date:0055, value: ee (temp)
- date:0055, value: ff (humid)
66:
- date:0066, value: gg (humid)
Now, in your .Select() at the end, you have to inspect not only the Key with the date, but also the whole set of items held in newObj
and find an item for Sound, and use its value for Sound, find an item for Temp and use its value for Temp, and so on.
For example, just to give you a general idea:
.Select(newObj => new SensorReading()
{
DateTimeStamp = newObj.Key,
Sound = newObj.Where(.....).FirstOrDefault()?.Value,
...
}).ToList();
Mind three things though:
Upvotes: 1