Generating sublist of list given strings - c#

I am very new to LINQ and am wondering if there is a way to extract a sublist from a list of strings given that the string values are provided beforehand.
For example, if I have:
var movies = new List<Movie>
{
new Movie { Name = "Noah" },
new Movie { Name = "Terminator" },
new Movie { Name = "Troy" },
new Movie { Name = "Gladiator" },
};
I would like to use LINQ to create a sublist if I provide the Name strings "Noah" and "Troy".
I have tried googling and results point me to SelectMany and GroupBy but all of the examples involve lists that contains primitive values, not primitives values contained in objects.

Is this what you are looking for?
var sublistItems = new List<string>() {"Noah", "Troy"} ;
var subList = movies.where(m=> sublistItems.Contains(m.Name));

var newlist = from m in movies
where (m.Name == "Troy" || m.Name == "Noah")
select m;

Using Linq lambda it would be:
var result = movies.where(x => x.Name == "Troy" || x.Name == "Noah");
This would return a IEnumerable<Movie> containing the ones searched for using Where.

Related

C#: Count occurrences of a string in a list which is in another list using LINQ?

I am trying to count occurrences of a string in dynamically added lists in a main list. This is the main list:
public static List<string>[] tables = new List<string>[30];
This is how I add items to it:
public static int takenTablesDayTotal;
public static void AddProductToTable()
{
int tableNum = int.Parse(Console.ReadLine());
if (tableNum < 1 || tableNum > 30) { throw new Exception(); }
choiceName = Console.ReadLine();
if (tables[tableNum] is null)
{
tables[tableNum] = new List<string>();
takenTablesDayTotal++;
}
tables[tableNum].Add(choiceName);
}
And this is how I have tried to do the counting, but it doesn't seem to work right for some reason (starts at 1 and stops counting there when the required string is detected)
salesProductDayTotal = tables.Where(s => s != null && s.Contains("string")).Count();
I'm not sure how to make this work, so any help will be appreciated!
Thanks in advance!
You can use SelectMany to deliminate the two-nest structure.
Then use Count to get what you want.
For example - count the daily apple sales number
List<string>[] tables = new List<string>[30];
tables[0] = new List<string>{
"Apple", "Banana", "Cherry"
};
tables[1] = new List<string>{
"Peach", "Apple", "Watermelon"
};
tables[2] = new List<string>{
"Mango", "Grape", "Apple"
};
//the daily sales count of Apple.
var dailyAppleSalesCount = tables.Where(x => x != null)
.SelectMany(s => s).Count(x => x == "Apple");
You can use SelectMany to flatten the List<List<string>> into one large List<string>, and then count the products.
You don't need to use Contains, IMO ("Chicken soup" is probably a different product on the menu that "Spicy Chicken Soup"), so it simplifies the condition a bit.
salesProductDayTotal = tables
.Where(t => t != null)
.SelectMany(products => products)
.Count(p => p == "string")
You could also use a GroupBy clause to do this calculations on all the products at once.
Explanation of your problem:
You were using the Count on the outer list, the list of tables. So you had just "one match" for each table that contains the product at least once.

Determine duplicates based off minimum N characters from smaller comparing string

