c# Merging 3 collection list into one list - c#

I have 3 collection list as below.
public static List<Thing> English = new List<Thing>
{
new Thing {ID = 1, Stuff = "one"},
new Thing {ID = 2, Stuff = "two"},
new Thing {ID = 3, Stuff = "three"}
};
public static List<Thing> Spanish = new List<Thing>
{
new Thing {ID = 1, Stuff = "uno"},
new Thing {ID = 2, Stuff = "dos"},
new Thing {ID = 3, Stuff = "tres"},
new Thing {ID = 4, Stuff = "cuatro"}
};
public static List<Thing> German = new List<Thing>
{
new Thing {ID = 1, Stuff = "eins"},
new Thing {ID = 2, Stuff = "zwei"},
new Thing {ID = 3, Stuff = "drei"}
};
During runtime, the length of the list may vary. For eg, German may take 5 values, english with 2 and spanish with one.
I need to find which list has the max value and need to get the output in the below format.
Id English German Spanish
1 one eins uno
2 two zwei dos
3 three drei tres
4 cuatro
Can you please help me to solve this.

Try this:
English.Select(t => new Tuple<Thing,int>(t, 1)).Concatenate(
German.Select(t => new Tuple<Thing,int>(t, 2)).Concatenate(
Spanish.Select(t => new Tuple<Thing,int>(t, 3))
)
).GroupBy(p => p.Item1.ID)
.Select(g => new {
Id = g.Key
, English = g.Where(t => t.Item2==1).Select(t => t.Item2.Stuff).SingleOrDefault()
, German = g.Where(t => t.Item2==2).Select(t => t.Item2.Stuff).SingleOrDefault()
, Spanish = g.Where(t => t.Item2==3).Select(t => t.Item2.Stuff).SingleOrDefault()
});
The idea is to tag the original items with their collection origin (1 for English, 2 for German, 3 for Spanish), group them by ID, and then pull the details for individual languages using the tag that we added in the first step.

If they all start at one and never skip any numbers (but can end at any point) then you can use a more simple approach, such as this:
int count = Math.Max(English.Count, Math.Max(Spanish.Count, German.Count));
var query = Enumerable.Range(0, count)
.Select(num => new
{
Id = num + 1,
English = GetValue(English, num),
Spanish = GetValue(Spanish, num),
German = GetValue(German, num),
});
If it's possible for numbers to be skipped, or not start at one, then you could use this slightly more complex approach:
var englishDic = English.ToDictionary(thing => thing.ID, thing => thing.Stuff);
var spanishDic = Spanish.ToDictionary(thing => thing.ID, thing => thing.Stuff);
var germanDic = German.ToDictionary(thing => thing.ID, thing => thing.Stuff);
var query = englishDic.Keys
.Union(spanishDic.Keys)
.Union(germanDic.Keys)
.Select(key => new
{
Id = key,
English = GetValue(englishDic, key),
Spanish = GetValue(spanishDic, key),
German = GetValue(germanDic, key),
});
A few helper functions were needed to avoid invalid argument errors:
public static string GetValue(Dictionary<int, string> dictionary, int key)
{
string output;
if (dictionary.TryGetValue(key, out output))
return output;
else
return "";
}
public static string GetValue(List<Thing> list, int index)
{
if (index < list.Count)
return list[index].Stuff;
else
return "";
}

This was fun :)
I did this, which works, but, like many of these answers, isn't very efficient:
public IEnumerable ListEmAll() {
return new List<int>() // just for balance, start with empty list
.Union( English.Select(o => o.ID) )
.Union( Spanish.Select(o => o.ID) )
.Union( German.Select(o => o.ID) )
.OrderBy(id => id)
.Select(id =>
new
{
ID = id,
English = English.Where(o => o.ID == id).Select(o => o.Stuff),
Spanish = Spanish.Where(o => o.ID == id).Select(o => o.Stuff),
German = German.Where(o => o.ID == id).Select(o => o.Stuff)
});
}
But what I like better is to not use Linq, and return a compound dictionary... no expensive lookups on the lists.
// keep a list of the languages for later
static Dictionary<string, List<Thing>> languages = new Dictionary<string, List<Thing>>(){
{"English", English},
{"Spanish", Spanish},
{"German", German}
};
// result[3]["English"] = "three"
public Dictionary<int, Dictionary<string, string>> ListEmAll_better() {
Dictionary<int, Dictionary<string, string>> result = new Dictionary<int, Dictionary<string, string>>();
foreach(var lang in languages.Keys) {
foreach(var thing in languages[lang]) {
if(!result.ContainsKey(thing.ID)) {
result[thing.ID] = new Dictionary<string, string>();
}
result[thing.ID][lang] = thing.Stuff;
}
}
return result;
}

