How to sort a lookup? - linq

Hi I have a lookup type that stores strings and ints.
static Lookup<string, int> lookup;
lookup = (Lookup<string, int>)list.ToLookup(i => i.IP, i => i.Number);
But now I need to sort this lookup by the values (number), and get the top 10 keys with their values.
How is this possible?

Unfortunately elements inside a Lookup cannot be reordered.
But the ToLookup() method has a nice property that elements in all the groupings have the same order as the elements in the original sequence.
This means that with some Linq gymnastics, you can achieve what you want by using GroupBy:
var l = (from l in list
// group elements by key
group l by l.IP into g
// for each group order the elements and take top 10
select new { g.Key, Items = g.OrderBy(g1 => g1.Number).Take(10)} into g2
// flaten group into an enumerable using select many
from g in g2.Items
select g)
// get the desired lookup containing the top 10 ordered elements for each key
.ToLookup(g => g.IP, g => g.Number);

I'm not sure why you are casting a Lookup<string, int> to a Lookup<string, string>, but the general answer you want is:
var list = new List<Test>
{
new Test { IP = "A", Number = 1 }, new Test { IP = "A", Number = 3 }, new Test { IP = "A", Number = 4 },
new Test { IP = "B", Number = 1 }, new Test { IP = "B", Number = 1 }, new Test { IP = "B", Number = 1 },
new Test { IP = "C", Number = 1 },
new Test { IP = "D", Number = 1 },
new Test { IP = "E", Number = 1 }, new Test { IP = "E", Number = 1 }, new Test { IP = "E", Number = 1 }
};
var values = list.ToLookup(s => s.IP, s => s.Number)
.OrderByDescending(s => s.Count())
.Take(10);

