C# Compare two Lists - c#

Background: I have two lists that hold strings. List a and List b. At the moment I write the values of List a in an excel spreadsheet to column A, and the values of List b into Column. List b should have the same data as List a and be in sequence. This is not always the case.
Problem: When I write values of List b in excel, I want to write the value in the cell if it is in list a at the same point, if not I want to write an empty string into the cell.
Edit:
Thanks for replies and answers work very well, just realised that what I really need is :
If two lists are:
a = {"a", "b", "c", "d", "e" }
b = {"a", "d", "e" }
the result of the operation should be:
{ "a", "", "", "d", "e" }

One way is to zip your lists together and replace the "wrong" value in list b with an empty string:
var a = new [] {"a", "b", "c", "d"};
var b = new [] {"a", "Foo", "c", "Bar"};
var fixed_b = a.Zip(b, (x, y) => x == y ? x : "");
fixed_b now yields "a", "", "c" and "".
When writing your data to your excel spreadsheet, simply iterate over fixed_b instead of b
Edit:
According to your comments:
You could create a little helper method like this:
IEnumerable<T> FillBlanks<T>(IEnumerable<T> source, IEnumerable<T> collection, T blank)
{
using(var e = collection.GetEnumerator())
{
bool more = e.MoveNext();
foreach(var x in source)
if(more && x.Equals((T)e.Current))
{
yield return x;
more = e.MoveNext();
}
else
yield return blank;
}
}
var fixed_b = FillBlanks(a, b, String.Empty);

int max = aList.Count > bList.Count ? aList.Count : bList.Count;
for(int i = 0; i < max; ++i)
{
if(i < aList.Count)
Write(aList[i]);
if(i < bList.Count)
{
if(i < aList.Count)
Write(aList[i] == bList[i] ? bList[i] : "");
else
Write(bList[i]);
}
}
This assumes Write actually writes data to the spreadsheet.

Try this:
class Program
{
static void Main(string[] args)
{
List<string> listA = new List<string>() { "a", "b", "c" };
List<string> listB = new List<string>() { "a", "c", "b" };
var result = listB.Select((b, index) =>
(index == listA.IndexOf(b)) ? b : "");
}
}

Related

Count occurrences of order dependent List<T> in List<List<T>>

There is a similar question that doesn't answer my question. --> Count number of element in List>
I have a list which contains sublists:
List<string> sublist1 = new List<string>() { "a", "b" };
List<string> sublist2 = new List<string>() { "a", "b" };
List<string> sublist3 = new List<string>() { "a", "c" };
Now I want to count the occurrences of each list.
a, b --> 2
a, c --> 1
I used distinct() from LINQ, but I got the output:
a, b --> 1
a, b --> 1
a, c --> 1
I assume that the hashcode is different.
Is there an alternative to distinct() which is looking at the list values instead?
I want to solve this in LINQ if possible.
Edit:
The order of list items has to be the same!
To use GroupBy() to do this, you will need a suitable IEqualityComparer<List<string>> that compares lists of strings. There is no built-in implementation, so you have to roll your own:
public sealed class StringListEqualityComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
{
if (ReferenceEquals(x, y))
return true;
if (x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(List<string> strings)
{
int hash = 17;
foreach (var s in strings)
{
unchecked
{
hash = hash * 23 + s?.GetHashCode() ?? 0;
}
}
return hash;
}
}
Once you've got that, you can use it with GroupBy() as follows:
public static void Main()
{
var sublist1 = new List<string>{ "a", "b" };
var sublist2 = new List<string>{ "a", "b" };
var sublist3 = new List<string>{ "a", "c" };
var listOfLists = new List<List<string>> {sublist1, sublist2, sublist3};
var groups = listOfLists.GroupBy(item => item, new StringListEqualityComparer());
foreach (var group in groups)
{
Console.WriteLine($"Group: {string.Join(", ", group.Key)}, Count: {group.Count()}");
}
}
public JsonResult CountList(){
List<List<string>> d = new List<List<string>>(); //SuperList
d.Add(new List<string> { "a", "b" }); //List 1
d.Add(new List<string> { "a", "b" }); // List 2
d.Add(new List<string> { "a", "c" }); // List 3
d.Add(new List<string> { "a", "c", "z" }); //List 4
var listCount = from items in d
group items by items.Aggregate((a,b)=>a+""+b) into groups
select new { groups.Key, Count = groups.Count() };
return new JsonResult(listCount);
}
This will give the following Result as output in Post Man or Advanced REST Client
[{
"key": "ab",
"count": 2
},
{
"key": "ac",
"count": 1
},
{
"key": "acz",
"count": 1
}],
I think this will be helpful
var list = new List<List<string>>() { sublist1, sublist2, sublist3};
var result = list.GroupBy(x => string.Join(",",x)).ToDictionary(x => x.Key.Split(',').ToList(), x => x.Count());
You can try the below code:-
List<string> sublist1 = new List<string>() { "a", "b" };
List<string> sublist2 = new List<string>() { "a", "b" };
List<string> sublist3 = new List<string>() { "a", "c" };
List<List<string>> listOfLists = new List<List<string>> { sublist1, sublist2, sublist3 };
Dictionary<string, int> counterDictionary = new Dictionary<string, int>();
foreach (List<string> strList in listOfLists)
{
string concat = strList.Aggregate((s1, s2) => s1 + ", " + s2);
if (!counterDictionary.ContainsKey(concat))
counterDictionary.Add(concat, 1);
else
counterDictionary[concat] = counterDictionary[concat] + 1;
}
foreach (KeyValuePair<string, int> keyValue in counterDictionary)
{
Console.WriteLine(keyValue.Key + "=>" + keyValue.Value);
}
I think I will solve this with:
var equallists = list1.SequenceEqual(list2);
Therefore I compare distinct lists and lists with SequenceEquals() and counting them.
Better solutions welcome. :)