Related

Select all from one List, replace values that exist on another List

Few days ago I asked same question with SQL, but now it arises in C# code
Lets say we have this kind of class for holding different id/text pairs:
public class Text {
public int id { get; set; }
public string text { get; set; }
...
}
Now lets populate some data,
ListA gets a lot of data:
List<Text> ListA = new List<Text>{
new () {id = 1, text = "aaa1"},
new () {id = 2, text = "aaa2"},
new () {id = 3, text = "aaa3"},
new () {id = 4, text = "aaa4"},
new () {id = 5, text = "aaa5"},
new () {id = 6, text = "aaa6"},
};
And ListB gets just a little bit of data:
List<Text> ListB = new List<Text>{
new () {id = 4, text = "bbb4"},
new () {id = 5, text = "bbb5"},
};
And now what we are looking:
var result = ... // Some Linq or Lambda magic goes here
// and if we do:
foreach(var item in result){
Console.WriteLine(item.Id + " " + item.Text);
}
// Result will be:
1 : aaa1
2 : aaa2
3 : aaa3
4 : bbb4
5 : bbb5
6 : aaa6
You can try looking for id within ListB:
var result = ListA
.Select(a => ListB.FirstOrDefault(b => b.id == a.id) ?? a);
Here for each a within ListA we try to find corresponding by id (b.id == a.id) item within ListB. If no such item is found we just return ListA item: ?? item
In case of .Net 6 you can use overloaded .FirstOrDefault version (we can pass a as a default value):
var result = ListA
.Select(a => ListB.FirstOrDefault(b => a.id == b.id, a));
It might be more efficient to convert ListB to a Dictionary first:
var dictB = ListB.ToDictionary(x=> x.id)
Then you can write
var result = ListA.Select(x => dictB.TryGetValue(x.id, out var b) ? b : x)
UPD Rewrote taking comment suggestions into account
One option is to do an Union operation, by specifying an EqualityComparer. If the order is important, you can do an OrderBy operation at the end.
class TextIdComparer : EqualityComparer<Text> {
public override bool Equals(Text x, Text y) => x.id == y.id;
}
var result = ListB.Union(ListA, new TextIdComparer()).OrderBy(x => x.id)

getting error "The given key was not present in the dictionary" due to "SubKey2" not present for "BaseKey2"

