Initialise Generic List whilst providing multiple collections? c# - c#

var a = new []{"a"};
var b = new []{"b"};
var c = new []{"c"};
Is it possible to declare / initialise a generic list, providing the three collections above in one line?
var l = new List<string>(a); //fine for one
var l2 = new List<string>(new[] { a, b, c }.SelectMany(x => x)); //this will work but its horrible!

How about:
var list = a.Concat(b).Concat(c).ToList();

Related

Dictionary with a list of integers as key where the order of the values in the list should matter

Hi I'm trying to use a Dictionary with a list of integers as key where the order of the values in the list should matter. So if a have something like this:
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
list1.Add(3);
List<int> list2 = new List<int>();
list2.Add(1);
list2.Add(2);
list2.Add(3);
Dictionary<List<int>,int> dictionary = new Dictionary<List<int>,int>();
dictionary.Add(list2 , 100);
I want to be able to access the value in the dictionary with list2. So in term of functionality I want it to work similar to
Enumerable.SequenceEqual(list1, list2)
You should build your Dictionary using the constructor accepting an IEqualityComparer.
In the implementation of that IEqualityComparer you should use SequenceEqual instead of / in addition to standard equality.
Example (on C# PlayGround):
public class EnumerableComparer<T> : IEqualityComparer<IEnumerable<T>>
{
public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
{
return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y));
}
public int GetHashCode(IEnumerable<T> obj)
{
unchecked
{
return obj.Where(e => e != null).Select(e => e.GetHashCode()).Aggregate(17, (a, b) => 23 * a + b);
}
}
}
var key1 = new List<int> { 1, 3, 5 };
var key2 = new List<int> { 2, 4, 6 };
var key3 = new List<int> { 1, 3, 5 };
var comparer = new EnumerableComparer<int>();
var dictionary = new Dictionary<List<int>, string>(comparer);
dictionary.Add(key1, "hello");
dictionary.Add(key2, "world");
Console.WriteLine(dictionary[key3]); // prints "hello"
Info: A quick research shows that the mentioned Dictionary constructor is available since at least .NET Framework 2.0. It's available in all versions up to the most current one (at the time of writing: .NET 6).
Update (2022-03-25): renamed IEnumerableComparer to EnumerableComparer
The easiest solution would be to just serialize the list of ints into a string.
For example:
Dictionary<string,int> dictionary = new Dictionary<string,int>();
dictionary.Add(string.Join(",", list2), 100);
You could do a less hacky thing where you define your own class which encapsulates the list and properly implements GetHashCode() and Equals(). important: You will need to be careful with this class since lists are mutable. If you insert something into the list after you have added the item to the dictionary you won't get the expected behavior:
For example:
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
list1.Add(3);
MyListWrapper wrapper1 = new MyListWrapper(list1);
List<int> list2 = new List<int>();
list2.Add(1);
list2.Add(2);
list2.Add(3);
MyListWrapper wrapper2 = new MyListWrapper(list2);
Dictionary<MyListWrapper,int> dictionary = new Dictionary<MyListWrapper,int>();
dictionary.Add(wrapper1, 100);
Assert.AreEqual(100, dictionary[wrapper2]); // this will work
list1.Add(5);
list2.Add(5);
Assert.AreEqual(100, dictionary[wrapper2]); // this will NOT work

Not Equal two Generic Lists

I have two generic lists. They have mostly different fields, but there are 4 fields they have in common. I want to get the list of items that are in one of the lists but not the other using those four fields as the definition of "equality".
Here was my attempt at solving the problem.
var unMatchedData = from liveLines in liveList
join oldList in comapreSnapshotList
on new {liveLines.ClientNo, liveLines.SequenceNo, liveLines.LineNo, liveLines.Text} equals
new {oldList.ClientNo, oldList.SequenceNo, oldList.LineNo, oldList.Text}
select new KNOWTXTS
{
ClientNo = liveLines.ClientNo,
SequenceNo = liveLines.SequenceNo,
LineNo = liveLines.LineNo,
Text = liveLines.Text
};
You can use Except to find the set difference.
var newElements = liveList.Select(l => new {
l.ClientNo, l.SequenceNo, l.LineNo, l.Text
});
var oldElements = comapreSnapshotList.Select(l => new {
l.ClientNo, l.SequenceNo, l.LineNo, l.Text
});
var newElementsInNew = newElements.Except(oldElements);
var deletedFromNew = oldElements.Except(newElements);
// if you need the original object in the list
var newElements = from obj in liveList
join newEle in newElementsInNew
on new {obj.ClientNo, obj.SequenceNo, obj.LineNo, obj.Text} equals newEle
select obj;
var deletedElements = from obj in comapreSnapshotList
join deletedEle in deletedFromNew
on new {obj.ClientNo, obj.SequenceNo, obj.LineNo, obj.Text} equals deletedEle
select obj;

