Select Single Element from Jagged Array - c#

I'm working on a problem that's making my brain melt although I don't think it should be this hard. My example is long so I'll try to keep my question short!
I have an Array object that contains some elements that are also Arrays. For example:
customerAddresses = new customer_address[]
{
new // address #1
{
customer_id = 6676979,
customer_address_seq = 1,
customer_address_match_codes = new []
{
new
{
customer_address_seq = 1,
customer_id = 6676979,
customer_match_code_id = 5
}
}
},
new // address #2
{
customer_id = 6677070,
customer_address_seq = 1,
customer_address_match_codes = new []
{
new
{
customer_address_seq = 1,
customer_id = 6677070,
customer_match_code_id = 4
},
new
{
customer_address_seq = 1,
customer_id = 6677070,
customer_match_code_id = 5
},
new
{
customer_address_seq = 1,
customer_id = 6677070,
customer_match_code_id = 3
}
}
},
new // address #3
{
customer_id = 6677070,
customer_address_seq = 2,
customer_address_match_code = new []
{
new
{
customer_address_seq = 2,
customer_id = 6677070,
customer_match_code_id = 4
},
new
{
customer_address_seq = 2,
customer_id = 6677070,
customer_match_code_id = 5
}
}
}
};
As you can see, the Array contains a number of address records, with one record per combination of customer_id and customer_address_seq. What I'm trying to do is find the best matching customer_address according to the following rules:
There must be customer_match_code_id equal to 4 and there must be one equal to 5
If there is a customer_match_code_id equal to 3, then consider that customer_address a stronger match.
According to the above rules, the 2nd customer_address element is the "best match". However, the last bit of complexity in this problem is that there could be multiple "best matches". How I need to handle that situation is by taking the customer_address record with the minimum customer_id and minimum customer_address_seq.
I was thinking that using LINQ would be my best bet, but I'm not experienced enough with it, so I just keep spinning my wheels.

Had to make a change to your class so that you are actually assigning your one collection to something:
customer_address_match_codes = new customer_address_match_code[]
{
new
{
customer_address_seq = 1,
customer_id = 6676979,
customer_match_code_id = 5
}
}
And then here is the LINQ that I've tested and does what you specify:
var result = (from c in customerAddresses
let isMatch = c.customer_address_match_codes
.Where (cu => cu.customer_match_code_id == 4).Any () &&
c.customer_address_match_codes
.Where (cu => cu.customer_match_code_id == 5).Any ()
let betterMatch = isMatch && c.customer_address_match_codes
.Where (cu => cu.customer_match_code_id == 3).Any () ? 1 : 0
where isMatch == true
orderby betterMatch descending, c.customer_id, c.customer_address_seq
select c)
.FirstOrDefault ();
I've worked up an example using your data with anonymous types here: http://ideone.com/wyteM

Not tested and not the same names but this should get you going
customer cb = null;
customer[] cs = new customer[] {new customer()};
foreach (customer c in cs.OrderBy(x => x.id).ThenBy(y => y.seq))
{
if(c.addrs.Any(x => x.num == "5"))
{
if(c.addrs.Any(x => x.num == "3"))
{
if (cb == null) cb = c;
if (c.addrs.Any(x => x.num == "2"))
{
cb = c;
break;
}
}
}
}

This sounds like a job for LINQ
var bestMatch = (from address in DATA
where address.customer_address_match_code.Any(
x => x.customer_match_code_id == 4)
where address.customer_address_match_code.Any(
x => x.customer_match_code_id == 5)
select address).OrderBy(
x => x.customer_address_match_code.Where(
y => y.customer_match_code_id >= 3)
.OrderBy(y => y.customer_match_code_id)
.First()
.customer_match_code_id).FirstOrDefault();
My theory is this: Select addresses that have both a customer_match_code_id == 4 and a customer_match_code_id == 5. Then sort them by the the lowest customer_match_code_id they have that are at least 3, and then take the very first one. If there are a customer_match_code_id that equals 3 then that one is selected, if not, some else is selected. If nothing matches both 4 and 5 then null is returned.
Untested.

Seems quite straight forward in LINQ:
var query =
from ca in customerAddresses
where ca.customer_address_match_codes.Any(
mc => mc.customer_match_code_id == 4)
where ca.customer_address_match_codes.Any(
mc => mc.customer_match_code_id == 5)
orderby ca.customer_id
orderby ca.customer_address_seq
orderby ca.customer_address_match_codes.Any(
mc => mc.customer_match_code_id == 3) descending
select ca;
var result = query.Take(1);
How does that look?

Related

C# Append lists in a list

