Can we convert this For-loop into LINQ? - linq

Can we convert this For-loop into LINQ expresion; both in Query Syntax and Method Syntax ?
List<INode> sds = new List<INode>();
foreach (INode n in lnd)
{
foreach(string s in Pages)
{
if (n.NiceUrl == s)
{
sds.Add(n);
}
}
}

from n in lnd
from s in Pages
where n.NiceUrl == s
select n

sds = lnd.Join(Pages, n => n.NiceUrl, p => p, (n, p) => n).ToList();

Pages.Where(y => lnd.Select(x => x.NiceUrl).Contains(y)).Tolist();
lnd.Select(x => x.NiceUrl) part can be replaced with a HashSet.

Related

From a List representation of a Map, to a real Map in Scala

I want to transform a Seq of keys/values to a Map. The first element of the sequence is reserved, so the list of pairs starts in the position 1.
The question is: Is possible to implement this function using a more functional way?
def list2Map(plainMap:Seq[String]) = {
var map = Map[String, String]()
var idx = 1;
while(plainMap.size > idx) {
val key = plainMap(idx)
idx += 1
val value = plainMap(idx)
idx += 1
map = map + (key -> value)
}
map
}
assert( list2Map( Seq("reserved slot","key0","value0","key1","value1","key2","value2") ) == Map( ("key0"->"value0"),("key1"->"value1"),("key2"->"value2") ) )
I am new in Scala, and I know that there are a lot of different ways to iterate over a collection, but I don't found a forEach way to read two elements per iteration, starting in the element 1.
PS: Thanks folks. I am learning a lot with everyone response!!
list.drop(1).grouped(2).map { x => x.head -> x.last }.toMap
You mean something like this?:
val list = List("reserved slot", "key0", "value0", "key1", "value1", "key2", "value2")
val list4Map = list.tail.grouped(2).map { listBy2 => (listBy2(0), listBy2(1)) }.toList
val map = Map(list4Map: _*)
Maybe you would like some recursive one:
def seq2Map[T](seq: Seq[T]) = {
def rec(seq: Seq[T], map: Map[T,T] = Map.empty[T,T]): Map[T,T] = seq match {
case h :: t :: e => rec(e, map + (h -> t))
case _ => map
}
rec(seq.tail)
}
(for { Seq(k, v) <- list.tail.grouped(2) } yield k -> v).toMap
covers trailing key too

Linq - return index of collection using conditional logic

I have a collection
List<int> periods = new List<int>();
periods.Add(0);
periods.Add(30);
periods.Add(60);
periods.Add(90);
periods.Add(120);
periods.Add(180);
var overDueDays = 31;
I have a variable over due days. When the vale is between 0 to 29 then I want to return the index of 0. When between 30 - 59 I want to return index 1. The periods list is from db so its not hard coded and values can be different from what are here. What is the best way to to it using LINQ in one statement.
It's not really what Linq is designed for, but (assuming that the range is not fixed) you could do the following to get the index
List<int> periods = new List<int>();
periods.Add(0);
periods.Add(30);
periods.Add(60);
periods.Add(90);
periods.Add(120);
periods.Add(180);
var overDueDays = 31;
var result = periods.IndexOf(periods.First(n => overDueDays < n)) - 1;
You can use .TakeWhile():
int periodIndex = periods.TakeWhile(p => p <= overDueDays).Count() - 1;
how about this ?
var qPeriods = periods.Where(v => v <= overDueDays)
.Select((result, i) => new { index = i })
.Last();
Assuming that periods is sorted, you can use the following approach:
var result = periods.Skip(1)
.Select((o, i) => new { Index = i, Value = o })
.FirstOrDefault(o => overDueDays < o.Value);
if (result != null)
{
Console.WriteLine(result.Index);
}
else
{
Console.WriteLine("Matching range not found!");
}
The first value is skipped since we're interested in comparing with the upper value of the range. By skipping it, the indices fall into place without the need to subtract 1. FirstOrDefault is used in case overDueDays doesn't fall between any of the available ranges.

How can I intersect more than two sets/lists of values?

Here is an example that works in Linqpad. The problem is that I need it to work for more than two words, e.g. searchString = "headboard bed railing". This is a query against an index and instead of "Match Any Word" which I've done, I need it to "Match All Words", where it finds common key values for each of the searched words.
//Match ALL words for categories in index
string searchString = "headboard bed";
List<string> searchList = new List<string>(searchString.Split(' '));
string word1 = searchList[0];
string word2 = searchList[1];
var List1 = (from i in index
where i.word.ToUpper().Contains(word1)
select i.category.ID).ToList();
var List2 = (from i in index
where i.word.ToUpper().Contains(word2)
select i.category.ID).ToList();
//How can I make this work for more than two Lists?
var commonCats = List1.Intersect(List2).ToList();
var category = (from i in index
from s in commonCats
where commonCats.Contains(i.category.ID)
select new
{
MajorCategory = i.category.category1.description,
MinorCategory = i.category.description,
Taxable = i.category.taxable,
Life = i.category.life,
ID = i.category.ID
}).Distinct().OrderBy(i => i.MinorCategory);
category.Dump();
Thanks!
Intersection of an intersection is commutative and associative. This means that (A ∩ B ∩ C) = (A ∩ (B ∩ C)) = ((A ∩ B) ∩ C), and rearranging the order of the lists will not change the result. So just apply .Intersect() multiple times:
var commonCats = List1.Intersect(List2).Intersect(List3).ToList();
So, to make your code more general:
var searchList = searchString.Split(' ');
// Change "int" if this is not the type of i.category.ID in the query below.
IEnumerable<int> result = null;
foreach (string word in searchList)
{
var query = from i in index
where i.word.ToUpper().Contains(word1)
select i.category.ID;
result = (result == null) ? query : result.Intersect(query);
}
if (result == null)
throw new InvalidOperationException("No words supplied.");
var commonCats = result.ToList();
To build on #cdhowie's answer, why use Intersect? I would think you could make it more efficient by building your query in multiple steps. Something like...
if(string.IsNullOrWhitespace(search))
{
throw new InvalidOperationException("No word supplied.");
}
var query = index.AsQueryable();
var searchList = searchString.Split(' ');
foreach (string word in searchList)
{
query = query.Where(i => i.word.ToUpper().Contains(word));
}
var commonCats = query.Select(i => i.category.ID).ToList();