Identify common Lists in List and return Distinct Lists in List

I have a following List, I need to iterate through the list and see if the list has identical elements in it and return only a unique list. Could anyone please let me know what's wrong with the following code and a proper way to do it?
Also, Linq way to do it, if any?
Expected Solution would be = {{"a", "b", "c"},{"e", "b", "c" }}
class Program1
{
static void Main(string[] args)
{
List<string>[] stringLists = new List<string>[3]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "e", "b", "c" },
new List<string>(){ "a", "b", "c" }
};
List<List<string>> prt = new List<List<string>>();
/* I DONT UNDERSTAND WHY THIS IS NOT WORKING !!!!!!!!!!!!!!! */
foreach (var item in stringLists)
{
for (int i = 0; i < item.Count; i++)
{
if (item == stringLists[i] && (!prt.Contains(item)))
{
prt.Add(item);
}
}
}
}
}
You can try good old Distinct with a custom IEqualityComparer<T>:
using System.Linq;
...
public class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>> {
public bool Equals(IEnumerable<T> x, IEnumerable<T> y) {
return Enumerable.SequenceEqual(x, y);
}
//TODO: Suboptimal HashCode implementation
public int GetHashCode(IEnumerable<T> obj) {
return obj == null
? 0
: obj.Count();
}
}
...
var stringLists = new List<string>() {
new List<string> {"a", "b", "c"},
new List<string> {"e", "b", "c"},
new List<string> {"a", "b", "c"}
};
// All you have to do is to put Distinct
var result = stringLists
.Distinct(new SequenceComparer<string>())
.ToList(); // If you want materialization
Test:
Console.Write(string.Join(Environment.NewLine, result
.Select(list => string.Join(", ", list))));
Outcome:
a, b, c
e, b, c
You need to do union on all the list and than do a distinct.
Something like this, just iterate through the list and union it with last result:
List<string> result = new List<string>();
foreach (var list in stringLists)
{
result = result.Union(list).ToList();
}
result = result.Distinct().ToList();
prt.Contains(item) expression compares List's with their reference not by their elements, so it's a bad choice for determining if two lists are duplicates.
Try following
var stringLists = new List<string>[3]
{
new List<string> {"a", "b", "c"},
new List<string> {"e", "b", "c"},
new List<string> {"a", "b", "c"}
};
var prt = new List<List<string>>();
foreach (var item in stringLists)
{
if(prt.All(it => it.Count != item.Count || it.Except(item).Any()))
prt.Add(item);
}
Dmitry Bychenko's answer is the way to go.
For your special case and with data that does not contain , you could get away with:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program1
{
public static void Main()
{
var stringLists = new List<List<string>>{
new List<string> {"a", "b", "c"},
new List<string> {"e", "b", "c"},
new List<string> {"a", "b", "c"}
};
var prt = stringLists
.Select(l => string.Join(",", l)) // make it a string separated by ,
.Distinct() // distinct it using string.Distinct()
.Select(l => l.Split(',').ToList()); // split it again at , and make it List
foreach (var p in prt)
{
foreach (var c in p)
Console.WriteLine(c);
Console.WriteLine();
}
}
}
There is lots of un-needed object creation in this approach - but i would work (until your data contains a , - then is messes your lists up).
Output:
a
b
c
e
b
c
Your misunderstanding is that you expect the statement
prt.Contains(item)
to return true when the sequence of strings in item already exists in prt. However the test internal to Contains used to determine this is a reference equality, not a item by item equality. Here is a minimal example to illustrate this:
void Main()
{
Console.Write( (new []{new []{ "a", "b", "c" }}).Contains(new[] { "a", "b", "c" }));
// Prints false
}
You either need to use a deep equals comparer like #Dmitry's answer which creates a digest (hash) of each list and compares the digests, or to do it explicitly, like this code:
class Program1
{
static void Main(string[] args)
{
List<string>[] stringLists = new List<string>[3]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "e", "b", "c" },
new List<string>(){ "a", "b", "c" }
};
List<List<string>> prt = new List<List<string>>();
for(int i = 0; i < 3; i++)
{
bool isDifferentFromAllOthers = true;
for(int j = 0; j < i; j++)
{
bool isSameAsThisItem = true;
for(int item = 0; item < 3; item++)
{
// !!! Here is the explicit item by item string comparison
if (stringLists[i][item] != stringLists[j][item])
{
isSameAsThisItem = false;
break;
}
}
if (isSameAsThisItem)
{
isDifferentFromAllOthers = false;
break;
}
}
if (isDifferentFromAllOthers)
{
prt.Add(stringLists[i]);
}
}
// /* I DONT UNDERSTAND WHY THIS IS NOT WORKING !!!!!!!!!!!!!!! */
// foreach (var item in stringLists)
// {
// for (int i = 0; i < item.Count; i++)
// {
// if (item == stringLists[i] && (!prt.Contains(item)))
// {
// prt.Add(item);
// }
// }
// }
}
}

