How can I distinct with condition? - c#

Here is class UserArrived:
public class UserArrived{
public string id{get;set;}
}
Here is class OldUser:
public class OldUser{
public string id{get;set;}
public DateTime lastArrived{get;set;}
}
And here is class User:
public class User{
public string id{get;set;}
public Boolean newUser{get;set;}
}
Finally, here is two List:
List<UserArrived> UserArrivedList=new List<UserArrived>();
List<OldUser> OldUserList=new List<OldUser>();
All the id in each class is unique.
Now I need to combine UserArrived and OldUser to a brand new List<User>.
As we know, the user arrives the shop may is a new user or an old user. If the user id in UserArrived also contains in OldUser, the property newUser in the new List is false for true.
In my opinion, I will combine two List into one first and then use the distinct method to remove the duplicates.
However, it seems the distinct can not run with a condition.
Although I can use several foreach to solve this while I feel it is so troublesome. I want to use something easy just like lambda or linq. How can I achieve this?
=============================
Here is an example of the input:
List<UserArrived> UserArrivedList=new List<UserArrived>(){new UserArrived(){id="A"},new UserArrived(){id="B"},new UserArrived(){id="C"}};
List<OldUser> OldUserList=new List<OldUser>(){new OldUser(){id="B",lastArrived=DateTime.Now}};
the output is:
A,true
B,false
C,true

If I understand your requirement you're saying that if an id is in both lists then the user is an old user, otherwise it is a new user.
So here's the simplest way that I could come up with to do it:
IEnumerable<User> users =
Enumerable
.Concat(
UserArrivedList.Select(i => i.id),
OldUserList.Select(i => i.id))
.ToLookup(x => x)
.Select(x => new User() { id = x.Key, newUser = x.Count() == 1 });
Let's test with some input:
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B" },
new OldUser() { id = "C" },
};
Here are my results:
B is the only user who appears in both lists so should be False.
So, there's a bit of confusion about the requirements here.
The OP has added a concrete example of the input data and the expected output.
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
new UserArrived() { id = "C" }
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B", lastArrived = DateTime.Now }
};
With this input the OP is expecting True, False, True for A, B, C respectively.
Here is the code of the four current answers:
var results = new []
{
new
{
answered = "Enigmativity",
users = Enumerable
.Concat(
UserArrivedList.Select(i => i.id),
OldUserList.Select(i => i.id))
.ToLookup(x => x)
.Select(x => new User() { id = x.Key, newUser = x.Count() == 1 })
},
new
{
answered = "JQSOFT",
users = UserArrivedList.Select(x => x.id)
.Concat(OldUserList.Select(y => y.id))
.Distinct()
.Select(x => new User
{
id = x,
newUser = OldUserList.Count(o => o.id == x) == 0,
})
},
new
{
answered = "Anu Viswan",
users =
UserArrivedList
.Join(OldUserList, ual => ual.id, oul => oul.id, (ual, oul) => new User { id = oul.id, newUser = false })
.Concat(UserArrivedList.Select(x => x.id).Except(OldUserList.Select(x => x.id))
.Concat(OldUserList.Select(x => x.id).Except(UserArrivedList.Select(x => x.id)))
.Select(x=> new User{ id = x, newUser = true}))
},
new
{
answered = "Barns",
users =
UserArrivedList.Select(i => i.id)
.Union(OldUserList.Select(i => i.id))
.Select(j => new User
{
id = j,
newUser =
!(UserArrivedList.Select(i => i.id).Contains(j)
&& OldUserList.Select(i => i.id).Contains(j))})
}
};
That gives the output of:
So, currently all of the answers presented match the OP's example.
I'd be interested in the OP commenting on this as the input data:
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B" },
new OldUser() { id = "C" },
};
When I run this I get this output:
Here three users match and one does not.
This all boils down to what the description means:
As we know, the user arrives the shop may is a new user or an old user. If the user id in UserArrived also contains in OldUser, the property newUser in the new List is false for true.

