Find objects in list that match elements in array - c#

Starter code: https://dotnetfiddle.net/lJhMyo
string[] names = { "Burke", "Laptop", "Computer",
"Mobile", "Ahemed", "Sania",
"Kungada", "David","United","Sinshia" };
var empList = new List<Employee> {
new Employee {Name = "Burke", ID = "IHED123"},
new Employee {Name = "David", ID = "QIUHD454"},
new Employee {Name = "Batman", ID = "OIWQE565"},
};
How do I construct a linq query (method syntax) that gets all Employee objects where employee name is in the "names" array?
If there is a string in "names" that is not in empList, throw exception.
EDIT: What if empList is large and I want case-insensitive match for Name?

You can use .Contains. ie:
var result = empList.Where(x => names.Contains(x.Name));
You can check if there is a missing name:
bool noneMissing = !names.Any(n => empList.Any(x => x.Name == n));

.Contains Tests if an array or list contains the item:
var Searched = emptList.Where(x => names.Contains(x.Name));
If Searched.Length == 0, so no Items
If you want performance use for loop (nothing is more performant) and for case insensitive use StringComparer.OrdinalIgnoreCase.
List<string> Searched = new List<string>;
for(int i = 0; i < names.Length; i++)
{
if(emptList.contains(name[i], StringComparer.OrdinalIgnoreCase)
Searched.Add(name[i]);
}

For large lists and case-insensitive comparison you can use HashSet with provided IEqualityComparer<string> :
var hashSet = new HashSet<string>(names, StringComparer.OrdinalIgnoreCase);
empList.Where(e => hashSet.Contains(e.Name));
And possibly moving from LINQ to for loop.

Related

Removing strings with duplicate letters from string array

I have array of strings like
string[] A = { "abc", "cccc", "fgaeg", "def" };
I would like to obtain a list or array of strings where any letter appears only one time. I means that "cccc", "fgaeg" will be removed from input array.
I managed to do this but I feel that my way is very messy, unnecessarily complicated and not efficient.
Do you have any ideas to improve this algorythm (possibliy replacing with only one Linq query)?
My code:
var goodStrings = new List<string>();
int i = 0;
foreach (var str in A)
{
var tempArr = str.GroupBy(x => x)
.Select(x => new
{
Cnt = x.Count(),
Str = x.Key
}).ToArray();
var resultArr = tempArr.Where(g => g.Cnt > 1).Select(f => f.Str).ToArray();
if(resultArr.Length==0) goodStrings.Add(A[i]);
i++;
}
You can use Distinct method for every array item and get items with count of distinct items equals to original string length
string[] A = { "abc", "cccc", "fgaeg", "def" };
var result = A.Where(a => a.Distinct().Count() == a.Length).ToList();
You'll get list with abc and def values, as expected

Linq groupby list matching and count

I have a line of json that I deserialize and create a list of lists:
var json = #"[{'names': ['a','b']} ,{'names': ['z','y','j']} ,{'names':
['a','b']}]";
var json_converted = JsonConvert.DeserializeObject<List<RootObject>>(json);
var namelist = json_converted
I want to use linq to compare the list contents and see if the items in the list match another list
ex)
names............matching list count
List {a,b} = 2
List {z,y,j} = 1
I've tried the following, but no dice :/
var namelist = json_converted
.GroupBy(n => n.names)
.Select(i => new { name = i.Key, Count = i.Count() });
Any suggestions?
You can group by a string produced from your list items taken in the same order. Assuming that '|' character is not allowed inside names, you can do this:
var namelist = json_converted
.GroupBy(n => string.Join("|", n.names.OrderBy(s => s)))
.Select(g => new {
Name = g.First().names
, Count = g.Count()
});
This approach constructs a string "a|b" from lists ["a", "b"] and ["b", "a"], and use that string for grouping the content.
Since the keys are composed of ordered names, g.First().names used as Name may not be in the same order for all elements of the group.
You can write a comparer for the list of names.
public class NamesComparer : IEqualityComparer<IEnumerable<string>>
{
public bool Equals(IEnumerable<string> x, IEnumerable<string> y)
{
//do your null checks
return x.SequenceEqual(y);
}
public int GetHashCode(IEnumerable<string> obj)
{
return 0;
}
}
Then use that in GroupBy:
var namelist = json_converted.GroupBy(n => n.names, new NamesComparer())
.Select(i => new { name = i.Key, Count = i.Count() });