i have a loop that fetch some countries in my DB and according to a condition i need to put some value in that array and then at the end have a final list with all values. Note that i will need 3 checks ( level 1, 2 and 3) inside the loop:
var countries= db.Countries
.Where(x => x.name.Contains(searchText))
.OrderByDescending(o => o.id);
foreach (var country in countries)
{
if(country.Cities.Count() == 1)
{
customList.Add(new CustomClass
{
countCities = 1,
name = country.name,
level = "low"
});
}
else if (country.Cities.Count() == 2)
{
customList.Add(new CustomClass
{
countCities = 2,
name = country.name
level = "medium"
});
}
else if (country.Cities.Count() == 3)
{
customList.Add(new CustomClass
{
countCities = 3,
name = country.name
level = "high"
});
}
}
at the end i want to have something like this :
[ { name="Canada", countCities = 1, level ="low"}, { name = "Australia", countCities = 2, level ="medium"}, { name = "China", countCities = 3, level ="high"} ]
In PHP i know how to do it, but with c# im stuck !
customArray.AddRange( db.Countries
.Where(x => x.name.Contains(searchText))
.OrderByDescending(o => o.id)
.Select(c => new CustomClass { name = c.name, countCities = c.Cities.Count() })
);
Where CustomClass has a simple getter that returns a 'level' label does what you want but with less code
public string level {
get {
return countCities <= 1 ? "low" : (countCities == 2 ? "medium" : "high");
}
}
Use generic lists, those can be easily expanded:
List<string> stringarray = new List<string>();
stringarray.Add("New entry");
Combine them like this:
List<List<string>> lls = new List<List<string>>();
lls.Add(stringarray);
// Don't forget to re-initialize the startingarray if you want to reuse it.
stringarray = new List<string>();
If you want, you can even create a custom class 'CustomClass' and list that:
List<CustomClass> lcc = new List<CustomClass>();
I would eliminate the "SearchText"
customArray.AddRange(from con in db.Countries
join cit in db.Cities on con.id equals cit.id
orderby con.id
select new CustomClass { name= con.name, countCities= con.Cities.Count() }
);
I found it !
for (var index = 0; index <= countries.Count()-1; index++)
{ if ()...
else if()...
}
is the trick !
Dont vote down if you dont have the good answer and try to have your own question. Thanks

Using LINQ, how would you filter out all but one item of a particular criteria from a list?

