I have a List<ReportObject> and want to be able to combine certain elements of the list into a single element based on the equality of certain properties from one element matching certain other properties from a second element in the list. In this case, I want to update the first element with values from the second element and then return a list with only the collection of "first elements".
Perhaps GroupBy (or LINQ in general) isn't the right solution here, but it does seem like it would be a lot cleaner than doing a traditional foreach loop and newing up a second list. What I want is something like this:
List<ReportObject> theList = new List<ReportObject>()
{ new ReportObject() { Property1 = "1", Property2 = "2" },
new ReportObject() { Property1 = "2", Property2 = "3" }
new ReportObject() { Property1 = "1", Property2 = "3" } };
List<ReportObject> newList = new List<ReportObject>();
for(int i = 0; i < theList.Count; i++)
{
for(int j = i + 1; i < theList.Count; j++)
{
if (theList[i].Property1 == theList[j].Property2)
{
theList[i].Property2 = theList[j].Property2);
newList.Add(theList[i]);
theList.RemoveAt(j);
}
}
}
return newList;
var newList = theList.GroupBy(x => x.Property1).Select(g => g.First()).ToList();
From your code, it's obviously that the newList will contain all items which have Property1 = Property2:
var newList = theList.SelectMany((x,i)=>
theList.Where((y,j)=>j>i && y.Propery2 == x.Propery1)
.Select(a=> new ReportObject{
Property1=x.Property1,
Property2=x.Property1
});
I think something like theList.GroupBy(x => x.Property1, x => x.Property2); will do what you want.
Related
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.
I have a class where it has a collection of list. I want to search a parameter inside one of the list. So the location that I found the list, I want to get at the same location at other list in the same class...
How to achieve this?
void Main()
{
var myListCollectionObj = new myListCollection();
Console.WriteLine(myListCollectionObj.firstList);
Console.WriteLine(myListCollectionObj.secondList);
Console.WriteLine(myListCollectionObj.thirdList);
var testFirstList = myListCollectionObj.firstList.Where(x => x == 3); //then i want to get "33", and 333 from secondList and thirdList respectively
Console.WriteLine(testFirstList);
}
class myListCollection
{
public List<int> firstList = new List<int>(){ 1, 2, 3, 4, 5};
public List<string> secondList = new List<string>(){ "11", "22", "33", "44", "55"};
public List<int> thirdList = new List<int>(){ 111, 222, 333, 444, 555};
}
int index = myListCollectionObj.firstList.IndexOf(3);
string elem2;
int elem3;
if (index >= 0 && index < myListCollectionObj.secondList.Length)
elem2 = myListCollectionObj.secondList[index]
if (index >= 0 && index < myListCollectionObj.thirdList.Length)
elem3 = myListCollectionObj.thirdList[index]
You don't need LINQ for that, only List<T>'s own IndexOf() method and indexer property:
int index = myListCollectionObj.firstList.IndexOf(3);
string secondValue = myListCollectionObj.secondList[index];
int thirdValue = myListCollectionObj.thirdList[index];
You may want to add error handling: if 3 is not contained in firstList, an index of -1 is returned by IndexOf().
I guess the best way if there are more than one 3 values would be using simple for loop:
var testFirstList = new List<int>();
var testSecondList = new List<string>();
var testThirdList = new List<int>();
for (var i = 0; i < myListCollectionObj.firstList.Length; ++i) {
if (myListCollectionObj.firstList[i] == 3) {
testFirstList.Add(myListCollectionObj.firstList[i]);
testSecondList.Add(myListCollectionObj.secondList[i]);
testThirdList.Add(myListCollectionObj.thirdList[i]);
}
}
A good guideline is that if you find yourself combining indices and LINQ, you probably have other options available. In this case, a good alternative would be using Zip
This approach lets you combine the 3 collections and act upon the resulting zipped collection as a single entity such that indices are no longer directly required.
var result = firstList.Zip(
secondList.Zip(thirdList,
(b, c) => new { b, c }),
(a, b) => new { Value1 = a, Value2 = b.b, Value3 = b.c })
.Where(x => x.Value1 == 3).ToList();
result.ForEach(v => Console.WriteLine(v));
correct me if i am wrong are you looking for the index of the item you searched in first list and then use the same index to retrieve from other list
If yes
Try this
var testFirstList = myListCollectionObj.firstList.Where(x => x == 3).FirstOrDefault(); //then i want to get "33", and 333 from secondList and thirdList respectively
var index = myListCollectionObj.firstList.IndexOf(testFirstList);
I have a List with Users:
List<UserEntry> list1 = new List<UserEntry>();
list1.Add(new UserEntry { login = "1", requestorList = new List<string>() { "1", "2", "3" } });
list1.Add(new UserEntry { login = "2", requestorList = new List<string>() { "1", "4", "3" } });
list1.Add(new UserEntry { login = "3", requestorList = new List<string>() { "1", "2", "3" } });
I want to find the elements that have same requestorList and group them in a second List. In the above example the first and third element have 1,2,3.
I tried this and it doesn't work:
for (int i = 0; i < list1.Count; i++)
{
var kar = list1.ElementAt(i);
for (int j = 0; j < list1.Count; j++)
{
if(kar.requestorList.Equals(list1.ElementAt(j).requestorList))
{
list2.Add(kar);
}
}
}
EDIT: The secoond List should have only 2 elements, since the first one and the third have same requestorLists
If you want to obtain the List you want, fancy doing this:
First of all, you need to add a second if, like this:
if(list1.ElementAt(i).Equals(list1.ElementAt(j))){
continue;
}
in order to skip the cases where you would compare an element to itself.
Also, if you don't want duplicates, use this instead of only doing list2.Add(kar); :
if(!list2.Contains(kar)){
list2.Add(kar);
}
Edit: The full code should look something like this if i didn't mess up:
for (int i = 0; i < list1.Count; i++)
{
var kar = list1.ElementAt(i);
for (int j = 0; j < list1.Count; j++)
{
if(kar.requestorList.SequenceEqual(list1.ElementAt(j).requestorList))
{
if(list1.ElementAt(i).Equals(list1.ElementAt(j))){
continue;
}
if(!list2.Contains(kar)){
list2.Add(kar);
}
}
}
}
This is your code with a slight difference
for (int i = 0; i < list1.Count; i++)
{
var kar = list1.ElementAt(i);
for (int j = i+1; j < list1.Count; j++)
{
if (Enumerable.SequenceEqual(kar.requestorList.OrderBy(t => t), list1.ElementAt(j).requestorList.OrderBy(t => t)))
{
list2.Add(kar);
list2.Add(list1.ElementAt(j));
}
}
}
There are several problems with the code:
You can use indexer to access the element from the list instead of ElementAt() which is more verbal. It looks cleaner to me
Equals() only compares reference of your list, not the content. So ["1", "2", "3"] and ["1", "3", "2"] will give false
And the other thing is you compare element in list1 with itself like other answers mention
Here's my attempt using LINQ:
var list2 = list1.GroupBy(e =>
e.requestorList.OrderBy(r => r) //Order list ascending
.Aggregate((i, j) => i + j)) //Append elements in the list together to create "signature" of the list
.Where(g => g.Count() > 1) //Select grouping that has at least 2 elements
.SelectMany(g => g); //Flatten all the lists into single list
I use OrderBy() and Aggregate() to create kind of "signature" for your requestorList, so that ["1", "3", "2"] and ["1", "2", "3"] having the same signature "123", then group the elements by that signature
Not sure what you expect in the list2, so I just flatten everything that have duplicate requestorList into single list
I have two lists like below in C#.
List 1 = [{Item="A",position =1},{Item="B",position =2},{Item="A",position =3}]
List 2 = [{Item="AA",position =1},{Item="BB",position =2},{Item="AC",position =3}]
Now i want to remove duplicate values in the List 1 and that position should be removed in the List 2.
Example o/p
List 1 = [{Item="A",position =1},{Item="B",position =2}]
List 2 = [{Item="AA",position =1},{Item="BB",position =2}]
Can any one help me. Thanks.
List<string> lst1 = new List<string> { "A", "B", "A" };
List<string> lst2 = new List<string> { "AA", "BB", "AC" };
HashSet<string> seen = new HashSet<string>();
for (int i = 0; i < lst1.Count; i++) {
if (!seen.Add(lst1[i])) {
lst1.RemoveAt(i);
lst2.RemoveAt(i);
i--;
}
}
I used a HashSet to "save" the "already seen" elements of lst1 and then simply cycle the lst1 and remove the duplicate elements. HashSet.Add returns true if the HashSet doesn't already have an element, false if it already has it.
It isn't exactly clear what you want/what you have, but here there is the solution for another possible use case:
public class MyObject {
public string Item;
public int Position;
}
List<MyObject> lst1 = new List<MyObject> {
new MyObject { Item = "A", Position = 1 },
new MyObject { Item = "B", Position = 2 },
new MyObject { Item = "A", Position = 3 },
};
List<MyObject> lst2 = new List<MyObject> {
new MyObject { Item = "AA", Position = 1 },
new MyObject { Item = "BB", Position = 2 },
new MyObject { Item = "AC", Position = 3 },
};
HashSet<string> seen = new HashSet<string>();
HashSet<int> toBeDeleted = new HashSet<int>();
for (int i = 0; i < lst1.Count; i++) {
if (!seen.Add(lst1[i].Item)) {
toBeDeleted.Add(lst1[i].Position);
lst1.RemoveAt(i);
i--;
}
}
if (toBeDeleted.Count > 0) {
for (int i = 0; i < lst2.Count; i++) {
if (toBeDeleted.Contains(lst2[i].Position)) {
lst2.RemoveAt(i);
i--;
}
}
// or equivalent and shorter, without the for cycle
//lst2.RemoveAll(x => toBeDeleted.Contains(x.Position));
}
In this case in a first pass on lst1 we remove the duplicate items (as seen in the first example) and "save" the Positions that need to be deleted in the HashSet<int> tobedeleted and then we do a second pass on lst2 to remove the elements that need deleting.
Much not clear what you want do, but I try with this:
var filteredList1 = list1.GroupBy(x => x.Item).Select(g => g.First()).ToList();
var removeElements = list2.Where(f => !filteredList1.Any(t => t.Position == f.Position)).ToList();
removeElements.ForEach(x => list2.Remove(x));
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.