C# LINQ Group By multiple fields with Custom Properties - c#

I'm trying to agregate a list of multiple propertys with Linq.
My Second field is a List of Strings + an other List of strings inside.
Here's a sample of my code :
using System;
using System.Collections.Generic;
using System.Linq;
public class RefValueData
{
public int ReferenceId { get; set; }
public int SiteId { get; set; }
public string SiteName { get; set; }
public string Code { get; set; }
public decimal UnitPoints { get; set; }
public List<TranslationData> Texts { get; set; }
}
public class TranslationData
{
public string Text { get; set; }
public List<TranslationValue> Translations { get; set; }
}
public class TranslationValue
{
public string Culture { get; set; }
public string TranslationText { get; set; }
}
public class Program
{
public static void Main()
{
var values = new List<RefValueData>
{
new RefValueData(){
ReferenceId = 4,
Code = "Code",
SiteId = 2,
SiteName = "Paris",
UnitPoints = 50,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "A",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
}
}
}
},
new RefValueData()
{
ReferenceId = 5,
Code = "Code",
SiteId = 4,
SiteName = "Lyon",
UnitPoints = 50,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "A",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
}
}
}
},
new RefValueData()
{
ReferenceId = 6,
Code = "Code",
SiteId = 3,
SiteName = "Paris",
UnitPoints = 52,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "B",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Salut" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Ciao" },
}
}
}
}
};
var values2 = values
.Distinct()
.GroupBy(x => new
{
x.UnitPoints,
x.Texts
})
.Select(x => new
{
x.Key.UnitPoints,
Texts = x.Key.Texts,
Site = x.Select(y=>y.SiteName)
})
.ToList();
Console.WriteLine(values2.Count);
}
}
I want to have only two lines in my values2 list, but everytime it returns me the whole list.
When I only group by Unit Point, it's work great !
I tried to group the first two lines of my list with some custom Linq query but it doesn't work at all...
Any help / advice is much appreciated :) !
EDIT :
I also tried with an override of the Equals methods like this, but I can't make it work :
public class TranslationValue
{
public string Culture { get; set; }
public string TranslationText { get; set; }
public override bool Equals(object obj)
{
var other = obj as TranslationValue;
if (other == null)
{
return false;
}
return Culture == other.Culture && TranslationText == other.TranslationText;
}
public override int GetHashCode()
{
var hashCode = -2095322044;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Culture);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(TranslationText);
return hashCode;
}
}
public class TranslationData
{
public string Text { get; set; }
public List<TranslationValue> Translations { get; set; }
public override bool Equals(object obj)
{
var other = obj as TranslationData;
if (other == null)
{
return false;
}
return Text == other.Text && Translations.SequenceEqual(other.Translations);
}
public override int GetHashCode()
{
var hashCode = -1551681861;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Text);
hashCode = hashCode * -1521134295 + EqualityComparer<List<TranslationValue>>.Default.GetHashCode(Translations);
return hashCode;
}
}
EDIT2 : Here's my 'real' code :
var values = referenceValues.Select(value => new
{
ReferenceId = value.ReferenceId,
SiteId = value.Reference.SiteId ?? -1,
SiteName = value.Reference.Site.Name ?? allSitesName,
Code = value.Code,
UnitPoints = value.UnitPoints,
Texts = // Type: List<TranslationData> , but it will not use the TranslationDataList class that normally work thanks to your help
value.ReferenceValueTexts.Select(text =>
new TranslationData
{
Text = text.Text, // string
Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
new TranslationValue {
Culture = translation.Language.StrCulture,
TranslationText = translation.Value
}).ToList()
}).ToList()
}
Julien.