I have common subkeys (SubKey1/SubKey2, etc) for each set of BaseKey1, BaseKey2, .... etc, but occurrence of all subkeys for each basekey is NOT fixed. In below example "SubKey2" not present for "BaseKey2".
var model = new Dictionary<string, Dictionary<string, Model>>
{
{
"BaseKey1",
new Dictionary<string, Model>
{
{"SubKey1", new Model { Time = new DateTime(2020, 09, 15)}},
{"SubKey2", new Model { Time = new DateTime(2020, 12, 15) }}
}
},
{
"BaseKey2",
new Dictionary<string, Model>
{
{"SubKey1", new Model { Time = new DateTime(2020, 11, 15) }},
}
}
};
I need to pull Min Time for each subkey from all basekey and to do so I am doing below,
var query = model.Values
.SelectMany(d => d.Keys)
.Distinct()
.Select(key => new
{
Key = key,
Time = model.Values.Min(v => v[key].Time)
})
.ToList();
But it's giving error "The given key was not present in the dictionary" due to "SubKey2" not present for "BaseKey2". What could be solution here? I need below output. Thanks!
You're not going to be able to do this without some loops -- the fact that you're using a dictionary is more or less irrelevant.
My understanding is that you want something like this:
var result = new Dictionary<string, Model>();
// For each "BaseKey" dictionary...
foreach (var subDictionary in model.Values)
{
// For each "SubKey" and its corresponding value
foreach (var (key, value) in subDictionary)
{
// If we haven't yet recorded a value for this SubKey, add this value
// If we have, but it's higher than the value for this SubKey, replace it
if (!result.TryGetValue(key, out var existingValue) || value.Time < existingValue.Time)
{
result[key] = value;
}
}
}
See it working here.
You can sprinkle in a bit of LINQ to remove one of the loops quite easily:
var result = new Dictionary<string, Model>();
foreach (var (key, value) in model.Values.SelectMany(x => x))
{
if (!result.TryGetValue(key, out var existingValue) || value.Time < existingValue.Time)
{
result[key] = value;
}
}
See it working here.
If you really want to LINQ it up, you'll want something like:
var result = model.Values
.SelectMany(x => x)
.GroupBy(x => x.Key)
.ToDictionary(
x => x.Key,
x => x.Select(m => m.Value)
.MinBy(m => m.Time));
... where MinBy is provided by e.g. this answer. That's probably going to be measurably slower though, and isn't any shorter.
See it working here.
Check this
Dictionary<string, Model> flatSubDict = new Dictionary<string, Model>();
foreach(var key in model.Keys)
{
foreach(var mKey in model[key].Keys)
{
if(flatSubDict.TryGetValue(mKey, out var m))
{
// if existing time greater than new time then replace
if(m.Time > model[key][mKey].Time)
{
flatSubDict[mKey] = m;
}
}
else
flatSubDict[mKey] = m;
}
}
return flatSubDict.Values;

LINQ merging 2 lists, keeping seqeunce and origin [duplicate]

This question already has answers here:
LINQ - Full Outer Join
(16 answers)
Closed 4 years ago.
Here I have 2 lists of same object type.
object = {id: xxx, ...} // attribute "id" is used to find the identical obj
List oldSet = [old1, old2, old3];
List newSet = [new2, new3, new4];
// old2 = {id= 2, result = 5, ...}
// new2 = {id= 2, result = 1, ...}
// expected result = {oldSet: old2; newSet: new2}
I want to merge both lists, also keeping the origin of which list it came from.
The expected result as below:
List mergedSet = [{old1, null}, {old2, new2}, {old3, new3}, {null, new4}];
I'm thinking to use LINQ C# for it, but stuck somewhere.
Kindly advise.
Thanks! :)
Here's some code that does what you want using Linq. It basically walks through all the old list, and adds pairs to the merged list by looking for matches from the new list (and adding null as the second item if no match was found). Then it walks through the remaining items in the new list and adds them with null for the first item. It selects a dynamic type with two properties: OldSet and NewSet, so you know where each item came from.
The merge code is simply:
var mergedSet = oldSet.Select(o =>
new {OldSet = o, NewSet = newSet.FirstOrDefault(n => n.id == o.id)})
.Concat(newSet.Where(n => oldSet.All(o => o.id != n.id)).Select(n =>
new {OldSet = (Item) null, NewSet = n}));
This is based on the following item class:
class Item
{
public int id { get; set; }
public string result { get; set; }
public override string ToString()
{
return $"{result}{id}";
}
}
We create our lists:
List<Item> oldSet = new List<Item>
{
new Item {id = 1, result = "old"},
new Item {id = 2, result = "old"},
new Item {id = 3, result = "old"},
};
List<Item> newSet = new List<Item>
{
new Item {id = 2, result = "new"},
new Item {id = 3, result = "new"},
new Item {id = 4, result = "new"},
};
Run the merge code (very first snippet), and then display results:
foreach (var item in mergedSet)
{
Console.WriteLine($"{item.NewSet},{item.OldSet}");
}
Output
Try something like this :
List<string> oldSet = new List<string>() {"old1", "old2", "old3"};
List<string> newSet = new List<string>() {"new2", "new3", "new4"};
var results = oldSet.Select((x,i) => new { oldSet = x, newSet = newSet[i]}).ToList();
You can left join the two lists. I edited the answer as you actually need to left join twice, union, and apply a select distinct to get the cases where oldSet = null and no duplicates...
var mergedSet = (from o in oldSet
join n in newSet on o.id equals n.id into ns
from n in ns.DefaultIfEmpty()
select new { OldSet = o, NewSet = n })
.Union(from n in newSet
join o in oldSet on n.id equals o.id into os
from o in os.DefaultIfEmpty()
select new { OldSet = o, NewSet = n })
.Distinct();
Might be an overkill, but if you really want to use LINQ
List<Item> oldSet = new List<Item>
{
new Item {id = 1, result = "old"},
new Item {id = 2, result = "old"},
new Item {id = 3, result = "old"},
};
List<Item> newSet = new List<Item>
{
new Item {id = 2, result = "new"},
new Item {id = 3, result = "new"},
new Item {id = 4, result = "new"},
};
var resultL = oldSet.GroupJoin(
newSet,
o => o.id,
n => n.id,
(o,n) => new { Old = o, New = n })
.SelectMany(
n => n.New.DefaultIfEmpty(),
(o,n) => new Tuple<Item,Item>(o.Old,n));
var resultR= newSet.GroupJoin(
oldSet,
n => n.id,
o=> o.id,
(n,o) => new { Old = o, New = n })
.SelectMany(
o=> o.Old.DefaultIfEmpty(),
(n,o) => new Tuple<Item,Item>(o,n.New));
var result = resultL.Union(resultR).Distinct();
In this case, you have to use two GroupJoin and the Union the results.
Look at the following code:
var res1 = oldSet.GroupJoin(newSet, o => o, k => k, (x, y) => { var yy = y.FirstOrDefault(); return new { X = x, Y = yy }; });
var res2 = newSet.GroupJoin(oldSet, o => o, k => k, (x, y) => { var yy = y.FirstOrDefault(); return new { X = yy, Y = x }; });
var result = res1.Union(res2).ToList();// Your result is here