Go find a Priority Queue (you can find one at http://www.itu.dk/research/c5/). Iterate over your look up and insert an IComparable item created from each entry in the look up, into the priority queue. Select the top ten items from the priority queue. Or just sort them by the count as the key.
var lookup = list.ToLookup( l => l.IP, l => l.Number );
var topten = lookup.OrderByDescending( l => l.Count() )
.Take( 10 );
foreach (var item in topten)
{
Console.WriteLine( "{0}: {1}", item.Key, item.Count() );
}
Note that sorting will have at best O(nlogn) performance while a good, heap-based priority queue will have O(logn) performance. If the collection isn't large, sorting is simpler given the built in support for it and not needing an intermediate class to support the priority queue implementation.

Take a look at the Take() LINQ function you should be able to do something like Take(10) to just return 10 results. As for sorting, check out the OrderBy() function that accepts a lambda expression as a sorting mechanism. Combining them both should give you what you're after.

Related

filter elements in nested dictionary LINQ

I have the following data structure:
Dictionary<string, Dictionary<string, List<int>>> data =
new Dictionary<string, Dictionary<string, List<int>>>();
I want to filter some of the elements in that dictionary based on value in first element of the list of the inner dictionary.
for example:
{legion1
{soldier1, [10,1000]},
{soldier2, [50,1000]}
}
Now let's say I want to do foreach loop in which to work only elements where
the value of the first element of the list is less than 20
expected result in the foreach loop is:
{legion1{soldier1, [10,1000]}}
What I've tried:
I do foreach loop and then I want to use something similar:
data.where(x => x.value.where(o => o[0] < 20 ))
I always get error that that way is incorrect.
Please tell how can I solve the issue and why my way is failing.
You can filter and iterate over the result set like so:
var resultSet =
data.ToDictionary(e => e.Key,
e => e.Value.Where(x => x.Value[0] < 20)
.ToDictionary(k => k.Key, v => v.Value)
);
foreach(var item in resultSet){
var key = item.Key; // string
var values = item.Value; // Dictionary<string, List<int>>
...
...
}
The problem is that you are applying operator [] incorrectly. Moreover, since you want to use both Legion and Soldier, you should construct a tuple combining the two of them:
foreach (var t in data.SelectMany(lg => lg.Value.Select(s => new {
Legion = lg
, Soldier = s
})).Where(ls => ls.Soldier.Value[0] < 20)) {
Console.WriteLine("Legion={0} Soldier = {1}", t.Legion.Key, t.Soldier.Key);
}

LINQ Get Grouped ID by condition

Hi I have a List so:
A 1
A 2
A 3
A 4
B 1
B 2
C 1
I want to select the letter that contains AT LEAST these 3 numbers: 1,2,3
So in this case would be selected the letter A.
Can you help me to write this as LINQ expression?
Thanks a lot!
First, make a collection of the numbers you require.
var required = new[] { 1, 2, 3 };
Then, group your pairings by letter.
var groupedPairings = pairings.GroupBy(p => Letter, p => Number);
Then, discard those pairings that don't have your three required items. (The logic here is "take the collection of required items, remove anything in the group, and make sure there is nothing left".)
var groupsWithRequired = groupedPairings
.Where(g => !required.Except(g).Any());
Now, if you just want the letters, you can simply do
var lettersWithRequired = groupsWithRequired.Select(g => g.Key);
or if you want a dictionary mapping from the letter to its collection of numbers, you can do
var dictionary = groupsWithRequired.ToDictionary(g => g.Key, g => g.ToArray());
var numbersForA = dictionary["A"]; // = {1, 2, 3, 4}
You could try this, although I don't feel it's the best answer:
var items = new List<Item>{
new Item{Name="A", Value=1},
new Item{Name="A", Value=2},
new Item{Name="A", Value=3},
new Item{Name="A", Value=3},
new Item{Name="A", Value=4},
new Item{Name="B", Value=1},
new Item{Name="B", Value=2},
new Item{Name="C", Value=1},
};
var values = new List<int>{1,2,3};
var query = items.GroupBy (i => i.Name)
.Where (i => i.Select (x => x.Value)
.Intersect(values).Count() == values.Count)
.Select (i => i.Key);
Where
class Item{
public string Name{get;set;}
public int Value{get;set;}
}

How can I Make a LINQ query expression dynamic

I'm trying to implement cascading controls using the following LINQ query expression.
The idea is that I have three option lists represented by the tables OptionA, OptionB and OptionC and a view called OptionIndex with one column each for OptionA_ID, OptionB_ID, OptionC_ID and that table has of all the combinations of tags from the option lists that are in use. Left outer joining the OptionIndex on the option list produces a boolean for the Disabled attributed in the option tag.
How do I make the on clause, which is .Where(...) in the following sample code, allow for any combination of the controls being used?
For example, lets say the user initially selects option value 123 in OptionA. The code to return the Values, Labels and Disabled booleans for OptionC would look like the following:
from t1 in OptionCs
from t2 in OptionIndexes.Where(x => t1.OptionC_ID == x.OptionC_ID && new List<int> { 123 }.Contains(x.OptionA_ID)).DefaultIfEmpty()
group new {t1, t2} by new { t1.OptionC_ID, t1.Label } into g
select new { g.Key.OptionC_ID, g.Key.Label, Disabled = g.Count(t => t.t2.OptionC_ID == null) > 0 }
Then lets say the user selects option values 456 and 789 in OptionB. The code to return the Values, Labels and Disabled booleans for OptionC change to:
from t1 in OptionCs
from t2 in OptionIndexes.Where(x => t1.OptionC_ID == x.OptionC_ID && new List<int> { 123 }.Contains(x.OptionA_ID) && new List<int> { 456, 789 }.Contains(x.OptionB_ID)).DefaultIfEmpty()
group new {t1, t2} by new { t1.OptionC_ID, t1.Label } into g
select new { g.Key.OptionC_ID, g.Key.Label, Disabled = g.Count(t => t.t2.OptionC_ID == null) > 0 }
To make the example code easier to understand I used new List<int>. In the actual project, however I would be passing the integers from the option list in as integer arrays from the controls themselves.
The trick is somehow making the query expression dynamic so that it can represent any combination of 0 to N multi-select controls being used or passing something that tells the join to accept any value for any given control such as
{x.OptionB_ID.Any}.Contains(x.OptionB_ID)
What is the best way to handle this?
Thanks!
Distilling your issue down to a simple example, consider this list of integers:
List<int> l = new List<int> { 1, 25, 3, 99, -23, 0, 15, 75 };
Say that you want to conditionally filter this list based on external criteria. Sometimes you want positive numbers, sometimes you want numbers smaller than 50, sometimes you want numbers divisible by 5, or any combination of these. Applying all filters with a static expression would look like this:
l.Where(n => n > 0).Where(n => n < 50).Where(n => n % 5 == 0);
To apply any or all of these dynamically, just build the LINQ query in pieces:
// These switches simulate your external conditions.
bool conditionA = true;
bool conditionB = false;
bool conditionC = true;
IEnumerable<int> myList = l;
if (conditionA) { myList = myList.Where(n => n > 0 ); }
if (conditionB) { myList = myList.Where(n => n < 50 ); }
if (conditionC) { myList = myList.Where(n => n % 5 == 0); }
With the switches set as in my example, the output is 25, 15, 75.
Side note: if you are not aware of it, use LINQPad to experiment with things like this. It is a fantastic tool for essentially executing code interactively, be it LINQ code or not. When I built the above sample, I inserted myList.Dump(); calls after each of the last 4 lines so I could see how each filter was applied. Here is the output:

Row number in LINQ

I have a linq query like this:
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = ?
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
I want to populate recordIndex with the value of current row number in collection returned by the LINQ. How can I get row number ?
Row number is not supported in linq-to-entities. You must first retrieve records from database without row number and then add row number by linq-to-objects. Something like:
var accounts =
(from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new
{
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
})
.AsEnumerable() // Moving to linq-to-objects
.Select((r, i) => new AccountReport
{
RecordIndex = i,
CreditRegistryId = r.CreditRegistryId,
AccountNumber = r.AccountNo,
});
LINQ to objects has this builtin for any enumerator:
http://weblogs.asp.net/fmarguerie/archive/2008/11/10/using-the-select-linq-query-operator-with-indexes.aspx
Edit: Although IQueryable supports it too (here and here) it has been mentioned that this does unfortunately not work for LINQ to SQL/Entities.
new []{"aap", "noot", "mies"}
.Select( (element, index) => new { element, index });
Will result in:
{ { element = aap, index = 0 },
{ element = noot, index = 1 },
{ element = mies, index = 2 } }
There are other LINQ Extension methods (like .Where) with the extra index parameter overload
Try using let like this:
int[] ints = new[] { 1, 2, 3, 4, 5 };
int counter = 0;
var result = from i in ints
where i % 2 == 0
let number = ++counter
select new { I = i, Number = number };
foreach (var r in result)
{
Console.WriteLine(r.Number + ": " + r.I);
}
I cannot test it with actual LINQ to SQL or Entity Framework right now. Note that the above code will retain the value of the counter between multiple executions of the query.
If this is not supported with your specific provider you can always foreach (thus forcing the execution of the query) and assign the number manually in code.
Because the query inside the question filters by a single id, I think the answers given wont help out. Ofcourse you can do it all in memory client side, but depending how large the dataset is, and whether network is involved, this could be an issue.
If you need a SQL ROW_NUMBER [..] OVER [..] equivalent, the only way I know is to create a view in your SQL server and query against that.
This Tested and Works:
Amend your code as follows:
int counter = 0;
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = counter++
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
Hope this helps.. Though its late:)