Here's one solution. It works for the sample code that you wrote. But it needs a little work to be robust:
// and also change the declarations in the main method to: new TranslationDataList
public class TranslationDataList : List<TranslationData>
{
public override int GetHashCode()
{
int hash = 13;
// string.GetHashCode() is not reliable. This should be an algorithm that returns the same value for two different lists that contain the same data
foreach (var data in this)
hash = (hash * 7) + data.Text.GetHashCode();
return hash;
}
public override bool Equals(object obj)
{
var other = obj as TranslationDataList;
if (other == null) return false;
if (other.Count != Count) return false;
// write the equality logic here. I don't know if it's ok!
for (int i = 0; i < other.Count; i++)
if (other[i].Text != this[i].Text)
return false;
return true;
}
}

First of all you should add a constructor to the TranslationDataList:
public class TranslationDataList : List<TranslationData>
{
public TranslationDataList(IEnumerable<TranslationData> translationData)
: base(translationData)
{ }
// other members ...
}
Now you can use the TranslationDataList in your query:
var values = referenceValues.Select(value => new
{
ReferenceId = value.ReferenceId,
SiteId = value.Reference.SiteId ?? -1,
SiteName = value.Reference.Site.Name ?? allSitesName,
Code = value.Code,
UnitPoints = value.UnitPoints,
Texts = new TranslationDataList( value.ReferenceValueTexts.Select(text =>
new TranslationData
{
Text = text.Text, // string
Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
new TranslationValue {
Culture = translation.Language.StrCulture,
TranslationText = translation.Value
}).ToList()
})); // don't ToList() here anymore
}

And here is another solution:
The GroupBy method takes an IEqualityComparer that can do the responsibility of comparing items for the grouping. But the problem is you used an anonymous type for the key in your grouping "GroupBy(x=>new{x.UnitPoints, x.Texts})". First we have to create a class to play the key role:
public class Key
{
public Key(decimal unitPoints, List<TranslationData> texts)
{
UnitPoints = unitPoints;
Texts = texts;
}
public decimal UnitPoints { get; set; }
public List<TranslationData> Texts { get; set; }
}
then we can implement the comparer:
public class Comparer : IEqualityComparer<Key>
{
public bool Equals(Key x, Key y)
{
if (x.UnitPoints != y.UnitPoints) return false;
if (!ListsAreEqual(x.Texts, y.Texts)) return false;
return true;
}
private bool ListsAreEqual(List<TranslationData> x, List<TranslationData> y)
{
if (x.Count != y.Count) return false;
for (int i = 0; i < x.Count; i++)
if (x[i].Text != y[i].Text)
return false;
return true;
}
public int GetHashCode(Key key)
{
int hash = 13;
hash = (hash * 7) + key.UnitPoints.GetHashCode();
foreach (var data in key.Texts)
hash = (hash * 7) + data.Text.GetHashCode();
return hash;
}
}
and finally this is what your query will look like:
var values2 = values
.Distinct()
.GroupBy(x => new Key(x.UnitPoints, x.Texts), new Comparer())
.Select(x => new
{
x.Key.UnitPoints,
Texts = x.Key.Texts,
Site = x.Select(y => y.SiteName)
}).ToList();
I think the first solution (creating the customized list class) is better, because you can also refactor your code and extract some logic to it.

Related

Best approach to compare if one list is subset of another in C#