The thing about LINQ--it isn't always easy. In fact it can get quit cluttered. In the question statement I read,
I want to use something easy just like lambda or linq.
Well, that is relative. But, I think that when using LINQ, one should try to keep it simple. Even break the statement down into multiple statements if necessary. For that reason I propose this solution (demonstrated in a console app):
static void Main(string[] args)
{
Console.WriteLine();
Console.WriteLine("--------------------Test This Code -----------------------");
var combined = TestUserCombined();
//The following is just to demonstrate the list is populated properly
combined.OrderBy(s => s.id.PadLeft(4, '0')).ToList().ForEach(k => Console.WriteLine($"X id: {k.id} | isNew:{k.newUser}"));
}
private static IEnumerable<User> TestUserCombined()
{
List<UserArrived> userArrivedList=new List<UserArrived>();
List<OldUser> oldUserList=new List<OldUser>();
//populate the lists...
for(int i = 0; i < 20; i+=2)
{
var userArrived = new UserArrived();
userArrived.id = i.ToString();
userArrivedList.Add(userArrived);
}
for(int i = 0; i < 20; i+=3)
{
var oldUser = new OldUser();
oldUser.id = i.ToString();
oldUserList.Add(oldUser);
}
//Now for the solution...
var selectedUserArrived = userArrivedList.Select(i => i.id);
var selectedOldUser = oldUserList.Select(i => i.id);
var users = selectedUserArrived
.Union(selectedOldUser)
.Select(j => new User{id=j,newUser=!(selectedUserArrived.Contains(j) && selectedOldUser.Contains(j))});
return users;
}
Certainly, this all could have been done in one statement, but I believe this makes it more readable and understandable.
EDIT:
There has been some discussion amongst the coders posting solutions as to exactly what conditions must be met in order for the value "newUser" to be set to "true". It was my understanding from the initial posted question that the "id" must be present in both lists "UserArrivedList" AND "OldUserList", but I tend to agree with #JQSOFT that it makes more sense that the only condition that must be met should be that the "id" need only be present in "OldUserList". If that is indeed the case than the Select() expression above should be .Select(j => new User{id=j,newUser=!selectedOldUser.Contains(j)});

I hope I understood your query. One way to achieve this using Linq would be
var users = UserArrivedList.Join(OldUserList,ual=>ual.id,oul=>oul.id,(ual,oul)=>new User{id=oul.id,newUser=false})
.Concat(UserArrivedList.Select(x=>x.id).Except(OldUserList.Select(x=>x.id))
.Concat(OldUserList.Select(x=>x.id).Except(UserArrivedList.Select(x=>x.id)))
.Select(x=> new User{id=x,newUser=true}));

Now you need to create a distinct list of User type from two lists of different types; UserArrived and OldUser objects. A user is identified by a unique id of string type.
Accordingly, I'd suggest this:
var users = UserArrivedList.Select(x => x.id)
.Concat(OldUserList.Select(y => y.id))
.Distinct()
.Select(x => new User
{
id = x,
newUser = OldUserList.Count(o => o.id == x) == 0,
}).ToList();
Which gets the unique ids from both UserArrivedList and OldUserList and creates new User object for each. The OldUserList.Count(o => o.id == x) == 0, assigns false to the newUser property if the user id exists in the OldUserList otherwise true.

Related

Problem returning entity grouped with LINQ in HTTP GET

What I'm doing wrong in this method below? I created a group with linq because I need to group the list by 2 columns and for this grouping I will have a list of files.
[HttpGet]
[Route("versions-by-period")]
public IActionResult GetVersionsByPeriodId(int entityId, int periodId)
{
var versionsInvoiceBillet = db.RemittanceInvoiceBilletVersionsCompacts
.Where(x => x.LegalEntityId == entityId && x.PeriodId == periodId && x.IsCurrent && x.DownloadHash != null)
.GroupBy(x => new { x.LifePolicyNumber, x.LegalEntityGroupNumber },
i => new { i.DownloadHash, i.FileTypeEnum, i.DueDate }, (key, group) => new
{
LifePolicyNumber = key.LifePolicyNumber,
LegalEntityGroupNumber = key.LegalEntityGroupNumber,
Files = group.ToList()
});
return Ok(versionsInvoiceBillet.Select(x => new {
lifePolicyNumber = x.LifePolicyNumber,
legalEntityGroupNumber = x.LegalEntityGroupNumber,
invoiceAndBillet = x.Files.Select(f => new {
downloadHash = f.DownloadHash,
fileTypeEnum = f.FileTypeEnum,
dueDatet = f.DueDate
})
}));
}
If I try to call this method with Postman, the body comes empty. The problem is in invoiceAndBillet information that is returned, if I change to below, the body comes filled.
return Ok(versionsInvoiceBillet.Select(x => new {
lifePolicyNumber = x.LifePolicyNumber,
legalEntityGroupNumber = x.LegalEntityGroupNumber,
invoiceAndBillet = x.Files.Select
}));
If I try to debug the selection that I'm trying to return, I get this message below:

How to select items using lambda expression