Join 2 lists by order instead of condition in LINQ

How can I join 2 lists of equal lengths (to produce a 3rd list of equal length) where I do not want to specify a condition but simply rely on the order of items in the 2 lists.
Eg how can I join:
{1,2,3,4} with {5,6,7,8}
to produce:
{{1,5}, {2,6}, {3,7}, {4,8}}
I have tried the following:
from i in new []{1,2,3,4}
from j in new []{5,6,7,8}
select new { i, j }
but this produces a cross join. When I use join, I always need to specify the "on".
You could use Select in the first list, use the item index and access the element on the second list:
var a = new [] {1,2,3,4};
var b = new [] {5,6,7,8};
var qry = a.Select((i, index) => new {i, j = b[index]});
If you are using .Net 4.0, you can use the Zip extension method and Tuples.
var a = new [] {1,2,3,4};
var b = new [] {5,6,7,8};
var result = a.Zip(b, (an, bn) => Tuple.Create(an, bn));
Alternatively, you can keep them as arrays:
var resultArr = a.Zip(b, (an, bn) => new []{an, bn});
There is a half way solution, if you want to use query syntax. Half way in the sense that you need to use the Select method on both lists in order to get the indexes that you will use in the where clause.
int[] list1 = {1,2,3,4};
int[] list2 = {5,6,7,8};
var result = from item1 in list1.Select((value, index) => new {value, index})
from item2 in list2.Select((value, index) => new {value, index})
where item1.index == item2.index
select new {Value1 = item1.value, Value2 = item2.value};
The benefit with this solution is that it wont fail if the lists have different lengths, as the solution using the indexer would do.

Resources