I have the below two classes:
public class FirstInner
{
public int Id { get; set; }
public string Type { get; set; }
public string RoleId { get; set; }
}
public class SecondInner
{
public int Id { get; set; }
public string Type { get; set; }
}
Again, there are lists of those types inside the below two classes:
public class FirstOuter
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<FirstInner> Inners { get; set; }
}
public class SecondOuter
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondInner> Inners { get; set; }
}
Now, I have list of FirstOuter and SecondOuter. I need to check if FirstOuter list is a subset of SecondOuter list.
Please note:
The names of the classes cannot be changed as they are from different systems.
Some additional properties are present in FirstOuter but not in SecondOuter. When comparing subset, we can ignore their presence in SecondOuter.
No.2 is true for FirstInner and SecondInner as well.
List items can be in any order---FirstOuterList[1] could be found in SecondOuterList[3], based on Id, but inside that again need to compare that FirstOuterList[1].FirstInner[3], could be found in SecondOuterList[3].SecondInner[2], based on Id.
I tried Intersect, but that is failing as the property names are mismatching. Another solution I have is doing the crude for each iteration, which I want to avoid.
Should I convert the SecondOuter list to FirstOuter list, ignoring the additional properties?
Basically, here is a test data:
var firstInnerList = new List<FirstInner>();
firstInnerList.Add(new FirstInner
{
Id = 1,
Type = "xx",
RoleId = "5"
});
var secondInnerList = new List<SecondInner>();
secondInner.Add(new SecondInner
{
Id = 1,
Type = "xx"
});
var firstOuter = new FirstOuter
{
Id = 1,
Name = "John",
Title = "Cena",
Inners = firstInnerList
}
var secondOuter = new SecondOuter
{
Id = 1,
Name = "John",
Inners = secondInnerList,
}
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
Need to check if firstOuterList is part of secondOuterList (ignoring the additional properties).
So the foreach way that I have is:
foreach (var item in firstOuterList)
{
var secondItem = secondOuterList.Find(so => so.Id == item.Id);
//if secondItem is null->throw exception
if (item.Name == secondItem.Name)
{
foreach (var firstInnerItem in item.Inners)
{
var secondInnerItem = secondItem.Inners.Find(sI => sI.Id == firstInnerItem.Id);
//if secondInnerItem is null,throw exception
if (firstInnerItem.Type != secondInnerItem.Type)
{
//throw exception
}
}
}
else
{
//throw exception
}
}
//move with normal flow
Please let me know if there is any better approach.
First, do the join of firstOuterList and secondOuterList
bool isSubset = false;
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
var jointOuterList = firstOuterList.Join(
secondOuterList,
p => new { p.Id, p.Name },
m => new { m.Id, m.Name },
(p, m) => new { FOuterList = p, SOuterList = m }
);
if(jointOuterList.Count != firstOuterList.Count)
{
isSubset = false;
return;
}
foreach(var item in jointOuterList)
{
var jointInnerList = item.firstInnerList.Join(
item.firstInnerList,
p => new { p.Id, p.Type },
m => new { m.Id, m.type },
(p, m) => p.Id
);
if(jointInnerList.Count != item.firstInnerList.Count)
{
isSubset = false;
return;
}
}
Note: I am assuming Id is unique in its outer lists. It means there will not be multiple entries with same id in a list. If no, then we need to use group by in above query
I think to break the question down..
We have two sets of Ids, the Inners and the Outers.
We have two instances of those sets, the Firsts and the Seconds.
We want Second's inner Ids to be a subset of First's inner Ids.
We want Second's outer Ids to be a subset of First's outer Ids.
If that's the case, these are a couple of working test cases:
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsTrue(isInnerSubset);
Assert.IsTrue(isOuterSubset);
}
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreNotSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
firstInnerIds.Clear();
firstInnerIds.Add(5);
firstOuterIds.Clear();
firstOuterIds.Add(5);
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsFalse(isInnerSubset);
Assert.IsFalse(isOuterSubset);
}
private List<FirstOuter> GetFirstOuterList() { ... }
private List<SecondOuter> GetSecondOuterList() { ... }

C# Compare two list and return value