C# List - remove list from another list

Say I have a list of strings
1,2,3,a,b,a,b,c,1,2
I have a second list of strings
a,b,c
I want to remove the second list from the first resulting in
1,2,3,a,b,1,2
What's the best way to do this with with two List<string>?
Most methods/questions/answers I see involve lists revolve around individual lines of second list being removed from the first (all a's... all b's... all c's...).
I don't want that... I only want to remove those where the a followed by b followed by c.
Edit:
Couple caveats: The Second List is generally two or three strings and CAN appear multiple times (Say, instead, the second list is 1,2. It's contained in the first list twice).
var list = new List<string>(new[] { "1", "2", "3", "a", "b", "a", "b", "c", "1", "2" });
var sublist = new List<string>(new[] { "a", "b", "c" });
var start = -1;
var index = 0;
while (index < list.Count - sublist.Count)
{
for (int i = 0; i < sublist.Count; i++)
{
if (list[i + index] == sublist[i] && i == 0)
{
start = i + index;
}
else if (list[i + index] != sublist[i])
{
start = -1;
index++;
break;
}
}
if (start != -1)
{
list.RemoveRange(start, sublist.Count);
index -= sublist.Count;
}
}
foreach (var item in list)
{
Console.Write(item + ",");
}
With hack:
var list = new List<string>(new[] { "1", "2", "3", "a", "b", "a", "b", "c", "1", "2" });
var sublist = new List<string>(new[] { "a", "b", "c" });
var a = string.Join("#", list);
var b = string.Join("#", sublist);
var result =
new List<string>(a.Replace(b, string.Empty).Split(new[] { '#' }, StringSplitOptions.RemoveEmptyEntries));
foreach (var item in result)
{
Console.Write(item + ",");
}
This solution has very bad perfomance, but it can work for small lists.
With this few lines you could achieve the same. First convert it to String and then replace with 2nd string and convert it back to char array.
List<string> listA = new List<string>() { "1", "2", "3", "a", "b", "a", "b", "c", "1", "2" };
List<string> listB = new List<string>() { "a", "b", "c" };
string strA = string.Join("", listA);
string strB = string.Join("", listB);
strA = strA.Replace(strB, string.Empty);
List<string> resultList = strA.ToCharArray().Select(c => c.ToString()).ToList();
Below code if you need to support full fledged strings
List<string> listA = new List<string>() { "abc1", "2abc2", "3", "a", "b", "a", "b", "c", "1", "2" };
List<string> listB = new List<string>() { "a", "b", "c" };
string strA = string.Join(",", listA);
string strB = string.Join(",", listB) ;
strA = strA.Replace(strB, string.Empty).Replace(",,", ",");
List<string> resultList = strA.Split(',').ToList();
Removes multiple matches if this is what you expect. I'm not thrilled with the implementation, but it appears to work. I used a Stack (last in first out) because I'm lazy.
List<string> target = new List<string> { "1", "2", "3", "a", "b", "a", "b", "c", "1", "2", "a", "b", "c", "1" };
List<string> match = new List<string> { "a", "b", "c" };
Stack<int> matchIndexes = new Stack<int>();
for (int x = 0; x < target.Count - match.Count; x++)
{
int matches = 0;
for (int y = 0; y < match.Count; y++)
{
if (target[x + y] != match[y])
{
break;
}
else
{
matches++;
}
}
if (matches == match.Count)
{
matchIndexes.Push(x);
}
}
while(matchIndexes.Count > 0)
{
int index = matchIndexes.Pop();
target.RemoveRange(index, match.Count);
}
Just iterate through each item in list 2, and remove it from list 1.
foreach(string listItem in list2)
{
list1.Remove(listItem);
}

Linq: Sorting the list based on another list

I have a list of strings List{"X", "W", "C", "A", "D", "B" } and I have another list of strings List{"A", "B", "C", "D"} that tells how the first list must be ordered. But the second list has only four items in it. I would like my first list to be ordered like this:
A, B, C, D, X, W. Actually the last two letters X and W doesn't matter how they are ordered, but they should be at the end of the list.
I tried this:
var newList = list1.OrderBy(i=>list2.IndexOf(i));
but this gives me only four items.
Your current code will give you 6 items. However, it will put X and W in the beginning since they have an index of -1 in list 2.
Here is how to fix that:
var list1 = new List<string> {"X", "W", "C", "A", "D", "B"};
var list2 = new List<string> {"A", "B", "C", "D"};
var newList = list1.OrderBy(x =>
{
var index = list2.IndexOf(x);
if (index == -1)
index = Int32.MaxValue;
return index;
})
.ToList();
One more way along with others.
List<string> list1 = new List<string>() {"X", "W", "C", "A", "D", "B" } ;
List<string> list2 = new List<string>() { "A", "B", "C", "D" } ;
var newList = list2.Intersect(list1)
.Union(list1.Except(list2));
Check Demo
This should work:
var newList = list1.OrderBy(i => {
var x = list2.IndexOf(i);
if(x == -1)
return int.MaxValue;
return x; });
Result (from LinqPad):

Easily Change A List After THE nth item

I got a list:
var x = new List<string>(){"a","b","c"}
I am looking for an pretty easy way to change all items after a
example:
var x = new List<string>(){"a","b","c"}
var y = new List<string>(){"d","e","f"}
x.addAfterFirst(y);
result x= "a","d","e","f"
I know that' x.Skip(1)' can return me the info. I need to set it.
You can use the Take Extension Method to take the first n items from x and concat them with y using the Concat Extension Method:
List<string> x = new List<string> { "a", "b", "c" };
List<string> y = new List<string> { "d", "e", "f" };
int n = 1;
List<string> result = x.Take(n).Concat(y).ToList();
// result == { "a", "d", "e", "f" }
If you want to modify x in-place instead of creating a new list, you can use the RemoveRange Method to remove all items after the first n items, and the AddRange Method to append y to x:
List<string> x = new List<string> { "a", "b", "c" };
List<string> y = new List<string> { "d", "e", "f" };
int n = 1;
x.RemoveRange(n, x.Count - n);
x.AddRange(y);
// x == { "a", "d", "e", "f" }
Make use of InsertRange will do you task
var x = new list<string>(){"a","b","c"}
var y = new list<string>(){"d","e","f"}
x.InsertRange(2,y);
Edit
now if you want to remove element
var x = new list<string>(){"a","b","c"};
int xlength = x.Count() - 1;
var y = new list<string>(){"d","e","f"};
int ylength = y.Count() - 1;
x.InsertRange(2,y);
x.RemoveRang( 2 + ylength, xlength- 2);
Your result doesn't match the full description
Are you wishing to insert or replace
Are you needing to modify the existing collection or could you accept a new collection?
All examples use the following initialization
var insertIndex=1;
var x = new List<string>(){"a","b","c"};
var y = new List<string>(){"d","e","f"};
New Collection Replace
var result=x.Take(insertIndex).Concat(y).ToList();
New Collection Insert
var result=x.Take(insertIndex).Concat(y).Concat(x.Skip(insertIndex)).ToList();
Modify Collection Replace
x.RemoveRange(insertIndex,x.Count-insertIndex);
x.AddRange(y);
Modify Collection Insert
x.InsertRange(insertIndex,y);
Non-LINQ extention way:
int i;
for (i=0;i < y.Count;i++)
{
if (i+1 < x.Count)
x[i+1] = y[i];
else
x.Add(y[i]);
}
//If you dont want trailing elements to remain in x
for (;i < x.Count;i++)
x.RemoveAt(i);

Categories