How to return Distinct Row using LINQ

I have two rows which have all the data same except one column.
I want to show only one row on the UI but one row which has different data should be shown as comma seperated values.
Sample Data
PricingID Name Age Group
1 abc 56 P1
1 abc 56 P2
Output should be :
PricingID Name Age Group
1 abc 56 P1,P2
I am using this approach but it is not working , it gives me two rows only but data i am able to concatenate with comma.
List<PricingDetailExtended> pricingDetailExtendeds = _storedProcedures.GetPricingAssignment(pricingScenarioName, regionCode, productCode, stateCode, UserId, PricingId).ToList();
var pricngtemp = pricingDetailExtendeds.Select(e => new
{
PricingID = e.PricingID,
OpportunityID = e.OpportunityID,
ProductName = e.ProductName,
ProductCD = e.ProductCD
});
pricingDetailExtendeds.ForEach(e=>
{
e.ProductCD = string.Join(",",string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductCD).ToArray())).Split(',').Distinct().ToArray());
e.OpportunityID =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.OpportunityID).ToArray())).Split(',').Distinct().ToArray());
e.ProductName =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductName).ToArray())).Split(',').Distinct().ToArray());
}
);
// pricingDetailExtendeds = GetUniquePricingList(pricingDetailExtendeds);
return pricingDetailExtendeds.Distinct().AsEnumerable();
Any body can suggest me better approach and how to fix this issue ?
Any help is appreciated.
You want to use the GroupBy linq function.
I then use the String.Join function to make the groups comma seperated.
So something like this:
var pricingDetailExtendeds = new[]
{
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P1"
},
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P2"
}
};
var pricngtemp =
pricingDetailExtendeds.GroupBy(pde => new {pde.PricingID, pde.Name, pde.Age})
.Select(g => new {g.Key, TheGroups = String.Join(",", g.Select(s => s.Group))}).ToList();
You can easily extrapolate this to the other fields.
To return the PricingDetailExtended, the just create it in the select. So something like this
.Select(g => new PricingDetailExtended {
PricingID = g.Key.PricingId,
TheGroups = String.Join(",", g.Select(s => s.Group))
}).ToList();
You won't have the field TheGroups though, so just replace that field with the proper one.
An example of what I was describing in my comment would be something along the lines of the following. I would expect this to be moved into a helper function.
List<PriceDetail> list = new List<PriceDetail>
{
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P1"},
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P2"},
new PriceDetail {Id = 2, Age = 56, Name = "abc", group = "P1"}
};
Dictionary<PriceDetailKey, StringBuilder> group = new Dictionary<PriceDetailKey, StringBuilder>();
for (int i = 0; i < list.Count; ++i)
{
var key = new PriceDetailKey { Id = list[i].Id, Age = list[i].Age, Name = list[i].Name };
if (group.ContainsKey(key))
{
group[key].Append(",");
group[key].Append(list[i].group);
}
else
{
group[key] = new StringBuilder();
group[key].Append(list[i].group);
}
}
List<PriceDetail> retList = new List<PriceDetail>();
foreach (KeyValuePair<PriceDetailKey, StringBuilder> kvp in group)
{
retList.Add(new PriceDetail{Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
}
you could even convert the final loop into a LINQ expression like:
group.Select(kvp => new PriceDetail {Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
Its worth noting you could do something similar without the overhead of constructing new objects if, for example, you wrote a custom equality comparer and used a list instead of dictionary. The upside of that is that when you were finished, it would be your return value without having to do another iteration.
There are several different ways to get the results. You could even do the grouping in SQL.

Is it possible to use Intersect on complex arrays in C#?

I have two arrays of student names and test scores.
Each array contains only distinct students (no duplicates) and is structured such that arrStudentGroup1[0][0] = "Bob" and arrStudentGroup1[0][1] = "98".
Given two arrays is it possible to use Intersect to create a third array of students that exists in both arrStudentGroup1 and arrStudentGroup2?
I'd like the third array to have their names and test scores. How do I do this?
If you want just the names students that are in both groups and not the associated test scores, just intersect on the name array element:
var students1 = arrStudentGroup1.Select(group => group[0]);
var students2 = arrStudentGroup2.Select(group => group[0]);
var studentsInBoth = students1.Intersect(students2);
If you also want the associated test scores you'll need to implement an IEqualityComparer<T> that compares the first element of each array.
If you want the associated test scores then join the two arrays:
var intersection = from s1 in arrStudentGroup1
join s2 in arrStudentGroup2 on s1[0] equals s2[0]
select new {Name = s1[0], Score1 = s1[1], Score2 = s2[1]}
foreach (var item in intersection)
{
Console.Writeline("{0}: s1={1}, s2={2}", Name, Score1, Score2);
}
First zip (in the general sense, rather than the Zip method) the arrays into a structure that groups students with scores:
Given:
string[][] arrStudentGroup1 = new string[][]{new string[]{"Bob","98"}, new string[]{"Alice","98"}, new string[]{"Charles","78"}, new string[]{"Dariah","99"}};
string[][] arrStudentGroup2 = new string[][]{new string[]{"Bob","98"}, new string[]{"Fiona","98"}, new string[]{"Eve","78"}, new string[]{"Dariah","99"}};
Then:
var zipped1 = arrStudentGroup1.Select(student => new {Name = student[0], Score = student[1]});
var zipped2 = arrStudentGroup2.Select(student => new {Name = student[0], Score = student[1]});
Now get the intersection. Note that if the same student name was in one but with a different score, it would not count as an intersection. That can be dealt with too, but I'm interpreting your question as not wanting that case. Let me know if I read you wrong:
var inter = zipped1.Intersect(zipped2);
Now, you can ideally work with this anyway, or even with new {Name = student[0], Score = int.Parse(student[1])} above and have a number instead of a string (more useful in most cases), which frankly is nicer than dealing with an array of arrays, along with being more typesafe. Still, if you really want it in the same format string[] format:
var interArray = inter.Select(st => new string[]{st.Name, st.Score});
And if you really, really want the whole thing in the same string[][] format:
var interArrays = interArray.ToArray();
Or for the one-line wonder (less good readability mostly, but sometimes it's nice to put a query on one line if there's other things going on in the same method):
var interArrays = arrStudentGroup1
.Select(student => new {Name = student[0], Score = student[1]})
.Intersect(
arrStudentGroup2
.Select(student => new {Name = student[0], Score = student[1]})
).Select(st => new string[]{st.Name, st.Score}).ToArray()
Output:
{"Bob", "98"},{"Dariah", "99"}
Edit: Alternatively, define an IEqualityComparer<string[]> like:
public class StudentComparer : IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if(ReferenceEquals(x, y))
return true;
if(x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(string[] arr)
{
return arr == null ? 0 : arr.Select(s => s == null ? 0 : s.GetHashCode()).Aggregate((x, y) => x ^ y);
}
}
Then just use it directly:
var intersection = arrStudentGroup1.Intersect(arrStudentGroup2, new StudentComparer());
Gives the same output. Simpler really, but my instincts upon seeing arrays being used as objects was to get it into a real object as soon as I can, and really, it's not a bad instinct - it can make much else easier too.
Well, you could do somthing like this,
var studentsInGroup1 = arrStudentGroup1.Select(s => new
{
Name = s[0],
Score = s[1]
});
var studentsInGroup2 = arrStudentGroup2.Select(s => new
{
Name = s[0],
Score = s[1]
});
var studentsInBothGroups = studentsInGroup1.Join(
studentsInGroup2,
s => s.Name,
s => s.Name,
(one, two) => new
{
Name = one.Name,
Scores = new[] { one.Score, two.Score }
});
Which should give you a handy anonymous type you can access like this.
foreach(var student in studentsInBothGroups)
{
var Name = student.Name;
var Group1Score = student.Scores[0];
var Group2Score = student.Scores[1];
}

Parse string and order by parsed value

i am trying to build linq expression to solve my problem. I have list of strings
List<string> arr = new List<string>();
arr.Add("<desc><ru>1</ru><en>3</en></desc>");
arr.Add("<desc><ru>2</ru><en>4</en></desc>");
i want to parse every item and order results
fake sample:
arr.Select(ParseItem("en")).OrderBy(x)
then we have two items in ru in order 1,2
Thanks for all and sorry for my bad English
Thanks for all response but how to convert now results to IQueryable
class Test { public string data { get; set; } }
List<Test> arr = new List<Test>();
arr.Add(new Test { data = "<desc><ru>AAA</ru><en>One</en></desc>" });
arr.Add(new Test { data = "<desc><ru>1</ru><en>Two</en></desc>" });
arr.Add(new Test { data = "<desc><ru>22</ru><en>Ab</en></desc>" });
IQueryable<Test> t = arr.AsQueryable();
// here the trouble how to convert to IQueryable<Test>
t = t.Select(s => XElement.Parse(s.data)).Select(x => x.Element("en")).
OrderBy(el => el.Value);
Thanks again
After the question update - this will return your ordered data by <en> node value:
var result = arr
.OrderBy(t=>
XElement.Parse(t.data).Element("en").Value
);
The result valiable is of IOrderedEnumerable<Test> type.
This will produce a list of the values in ru tags (assuming they are integers), ordered by the values in en tags (again, assuming integers).
List<string> items = arr.Select(s => XElement.Parse(s))
.OrderBy(xml => (int)xml.Element("en"))
.Select(xml => (int)xml.Element("ru"))
.ToList();
If you simply want to enumerate, you can omit the ToList call:
foreach (var item in arr.Select(s => XElement.Parse(s))
.OrderBy(xml => (int)xml.Element("en"))
.Select(xml => (int)xml.Element("ru")))
{
// do something with item
}
I'm not sure I've got what the excepted results are, but if you need to select values in en ordered by the value in ru then here it is:
var orderedItems = (
from item in arr
let x = XElement.Parse(item)
let ruValue = (int)x.Element("ru")
let enValue = (int)x.Element("en")
orderby ruValue
select enValue
).ToList();
I don't know if it is too late, but if you are wanting to parse the text and if it is an integer then sort by value otherwise sort by text, then this might help.
You need to define a function like this to enable parsing in LINQ expressions:
Func<string, int?> tryParseInteger = text =>
{
int? result = null;
int parsed;
if (int.TryParse(text, out parsed))
{
result = parsed;
}
return result;
};
Then you can do queries like this:
var xs = new [] { "Hello", "3ff", "4.5", "5", };
var rs =
(from x in xs
select tryParseInteger(x)).ToArray();
// rs == new int?[] { null, null, null, 5, };
In your case you possibly want something like this:
var elements = new []
{
"<desc><ru>AAA</ru></desc>",
"<desc><ru>1</ru></desc>",
"<desc><ru>42</ru></desc>",
"<desc><ru>-7</ru></desc>",
"<desc><ru>BBB</ru></desc>",
"<desc><ru>22</ru></desc>",
};
var query =
from e in elements
let xe = XElement.Parse(e)
let v = xe.Element("ru").Value
orderby v
orderby tryParseInteger(v)
select v;
Which would give you:
{ "AAA", "BBB", "-7", "1", "22", "42" }
If you want to treat non-integers (ie parsed as null) to be zero then change the query by using this line:
orderby tryParseInteger(v) ?? 0
Then you'll get this:
{ "-7", "AAA", "BBB", "1", "22", "42" }
I hope this helps.

Categories