I realize my title probably isn't very clear so here's an example:
I have a list of objects with two properties, A and B.
public class Item
{
public int A { get; set; }
public int B { get; set; }
}
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 2, B = 1 },
new Item() { A = 2, B = 2 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
Using LINQ, what's the most elegant way to collapse all the A = 2 items into the first A = 2 item and return along with all the other items? This would be the expected result.
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
I'm not a LINQ expert and already have a "manual" solution but I really like the expressiveness of LINQ and was curious to see if it could be done better.
How about:
var collapsed = list.GroupBy(i => i.A)
.SelectMany(g => g.Key == 2 ? g.Take(1) : g);
The idea is to first group them by A and then select those again (flattening it with .SelectMany) but in the case of the Key being the one we want to collapse, we just take the first entry with Take(1).
One way you can accomplish this is with GroupBy. Group the items by A, and use a SelectMany to project each group into a flat list again. In the SelectMany, check if A is 2 and if so Take(1), otherwise return all results for that group. We're using Take instead of First because the result has to be IEnumerable.
var grouped = list.GroupBy(g => g.A);
var collapsed = grouped.SelectMany(g =>
{
if (g.Key == 2)
{
return g.Take(1);
}
return g;
});
One possible solution (if you insist on LINQ):
int a = 2;
var output = list.GroupBy(o => o.A == a ? a.ToString() : Guid.NewGuid().ToString())
.Select(g => g.First())
.ToList();
Group all items with A=2 into group with key equal to 2, but all other items will have unique group key (new guid), so you will have many groups having one item. Then from each group we take first item.
Yet another way:
var newlist = list.Where (l => l.A != 2 ).ToList();
newlist.Add( list.First (l => l.A == 2) );
An alternative to other answers based on GroupBy can be Aggregate:
// Aggregate lets iterate a sequence and accumulate a result (the first arg)
var list2 = list.Aggregate(new List<Item>(), (result, next) => {
// This will add the item in the source sequence either
// if A != 2 or, if it's A == 2, it will check that there's no A == 2
// already in the resulting sequence!
if(next.A != 2 || !result.Any(item => item.A == 2)) result.Add(next);
return result;
});
What about this:
list.RemoveAll(l => l.A == 2 && l != list.FirstOrDefault(i => i.A == 2));
if you whould like more efficient way it would be:
var first = list.FirstOrDefault(i => i.A == 2);
list.RemoveAll(l => l.A == 2 && l != first);

Pivot table in Linq

I know you cannot cannot directly use the tSQL PIVOT function in Linq, but I cannot get the correct Linq syntax for what I feel is a simple transform (which I can do in straight tSQL)
I need to take this data set:
and pivot it into this:
Can someone please help me with the correct Linq syntax?
Here's how I can accomplish this in tSQL (both using PIVOT() and not)
Select piv.* from (
select custom_data_key, custom_data_value from dbo.kb_article_custom_data cd
inner join dbo.kb_article kb on cd.article_id = kb.article_id
where (custom_data_key='article_problem' or custom_data_key = 'article_cause' or custom_data_key='article_solution') and article_number='AKB26'
) d
pivot
(max(custom_data_value) for custom_data_key in([article_problem],[article_cause], [article_solution])) piv;
--WITHOUT USING PIVOT()
select
max(case when t.[custom_data_key]='article_problem' then t.[custom_data_value] end) as Article_problem,
max(case when t.[custom_data_key]='article_cause' then t.[custom_data_value] end) as Article_cause,
max(case when t.[custom_data_key]='article_solution' then t.[custom_data_value] end) as Article_solution
from(select custom_data_key, custom_data_value from dbo.kb_article_custom_data cd
inner join dbo.kb_article kb on cd.article_id = kb.article_id
where (custom_data_key='article_problem' or custom_data_key = 'article_cause' or custom_data_key='article_solution') and article_number='AKB26')t
This LINQ statement will get me the results in the first image above:
var query =
from a in custdata
join b in kbase on a.article_id equals b.article_id
where (a.custom_data_key == "article_problem" || a.custom_data_key == "article_cause" || a.custom_data_key == "article_solution") && b.article_number == id
select new { Key = a.custom_data_key, Value = a.custom_data_value };
Here's what is failing. I take the results of the Linq query above-
var q2 = from row in query
group row by "Value" into g
select new TO_Kbase
{
Problem= g.Where(c => c.Key =="article_problem" ).Select(c => c.Value).ToString(),
Cause = g.Where(c => c.Key =="article_cause").Select(c => c.Value).ToString(),
Solution = g.Where(c => c.Key =="article_solution").Select(c => c.Value).ToString()
};
foreach(var x in q2)
{
TO_Kbase kb = new TO_Kbase();
kb.Problem =x.Problem;
kb.Cause = x.Cause;
kb.Solution = x.Solution;
ta.Add(kb);
}
The output is this:
Problem: System.Data.Linq.SqlClient.Implementation.ObjectMaterializer`1+d__0`1[System.Data.SqlClient.SqlDataReader,System.String]
Cause: System.Data.Linq.SqlClient.Implementation.ObjectMaterializer`1+d__0`1[System.Data.SqlClient.SqlDataReader,System.String]
Solution: System.Data.Linq.SqlClient.Implementation.ObjectMaterializer`1+d__0`1[System.Data.SqlClient.SqlDataReader,System.String]
If I understood your data structure correctly, you could do something very similar to your "WITHOUT USING PIVOT()" T-SQL query:
// Test data.
var kb_article_custom_data = new CustomData[] {
new CustomData() { article_id = 1, custom_data_key = "article_problem", custom_data_value = "when you try ... 1"},
new CustomData() { article_id = 1, custom_data_key = "article_cause", custom_data_value = "the issues may occur ... 1"},
new CustomData() { article_id = 1, custom_data_key = "article_solution", custom_data_value = "1. Click start, then ... 1"},
new CustomData() { article_id = 2, custom_data_key = "article_problem", custom_data_value = "when you try ... 2"},
new CustomData() { article_id = 2, custom_data_key = "article_cause", custom_data_value = "the issues may occur ... 2"},
new CustomData() { article_id = 2, custom_data_key = "article_solution", custom_data_value = "1. Click start, then ... 2"},
new CustomData() { article_id = 3, custom_data_key = "article_problem", custom_data_value = "when you try ... 3"},
//new CustomData() { article_id = 3, custom_data_key = "article_cause", custom_data_value = "the issues may occur ... 3"},
new CustomData() { article_id = 3, custom_data_key = "article_solution", custom_data_value = "1. Click start, then ... 3"},
};
var kb_article = new Article[] {
new Article() { article_id = 1, article_title = "Title ... 1"},
new Article() { article_id = 2, article_title = "Title ... 2"},
new Article() { article_id = 3, article_title = "Title ... 3"},
};
// Query resembling your "without pivot" query.
var result =
from article in kb_article
join custom in kb_article_custom_data on article.article_id equals custom.article_id into ac
select new {
id = article.article_id,
title = article.article_title,
problem = ac.Where(x => x.custom_data_key == "article_problem").Select(x => x.custom_data_value).FirstOrDefault(x => x != null),
cause = ac.Where(x => x.custom_data_key == "article_cause").Select(x => x.custom_data_value).FirstOrDefault(x => x != null),
solution = ac.Where(x => x.custom_data_key == "article_solution").Select(x => x.custom_data_value).FirstOrDefault(x => x != null)
};
foreach (var r in result)
Console.WriteLine(r);
Which produces the following output:
{ id = 1, title = Title ... 1, problem = when you try ... 1, cause = the issues may occur ... 1, solution = 1. Click start, then ... 1 }
{ id = 2, title = Title ... 2, problem = when you try ... 2, cause = the issues may occur ... 2, solution = 1. Click start, then ... 2 }
{ id = 3, title = Title ... 3, problem = when you try ... 3, cause = , solution = 1. Click start, then ... 3 }
If you want to filter by a specific article_id, you need to add a where clause:
var id = 1;
var result =
from article in kb_article
where article.article_id == id
join custom in kb_article_custom_data on article.article_id equals custom.article_id into ac
select new
{
id = article.article_id,
title = article.article_title,
problem = ac.Where(x => x.custom_data_key == "article_problem").Select(x => x.custom_data_value).FirstOrDefault(x => x != null),
cause = ac.Where(x => x.custom_data_key == "article_cause").Select(x => x.custom_data_value).FirstOrDefault(x => x != null),
solution = ac.Where(x => x.custom_data_key == "article_solution").Select(x => x.custom_data_value).FirstOrDefault(x => x != null)
};
Producing the following output:
{ id = 1, title = Title ... 1, problem = when you try ... 1, cause = the issues may occur ... 1, solution = 1. Click start, then ... 1 }
Solution:
var query =
from a in custdata
join b in kbase on a.article_id equals b.article_id into ac
where (a.custom_data_key == "article_problem" || a.custom_data_key == "article_cause" || a.custom_data_key == "article_solution") && a.article_id == id
group a by new { a.article_id} into abc
select new
{
ID = abc.Key.article_id,
Cause = abc.Where(a =>a.custom_data_key == "article_cause").Select(a => a.custom_data_value).FirstOrDefault(x => x != null),
Problem = abc.Where(a => a.custom_data_key == "article_problem").Select(a => a.custom_data_value).FirstOrDefault(x => x != null),
Solution = abc.Where(a => a.custom_data_key == "article_solution").Select(a => a.custom_data_value).FirstOrDefault(x => x != null)
};

Conditional Where clause in LINQ

suppose i am showing data in grid and i have many textboxes for filter the data.
textbox for employee id. if employee id textbox is empty then no where clause will be added but if it is not empty then where clause will be added for that. the same way we can filter data if salary textbox has value or employee name textbox has value.
i try to compose a conditional LINQ query but got error. here is mine
var sName="";
var r = from t in TblFamilies
where 1 == 1
if(sName!="")
{
&& t.Name="Keith";
};
select new
{
t.ID,
t.ParentID,
t.Name,
t.CurDate
};
r.Dump();
Try this:-
First select the data:-
var r = from t in TblFamilie
select new
{
t.ID,
t.ParentID,
t.Name,
t.CurDate
};
Then you can filter based on condition:-
if (sName!="")
r = r.Where(x => x.Name == sName);
If you want to mix And operator and Or operator together, check PredicateBuilder out here: http://www.albahari.com/nutshell/predicatebuilder.aspx
You can simply write like:
// begin with true if you start with And operator.
var predicate = PredicateBuilder.True<TblFamilie>();
predicate = predicate.And(t => t.CureDate < DateTime.UtcNow.AddDays(-1));
// you can mix with Or operator too.
predicate = predicate.Or(t => t.Name.Contains("blah"));
var results = context.TblFamilie
.Where(predicate)
.Select(new
{
// your projection here...
});
// begin with false if you start with Or operator.
var predicate2 = PredicateBuilder.False<TblFamilie>();
predicate2 = predicate2.Or(t => t.CureDate < DateTime.UtcNow.AddDays(-1));
// you can mix with And operator too.
predicate2 = predicate2.And(t => t.Name.Contains("blah"));
var results = context.TblFamilie
.Where(predicate)
.Select(new
{
// your projection here...
});
// even nesting is possible
var inner = PredicateBuilder.False<TblFamilie>();
inner = inner.Or (p => p.Name.Contains("foo"));
inner = inner.Or (p => p.Name.Contains("bar"));
var outer = PredicateBuilder.True<TblFamilie>();
outer = outer.And (p => p.CureDate > DateTime.UtcNow.AddDays(-3));
outer = outer.And (p => p.CureDate < DateTime.UtcNow.AddDays(-1));
outer = outer.And (inner);
var results = context.TblFamilie
.Where(outer)
.Select(new
{
// your projection here...
});
Updated
Okay, lets assume you have a Family class, and you get 'Families' from some where. You can use PredicateBuilder like this:
// you have 4 families from DB, API or anywhere.
var failies = new List<Family>
{
new Family { Id = 1, ParentId = 1, Name = "foo", Birthday = new DateTime(1971, 1, 1) },
new Family { Id = 1, ParentId = 1, Name = "bar", Birthday = new DateTime(1982, 1, 1) },
new Family { Id = 1, ParentId = 1, Name = "foobar", Birthday = new DateTime(1993, 1, 1) },
new Family { Id = 1, ParentId = 1, Name = "fake", Birthday = new DateTime(2000, 1, 1) },
};
// make predicate!
// if a family's Birthday is before than 1980 'or' Name contains "ke".
var predicate = PredicateBuilder.True<Family>();
predicate = predicate.And(o => o.Birthday < new DateTime(1980, 1, 1));
predicate = predicate.Or(o => o.Name.Contains("ke"));
// you should make IQueryable in order to use PredicateBuilder.
var result = failies.AsQueryable()
.Where(predicate)
.Select(o => new
{
o.Id, o.Name, o.Birthday // only project what you want.
})
.ToList();
// now, result should contains "foo" and "fake".
foreach (var family in result)
{
Debug.WriteLine("Name: " + family.Name);
}
Updated2
You can copy & paste to LinqPad in order to test how it works. Before you run this in the LinqPad,
Download LinqKit.dll from above link.
Make sure press 'F4' > Add > Browse > select LinqKit.dll > Add LinqKit namespace in the 'Additional Namespace Imports' tab.
In the Query panel, choose Language to 'C# Statement(s)'
paste this and run.
// you have 4 strings from DB, API or anywhere.
var strings = new List<string>
{
"foo",
"bar",
"foobar",
"fake"
};
// make predicate!
// if a string contains "oo" or "ke"
var predicate = PredicateBuilder.True<string>();
predicate = predicate.And(o => o.Contains("oo"));
predicate = predicate.Or(o => o.Contains("ke"));
// you should make IQueryable in order to use PredicateBuilder.
var result = strings.AsQueryable()
.Where(predicate)
.ToList();
// now, result should contains "foo", "foobar" and "fake".
foreach (var stringResult in result)
{
Debug.WriteLine("Name: " + stringResult);
}

C# Linq Average

I have a table with data similar to below:
Group TimePoint Value
1 0 1
1 0 2
1 0 3
1 1 3
1 1 5
I want to project a table as such:
Group TimePoint AverageValue
1 0 2
1 1 4
EDIT: The data is in a datatable.
Anybody any ideas how this can be done with LINQ or otherwise?
Thanks.
You need to perform Group By
The linq you need is something like:
var query = from item in inputTable
group item by new { Group = item.Group, TimePoint = item.TimePoint } into grouped
select new
{
Group = grouped.Key.Group,
TimePoint = grouped.Key.TimePoint,
AverageValue = grouped.Average(x => x.Value)
} ;
For more Linq samples, I highly recommend the 101 Linq samples page - http://msdn.microsoft.com/en-us/vcsharp/aa336747#avgGrouped
Here's a more function-oriented approach (the way I prefer it). The first line won't compile, so fill it in with your data instead.
var items = new[] { new { Group = 1, TimePoint = 0, Value = 1} ... };
var answer = items.GroupBy(x => new { TimePoint = x.TimePoint, Group = x.Group })
.Select(x => new {
Group = x.Key.Group,
TimePoint = x.Key.TimePoint,
AverageValue = x.Average(y => y.Value),
}
);
You can do:
IEnumerable<MyClass> table = ...
var query = from item in table
group item by new { item.Group, item.TimePoint } into g
select new
{
g.Key.Group,
g.Key.TimePoint,
AverageValue = g.Average(i => i.Value)
};
Assuming a class like this:
public class Record
{
public int Group {get;set;}
public int TimePoint {get;set;}
public int Value {get;set;}
}
var groupAverage = from r in records
group r by new { r.Group, r.TimePoint } into groups
select new
{
Group = groups.Key.Group,
TimePoint = groups.Key.TimePoint,
AverageValue = groups.Average(rec => rec.Value)
};

Categories