Merge, Union, Intersect C# List of Objects [closed] - c#

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I am trying to solve this problem:
I have multiple array of string with some having duplicate items. I need to come up with a final list that has most items in each list
a1 = Array{"A", "B", "C","D","E","F"};
a2 = Array{"A", "B", "B", "C","D","D","D","E","F"};
a3 = Array{"A", "B", "B", "C","D","D","E","F"};
a4 = Array{"A", "B", "B", "B", "C","D","D","E","F"};
a5 = Array{"A", "B", "B", ","D","D","E","E","F"};
Final result should be:
FinalArray = {"A", "B", "B", "B", "C","D","D","D","E","E""F"};
Max. occurrence each items accounted in the final result.
How can I achieve this?

One simple way to solve do this is to first create a list to store the results, and then iterate through the unique items in each array, and add the difference between the count of items in the current array with the count of items in the results (if it's a positive number).
For example:
var arrays = new[]
{
new[] {"A", "B", "C", "D", "E", "F"},
new[] {"A", "B", "B", "C", "D", "D", "D", "E", "F"},
new[] {"A", "B", "B", "C", "D", "D", "E", "F"},
new[] {"A", "B", "B", "B", "C", "D", "D", "E", "F"},
new[] {"A", "B", "B", "C", "D", "E", "E", "F"},
};
var result = new List<string>();
foreach (var array in arrays)
{
var distinctItems = array.Distinct();
foreach (var distinctItem in distinctItems)
{
var diff = array.Count(i => i == distinctItem) -
result.Count(i => i == distinctItem);
if (diff > 0) result.AddRange(Enumerable.Repeat(distinctItem, diff));
}
}
Console.WriteLine(string.Join(", ", result.OrderBy(i => i)));
Output

Simple.
var arrays = new[]
{
new[] {"A", "B", "C", "D", "E", "F"},
new[] {"A", "B", "B", "C", "D", "D", "D", "E", "F"},
new[] {"A", "B", "B", "C", "D", "D", "E", "F"},
new[] {"A", "B", "B", "B", "C", "D", "D", "E", "F"},
new[] {"A", "B", "B", "C", "D", "E", "E", "F"},
};
var result =
arrays
.SelectMany(xs => xs.GroupBy(x => x).Select(x => new { x.Key, Count = x.Count() }))
.GroupBy(x => x.Key, x => x.Count)
.Select(x => new { x.Key, Count = x.Max() })
.SelectMany(x => Enumerable.Repeat(x.Key, x.Count))
.ToArray();
That gives: A, B, B, B, C, D, D, D, E, E, F

Sounds like a homework problem so I'm not sure if you're supposed to use specific methods/logic to solve it but I'll describe how I'd solve it.
Break down the problem. The requirement (as I understand it) is to display each value the max number of times it appears in any one set. The first step would be to count how many times each value appears in each set, which can be accomplished using ‘GroupBy’ then the ‘Count’ aggregate:
aN.GroupBy( v => v )
.Select( g => new
{
Value = g.Key,
Count = g.Count(),
} );
Similarly, we would then combine the results into a single set and group them by value to obtain the ‘Max’ Count value for use in producing the result set:
combinedResults.GroupBy( at => at.Value )
.Select( g => new
{
Value = g.Key,
Count = g.Max( at => at.Count ),
} );
Before we continue, let's combine the first two steps, but even before that, let's combine the arrays into their own set of sets.
var sets = new List<IEnumerable<string>>
{
new string[]{ "A", "B", "C", "D", "E", "F" },
new string[]{ "A", "B", "B", "C", "D", "D", "D", "E", "F" },
... etc ...
};
var valueMaxCounts = sets
.Select( s =>
s.GroupBy( v => v )
.Select( g => new
{
Value = g.Key,
Count = g.Count(),
} ) )
.GroupBy( at => at.Value )
.Select( g => new
{
Value = g.Key,
Count = g.Max( at => at.Count ),
} );
So now we have a set of Values with the max number of times each value appeared in one of the input sets. We now want to iterate through the results and add each value Count times.
var resultList = new List<string>();
foreach( var vmc in valueMaxCounts )
{
//for( var i = 0; i < vmc.Count; ++I )
//{
// resultList.Add( vmc.Value );
//}
resultList.AddRange( Enumerable.Repeat( vmc.Value, vmc.Count ) );
}
The final Select of the query and loop can be replaced with a call to SelectMany:
...query up to .GroupBy( at => at.Value )...
.SelectMany( g => Enumerable.Repeat( g.Key, g.Max( at => at.Count ) ) )

Related

Removing almost-duplicates from nested list

If I have the following sublists, how can I remove 'duplicates' so that I only have L1, L2 and L3 remaining? I don't mind which variant remains, as long as the duplicates are gone.
List<List<string>> mylist = new List<List<string>>();
List<string> L1 = new List<string> { "a", "b", "c" };
List<string> L2 = new List<string> { "d", "e", "f" };
List<string> L3 = new List<string> { "g", "h", "i" };
List<string> L4 = new List<string> { "c", "a", "b" };
List<string> L5 = new List<string> { "a", "c", "b" };
List<string> L6 = new List<string> { "f", "d", "e" };
It's worth mentioning that I'm removing the duplicates to improve performance in another part of my program, so anything too intensive would not be appropriate. Thanks!
you can use Linq by applying Distinct function with a custom comparer like the following code:
1 - Create Custom generic comparer for List<T>:
public class GenericComparer<T> : IEqualityComparer<List<T>>
{
public bool Equals(List<T> x, List<T> y)
{
return x.Count == y.Count && x.All(xx => y.Contains(xx));
}
public int GetHashCode(List<T> obj)
{
int hashCode = 0;
foreach(T str in obj)
{
hashCode ^= str.GetHashCode();
}
return hashCode;
}
}
2 - call Distinct function with StringListComparer like :
List<List<string>> mylist = new List<List<string>>()
{
new List<string> { "a", "b", "c" },
new List<string> { "d", "e", "f" },
new List<string> { "g", "h", "i" },
new List<string> { "c", "a", "b" },
new List<string> { "a", "c", "b" },
new List<string> { "f", "d", "e" },
};
var result = mylist.Distinct(new GenericComparer<string>()).ToList();
3 - Demo
foreach(List<string> strList in result)
{
Console.WriteLine(string.Join(",", strList));
}
4- Result
a,b,c
d,e,f
g,h,i
If you have a list of integer, you can call Distinct method like :
var result1 = mylist1.Distinct(new GenericComparer<int>()).ToList();
I hope this help you out.

LINQ to get Distinct Count/Sort in List<List<string>>

I have a List<> that contains a List<string>, of which I need to determine the unique count from the List<string, and order by the frequency of the count.
Example:
"a","b","c"
"d","e","f"
"a","b"
"a", "b", "c"
"a", "b", "c"
"a","b"
This would output (rank / combination / frequency)
1 - "a", "b", "c" - 3
2 - "a", "b" - 2
3 "d", "e", "f" - 1
I can come up with a brute-force approach but can this be done more elegantly with LINQ? This isn't exactly a Cartesian approach from what I can tell.
Thanks.
You could write your own IEqualityComparer and use it with GroupBy.
public class StringArrayValueComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
=> x.SequenceEqual(y);
public int GetHashCode(List<string> obj)
=> obj.Aggregate(1, (current, s) => current * 31 + s.GetHashCode());
}
var list = new List<List<string>>(new[]
{
new List<string>(new [] { "a", "b", "c" }),
new List<string>(new [] { "d", "e", "f" }),
new List<string>(new [] { "a", "b" }),
new List<string>(new [] { "a", "b", "c" }),
new List<string>(new [] { "a", "b", "c" }),
new List<string>(new [] { "a", "b" })
});
var orderedList = list
.GroupBy(x => x, x => x, (x, enumerable) => new { Key = x, Count = enumerable.Count()}, new StringArrayValueComparer())
.OrderByDescending(x => x.Count)
.Select((x, index) => new { Rank = index + 1, Combination = x.Key, Frequency = x.Count });
foreach (var entry in orderedList)
{
Console.WriteLine($"{entry.Rank} - {string.Join(",", entry.Combination)} - {entry.Frequency}");
}
1 - a,b,c - 3
2 - a,b - 2
3 - d,e,f - 1

How to find if an element of a list is in another list and the name of element?

First I want to know if at least one element in a first list can be found in a second list.
List<string> list1 = new[] { "A", "C", "F", "H", "I" };
List<string> list2 = new[] { "B", "D", "F", "G", "L" };
I am using below code to do this -
bool isFound = list1.Intersect(list2).Any();
But I want to know which element is that. Like in above case it is 'F'
What is the best way to do this?
You just use Intersect only:
var result = list1.Intersect(list2);
Try:
List<string> list1 = new List<string> { "A", "C", "F", "H", "I" };
List<string> list2 = new List<string> { "B", "D", "F", "G", "L" };
String sel = list1.Intersect(list2).FirstOrDefault()??"";
Console.WriteLine(sel);
Try my Demo
You can use Enumerable.Intersect method only, you don't need to use Any in your case.
Produces the set intersection of two sequences.
List<string> list1 = new List<string>(){ "A", "C", "F", "H", "I" };
List<string> list2 = new List<string>(){ "B", "D", "F", "G", "L" };
var intersect = list1.Intersect(list2);
foreach (var i in intersect)
{
Console.WriteLine(i);
}
Output will be;
F
Here is a DEMO.
Instead of bool variable You can take another list variable like:
List<string> list3 Variable to get list of items which are forund in second list and assign the result to list3
List<string> list3= list1.Intersect(list2).ToList();

c# why is the intersect method returning this?

I have two arrays, array testAnswer holds "answers to a exam" and array inputAnswers holds "students answers to the exam".
I am trying to display the correct, and incorrect answers. In other words, trying to show what values testAnswer has that inputAnswers doesnt(incorrect answers), and also the values both arrays have in common(correct answers).
For this I have used the .Except and .Intersect method using linq; however I am getting this weird output:
B, D, A, C
Can anyone PLEASE help me, i've been at this for ages!
MY CODE:
private void button1_Click(object sender, EventArgs e)
{
string[] testAnswer = new string[20] { "B", "D", "A", "A", "C", "A", "B",
"A", "C", "D", "B", "C", "D", "A", "D", "C", "C", "B", "D", "A" };
string a = String.Join(", ", testAnswer);
// Reads text file line by line. Stores in array, each line of the
// file is an element in the array
string[] inputAnswer = System.IO.File
.ReadAllLines(#"C:\Users\Momo\Desktop\UNI\Software tech\test.txt");
string b = String.Join(", ", inputAnswer);
var inter = inputAnswer.Intersect(testAnswer);
foreach (var s in inter)
{
listBox1.Items.Add(s);
}
}
Intersect does set intersection, so it discards duplicate values. If you want to compare answers, a better option would be to go through the arrays in parallel:
for(int i=0; i<testAnswer.Length; i++) {
if(testAnswer[i] == inputAnswer[i])
listBox1.Items.Add(inputAnswer[i]); // or testAnswer[i], as appropriate
}

Displaying total number of common and uncommon elements between two arrays?

I have two arrays: array testAnswer holds "answers to a exam" and array inputAnswers holds "students answers to the exam".
When i run my code, it displays all the common elements of the two arrays(correct answers), and the uncommon elements (incorrect answers). However, instead of actually displaying the correct/incorrect answers, i want to be able to display the total number of correct/incorrect answers.
My code so far:
private void button1_Click(object sender, EventArgs e)
{
//Array holding answers to test
string[] testAnswer = new string[20] { "B", "D", "A", "A", "C", "A", "B", "A", "C", "D", "B", "C", "D", "A", "D", "C", "C", "B", "D", "A" };
string a = String.Join(", ", testAnswer);
//Reads text file line by line. Stores in array, each line of the file is an element in the array
string[] inputAnswer = System.IO.File.ReadAllLines(#"C:\Users\Momo\Desktop\UNI\Software tech\test.txt");
string b = String.Join(", ", inputAnswer);
//Increments through array elements in both arrays and checks for matching elements. Displays in listBox.
for (int i = 0; i < testAnswer.Length; i++)
{
if (testAnswer[i] == inputAnswer[i])
listBox1.Items.Add(inputAnswer[i]); // or testAnswer[i], as appropriate
}
//Increments through array elements in both arrays and checks for uncommon elements. Displays in listBox.
for (int i = 0; i < testAnswer.Length; i++)
{
if (testAnswer[i] != inputAnswer[i])
listBox2.Items.Add(inputAnswer[i]);
}
}
Here's how to get your results using LINQ:
var results =
testAnswer
.Zip(inputAnswer, (t, i) => new { t, i })
.Aggregate(new { Correct = 0, Incorrect = 0 },
(a, ti) => new
{
Correct = a.Correct + (ti.t == ti.i ? 1 : 0),
Incorrect = a.Incorrect + (ti.t != ti.i ? 1 : 0)
});
It'll produce an anonymous variable with this kind of result:
An alternative approach is:
var query =
testAnswer
.Zip(inputAnswer, (t, i) => t == i)
.ToLookup(x => x);
var results = new
{
Correct = query[true].Count(),
Incorrect = query[false].Count()
};
The following code will provide 2 integers at the end which will hold the answer:
private void button1_Click(object sender, EventArgs e)
{
string[] testAnswer = new string[20] { "B", "D", "A", "A", "C", "A", "B", "A", "C", "D", "B", "C", "D", "A", "D", "C", "C", "B", "D", "A" };
string a = String.Join(", ", testAnswer);
//Reads text file line by line. Stores in array, each line of the file is an element in the array
string[] inputAnswer = System.IO.File.ReadAllLines(#"C:\Users\Momo\Desktop\UNI\Software tech\test.txt");
string b = String.Join(", ", inputAnswer);
//Increments through array elements in both arrays and checks for matching elements.
//Displays in listBox.
for (int i = 0; i < testAnswer.Length; i++)
{
if (testAnswer[i] == inputAnswer[i])
listBox1.Items.Add(inputAnswer[i]); // or testAnswer[i], as appropriate
else
listBox2.Items.Add(inputAnswer[i]);
}
int correctAns = listbox1.Items.Count;
int wringAns = listbox2.Items.Count;
}
Common answers count would be Enumerable.Intersect result item count, uncommon - Enumerable.Except result item count.
Update: as long as it was mentioned in comments that it would produce wrong answers, proof that it would not:
var testAnswers = new[] { 1, 2, 3 };
var inputAnswers = new[] { 3, 2, 1 };
var commonAnswers = testAnswers
.Select((x, index) => Tuple.Create(x, index))
.Intersect(inputAnswers.Select((y, index) => Tuple.Create(y, index)));

Categories

Resources