Linq OrderByDescending but keep zero value first

I have a collection of integers that I want to order by descending value, with the exception of keeping a 0 value as first in the list.
For example:
0,1,2,3,4,5,6,7,8
Should result in:
0,8,7,6,5,4,3,2,1
Thanks!
var input = new [] {0,1,2,3,4,5,6,7,8};
Two sortings which work with both negative and positive numbers:
var result = input.OrderBy(i => i == 0? 0 : 1).ThenByDescending(i => i);
or this if all your numbers are non-negative:
var result = input.OrderByDescending(i => i == 0? int.MaxValue : i);
or some really weird solution if you have both negative and positive numbers but you don't want to sort twice (as I'm doing in first solution):
var result = input
.GroupBy(i => i == 0 ? 0 : 1)
.OrderBy(g => g.Key)
.Select(g => g.Key == 0 ? g : g.OrderByDescending(i => i)
.SelectMany(g => g);

LINQ: GroupBy with maximum count in each group

I have a list of duplicate numbers:
Enumerable.Range(1,3).Select(o => Enumerable.Repeat(o, 3)).SelectMany(o => o)
// {1,1,1,2,2,2,3,3,3}
I group them and get quantity of occurance:
Enumerable.Range(1,3).Select(o => Enumerable.Repeat(o, 3)).SelectMany(o => o)
.GroupBy(o => o).Select(o => new { Qty = o.Count(), Num = o.Key })
Qty Num
3 1
3 2
3 3
What I really need is to limit the quantity per group to some number. If the limit is 2 the result for the above grouping would be:
Qty Num
2 1
1 1
2 2
1 2
2 3
1 3
So, if Qty = 10 and limit is 4, the result is 3 rows (4, 4, 2). The Qty of each number is not equal like in example. The specified Qty limit is the same for whole list (doesn't differ based on number).
Thanks
Some of the other answers are making the LINQ query far more complex than it needs to be. Using a foreach loop is certainly faster and more efficient, but the LINQ alternative is still fairly straightforward.
var input = Enumerable.Range(1, 3).SelectMany(x => Enumerable.Repeat(x, 10));
int limit = 4;
var query =
input.GroupBy(x => x)
.SelectMany(g => g.Select((x, i) => new { Val = x, Grp = i / limit }))
.GroupBy(x => x, x => x.Val)
.Select(g => new { Qty = g.Count(), Num = g.Key.Val });
There was a similar question that came up recently asking how to do this in SQL - there's no really elegant solution and unless this is Linq to SQL or Entity Framework (i.e. being translated into a SQL query), I'd really suggest that you not try to solve this problem with Linq and instead write an iterative solution; it's going to be a great deal more efficient and easier to maintain.
That said, if you absolutely must use a set-based ("Linq") method, this is one way you could do it:
var grouped =
from n in nums
group n by n into g
select new { Num = g.Key, Qty = g.Count() };
int maxPerGroup = 2;
var portioned =
from x in grouped
from i in Enumerable.Range(1, grouped.Max(g => g.Qty))
where (x.Qty % maxPerGroup) == (i % maxPerGroup)
let tempQty = (x.Qty / maxPerGroup) == (i / maxPerGroup) ?
(x.Qty % maxPerGroup) : maxPerGroup
select new
{
Num = x.Num,
Qty = (tempQty > 0) ? tempQty : maxPerGroup
};
Compare with the simpler and faster iterative version:
foreach (var g in grouped)
{
int remaining = g.Qty;
while (remaining > 0)
{
int allotted = Math.Min(remaining, maxPerGroup);
yield return new MyGroup(g.Num, allotted);
remaining -= allotted;
}
}
Aaronaught's excellent answer doesn't cover the possibility of getting the best of both worlds... using an extension method to provide an iterative solution.
Untested:
public static IEnumerable<IEnumerable<U>> SplitByMax<T, U>(
this IEnumerable<T> source,
int max,
Func<T, int> maxSelector,
Func<T, int, U> resultSelector
)
{
foreach(T x in source)
{
int number = maxSelector(x);
List<U> result = new List<U>();
do
{
int allotted = Math.Min(number, max);
result.Add(resultSelector(x, allotted));
number -= allotted
} while (number > 0 && max > 0);
yield return result;
}
}
Called by:
var query = grouped.SplitByMax(
10,
o => o.Qty,
(o, i) => new {Num = o.Num, Qty = i}
)
.SelectMany(split => split);

Resources