How to return Distinct Row using LINQ

I have two rows which have all the data same except one column.
I want to show only one row on the UI but one row which has different data should be shown as comma seperated values.
Sample Data
PricingID Name Age Group
1 abc 56 P1
1 abc 56 P2
Output should be :
PricingID Name Age Group
1 abc 56 P1,P2
I am using this approach but it is not working , it gives me two rows only but data i am able to concatenate with comma.
List<PricingDetailExtended> pricingDetailExtendeds = _storedProcedures.GetPricingAssignment(pricingScenarioName, regionCode, productCode, stateCode, UserId, PricingId).ToList();
var pricngtemp = pricingDetailExtendeds.Select(e => new
{
PricingID = e.PricingID,
OpportunityID = e.OpportunityID,
ProductName = e.ProductName,
ProductCD = e.ProductCD
});
pricingDetailExtendeds.ForEach(e=>
{
e.ProductCD = string.Join(",",string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductCD).ToArray())).Split(',').Distinct().ToArray());
e.OpportunityID =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.OpportunityID).ToArray())).Split(',').Distinct().ToArray());
e.ProductName =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductName).ToArray())).Split(',').Distinct().ToArray());
}
);
// pricingDetailExtendeds = GetUniquePricingList(pricingDetailExtendeds);
return pricingDetailExtendeds.Distinct().AsEnumerable();
Any body can suggest me better approach and how to fix this issue ?
Any help is appreciated.
You want to use the GroupBy linq function.
I then use the String.Join function to make the groups comma seperated.
So something like this:
var pricingDetailExtendeds = new[]
{
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P1"
},
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P2"
}
};
var pricngtemp =
pricingDetailExtendeds.GroupBy(pde => new {pde.PricingID, pde.Name, pde.Age})
.Select(g => new {g.Key, TheGroups = String.Join(",", g.Select(s => s.Group))}).ToList();
You can easily extrapolate this to the other fields.
To return the PricingDetailExtended, the just create it in the select. So something like this
.Select(g => new PricingDetailExtended {
PricingID = g.Key.PricingId,
TheGroups = String.Join(",", g.Select(s => s.Group))
}).ToList();
You won't have the field TheGroups though, so just replace that field with the proper one.
An example of what I was describing in my comment would be something along the lines of the following. I would expect this to be moved into a helper function.
List<PriceDetail> list = new List<PriceDetail>
{
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P1"},
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P2"},
new PriceDetail {Id = 2, Age = 56, Name = "abc", group = "P1"}
};
Dictionary<PriceDetailKey, StringBuilder> group = new Dictionary<PriceDetailKey, StringBuilder>();
for (int i = 0; i < list.Count; ++i)
{
var key = new PriceDetailKey { Id = list[i].Id, Age = list[i].Age, Name = list[i].Name };
if (group.ContainsKey(key))
{
group[key].Append(",");
group[key].Append(list[i].group);
}
else
{
group[key] = new StringBuilder();
group[key].Append(list[i].group);
}
}
List<PriceDetail> retList = new List<PriceDetail>();
foreach (KeyValuePair<PriceDetailKey, StringBuilder> kvp in group)
{
retList.Add(new PriceDetail{Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
}
you could even convert the final loop into a LINQ expression like:
group.Select(kvp => new PriceDetail {Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
Its worth noting you could do something similar without the overhead of constructing new objects if, for example, you wrote a custom equality comparer and used a list instead of dictionary. The upside of that is that when you were finished, it would be your return value without having to do another iteration.
There are several different ways to get the results. You could even do the grouping in SQL.

How to sort List according to value in an array

I have
List<string> strs;
double[] values;
where the values array contains the value of each of the string in strs list
Say strs={"abc","def","ghi"}
and values={3,1,2}
this means "abc" has value 3 and so on.
I wish to sort strs and values ordered by values, such that it becomes
strs={"def","ghi","abc"}
values={1,2,3}
Is there any easy way to achieve this?
The Array.Sort method has an overload that takes two arrays and sorts both arrays according to the values in the first array, so make an array out of the list:
string[] strsArr = strs.ToArray();
Then sorting them can't be simpler:
Array.Sort(values, strsArr);
And then back to a list, if you need that:
strs = strsArr.ToList();
You can use Enumerable.Zip, then sort the result, then extract the list of strings.
Something like:
var result = strs.Zip(values, (first, second) => new Tuple<string, double>(first, second))
.OrderBy(x => x.Item2)
.Select(x => x.Item1)
.ToList();
How are you setting up these collections? Or are you given these two parameters?
You could create a StringAndOrder class and use LINQ:
public class StringAndOrder
{
public string String { get; set; }
public double Order { get; set; }
}
List<StringAndOrder> list; //create with this structure instead
var orderedStrings = list.OrderBy(item => item.Order).Select(item => item.String);
var sortedStrs = strs.Select((i, s) => new {Value = values[i], Str = s})
.OrderBy(x => x.Value)
.Select(x => x.Str).ToList();
If you could logically put those values as properties of a class, such as:
class NameAndOrder
{
public string Name;
public int Order;
}
Then it would be better and more organized, and then you could do:
var items = new List<NameAndOrder>(strs.Count);
for (var i = 0; i < strs.Count; i++)
{
items.Add(new NameAndOrder { Name = strs[i], Order = values[i] });
}
items.Sort((a, b) => a.Order.CompareTo(b.Order));
Why Don't you use Dictionary Object..
Dictionary<string, int> dictionary =
new Dictionary<string, int>();
dictionary.Add("cat", 2);
dictionary.Add("dog", 1);
dictionary.Add("llama", 0);
dictionary.Add("iguana", -1);
// Acquire keys and sort them.
var list = dictionary.Keys.ToList();
list.Sort();
var strs = new[] { "abc", "def", "ghi" };
var values = new[] { 3, 1, 2 };
var newArr = strs.Select((s, i) => new { s, i })
.OrderBy(x => values[x.i])
.Select(x => x.s)
.ToArray();

Categories