Compare 2 List<string> with a LIKE clause - c#

I have 2 lists, one a list of file names, the second a list of file name stubs, i would like to select everything from the first list where the file name is like the file name stub.
List<string> files = new List<string>() {"a.txt", "b.txt", "c.txt"};
List<string> fileStub = new List<string>() {"a", "b", "c"};
The query would return all records from the first list.
Thanks in advance.

var results = files.Where(x => fileStub.Any(y => x.Contains(y))).ToList();

if the order is important (of course, with this sample, the size of the two lists is important, if you don't want IndexOutOfRange exceptions)
var res = files.Where((file, index) => file.Contains(fileStub[index]));
if you don't mind order (than list sizes are not important)
var res = files.Where(file => fileStub.Any(fs => file.Contains(fs)));

var result = files.Where(item => fileStub.Any(stub => item.Contains(stub))).ToList();

Use the Any and Contains methods.
List<string> files = new List<string>() {"a.txt", "b.txt", "c.txt"};
List<string> fileStub = new List<string>() {"a", "c"};
var result = files.Where(x => fileStub.Any(y => x.Contains(y))).ToList();

Related

Count duplicates and combine them in a list

I have a list like this:
var list = new List<string>() { "a", "b", "c", "a" };
As you can see I have one duplicate inside this list.
The desired output should look like this:
a (2)
b (1)
c (1)
The code which I already have looks like this:
list.GroupBy(x => x).Select(x => x.Key + "(" + x.Count() + ")").ToList().ForEach(x => list2.Add(x));
But as you can see I need a second list to get the desired output. My question now is how can I get the desired output with using only one list.
Why don't we just add the items into existing list2?
var list2 = new List<string>();
...
list2.AddRange(list
.GroupBy(item => item)
.Select(group => $"{group.Key} ({group.Count()})"));
Or create list2 from scratch:
var list2 = list
.GroupBy(item => item)
.Select(group => $"{group.Key} ({group.Count()})")
.ToList();
This does what you need:
var list = new List<string>() { "a", "b", "c", "a" };
foreach (var group in list.GroupBy(b => b).OrderBy(g => g.Key))
{
Console.WriteLine($"{group.Key} ({group.Count()})");
}
Consequentially, if one wishes to dedup this list, one could do:
list = list.GroupBy(b => b).Select(g => g.Key).ToList();
The result would be a deduped list with "a", "b" and "c" in it.
You can do this in one statement (I've split it over multiple lines for readability):
var list = new List<string> { "a", "b", "c", "a" };
Console.WriteLine(
string.Join("\n",
list.GroupBy(_=>_).Select(g => $"{g.Key} ({g.Count()})")));
Output:
a (2)
b (1)
c (1)
You can use the Distinct method to remove duplicates.
This returns a IEnumerable and in order to use the ForEach method you have to apply the ToList method first.
The Foreach iterates over the items {"a", "b", "c"}
and prints the current item and the count of this item in the list with dulicates.
list.Distinct().ToList().ForEach(v1 => Console.WriteLine($"{v1} ({list.Count(v2 => v1 == v2)})"))

Retrieve comma-separated values from IQueryable and add to list

IQueryable<string> ScannedBarcodes = XYZDb.tblScannedBarcodes
.Where(i => i.PullNo == lblPullNo.Text)
.Select(i => i.Barcode);
The result view shows something like
"678765,090909,243454,675434"
Now I want to add all the comma-separated values into a list.
Rest of my code is below:
foreach (var item in ScannedBarcodes)
{
List<string> _barcodes = new List<string>();
_barcodes.Add(item);
}
How to split it?
Thanks in advance
The splitting can be done using Linq as follows.
var _barcodes =
ScannedBarcodes.SelectMany(iString => iString.Split(new char[]{ ','}))
.ToList();
Use
_barcodes.AddRange(item.Split(','));
There's no a single string with commas - that's just the debugger display. The IQueryable is a enumerable list of values already.....
IQueryable<string> ScannedBarcodes = XYZDb.tblScannedBarcodes
.Where(i => i.PullNo == lblPullNo.Text)
.Select(i => i.Barcode);
List<string> _barcodes = new List<string>();
_barcodes.AddRange(ScannedBarcodes);
There's really no need to loop over those values - just create your List<string> and then add the whole IQueryable collection to it in a single call to .AddRange() ...

Identifying and grouping similar items in a collection of strings

I have a collection of strings like the following:
List<string> codes = new List<string>
{
"44.01", "44.02", "44.03", "44.04", "44.05", "44.06", "44.07", "44.08", "46", "47.10"
};
Each string is made up of two components separated by a full stop - a prefix code and a subcode. Some of the strings don't have sub codes.
I want to be able combine the strings whose prefixes are the same and output them as follows with the other codes also:
44(01,02,03,04,05,06,07,08),46,47.10
I'm stuck at the first hurdle of this, which is how to identify and group together the codes whose prefix values are the same, so that I can combine them into a single string as you can see above.
You can do:
var query = codes.Select(c =>
new
{
SplitArray = c.Split('.'), //to avoid multiple split
Value = c
})
.Select(c => new
{
Prefix = c.SplitArray.First(), //you can avoid multiple split if you split first and use it later
PostFix = c.SplitArray.Last(),
Value = c.Value,
})
.GroupBy(r => r.Prefix)
.Select(grp => new
{
Key = grp.Key,
Items = grp.Count() > 1 ? String.Join(",", grp.Select(t => t.PostFix)) : "",
Value = grp.First().Value,
});
This is how it works:
Split each item in the list on the delimiter and populate an anonymous type with Prefix, Postfix and original value
Later group on Prefix
after that select the values and the post fix values using string.Join
For output:
foreach (var item in query)
{
if(String.IsNullOrWhiteSpace(item.Items))
Console.WriteLine(item.Value);
else
Console.WriteLine("{0}({1})", item.Key, item.Items);
}
Output would be:
44(01,02,03,04,05,06,07,08)
46
47.10
Try this:-
var result = codes.Select(x => new { SplitArr = x.Split('.'), OriginalValue = x })
.GroupBy(x => x.SplitArr[0])
.Select(x => new
{
Prefix= x.Key,
subCode = x.Count() > 1 ?
String.Join(",", x.Select(z => z.SplitArray[1])) : "",
OriginalValue = x.First().OriginalValue
});
You can print your desired output like this:-
foreach (var item in result)
{
Console.Write("{0}({1}),",item.Prefix,item.subCode);
}
Working Fiddle.
Outlined idea:
Use Dictionary<string, List<string>> for collecting your result
in a loop over your list, use string.split() .. the first element will be your Dictionary key ... create a new List<string> there if the key doesn't exist yet
if the result of split has a second element, append that to the List
use a second loop to format that Dictionary to your output string
Of course, linq is possible too, e.g.
List<string> codes = new List<string>() {
"44.01", "44.05", "47", "42.02", "44.03" };
var result = string.Join(",",
codes.OrderBy(x => x)
.Select(x => x.Split('.'))
.GroupBy(x => x[0])
.Select((x) =>
{
if (x.Count() == 0) return x.Key;
else if (x.Count() == 1) return string.Join(".", x.First());
else return x.Key + "(" + string.Join(",", x.Select(e => e[1]).ToArray()) + ")";
}).ToArray());
Gotta love linq ... haha ... I think this is a monster.
You can do it all in one clever LINQ:
var grouped = codes.Select(x => x.Split('.'))
.Select(x => new
{
Prefix = int.Parse(x[0]),
Subcode = x.Length > 1 ? int.Parse(x[1]) : (int?)null
})
.GroupBy(k => k.Prefix)
.Select(g => new
{
Prefix = g.Key,
Subcodes = g.Where(s => s.Subcode.HasValue).Select(s => s.Subcode)
})
.Select(x =>
x.Prefix +
(x.Subcodes.Count() == 1 ? string.Format(".{0}", x.Subcodes.First()) :
x.Subcodes.Count() > 1 ? string.Format("({0})", string.Join(",", x.Subcodes))
: string.Empty)
).ToArray();
First it splits by Code and Subcode
Group by you Code, and get all Subcodes as a collection
Select it in the appropriate format
Looking at the problem, I think you should stop just before the last Select and let the data presentation be done in another part/method of your application.
The old fashioned way:
List<string> codes = new List<string>() {"44.01", "44.05", "47", "42.02", "44.03" };
string output=""
for (int i=0;i<list.count;i++)
{
string [] items= (codes[i]+"..").split('.') ;
int pos1=output.IndexOf(","+items[0]+"(") ;
if (pos1<0) output+=","+items[0]+"("+items[1]+")" ; // first occurence of code : add it
else
{ // Code already inserted : find the insert point
int pos2=output.Substring(pos1).IndexOf(')') ;
output=output.Substring(0,pos2)+","+items[1]+output.Substring(pos2) ;
}
}
if (output.Length>0) output=output.Substring(1).replace("()","") ;
This will work, including the correct formats for no subcodes, a single subcode, multiple subcodes. It also doesn't assume the prefix or subcodes are numeric, so it leaves leading zeros as is. Your question didn't show what to do in the case you have a prefix without subcode AND the same prefix with subcode, so it may not work in that edge case (44,44.01). I have it so that it ignores the prefix without subcode in that edge case.
List<string> codes = new List<string>
{
"44.01", "44.02", "44.03", "44.04", "44.05", "44.06", "44.07", "44.08", "46", "47.10"
};
var result=codes.Select(x => (x+".").Split('.'))
.Select(x => new
{
Prefix = x[0],
Subcode = x[1]
})
.GroupBy(k => k.Prefix)
.Select(g => new
{
Prefix = g.Key,
Subcodes = g.Where(s => s.Subcode!="").Select(s => s.Subcode)
})
.Select(x =>
x.Prefix +
(x.Subcodes.Count() == 0 ? string.Empty :
string.Format(x.Subcodes.Count()>1?"({0})":".{0}",
string.Join(",", x.Subcodes)))
).ToArray();
General idea, but i'm sure replacing the Substring calls with Regex would be a lot better as well
List<string> newCodes = new List<string>()
foreach (string sub1 in codes.Select(item => item.Substring(0,2)).Distinct)
{
StringBuilder code = new StringBuilder();
code.Append("sub1(");
foreach (string sub2 in codes.Where(item => item.Substring(0,2) == sub1).Select(item => item.Substring(2))
code.Append(sub2 + ",");
code.Append(")");
newCodes.Add(code.ToString());
}
You could go a couple ways... I could see you making a Dictionary<string,List<string>> so that you could have "44" map to a list of {".01", ".02", ".03", etc.} This would require you processing the codes before adding them to this list (i.e. separating out the two parts of the code and handling the case where there is only one part).
Or you could put them into a a SortedSet and provide your own Comparator which knows that these are codes and how to sort them (at least that'd be more reliable than grouping them alphabetically). Iterating over this SortedSet would still require special logic, though, so perhaps the Dictionary to List option above is still preferable.
In either case you would still need to handle a special case "46" where there is no second element in the code. In the dictionary example, would you insert a String.Empty into the list? Not sure what you'd output if you got a list {"46", "46.1"} -- would you display as "46(null,1)" or... "46(0,1)"... or "46(,1)" or "46(1)"?

How do i sum a list of items by code(or any field)?

I have an object that has a list of another object in it. i.e Object1 contains List<Object2>.
Assuming this is the definition of object 2:
public class Object2
{
string code,
string name,
decimal amount
}
I want to be a able to make a list2 from the list whose value will contain what something similar to what a select name, code, sum(amount) group by code kinda statement could have given me
this is what i did but it didnt contain what i needed on passing through.
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new { Amount = g.Sum(x => x.amount) });
I want code and name in the new list just like the sql statement above.
You're almost there:
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new
{
Code = g.First().code,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
This groups the items by code and creates an anonymous object for each group, taking the code and name of first item of the group. (I assume that all items with the same code also have the same name.)
If you are grouping by code and not by name you'd have to choose something for name from the list, perhaps with First() or Last() or something.
var newlist = obj2List.GroupBy(x => x.code).Select(g => new {
Code = g.Key,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
var query = Object1.Obj2List
.GroupBy(obj2 => obj2.code)
.Select(g => new {
Names = string.Join(",", g.Select(obj2.name)),
Code = g.Key,
Amount = g.Sum(obj2 => obj2.Amount)
});
Since you group by code only you need to aggregate the name also in some way. I have used string.Join to create a string like "Name1,Name2,Name3" for each code-group.
Now you could consume the query for example with a foreach:
foreach(var x in query)
{
Console.WriteLine("Code: {0} Names: {1} Amount: {2}"
, x.Code, x.Names, x.Amount);
}
Instead of using the LINQ Extension Methods .GroupBy() and .Select() you could also use a pure LINQ statement which is way easier to read if you come from a SQL Background.
var ls = new List<Object2>();
var newLs = from obj in ls
group obj by obj.code into codeGroup
select new { code = codeGroup.Key, amount = codeGroup.Sum(s => s.amount) };

Get a new result from a List

I have a detailed list and I want a new one with the elements of one property with no duplicates like this.
List<Class1> list = List<Class1>();
list.Add(new Class1("1", "Walmart", 13.54));
list.Add(new Class1("2", "Target", 12.54));
list.Add(new Class1("3", "Walmart", 14.54));
list.Add(new Class1("4", "BestBuy", 16.54));
list.Add(new Class1("5", "Walmart", 19.54));
list.Add(new Class1("6", "Amazon", 12.33));
My new list
List<Stores> newList = list.Select / FindAll / Group ?
I want this collection
newList = "Walmart", "Target", "BestBuy", "Amazon"
You need Distinct and Select.
var newList = list.Select(x => x.Name).Distinct().ToList();
If you also want your original class, you would have to get a bit more fancy.
Either get MoreLINQ and use its DistinctBy method:
var newList = list.DistinctBy(x => x.Name).ToList();
Or use a clever GroupBy hack:
var newList = list.GroupBy(x => x.Name).Select(x => x.First());
Lets assume that Class1 is defined as :
public class Class1
{
string Id {get;set;}
string Store {get;set;}
double Price {get;set;}
}
You can have your result as:
var result = list.Select(x => x.Store).Distinct().ToList();
You want to use Select() to select a specific property of the items:
list.Select(c => c.Company);
This returns an IEnumerable<string>.
You would then want to call .Distinct().
newList = list.Select(x => x.Store).Distinct().ToList();

Categories

Resources