I am selecting data from a data store
I am able to fetch first array [0] {IHSWCFService.ServiceReference1.Observation} using below query
var newData = data.Select(a => new IHSData
{
PriceSymbol = Convert.ToString(a.PriceId),
PeriodData = Convert.ToDateTime(a.ObservationVector.Select(x => x.Period).FirstOrDefault()),
StatusID = Convert.ToInt32(a.ObservationVector.Select(x => x.StatusId).ToList()),
Price = Convert.ToDouble(a.ObservationVector.Select(x => x.price).FirstOrDefault()),
});
But I want to select next array also. as showing in below screen screenshot
[0]{IHSWCFService.ServiceReference1.Observation}
[1]{IHSWCFService.ServiceReference1.Observation}
[2]{IHSWCFService.ServiceReference1.Observation}
Could you please help me. Thanks
You might want all your properties in IHSData to be lists:
var newData = data.Select(a => new IHSData
{
PriceSymbol = Convert.ToString(a.PriceId),
PeriodData = a.ObservationVector.Select(x => Convert.ToDateTime(x.Period)).ToList(),
StatusID = a.ObservationVector.Select(x => Convert.ToInt32(x.StatusId)).ToList(),
Price = a.ObservationVector.Select(x => Convert.ToDouble(x.price)).ToList(),
});
Which is not such a good idea, because you have to index them separately. So another option would be to use SelectMany:
var newData = data
.SelectMany(a => a.ObservationVector.Select(v =>
new IHSData
{
PriceSymbol = Convert.ToString(a.PriceId), // parent PriceId
PeriodData = Convert.ToDateTime(v.Period),
StatusID = Convert.ToInt32(v.StatusId),
Price = Convert.ToDouble(v.price),
}))
.ToList();
The latter approach will create a separate IHSData instance for each ObservationVector, and some of them will share the same PriceId of the parent class.
Or, the third approach would be to have a new class, which would be the "parsed version of the ObservationVector", i.e. contain properties for parsed values, something like:
var newData = data.Select(a => new IHSData
{
PriceSymbol = Convert.ToString(a.PriceId),
Data = a.ObservationVector.Select(x => ConvertObservationVector(x)).ToList()
});
where ConvertObservationVector is a method which converts from an ObservationVector to your parsed class.

C# Linq GroupBy and Select performance

I'm working with a third part service of my client that is providing me a list of products and services, which is a little bit of a mess.
The list will return all of the services for the product but the product repeats itself, for example:
The product A has the service A and the product A also has the service B so, when i receive the list i will get two products A with services A and B
What i need to do is to group all of the products to get only one with all of it's services and i have done so but i'm worried about performance because i think my solution isn't the 'best' one:
var productsNormalized = products.Data.AsEnumerable().Select(x => new ProdutoSSO
{
CodigoServico = int.Parse(string.IsNullOrEmpty(x["CodigoServico"].ToString()) ? "0" : x["CodigoServico"].ToString()),
CodigoPeca = int.Parse(string.IsNullOrEmpty(x["CodigoPeca"].ToString()) ? "0" : x["CodigoPeca"].ToString()),
CodigoFamilia = int.Parse(string.IsNullOrEmpty(x["CodigoFamilia"].ToString()) ? "0" : x["CodigoFamilia"].ToString()),
Familia = x["Familia"].ToString(),
Servico = x["Servico"].ToString(),
Peca = x["Peca"].ToString(),
Hash = x["Hash"].ToString(),
Valor = decimal.Parse(string.IsNullOrEmpty(x["Valor"].ToString()) ? "0" : x["Valor"].ToString())
})
.GroupBy(x => new { x.CodigoPeca, x.CodigoFamilia, x.Familia, x.Peca })
.Select(x => new ProdutoGroup
{
Produto = new Produto
{
CodigoPeca = x.Key.CodigoPeca,
CodigoFamilia = x.Key.CodigoFamilia,
Familia = x.Key.Familia,
Peca = x.Key.Peca
},
Servicos = x.Select(y => new ProdutoServico
{
CodigoServico = y.CodigoServico,
Hash = y.Hash,
Servico = y.Servico,
Valor = y.Valor
}).ToList()
});
Is there a better way to achieve this or this is as good as it gets?
Using Aggregate you could do something like this (assuming you are starting with a list of ProdutoSSO, which might not be entirely necessary):
var productsNormalized = productoSSOs
.Aggregate(new Dictionary<Produto,List<ProdutoServico>>(ProductoComparer),
(p,c) => {
var product = new Produto
{
CodigoPeca = c.CodigoPeca,
CodigoFamilia = c.CodigoFamilia,
Familia = c.Familia,
Peca = c.Peca
};
var service = new ProdutoServico
{
CodigoServico = c.CodigoServico,
Hash = c.Hash,
Servico = c.Servico,
Valor = c.Valor
};
if (!p.ContainsKey(product))
{
p[product] = new List<ProductoServico>() { service };
}
else
{
p[product].Add(service);
}
return p;
});
Where ProductoComparer is an IEqualityComparer<Producto> (or alternatively you could implement Equals and GetHashCode in Producto, or you could just generate a key some other way - concatenating fields together, for example).
This is obviously untested since I don't have the original classes or data.
This would give you a Dictionary<Producto, List<ProductoServico>> which might be all you need, or you can easily transform it into an IEnumerable<ProdutoGroup> if you want.