Is it possible to use Intersect on complex arrays in C#?

I have two arrays of student names and test scores.
Each array contains only distinct students (no duplicates) and is structured such that arrStudentGroup1[0][0] = "Bob" and arrStudentGroup1[0][1] = "98".
Given two arrays is it possible to use Intersect to create a third array of students that exists in both arrStudentGroup1 and arrStudentGroup2?
I'd like the third array to have their names and test scores. How do I do this?
If you want just the names students that are in both groups and not the associated test scores, just intersect on the name array element:
var students1 = arrStudentGroup1.Select(group => group[0]);
var students2 = arrStudentGroup2.Select(group => group[0]);
var studentsInBoth = students1.Intersect(students2);
If you also want the associated test scores you'll need to implement an IEqualityComparer<T> that compares the first element of each array.
If you want the associated test scores then join the two arrays:
var intersection = from s1 in arrStudentGroup1
join s2 in arrStudentGroup2 on s1[0] equals s2[0]
select new {Name = s1[0], Score1 = s1[1], Score2 = s2[1]}
foreach (var item in intersection)
{
Console.Writeline("{0}: s1={1}, s2={2}", Name, Score1, Score2);
}
First zip (in the general sense, rather than the Zip method) the arrays into a structure that groups students with scores:
Given:
string[][] arrStudentGroup1 = new string[][]{new string[]{"Bob","98"}, new string[]{"Alice","98"}, new string[]{"Charles","78"}, new string[]{"Dariah","99"}};
string[][] arrStudentGroup2 = new string[][]{new string[]{"Bob","98"}, new string[]{"Fiona","98"}, new string[]{"Eve","78"}, new string[]{"Dariah","99"}};
Then:
var zipped1 = arrStudentGroup1.Select(student => new {Name = student[0], Score = student[1]});
var zipped2 = arrStudentGroup2.Select(student => new {Name = student[0], Score = student[1]});
Now get the intersection. Note that if the same student name was in one but with a different score, it would not count as an intersection. That can be dealt with too, but I'm interpreting your question as not wanting that case. Let me know if I read you wrong:
var inter = zipped1.Intersect(zipped2);
Now, you can ideally work with this anyway, or even with new {Name = student[0], Score = int.Parse(student[1])} above and have a number instead of a string (more useful in most cases), which frankly is nicer than dealing with an array of arrays, along with being more typesafe. Still, if you really want it in the same format string[] format:
var interArray = inter.Select(st => new string[]{st.Name, st.Score});
And if you really, really want the whole thing in the same string[][] format:
var interArrays = interArray.ToArray();
Or for the one-line wonder (less good readability mostly, but sometimes it's nice to put a query on one line if there's other things going on in the same method):
var interArrays = arrStudentGroup1
.Select(student => new {Name = student[0], Score = student[1]})
.Intersect(
arrStudentGroup2
.Select(student => new {Name = student[0], Score = student[1]})
).Select(st => new string[]{st.Name, st.Score}).ToArray()
Output:
{"Bob", "98"},{"Dariah", "99"}
Edit: Alternatively, define an IEqualityComparer<string[]> like:
public class StudentComparer : IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if(ReferenceEquals(x, y))
return true;
if(x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(string[] arr)
{
return arr == null ? 0 : arr.Select(s => s == null ? 0 : s.GetHashCode()).Aggregate((x, y) => x ^ y);
}
}
Then just use it directly:
var intersection = arrStudentGroup1.Intersect(arrStudentGroup2, new StudentComparer());
Gives the same output. Simpler really, but my instincts upon seeing arrays being used as objects was to get it into a real object as soon as I can, and really, it's not a bad instinct - it can make much else easier too.
Well, you could do somthing like this,
var studentsInGroup1 = arrStudentGroup1.Select(s => new
{
Name = s[0],
Score = s[1]
});
var studentsInGroup2 = arrStudentGroup2.Select(s => new
{
Name = s[0],
Score = s[1]
});
var studentsInBothGroups = studentsInGroup1.Join(
studentsInGroup2,
s => s.Name,
s => s.Name,
(one, two) => new
{
Name = one.Name,
Scores = new[] { one.Score, two.Score }
});
Which should give you a handy anonymous type you can access like this.
foreach(var student in studentsInBothGroups)
{
var Name = student.Name;
var Group1Score = student.Scores[0];
var Group2Score = student.Scores[1];
}