I have two lists, both containing models that share a common field, ID(String value). I am comparing the ID's for duplication.
I currently have a LINQ statement in place to determine the duplicated ID values, which stores them into a list of strings:
List<string> duplicateRecords = testData.TestRecords.GroupBy(aa => aa.ID).Where(x => x.Count() > 1).Select(y => y.Key).ToList();
And a second LINQ statement that maps a List of respected models based off the duplicated ID LINQ result:
List<Model> modelRecords = testData.Models.Where(x => duplicateRecords.Any(y => x.ID == y)).ToList();
These two LINQ statements do exactly what I expected them to do which is great. But now there is a recent request to determine duplicate ID's based off of their minimum N characters during a comparison. This minimum N comparison must happen for the last N characters in a string.
EX)
ID1: 123 == ID2: 123
ID1: 0123 == ID2: 123
ID1: 123 == ID2: 0123
ID1: 1230 != ID2: 123
ID1: 123 != ID2: 1230
ID1: 122110123 == ID2: 123
Hopefully those examples give some insight into the problem I am trying to solve. This could be done using foreach loops but I have come to experience the code becomes very messy and unmanageable on complex list query's.
So my question is this: How can I use the last N characters of the smaller of the two comparing strings to determine duplicates using LINQ?
Note: I am also very open to more elegant ways of solving this problem, would really appreciate excluding any for or foreach solutions.
I assume that when the input contains 123 and 0123 you want the result to have both of them
var input = new List<Model>()
{
new Model {ID = "123"},
new Model {ID = "0123"},
new Model {ID = "1230"},
new Model {ID = "12"},
new Model {ID = "122110123"}
};
var result = input.Where(x => input.Any(y => y != x && (y.ID.EndsWith(x.ID) || x.ID.EndsWith(y.ID)))).ToList();
\\this will return 123, 0123 and 122110123
If you want to check agains existing duplicateRecords list then this should work:
List<Model> modelRecords = testData.Models.Where(x => duplicateRecords.Any(y => x.ID.EndsWith(y) || y.EndsWith(x.ID))).ToList();
In order to efficiently find the duplicates you need to sort the IDs by length so you can minimize the comparisons necessary. (The sort adds some overhead, but greatly decreases the comparisons that must be done - in my test where 9 IDs have and 3 are duplicates of 8 values, it is 15 comparisons sorted versus 42 unsorted.) Once you have them sorted by length, just compare each one to the ones that are equal to or longer (in case of complete duplicates) to find which short IDs need to be kept, marking any matches so you can skip them and then find all the Models that end with the found matches.
Create the List of IDs ordered by their length:
var orderedIDs = testData.TestRecords.Select(tr => tr.ID).OrderBy(id => id.Length).ToList();
I don't think there is any way to do this efficiently with LINQ, but a nested for loop skipping previous matches optimizes the search for duplicates.
First, variables to keep track of IDsand whichID`s have already matched:
var dupRecordSubIDs = new List<string>();
var alreadyMatched = new bool[testData.TestRecords.Count];
Now loop through the IDs and save the shorter matching IDs:
// foreach ID in length order
for (int n1 = 0; n1 < testData.TestRecords.Count-1; ++n1) {
// skip the ones that already matched a shorter ID
if (!alreadyMatched[n1]) {
// remember if the shorter ID was alrady added
var added_n1 = false;
// compare the ID to all greater than or equal length IDs
for (int n2 = n1 + 1; n2 < testData.TestRecords.Count; ++n2) {
// if not previously matched, see if we have a new match
if (!alreadyMatched[n2] && orderedIDs[n2].EndsWith(orderedIDs[n1])) {
// only add the shorter ID once for new matches
if (!added_n1) {
dupRecordSubIDs.Add(orderedIDs[n1]);
added_n1 = true;
}
// remember which longer IDs are already matched
alreadyMatched[n2] = true;
}
}
}
}
Now find all the Models that match one of the IDs with a duplicate:
var modelRecords = testData.Models.Where(m => dupRecordSubIDs.Any(d => m.ID.EndsWith(d))).ToList();
I assume ID is string. If so, you can do this :
string match = "123";
var duplicate = list.Where(x=> x.Substring(x.Length - match.Length) == match).ToList();
If I understand your question correctly, it looks to be just a matter of chopping off the last N characters in each ID property while grouping.
Something like this:
using System;
using System.Linq;
public class TestRecord
{
public string ID { get; set; }
}
public class TestModel
{
public string ID { get; set; }
}
public class Program
{
public static void Main()
{
var N = 3; // This is where you define the desired N length
var rand = new Random();
var testRecords = new TestRecord[]
{
new TestRecord {ID = "123"},
new TestRecord {ID = "0123"},
new TestRecord {ID = "1230"},
new TestRecord {ID = "122110123"},
};
var testModels = new TestModel[]
{
new TestModel {ID = "123"},
new TestModel {ID = "0123"},
new TestModel {ID = "1230"},
new TestModel {ID = "122110123"},
};
bool SortEm(string a, string b) => a.Length < b.Length ? b.EndsWith(a) : a.EndsWith(b);
var models = testRecords
.Where(record => testRecords.Any(target => record.ID != target.ID && SortEm(target.ID, record.ID)))
.ToDictionary(
key => key,
key => testModels.Where(testModel => SortEm(key.ID, testModel.ID)).ToArray());
foreach (var kvp in models)
{
System.Console.WriteLine($"For duplicate key ({kvp.Key.ID}) found models: \r\n\t{string.Join("\r\n\t", kvp.Value.Select(x => x.ID))}");
}
}
}

C# LINQ - Comparing a IEnumerable<string> against an anonmyous list?

The basic question
I have:
IEnumerable<string> listA
var listB (this is an anonymous list generated by a LINQ query)
I want to query a list of objects that contain listA to see if they match to listB:
someObjectList.Where(x => x.listA == listB)
The comparison doesn't work - so how do I ensure that both lists are the same type for comparison?
The detailed question
I am grouping a larger list into a subset that contains a name and related date(s).
var listGroup = from n in list group n by new
{ n.NAME } into d
select new
{
NAME = d.Key.NAME, listOfDates = from x in d select new
{ Date = x.DATE } };
I have a object to hold the values for further processing:
class SomeObject
{
public SomeObject()
{
_listOfDates = new List<DateTime>();
}
private IEnumerable<DateTime> _listOfDates;
public IEnumerable<DateTime> ListOfDates
{
get { return _listOfDates; }
set { _listOfDates = value; }
}
}
I am then iterating over the listGroup and adding into a generic List<> of SomeObject:
foreach(var item in listGroup)
{
SomeObject so = new SomeObject();
// ...do some stuff
if (some match occurs then add into List<SomeObject>)
}
As I iterate through then I want to check the existing List<SomeOjbect> for matches:
var record = someObjectList.Where(x => x.NAME == item.NAME &&
x.ListOfDates == item.listOfDates)
.SingleOrDefault();
The problem is that comparing x.ListOfDates against item.listOfDates doesn't work.
There is no compiler error but I suspect that the returned value lists are different. How to I get the lists to commonize so they can be compared?
Update #1
This seems to work to get the listOfDates into a similar format:
IEnumerable<DateTime> tempList = item.listOfDates.Select(x => x.DATE).ToList()
Then I followed the 'SequenceEqual' suggestion from #Matt Burland
You can just compare one IEnumerable<DateTime> to another IEnumerable<DateTime>, you need to compare the sequence. Luckily, there's Enumerable.SequenceEquals (in both static and extension method flavors) which should work here.
So something like:
var record = someObjectList
.Where(x => x.NAME == item.NAME && x.ListOfDates.SequenceEquals(item.listOfDates))
.SingleOrDefault();

Linq query change from List<string> to List<T> where clause

I have a linq query that works when it I had a list of a single value now that I change to having a List that has several properties I need to change the where clause
So this works:
List<string> etchList = new List<string>();
etchList.Add("24");
var etchVect = (from vio in AddPlas
where etchList.Any(v => vio.Key.Formatted.Equals(v))
let firstOrDefault = vio.Shapes.FirstOrDefault()
where firstOrDefault != null
select new
{
EtchVectors = firstOrDefault.Formatted
}).ToList();
However I have a new hard coded list (which will represent incoming data:
List<ExcelViolations> excelViolations = new List<ExcelViolations>();
excelViolations.Add(new ExcelViolations
{
VioID = 24,
RuleType = "SPACING",
VioType = "Line-Line",
XCoordinate = 6132,
YCoordinate = 10031.46
});
So the NEW Linq query looks like this, but is obviously will not work as
AddPlas is a List and so using this other list of excelviolations, I wish to have it do where on each one of the properties in the excelviolations list
var etchVect = (from vio in AddPlas
where excelViolations.Any(vioId => vio.Key.Formatted.Equals(vioId))
let firstOrDefault = vio.Shapes.FirstOrDefault()
select new
{
EtchVectors = firstOrDefault.Formatted
}).ToList();
Now, since this is a list within a list, I would like to do something like add in each of the properties
so for example:
where excelViolations.VioID.Any(vioId => vio.Key.Formatted.Equals(vioId))
However that is not possible, but you see that I'm trying to access the property of VioID that is in the excelViolations and match it to the Key which is in vio list
Just change this line
where excelViolations.Any(vioId => vio.Key.Formatted.Equals(vioId))
to
where excelViolations.Any(excelVio => vio.Key.Formatted.Equals(excelVio.VioID))
then i thought it will works

List Collection Contains exact string

I have a List Collection and say that i am adding 3 items to them.
list.Add(new ContentDomain() { Id = "1" , Content = "aaa,bbb,ccc,ddd"});
list.Add(new ContentDomain() { Id = "2" , Content = "aa,bb,cc,dd"});
list.Add(new ContentDomain() { Id = "3" , Content = "a,b,c,d"});
Now what i want is to fetch the rows that have just 'a' in the Content attribute.
Like i tried something like
list = list.Where(x => x.Content.ToLower().Contains("a")).ToList();
but that would give me all the three rows.
i want to search in a string for the exact string only.
list.Where(x => x.ToString().ToLower().Split(',').Where(a => a.Trim() == "a").Any()).ToList();
edit: Changed Count() > 0 to Any() for better performance
Convert it to an array of strings, and find the string in the array.
list = list.Where(x => x.Content.ToLower().Split(',').IndexOf("a")>= 0).ToList();
Try this:
IList<ContentDomain> returned = new List<ContentDomain>();
foreach(ContentDomain myList in list)
{
var ret = myList.Content.Split(',');
bool exists = (from val in ret
where val.Contains('a')
select true).FirstOrDefault();
if (exists)
returned.Add(myList);
}

Categories