Search form has an Enum dropdown field whose value may be null (none selected) once it reaches my lambda Linq query

Basically, if the user selected no option from the dropdown combo, I want it to be left out from my Linq query that looks something like this:
// this is how I manage the form post data, please
// propose a better way if you know one
Dictionary<string, string> formdata = new Dictionary<string, string>();
foreach(string key in Request.Form.AllKeys)
{
formdata.Add(key, Request.Form[key]);
}
// getting the title
string title = "";
formdata.TryGetValue("postedTitle", out title);
// getting the level
string levelString = "";
formdata.TryGetValue("postedLevel", out levelString );
int level = -1;
if(levelString != "")
{
Int32.TryParse(levelString , out level);
}
var model = new FooIndexVM
{
Foos = _ctx.SomeDbSet.Where(w => w.Title.Contains(title) && w.Level == (Level?)level.Value).Select(x => new FooBarRow
{
FooBarId = x.Id,
....
Since I'm getting either 0 or -1 for the level -- I need a way to gracefully leave the Enum part from the query completely. I will also later add some additional fields similar to this one (may be unselected) so the solution will also work for those, I guess.
You can chain Where commands so this line:
Foos = _ctx.SomeDbSet.Where(w => w.Title.Contains(title) && w.Level == (Level?)level.Value).Select(x => new FooBarRow
{
FooBarId = x.Id,
....
Could be rewritten to be this without changing its behaviour (multiple Wheres effectively become combined with &&s):
Foos = _ctx.SomeDbSet.Where(w => w.Title.Contains(title)).Where(w => w.Level == (Level?)level.Value).Select(x => new FooBarRow
{
FooBarId = x.Id,
....
This then means that you can add some logic around whether to apply the second Where or not like this, for example:
var query = _ctx.SomeDbSet.Where(w => w.Title.Contains(title));
if (level != -1)
{
query = query.Where(w => w.Level == (Level?)level.Value)
}
Foos = query.Select(x => new FooBarRow
{
FooBarId = x.Id,

how to use an Anonymous Type

I have this code:
List<MyObjectOne> myListOne = new List<MyObjectOne>(){new MyObjectOne { ID = 1, field2 = 2}};
List<MyObjectTwo> myListTwo = new List<MyObjectTwo>(){new MyObjectTwo { ID = 4, field6 = "string"}};
bool hasSomething = false;
var result = new[] { new {ID = 0 }}.ToList();
if (hasSomething)
{
// Use list one.
result = myListOne.Select(x => new { ID = x.ID});
}
else
{
// Use list two.
result = myListTwo.Select(x => new { ID = x.ID });
}
foreach (var item in result)
{
// Some logic to manipulate item.ID.
item.ID;
}
What I trying to do it's to use the same anonymous type to select a list of IDs from two different lists. So I use the Select(x => new { ID = x.ID }) in order to create the anonymous type for each table in order to have only one for loop.
The error raised is "Cannot implicitly convert type IEnumerable to List"
¿any idea?
Assuming ID in MyObjectOne and MyObjectTwo are both int's, your code will work if you replace ToList with AsEnumerable:
var result = new[] { new { ID = 0 } }.AsEnumerable();
If the ID properties are some other type (e.g. long's), you need to specify that when creating the anonymous type here:
var result = new[] { new { ID = 0L } }.AsEnumerable();
Or like this:
var result = new[] { new { ID = (long)0 } }.AsEnumerable();
However, this kind of code is kind of confusing, and I wouldn't recommend it for a production environment. Here's an alternative solution that avoids creating a 'dummy' object just for implicit anonymous typing:
var result = hasSomething
? myListOne.Select(x => new { ID = x.ID })
: myListTwo.Select(x => new { ID = x.ID });

Categories

Resources