I want to remove items from a list of entities, when there is a concidence from a list (id). I have written this code, but I am guessing there is a better way to do it, and improve performance.
Here is my code:
List<int> toRemove; //includes the ids of the entities to be removed
if (people.Count > 1)
people.RemoveAll(x => people.Any(y => y != x && toRemove.Contains(x.ID)));
else
people.RemoveAll(x => toRemove.Contains(x.ID));
Given a list of people, for example:
var people = new List<Person>
{
new Person { ID = 1, Name = "Fred1" },
new Person { ID = 2, Name = "Fred2" },
new Person { ID = 3, Name = "Fred3" },
new Person { ID = 4, Name = "Fred4" },
new Person { ID = 5, Name = "Fred5" },
new Person { ID = 6, Name = "Fred6" },
new Person { ID = 7, Name = "Fred7" },
new Person { ID = 8, Name = "Fred8" },
new Person { ID = 9, Name = "Fred9" },
new Person { ID = 10, Name = "Fred10" }
};
And a list of IDs to remove:
List<int> toRemove = new List<int> { 3, 4, 5 };
You can remove the unwanted entries like this:
people = people.Where(p => !toRemove.Contains(p.ID)).ToList();
Oh, and for completeness, here's a Person class to complete the example!
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
And to show it working:
https://ideone.com/ERP3rk
Related
So here I have some code, which works ok. But I want to change the select part to something else, I am not sure what other methods I can use any help would be appreciated.
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var all = new List<People>{new People{Id = 1, Name = "andy1", Age = null}, new People{Id = 2, Name = "andy2", Age = null}, new People{Id = 3, Name = "andy3", Age = null}, new People{Id = 4, Name = "andy4", Age = null}, };
var someOfThem = new List<People>{new People{Id = 1, Name = null, Age = 1}, new People{Id = 2, Name = null, Age = 1},new People{Id = 3, Name = null, Age = 1}};
var test = someOfThem.Select(c =>
{
c.Name = all.Find(a => a.Id == c.Id).Name;
return c;
});
foreach (var item in test)
Console.WriteLine("{0}={1}={2}", item.Id, item.Name, item.Age);
}
}
public class People
{
public int Id
{
get;
set;
}
public int? Age
{
get;
set;
}
public string Name
{
get;
set;
}
}
And here is the result.
1=andy1=1
2=andy2=1
3=andy3=1
I am just wondering is there another way to achieve the same result but a more elegant way? or an easier way?
var test = someOfThem.Select(c =>
{
c.Name = all.Find(a => a.Id == c.Id).Name;
return c;
});
Update
Sorry I did not show my problem properly at first, I have updated my quesiton. Please have a look again.
You can use C#'s LINQ keywords and more specifically, the join keyword assosciated with it:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var all = new List<People> { new People { Id = 1, Name = "andy1", }, new People { Id = 2, Name = "andy2", }, new People { Id = 3, Name = "andy3", }, new People { Id = 4, Name = "andy4", }, };
var someOfThem = new List<People> { new People { Id = 1, Name = null, }, new People { Id = 2, Name = null, } };
var test = from item in someOfThem
join element in all on item.Id equals element.Id
select element;
foreach (var item in test)
Console.WriteLine("{0}={1}", item.Id, item.Name);
}
}
public class People
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
}
The code version would be
var test = someOfThem.Join(all, item => item.Id, element => element.Id, (item, element) => element);
as shown in Robert's comment
You can use the Join (you can also use a dictionary, but I'm not going to show it):
Here's the syntax for join:
var test = someOfThem.Join(all, item => item.Id, element => element.Id,
(item, element) => new Person {
Id = item.Id ?? element.Id,
Name = item.Name ?? element.Name,
Age = item.Age ?? element.Age
});
You can implement Equals and GetHashCode in your People class and use Intersect.
Or, create an EqualityComparer, that way your comparison logic is decoupled:
class Program
{
public static void Main()
{
var all = new List<People> { new People { Id = 1, Name = "andy1", }, new People { Id = 2, Name = "andy2", }, new People { Id = 3, Name = "andy3", }, new People { Id = 4, Name = "andy4", }, };
var someOfThem = new List<People> { new People { Id = 1, Name = null, }, new People { Id = 2, Name = null, } };
var test = all.Intersect(someOfThem, new PeopleIdComparer()).ToList();
foreach (var item in test)
Console.WriteLine("{0}={1}", item.Id, item.Name);
}
}
public class PeopleIdComparer : IEqualityComparer<People>
{
public bool Equals(People x, People y)
{
return x.Id == y.Id;
}
public int GetHashCode(People obj)
{
return HashCode.Combine(obj.Id);
}
}
public class People
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
}
The best way I can describe what I'm trying to do is "Nested DistinctBy".
Let's say I have a collection of objects. Each object contains a collection of nicknames.
class Person
{
public string Name { get; set; }
public int Priority { get; set; }
public string[] Nicknames { get; set; }
}
public class Program
{
public static void Main()
{
var People = new List<Person>
{
new Person { Name = "Steve", Priority = 4, Nicknames = new string[] { "Stevo", "Lefty", "Slim" }},
new Person { Name = "Karen", Priority = 6, Nicknames = new string[] { "Kary", "Birdie", "Snookie" }},
new Person { Name = "Molly", Priority = 3, Nicknames = new string[] { "Mol", "Lefty", "Dixie" }},
new Person { Name = "Greg", Priority = 5, Nicknames = new string[] { "G-man", "Chubs", "Skippy" }}
};
}
}
I want to select all Persons but make sure nobody selected shares a nickname with another. Molly and Steve both share the nickname 'Lefty' so I want to filter one of them out. Only the one with highest priority should be included. If there is a highest priority tie between 2 or more then just pick the first one of them. So in this example I would want an IEnumerable of all people except Steve.
EDIT: Here's another example using music album instead of person, might make more sense.
class Album
{
string Name {get; set;}
int Priority {get;set;}
string[] Aliases {get; set;}
{
class Program
{
var NeilYoungAlbums = new List<Album>
{
new Person{ Name = "Harvest (Remastered)", Priority = 4, Aliases = new string[] { "Harvest (1972)", "Harvest (2012)"}},
new Person{ Name = "On The Beach", Priority = 6, Aliases = new string[] { "The Beach Album", "On The Beach (1974)"}},
new Person{ Name = "Harvest", Priority = 3, Aliases = new string[] { "Harvest (1972)"}},
new Person{ Name = "Freedom", Priority = 5, Aliases = new string[] { "Freedom (1989)"}}
};
}
The idea here is we want to show his discography but we want to skip quasi-duplicates.
I would solve this using a custom IEqualityComparer<T>:
class Person
{
public string Name { get; set; }
public int Priority { get; set; }
public string[] Nicknames { get; set; }
}
class PersonEqualityComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x == null || y == null) return false;
return x.Nicknames.Any(i => y.Nicknames.Any(j => i == j));
}
// This is bad for performance, but if performance is not a
// concern, it allows for more readability of the LINQ below
// However you should check the Edit, if you want a truely
// LINQ only solution, without a wonky implementation of GetHashCode
public int GetHashCode(Person obj) => 0;
}
// ...
var people = new List<Person>
{
new Person { Name = "Steve", Priority = 4, Nicknames = new[] { "Stevo", "Lefty", "Slim" } },
new Person { Name = "Karen", Priority = 6, Nicknames = new[] { "Kary", "Birdie", "Snookie" } },
new Person { Name = "Molly", Priority = 3, Nicknames = new[] { "Mol", "Lefty", "Dixie" } },
new Person { Name = "Greg", Priority = 5, Nicknames = new[] { "G-man", "Chubs", "Skippy" } }
};
var distinctPeople = people.OrderBy(i => i.Priority).Distinct(new PersonEqualityComparer());
EDIT:
Just for completeness, this could be a possible LINQ only approach:
var personNicknames = people.SelectMany(person => person.Nicknames
.Select(nickname => new { person, nickname }));
var groupedPersonNicknames = personNicknames.GroupBy(i => i.nickname);
var duplicatePeople = groupedPersonNicknames.SelectMany(i =>
i.OrderBy(j => j.person.Priority)
.Skip(1).Select(j => j.person)
);
var distinctPeople = people.Except(duplicatePeople);
A LINQ-only solution
var dupeQuery = people
.SelectMany( p => p.Nicknames.Select( n => new { Nickname = n, Person = p } ) )
.ToLookup( e => e.Nickname, e => e.Person )
.SelectMany( e => e.OrderBy( p => p.Priority ).Skip( 1 ) );
var result = people.Except( dupeQuery ).ToList();
See .net fiddle sample
This works once, then you have to clear the set. Or store the results in a collection.
var uniqueNicknames = new HashSet<string>();
IEnumerable<Person> uniquePeople = people
.OrderBy(T => T.Priority) // ByDescending?
.Where(T => T.Nicknames.All(N => !uniqueNicknames.Contains(N)))
.Where(T => T.Nicknames.All(N => uniqueNicknames.Add(N)));
I have the following object:
public class Foo
{
public Int32 Id
public Int32 SampleId
public Int32 CompanyId
public Decimal Data
}
public class Bar
{
public Int32 CompanyId
public Decimal Data
}
I have a list of these objects. I want to perform a calculation where I group the Ids by "CompanyId" first. Then for each company ID, add 3 different SampleIds Data together and returning a new object for each company.
//I want to get the data from SampleId = 2, 4 and 6 added together
//from the Foo object and put into the Data of the new Bar object.
List.GroupBy(l => l.CompanyId).Select( x => new Bar { CompanyId = x.Key, ????? } );
I am stuck with how to perform the calculation after I do the grouping. Any help is much appreciated.
You look to be pretty close to me.
This should work:
var list = new List<Foo>
{
new Foo { CompanyId = 1, Data = 15, Id = 1, SampleId = 2 },
new Foo { CompanyId = 1, Data = 10, Id = 2, SampleId = 4 },
new Foo { CompanyId = 1, Data = 25, Id = 2, SampleId = 6 }
};
var output = list.GroupBy(
l => l.CompanyId,
(key, data) => new Bar { CompanyId = key, Data = data.Sum(d => d.Data) });
Or if you want to filter out just 2,4,6 as sample id (can't say I understand why to be honest) then this could work:
[Test]
public void Testing()
{
var list = new List<Foo>
{
new Foo { CompanyId = 1, Data = 15, Id = 1, SampleId = 2 },
new Foo { CompanyId = 1, Data = 10, Id = 2, SampleId = 4 },
new Foo { CompanyId = 1, Data = 25, Id = 3, SampleId = 8 },
new Foo { CompanyId = 1, Data = 25, Id = 4, SampleId = 12 },
new Foo { CompanyId = 1, Data = 25, Id = 5, SampleId = 14 }
};
var filterList = new List<int> { 2, 4, 6 };
var output = list.Where(l => filterList.Contains(l.SampleId))
.GroupBy(l => l.CompanyId, (key, data) => new Bar { CompanyId = key, Data = data.Sum(d => d.Data) });
Assert.True(output != null);
Assert.True(output.FirstOrDefault() != null);
Assert.True(output.FirstOrDefault().Data == 25);
}
i have a two list
List<Sent> SentList;
List<Messages> MsgList;
both have the same property called MsgID;
MsgList SentList
MsgID Content MsgID Content Stauts
1 aaa 1 aaa 0
2 bbb 3 ccc 0
3 ccc
4 ddd
5 eee
i want to compare the MsgID in Msglist with the sentlist and need items which are not in the sent list using linq
Result
MsgID Content
2 bbb
4 ddd
5 eee
You could do something like:
HashSet<int> sentIDs = new HashSet<int>(SentList.Select(s => s.MsgID));
var results = MsgList.Where(m => !sentIDs.Contains(m.MsgID));
This will return all messages in MsgList which don't have a matching ID in SentList.
The naive approach:
MsgList.Where(x => !SentList.Any(y => y.MsgID == x.MsgID))
Be aware this will take up to m*n operations as it compares every MsgID in SentList to each in MsgList ("up to" because it will short-circuit when it does happen to match).
Well, you already have good answers, but they're most Lambda. A more LINQ approach would be like
var NotSentMessages =
from msg in MsgList
where !SentList.Any(x => x.MsgID == msg.MsgID)
select msg;
You can do like this,this is the quickest process
Var result = MsgList.Except(MsgList.Where(o => SentList.Select(s => s.MsgID).ToList().Contains(o.MsgID))).ToList();
This will give you expected output.
You can do something like
var notSent = MsgSent.Except(MsgList, MsgIdEqualityComparer);
You will need to provide a custom equality comparer as outlined on MSDN
http://msdn.microsoft.com/en-us/library/bb336390.aspx
Simply have that equality comparer base equality only on MsgID property of each respective type. Since the equality comparer compares two instances of the same type, you would need to define an interface or common base type that both Sent and Messages implement that has a MsgID property.
Try,
public class Sent
{
public int MsgID;
public string Content;
public int Status;
}
public class Messages
{
public int MsgID;
public string Content;
}
List<Sent> SentList = new List<Sent>() { new Sent() { MsgID = 1, Content = "aaa", Status = 0 }, new Sent() { MsgID = 3, Content = "ccc", Status = 0 } };
List<Messages> MsgList = new List<Messages>() { new Messages() { MsgID = 1, Content = "aaa" }, new Messages() { MsgID = 2, Content = "bbb" }, new Messages() { MsgID = 3, Content = "ccc" }, new Messages() { MsgID = 4, Content = "ddd" }, new Messages() { MsgID = 5, Content = "eee" }};
int [] sentMsgIDs = SentList.Select(v => v.MsgID).ToArray();
List<Messages> result1 = MsgList.Where(o => !sentMsgIDs.Contains(o.MsgID)).ToList<Messages>();
Hope it should help.
In .NET 6 you can take advantage of .ExceptBy(), which lets you define which property of the first list to compare the items in the second list by:
List<Message> result = messages
.ExceptBy(sentList.Select(msg => msg.MsgID), msg => msg.MsgID)
.ToList();
messages is the first list, whereas a collection of the MsgID properties from sentList is the second list.
Example fiddle here.
Note:
.ExceptBy() produces the set difference between the two collections --> only distinct values will be in the resulting collection. This means that if messages contains the same value more than once (e.g. { "aaa", "bbb", "ccc", "ddd", "ddd", "eee" }), any duplicates will be removed in the resulting collection (--> { "bbb", "ddd", "eee" }).
As an extension method
public static IEnumerable<TSource> AreNotEqual<TSource, TKey, TTarget>(this IEnumerable<TSource> source, Func<TSource, TKey> sourceKeySelector, IEnumerable<TTarget> target, Func<TTarget, TKey> targetKeySelector)
{
var targetValues = new HashSet<TKey>(target.Select(targetKeySelector));
return source.Where(sourceValue => targetValues.Contains(sourceKeySelector(sourceValue)) == false);
}
eg.
public class Customer
{
public int CustomerId { get; set; }
}
public class OtherCustomer
{
public int Id { get; set; }
}
var customers = new List<Customer>()
{
new Customer() { CustomerId = 1 },
new Customer() { CustomerId = 2 }
};
var others = new List<OtherCustomer>()
{
new OtherCustomer() { Id = 2 },
new OtherCustomer() { Id = 3 }
};
var result = customers.AreNotEqual(customer => customer.CustomerId, others, other => other.Id).ToList();
Debug.Assert(result.Count == 1);
Debug.Assert(result[0].CustomerId == 1);
List<Person> persons1 = new List<Person>
{
new Person {Id = 1, Name = "Person 1"},
new Person {Id = 2, Name = "Person 2"},
new Person {Id = 3, Name = "Person 3"},
new Person {Id = 4, Name = "Person 4"}
};
List<Person> persons2 = new List<Person>
{
new Person {Id = 1, Name = "Person 1"},
new Person {Id = 2, Name = "Person 2"},
new Person {Id = 3, Name = "Person 3"},
new Person {Id = 4, Name = "Person 4"},
new Person {Id = 5, Name = "Person 5"},
new Person {Id = 6, Name = "Person 6"},
new Person {Id = 7, Name = "Person 7"}
};
var output = (from ps1 in persons1
from ps2 in persons2
where ps1.Id == ps2.Id
select ps2.Name).ToList();
Person class
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
If u wanna Select items of List from 2nd list:
MainList.Where(p => 2ndlist.Contains(p.columns from MainList )).ToList();
make single list and
var result = list.GroupBy(x => x.BillId).Where(x => x.Count() == 1).Select(x => x.First());
List<Car> cars = new List<Car>() { new Car() { Name = "Ford", Year = 1892, Website = "www.ford.us" },
new Car() { Name = "Jaguar", Year = 1892, Website = "www.jaguar.co.uk" },
new Car() { Name = "Honda", Year = 1892, Website = "www.honda.jp"} };
List<Factory> factories = new List<Factory>() { new Factory() { Name = "Ferrari", Website = "www.ferrari.it" },
new Factory() { Name = "Jaguar", Website = "www.jaguar.co.uk" },
new Factory() { Name = "BMW", Website = "www.bmw.de"} };
foreach (Car car in cars.Where(c => !factories.Any(f => f.Name == c.Name))) {
lblDebug.Text += car.Name;
}
I'm trying to combine 3 lists of objects. I have a person list, address list and an addressRelation list.
I want to combine these lists into a new list ordered by person.id, use it as a datasource for a listview, and then be able to access the properties in the aspx page.
Is this possible?
Roughly
using System.Linq;
class Person
{
public int Id;
public string Name;
}
class Address
{
public int Id;
public string Street;
}
class PersonAddress
{
public int PersonId, AddressId;
}
public class Program
{
public static void Main(string[] args)
{
var personList = new []
{
new Person { Id = 1, Name = "Pete" },
new Person { Id = 2, Name = "Mary" },
new Person { Id = 3, Name = "Joe" }
};
var addressList = new []
{
new Address { Id = 100, Street = "Home Lane" },
new Address { Id = 101, Street = "Church Way" },
new Address { Id = 102, Street = "Sandy Blvd" }
};
var relations = new []
{
new PersonAddress { PersonId = 1, AddressId = 101 },
new PersonAddress { PersonId = 3, AddressId = 101 },
new PersonAddress { PersonId = 2, AddressId = 102 },
new PersonAddress { PersonId = 2, AddressId = 100 }
};
var joined =
from par in relations
join p in personList
on par.PersonId equals p.Id
join a in addressList
on par.AddressId equals a.Id
select new { Person = p, Address = a };
foreach (var record in joined)
System.Console.WriteLine("{0} lives on {1}",
record.Person.Name,
record.Address.Street);
}
}
Outputs:
Pete lives on Church Way
Mary lives on Sandy Blvd
Mary lives on Home Lane
Joe lives on Church Way