I have a problem. I try compare two list currentItemsInColl and bPList. Inside bPList i have other list RequiredItems and now is what I need.
I want compare currentItemsInColl and RequiredItems and return bPList.craftingBlueprint.
I try Compare but I dont know how use it :/
using Devdog.InventoryPro;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CraftingAutoUpdate : MonoBehaviour {
public ItemCollectionBase itemCollection;
public ItemCollectionBase rewardCollection;
public CraftingCategory craftingCategory;
[Header("Blue Print List")]
public List<BlueprintList> bPList = new List<BlueprintList>();
public List<CurrentItemInCollList> currentItemsInColl = new List<CurrentItemInCollList>();
private CraftingBlueprint readyBlueprint;
public void OnShow()
{
GetBluePrint();
InvokeRepeating("StartUpdate",0f,0.05f);
}
public void OnHide()
{
CancelInvoke("StartUpdate");
}
private void StartUpdate()
{
UpdateDirectory();
UpdateFindMatchItems();
UpdateCraftResults();
}
private void GetBluePrint()
{
bPList.Clear();
foreach (var b in craftingCategory.blueprints)
{
if (b != null)
{
var rI = b.requiredItems;
var listReqItems = new List<RequiredItem>();
foreach (var e in rI)
{
listReqItems.Add(new RequiredItem(e.item.ID, e.amount));
}
bPList.Add(new BlueprintList(b.name, b, listReqItems));
}
}
}
private void UpdateDirectory()
{
currentItemsInColl.Clear();
foreach(var item in itemCollection)
{
if (item.item != null)
{
var cT = item.item.ID;
if (currentItemsInColl.Find(u =>u.itemID == cT) == null)
{
var itemCount = itemCollection.GetItemCount(item.item.ID);
currentItemsInColl.Add(new CurrentItemInCollList(item.item.ID, itemCount));
}
}
}
}
In this methode I try find same items in collections:
private void UpdateFindMatchItems()
{
readyBlueprint = null;
bool matchFailed = false;
int requiredItemCount = 0;
int currentItemsInCollCount = currentItemsInColl.Count;
foreach(var bp in bPList)
{
requiredItemCount = bp.RequiredItems.Count;
foreach(var rI in bp.RequiredItems)
{
if(CompareLists(currentItemsInColl, bp.RequiredItems))
{
print("aa");
}
print(currentItemsInCollCount);
}
}
private void UpdateCraftResults()
{
rewardCollection.Clear();
if (readyBlueprint != null)
{
foreach (var items in readyBlueprint.resultItems)
{
rewardCollection.AddItem(items.item,null,true,false);
}
}
}
I try somthing like this but is wont work with this lists:
public static bool CompareLists<T>(List<T> aListA, List<T> aListB)
{
if (aListA == null || aListB == null || aListA.Count != aListB.Count)
return false;
if (aListA.Count == 0)
return true;
Dictionary<T,T> lookUp = new Dictionary<T,T>();
// create index for the first list
for (int i = 0; i < aListA.Count; i++)
{
uint count = 0;
if (!lookUp.TryGetValue(aListA[i], out count))
{
lookUp.Add(aListA[i], 1);
continue;
}
lookUp[aListA[i]] = count + 1;
}
for (int i = 0; i < aListB.Count; i++)
{
uint count = 0;
if (!lookUp.TryGetValue(aListB[i], out count))
{
// early exit as the current value in B doesn't exist in the lookUp (and not in ListA)
return false;
}
count--;
if (count <= 0)
lookUp.Remove(aListB[i]);
else
lookUp[aListB[i]] = count;
}
// if there are remaining elements in the lookUp, that means ListA contains elements that do not exist in ListB
return lookUp.Count == 0;
}
}
And this is my lists:
/* LISTS */
[Serializable]
public class CurrentItemInCollList
{
public uint itemID;
public uint itemAmount;
public CurrentItemInCollList(uint newitemID, uint newItemAmount)
{
itemID = newitemID;
itemAmount = newItemAmount;
}
}
[Serializable]
public class BlueprintList
{
public string bluePrintName;
public CraftingBlueprint craftingBlueprint;
public List<RequiredItem> RequiredItems = new List<RequiredItem>();
public BlueprintList(string newBluePrintName, CraftingBlueprint newcraftingBlueprint, List<RequiredItem> list)
{
bluePrintName = newBluePrintName;
craftingBlueprint = newcraftingBlueprint;
RequiredItems = list;
}
}
[Serializable]
public class RequiredItem
{
public uint itemID;
public uint itemAmount;
public RequiredItem( uint newitemID, uint newItemAmount)
{
itemID = newitemID;
itemAmount = newItemAmount;
}
}
I forgot.. CurrentItemInCollList.itemAmount can be >= RequiredItems.itemAmount
Dictionary use hash values to compare objects.
The stored classes must implement public override int GetHashCode(){}
Use Linq - here is a small console example:
class Program
{
static void Main(string[] args)
{
//Required list
List<Order> currentItemsInColl = new List<Order>();
currentItemsInColl.Add(new Order() { Name = "bike1", Id = "01" });
currentItemsInColl.Add(new Order() { Name = "bike4", Id = "04" });
//List of all items
List<BPP> bPList = new List<BPP>();
bPList.Add(new BPP() { BikeName = "bike1", Idzzz = "01" });
bPList.Add(new BPP() { BikeName = "bike2", Idzzz = "02" });
bPList.Add(new BPP() { BikeName = "bike3", Idzzz = "03" });
bPList.Add(new BPP() { BikeName = "bike4", Idzzz = "04" });
bPList.Add(new BPP() { BikeName = "bike5", Idzzz = "05" });
//Blueprint List
List<BPP> Blueprint = new List<BPP>();
//get all items into the Blueprint list
foreach (Order i in currentItemsInColl)
{
List<BPP> tmp = bPList.FindAll(x => x.Idzzz.Contains(i.Id));
//here you add them all to a list
foreach (BPP item in tmp)
{
Blueprint.Add(item);
}
}
Console.ReadLine();
}
}
public class Order
{
public string Id { get; set; }
public string Name { get; set; }
}
public class BPP
{
public string Idzzz { get; set; }
public string BikeName { get; set; }
}
Sidenote: i am comparing the ID's in each of the lists! Hope it helps.

Looking for elegant / efficient solution for integer keyed collection of models

The "AddToLeg" method seems long winded is there a better pattern or more efficient way to implement this? I thought about using a dictionary but I want the key to remain an integer. I'm pretty new to linq / generics so I maybe missing something more obvious. When I've looked through documentation there aren't really any examples that match my scenario.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
public class Program
{
public static void Main()
{
var model = new TrainspotterItenaryViewModel();
var manchester = new Station() { Name = "Manchester", ExpectedTime = "13:30" };
var leeds = new Station() { Name = "Leeds", ExpectedTime = "15:00" };
var york = new Station() { Name = "York", ExpectedTime = "15:30" };
var london = new Station() { Name = "London", ExpectedTime = "21:00" };
model.AddToLeg(1, manchester);
model.AddToLeg(1, leeds);
model.AddToLeg(2, leeds);
model.AddToLeg(2, london);
model.AddToLeg(1, york); //another destination added to leg 1
//any number of legs can be added...
model.AddToLeg(3, manchester);
//show results contents
for(var i=1; i <= model.Legs.Count; i++)
{
var displayLeg = model.Legs.Single(x=>x.LegNumber==i);
foreach(var station in displayLeg.Stations){
string output = $"leg: {displayLeg.LegNumber} station: {station.Name}, expected:{station.ExpectedTime}";
Console.WriteLine(output);
}
}
}
}
public class TrainspotterItenaryViewModel
{
public List<Leg> Legs { get; set; }
public void AddToLeg(int legNumber, Station station)
{
if (Legs == null)
{
Legs = new List<Leg>();
}
var legCount = Legs.Count(x => x.LegNumber == legNumber);
if (legCount == 0)
{
var leg = new Leg
{
LegNumber = legNumber,
Stations = new List<Station> {station}
};
Legs.Add(leg);
Console.WriteLine($"Leg {leg.LegNumber} Not Found- Added new leg and {station.Name}");
}
else
{
foreach (var leg in Legs)
{
if (leg.LegNumber == legNumber)
{
leg.Stations.Add(station);
Console.WriteLine($"Leg {legNumber} Found- adding {station.Name}");
}
}
}
}
}
public class Leg
{
public int LegNumber { get; set; }
public List<Station> Stations { get; set; }
}
public class Station
{
public string Name { get; set; }
public string ExpectedTime { get; set; }
}
}
Use Dictionary for this task:
public class TrainspotterItenaryViewModel
{
private Dictionary<int, Leg> _legNumberToLegIndex { get; set; }
public IEnumerable<Leg> Legs => _legNumberToLegIndex?.Values
public void AddToLeg(int legNumber, Station station)
{
if (_legNumberToLegIndex == null)
{
_legNumberToLegIndex = new Dictionary<int, Leg>();
}
Leg leg;
if (!_legNumberToLegIndex.TryGetValue(legNumber, out leg))
{
leg = new Leg
{
LegNumber = legNumber,
Stations = new List<Station>()
};
_legNumberToLegIndex.Add(legNumber, leg);
}
leg.Stations.Add(station);
}
}
The method FirstOrDefault already returns the object that you're searching and so you can simplify your code
public List<Leg> Legs { get; set; } = new List<Leg>();
public void AddToLeg(int legNumber, Station station)
{
var leg = Legs.FirstOrDefault(x => x.LegNumber == legNumber);
if (leg == null)
{
leg = new Leg
{
LegNumber = legNumber,
Stations = new List<Station> { station }
};
Legs.Add(leg);
Console.WriteLine($"Leg {leg.LegNumber} Not Found- Added new leg and {station.Name}");
}
else
{
leg.Stations.Add(station);
Console.WriteLine($"Leg {legNumber} Found- adding {station.Name}");
}
}

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.

