Linq TakeWhile depending on sum (or aggregate) of elements - c#

I have a list of elements and want to takeWhile the sum (or any aggregation of the elements) satisfy a certain condition. The following code does the job, but i am pretty sure this is not an unusual problem for which a proper pattern should exist.
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
int tmp = 0;
var listWithSum = from x in list
let sum = tmp+=x
select new {x, sum};
int MAX = 10;
var result = from x in listWithSum
where x.sum < MAX
select x.x;
Does somebody know how to solve the task in nicer way, probably combining TakeWhile and Aggregate into one query?
Thx

It seems to me that you want something like the Scan method from Reactive Extensions (the System.Interactive part) - it's like Aggregate, but yields a sequence instead of a single result. You could then do:
var listWithSum = list.Scan(new { Value = 0, Sum = 0 },
(current, next) => new { Value = next,
Sum = current.Sum + next });
var result = listWithSum.TakeWhile(x => x.Sum < MaxTotal)
.Select(x => x.Value);
(MoreLINQ has a similar operator, btw - but currently it doesn't support the idea of the accumulator and input sequence not being the same type.)

Related

LINQ Select in descending order from collection, three different values that go up to the maximum value of the element

For example i have a collection like this
var c1 = new Collection<int>{0,0,2,2,2,3,3,4,4,4,4,5,5,6,6,7};
I would like to get result like this
(6,5,4)
You can do:
c1.Distinct()
.OrderByDescending(x => x)
.Skip(1)
.Take(3)
.ToList()
First remove all the duplicates, then sort despondingly. Skip(1) so that the max element is removed. Finally you can take 3 elements from the rest.
In the old days, before LINQ, we might have done this on a sorted collection like you have:
var maxes = new int[4];
var idx = 0;
var max = 0;
foreach(var c in c1)
if(c > max)
max = maxes[(idx++)%4] = c;
At the end of this you'll have an array with 4 max values - the 3 you want, and the one you don't (which is in (idx - 1) % 4). I don't know if I'd use it now, but it's more efficient than a "distinct, then sort, then skip then take" approach as it does its work in a single pass

C# Splitting a List<string> Value

I have a List with values {"1 120 12", "1 130 22", "2 110 21", "2 100 18"}, etc.
List<string> myList = new List<string>();
myList.Add("1 120 12");
myList.Add("1 130 22");
myList.Add("2 110 21");
myList.Add("2 100 18");
I need to count based on the first number (ID) is and sum the consequent values for this
IDs i.e. for ID = 1 -> 120+130=150 and 12+22=34 and so on... I have to return an array with these values.
I know I can get these individual values, add them to an array and split it by the empty space between them with something like:
string[] arr2 = arr[i].Split(' ');
and loop thru them to do the sum of each value, but... is there an easy way to do it straight using Lists or Linq Lambda expression?
You can do it in LINQ like this:
var result = myList.Select(x => x.Split(' ').Select(int.Parse))
.GroupBy(x => x.First())
.Select(x => x.Select(y => y.Skip(1).ToArray())
.Aggregate(new [] {0,0}, (y,z) => new int[] {y[0] + z[0], y[1] + z[1]}));
First, the strings are split and converted to int, then they are grouped by ID, then the ID is dropped, and in the end, they are summed together.
But I strongly recommend not doing it in LINQ, because this expression is not easy to understand. If you do it the classic way with a loop, it is quite clear what is going on at first sight. But put this code containing the loop into a separate method, because that way it won't distract you and you still only call a one-liner as in the LINQ solution.
To do it straight, no LINQ, perhaps:
var d = new Dictionary<string, (int A, int B)>();
foreach(var s in myList){
var bits = s.Split();
if(!d.ContainsKey(bits[0]))
d[bits[0]] = (int.Parse(bits[1]), int.Parse(bits[2]));
else {
(int A, int B) x = d[bits[0]];
d[bits[0]] = (x.A + int.Parse(bits[1]), x.B + int.Parse(bits[2]));
}
}
Using LINQ to parse the int, and switching to using TryGetValue, will tidy it up a bit:
var d = new Dictionary<int, (int A, int B)>();
foreach(var s in myList){
var bits = s.Split().Select(int.Parse).ToArray();
if(d.TryGetValue(bits[0], out (int A, int B) x))
d[bits[0]] = ((x.A + bits[1], x.B + bits[2]));
else
d[bits[0]] = (bits[1], bits[2]);
}
Introducing a local function to safely get either the existing nums in the dictionary or a (0,0) pair might reduce it a bit too:
var d = new Dictionary<int, (int A, int B)>();
(int A, int B) safeGet(int i) => d.ContainsKey(i) ? d[i]: (0,0);
foreach(var s in myList){
var bits = s.Split().Select(int.Parse).ToArray();
var nums = safeGet(bits[0]);
d[bits[0]] = (bits[1] + nums.A, bits[2] + nums.B);
}
Is it any more readable than a linq version? Hmm... Depends on your experience with Linq, and tuples, I suppose..
I know this question already has a lot of answers, but I have not seen one yet that focuses on readability.
If you split your code into a parsing phase and a calculation phase, we can use LINQ without sacrificing readability or maintainability, because each phase only does one thing:
List<string> myList = new List<string>();
myList.Add("1 120 12");
myList.Add("1 130 22");
myList.Add("2 110 21");
myList.Add("2 100 18");
var parsed = (from item in myList
let split = item.Split(' ')
select new
{
ID = int.Parse(split[0]),
Foo = int.Parse(split[1]),
Bar = int.Parse(split[2])
});
var summed = (from item in parsed
group item by item.ID into groupedByID
select new
{
ID = groupedByID.Key,
SumOfFoo = groupedByID.Sum(g => g.Foo),
SumOfBar = groupedByID.Sum(g => g.Bar)
}).ToList();
foreach (var s in summed)
{
Console.WriteLine($"ID: {s.ID}, SumOfFoo: {s.SumOfFoo}, SumOfBar: {s.SumOfBar}");
}
fiddle
If you want, but I think it will be much easier to edit and optimize using the usual value. I don't find using this kind of logic inside LINQ will stay that way for a long period of time. Usually, we need to add more values, more parsing, etc. Make it not really suitable for everyday use.
var query = myList.Select(a => a.Split(' ').Select(int.Parse).ToArray())
.GroupBy(
index => index[0],
amount => new
{
First = amount[1],
Second = amount[2]
},
(index, amount) => new
{
Index = index,
SumFirst = amount.Sum(a => a.First),
SumSecond = amount.Sum(a => a.Second)
}
);
fiddle
is there an easy way to do it straight using Lists or Linq Lambda expression?
Maybe, is it wise to do this? Probably not. Your code will be hard to understand, impossible to unit test, the code will probably not be reusable, and small changes are difficult.
But let's first answer your question as a one LINQ statement:
const char separatorChar = ' ';
IEnumerable<string> inputText = ...
var result = inputtext.Split(separatorChar)
.Select(text => Int32.Parse(text))
.Select(numbers => new
{
Id = numbers.First()
Sum = numbers.Skip(1).Sum(),
});
Not reusable, hard to unit test, difficult to change, not efficient, do you need more arguments?
It would be better to have a procedure that converts one input string into a proper object that contains what your input string really represents.
Alas, you didn't tell us if every input string contains three integer numbers, of that some might contain invalid text, and some might contain more or less than three integer numbers.
You forgot to tell use what your input string represents.
So I'll just make up an identifier:
class ProductSize
{
public int ProductId {get; set;} // The first number in the string
public int Width {get; set;} // The 2nd number
public int Height {get; set;} // The 3rd number
}
You need a static procedure with input a string, and output one ProductSize:
public static ProductSize FromText(string productSizeText)
{
// Todo: check input
const char separatorChar = ' ';
var splitNumbers = productSizeText.Split(separatorChar)
.Select(splitText => Int32.Parse(splitText))
.ToList();
return new ProductSize
{
ProductId = splitNumbers[0],
Width = splitNumbers[1],
Height = splitNumbers[2],
};
}
I need to count based on the first number (ID) is and sum the consequent values for this IDs
After creating method ParseProductSize this is easy:
IEnumerable<string> textProductSizes = ...
var result = textProductSizes.Select(text => ProductSize.FromText(text))
.Select(productSize => new
{
Id = productSize.Id,
Sum = productSize.Width + productSize.Height,
});
If your strings do not always have three numbers
If you don't have always three numbers, then you won't have Width and Height, but a property:
IEnumerable<int> Numbers {get; set;} // TODO: invent proper name
And in ParseProductSize:
var splitText = productSizeText.Split(separatorChar);
return new ProductSize
{
ProductId = Int32.Parse(splitText[0]),
Numbers = splitText.Skip(1)
.Select(text => Int32.Parse(text));
I deliberately keep it an IEnumerable, so if you don't use all Numbers, you won't have parsed numbers for nothing.
The LINQ:
var result = textProductSizes.Select(text => ProductSize.FromText(text))
.Select(productSize => new
{
Id = productSize.Id,
Sum = productSize.Numbers.Sum(),
});

How to filter a list<int> by comparing against a single int variable?

I have a list<int> with values like 10, 20, 30, 56. I also have a local variable int _marks = 30.
How can I print out the values in the list that are less than the value of _marks?
You can use Where() from System.Linq namespace to filter the array. It returns IEnumerable<int> object. And for printing the elements in this collection we can use List<T>.ForEach method. It performs the specified action on each element of the List<T>. And in case of single argument you can pass function by itself:
marks.Where(x => x < _marks).ToList().Foreach(Console.WriteLine);
By the way if you are newbie you can use non-LINQ solution also:
foreach(int item in marks)
{
if(item < _marks)
Console.WriteLine(item);
}
Also, as #Kjartan said, if the list is ordered, then it may be good option to use TakeWhile() or SkipWhile() functions to get desired elements:
// If the list is ordered in ascending order
marks.TakeWhile(x => x < _marks).ToList().Foreach(Console.WriteLine);
// If the list is ordered in descending order
marks.SkipWhile(x => x >= _marks).ToList().Foreach(Console.WriteLine);
You've got several options here. A couple of examples:
var yourList = new List<int>{10, 20, 30, 40, 50, 56, 60, 70};
var _marks = 55;
// Get a IEnumerable containing values matching your condition ( ie. < _marks):
var selected = yourList.Where(i => i < _marks);
Alternative if you know the values are sorted by increasing values (this will avoid iterating through the whole list unnecessarily):
var selected = yourList.TakeWhile(i => i < _marks);
To print, do e.g.:
selected.ToList().ForEach(i => Console.WriteLine(i));

LINQ changing local "let" variable

Using a LINQ query (with C#) how would I go about do something like this (pseudocode)?
I'd look to do something like this is in places where, for example, I might generate 1000's of lists of 100's of random (bounded) integers, where I want to track the smallest of them as they're generated.
Best <- null value
Foreach N in Iterations
NewList <- List of 100 randomly generated numbers
If Best is null
Best <- NewList
If Sum(NewList) < Sum(Best)
Best <- NewList
Select Best
I've tried all sorts of things, but I can't really get it working. This isn't for any kind of project or work, just for my own curiosity!
Example of what I was thinking:
let R = new Random()
let Best = Enumerable.Range(0, 100).Select(S => R.Next(-100, 100)).ToArray()
//Where this from clause is acting like a for loop
from N in Iterations
let NewList = Enumerable.Range(0, 100).Select(S => R.Next(-100, 100))
Best = (NewList.Sum() < Best.Sum())? NewList : Best;
select Best
I believe you are looking for fold (aka "reduce") which is known as Aggregate in LINQ.
(IEnumerable.Min/Max are special-cases, but can be written in terms of fold/Aggregate.)
int Max (IEnumerable<int> x) {
return x.Aggregate(int.MinValue, (prev, cur) => prev > cur ? prev : cur);
}
Max(new int[] { 1, 42, 2, 3 }); // 42
Happy coding.
Looks like you're just selecting the minimum value.
var minimum = collection.Min( c => c );
You are effectively finding the minimum value in the collection, if it exists:
int? best = null;
if (collection != null && collection.Length > 0) best = collection.Min();

lambda expression for Enumerable.Select

I'm trying to figure out how to start using linq and lambda expressions.
First of all, if someone could direct me to some good tutorials it will be most appreciated.
Secondly:
I'm trying to select all values which are equal to a specific value using Select method.
I have noticed that select could be defined with a
Select<TSource,TResult>(...lambda expression...)
Now for this purpose I want to select all the numbers which are equal to 5.
int[] numbers = { 1, 2, 3, 4, 5, 5, 5, 6, 7, 8 };
IEnumerable<int> res = numbers.Select( x=>5 );
This does not work, I just don't understand how this works.
And in what situation should I define TSource and TResult, and what would they be in this case?
Thanks in advance!
Select() is used to project each member of the old sequence into a new member of a new sequence. To filter, you use Where():
var evens = numbers.Where(x => x % 2 == 0);
var theFiveSequence = numbers.Where(x => x == 5);
An example of using Select() might be multiplying each number by two:
var doubledNumbers = numbers.Select(x => 2*x);
You can combine those methods together, too:
var doubledNumbersLessThanTen = numbers.Select(x => 2*x).Where(x < 10);
Two important things to remember about LINQ:
The elements of the base sequence are (almost always) not modified. You create new sequences from old sequences.
The queries you write are lazily evaluated. You won't get results from them until you use them in a foreach loop, or call .ToList(), .ToArray() etc.

Categories

Resources