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);
}
Related
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. :)
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);
// }
// }
// }
}
}
I want the output like this :
A=1
C=3
D=9
e=5
If second array value is 0 I don't want that value how to achieve this output in c#?
private static void Main(string[] args)
{
string[] a = new string[] { "a", "b", "c", "d" ,"e"};
string[] b = new string[] {1,0,3,0,5 };
foreach (string tmp in a)
{
bool existsInB = false;
foreach (string tmp2 in b)
{
if (tmp == tmp2)
{
existsInB = true;
break;
}
}
if (!existsInB)
{
Console.WriteLine(string.Format("{0} is not in b", tmp));
}
}
Console.ReadLine();
}
In that two arrays I want to print values like a=1,c=3,e=5, I don't want to print second array zero value. How do I achieve this?
I need a output in c#: a=1 c=3 e=5
I believe this will work for you
class Program
{
static void Main(string[] args)
{
string[] a = new string[] { "a", "b", "c", "d", "e" };
int[] b = new int[] { 1, 0, 3, 0, 5 };
for (int i = 0; i < a.Length; i++)
{
if (b[i] != 0)
Console.WriteLine(a[i] + "=" + b[i]);
}
Console.ReadLine();
}
}
Dictionary version (as per Matt Murdock's suggestion):
Dictionary<string, int> ab = new Dictionary<string, int>
{
{"a", 1},
{"b", 0},
{"c", 3},
{"d", 0},
{"e", 5}
};
foreach(var pair in ab)
{
if(pair.Value != 0)
Console.WriteLine(pair.Key + "=" + pair.Value);
}
The other answer by #interceptwind has already given a solution, this is just an alternative solution with Linq
string[] a = new string[] { "a", "b", "c", "d", "e" };
string[] b = new string[] { "1", "0", "3", "9", "5" };
var result = a.Zip(b, (strA, strB) => string.Format("{0}={1}", strA.ToUpper(), strB))
.Where(s => !s.Contains("=0"))
.ToArray();
I will add the values in the String[] in to the Arraylist. But, I want to access those string values from the ArrayList.
I tried this way.
private void Form1_Load()
{
fr = new string[5] { "1", "2", "3", "4", "5" };
bd = new string[5] {"a", "b","c", "d", "e"};
m = new ArrayList();
dosomething();
}
private void dosomething()
{
string[] record = new string[3];
for (int i = 0; i < 5; i++)
{
record[0] = "1";
record[1] = fr[i];
record[2] = bd[i];
m.Add(record);
}
}
I don't want to use the for loop is that any other way to do this???
I recommend you to use dictionaries. It is in my opinion the quickest way to store / access data. Also, with arraylists, at runtime, it performs a dynamic transtyping, which makes you loose so much time.
You maybe want to use :
fr = new string[5] { "1", "2", "3", "4", "5" };
bd = new string[5] { "a", "b", "c", "d", "e" };
m = new ArrayList();
fr.ToList().ForEach(_item => m.Add(new String[]{ "1", _item,bd[fr.ToList().IndexOf(_item)]}));
But I would prefere a solution like Fares already recommented...Use A Dictionary
Dictionary - MSDN
Not sure why you need an ArrayList. A generic list might be more suitable for you.
var fr = new string[5] { "1", "2", "3", "4", "5" };
var bd = new string[5] {"a", "b","c", "d", "e"};
int i = 1;
var results = fr.Zip<string, string, string[]>(bd, (a,b) => {
var v = new string [3] { i.ToString(), a,b };
i++;
return v;
}).ToList();
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 : "");
}
}