How to transform 2-levels-deep foreach loop to LINQ expression to produce Dictionary<string,SomeList<Item>>?

How do I transform this function:
void MyFunc () {
foreach (var k in problems.Keys)
{
var list = new ObservableCollection<ListViewItem>();
listViewItems.Add(k, list);
foreach (var i in problems[k].Items)
{
list.Add(new ListViewItem
{
Token = i.Token,
IsFatalError = i.IsFatal,
Checked = false,
Line = i.Token.Position.Line,
Description = i.Description,
BackgroundBrush = i.IsFatal ? Brushes.Red : null
});
}
}
}
to LINQ query syntax ? here are the types and variables:
public class ProblemsList {
public class Problem {
public IToken Token { get; set; }
public string Description { get; set; }
public bool IsFatal { get; set; }
}
public List<Problem> Items { get { return problems; } }
}
public class ListViewItem {
public bool IsFatalError { get; set; }
public bool Checked { get; set; }
public int Line { get; set; }
public string Description { get; set; }
public Brush BackgroundBrush { get; set; }
}
Dictionary<string, ProblemsList> problems;
Dictionary<string, ObservableCollection<ListViewItem>> listViewItems
= new Dictionary<string, ObservableCollection<ListViewItem>>();
Here's how I would do it (using chained methods syntax):
listViewItems = problems.ToDictionary(
p => p.Key,
p => new ObservableCollection<ListViewItem>(
p.Value.Items.Select(
i => new ListViewItem
{
Token = i.Token,
IsFatalError = i.IsFatal,
Checked = false,
Line = i.Token.Position.Line,
Description = i.Description,
BackgroundBrush = i.IsFatal ? Brushes.Red : null
}
)
)
);
Update
A version that uses query syntax as much as possible:
listViewItems = (
from p in problems
select new
{
Key = p.Key,
Value = from i in p.Value.Items
select new ListViewItem
{
Token = i.Token,
IsFatalError = i.IsFatal,
Checked = false,
Line = i.Token.Position.Line,
Description = i.Description,
BackgroundBrush = i.IsFatal
? Brushes.Red
: null
}
}
).ToDictionary(x => x.Key, x => x.Value);

Categories