How to cast anonymous type to primitive type member using Lambda or Linq

Hi I am trying to use Lambda Expression to cast anonymous type list to primitive type list, so far I have no luck.
I know I can use foreach to loop through 'a' and get the values, but I want to know how to do it in Lambda or Linq.
var a = new []{ new {name= "jerry", age = 32}, new {name="peter",age=23}};
List<string> b = a.Select(p => new System.String{p.name}).ToList();
List<int> c = a.Select(p => new System.Int32{p.age}).ToList();
var a = new[] { new { name = "jerry", age = 32 }, new { name = "peter", age = 23 } };
List<string> b = a.Select(p => p.name).ToList();
List<int> c = a.Select(p => p.age).ToList();
The problem is with your use of { and } in new System.String{p.name}. That isn't a valid way to instantiate a type.
var a = new[] { new { name = "jerry", age = 32 },new {name = "peter", age = 23}};
List<string> b = a.Select(p => p.name).ToList();
List<int> c = a.Select(p => p.age).ToList();
Why do you try to instantiate a new instance by calling constructors?
this:
var a = new []{ new {name= "jerry", age = 32}, new {name="peter",age=23}};
List<string> b = a.Select(p => p.name}).ToList();
List<int> c = a.Select(p => p.age).ToList();
works fine. p.Age is an integer and therefore a value type. That means that it's copied by value so you do get a unique copy in your list c. p.name is a string. In .NET strings are immutable so it's perfectly save to share the instance of a string.
Note also that both constructs dont even exist!
Another note is that when calling a constructor, you need to use () not { }

Merging Dictonaries in C# using LINQ

i've three Dictonaries like
Dictionary<int,List<string>> D1 = new Dictionary<int,List<string>>();
Dictionary<int,List<string>> D2= new Dictionary<int,List<string>>();
Dictionary<int,List<string>> D3 new Dictionary<int,List<string>>();
D1[1] = new List<string>{"a","b"};
D1[2] = new List<string>{"c","d"};
D1[3] = new List<string>{"e","f"};
D1[4] = new List<string>{"h"};
D2[1] = new List<string>{"a","b"};
D2[2] = new List<string>{"c","d"};
D2[3] = new List<string>{"e","f"};
D2[4] = new List<string>{"g"};
D2[5] = new List<string>{"b","h"};
D2[6] = new List<string>{"f","l"};
D2[7] = new List<string>{"z"};
i need to merge the two dictonary into a single dictonary
like
D3[1] = {"a","b","h"}
D3[2] = {"c","d"}
D3[3] = {"e","f","l"}
Merging Rule:
D1[1]={"a","b"} this list will be compared with the values in the D2
D2[1]={"a","b"}
D2[5]={"b","h"}
so the above three will be merged into
D3[1]={"a","b","h"}
is there any idea to do this using LINQ
However are you trying to merge the values, you will probably want to use one of these options:
D3[1] = D1[1].Union(D2[1]);
or
D3[1] = D1[1].Concat(D2[1]);
Edit - an ugly-looking method for joined merges Linq-style:
foreach (var kvp in D1)
{
D3[kvp.Key] =
(from string letter in kvp.Value
select
(from IEnumerable<string> list in D2.Values
where list.Contains(letter)
select list)
// Union all the D2 lists containing a letter from D1.
.Aggregate((aggregated, next) => aggregated.Union(next)))
// Union all the D2 lists containing all the letter from D1.
.Aggregate((aggregated, next) => aggregated.Union(next))
// Convert the unioned letters to a List.
.ToList();
}
The code keeps the lists in D2, it would be pretty easy to modify the code to remove the matched lists from D2.
Something like this (maybe needs optimisation)?
var lr =
(from gr in
(from pair in D1.Union(D2).Union(D3)
group pair by pair.Key)
select new KeyValuePair<int, IEnumerable<List<string>>>(gr.Key, gr.Select(x => x.Value))
).ToDictionary(k => k.Key, v => v.Value.Aggregate((t, s) => (new List<string>(t.Union(s)))));

Categories