LINQ Min() and Max() optimisation - c#

I'm having some trouble doing it right.
I need to pick the minimum and maximum for a list inside a list inside a list
Any thought how I can create a optimized type?
var range = allFilteredCars
.SelectMany(car => car.LeasingPlans
.SelectMany(plan => plan.Durations)
.Select(a => a.MonthlyPrice))
.ToList();
var min = range.Min();
var max = range.Max();
The last Min() and Max() doesn't feel right.
Any thoughts?

I can think of only one way to calculate min and max just by iterating one time on the entire collection which can be done using foreach loop
var min = Int32.MaxValue;
var max = Int32.MinValue;
foreach(var filteredCard in allFilteredCars)
{
foreach(var leasingPlan in filteredCard.LeasingPlans)
{
foreach(var car in leasingPlan.Durations)
{
if(car.MonthlyPrice < min)
min = car.MonthlyPrice;
else if(car.MonthlyPrice > max)
max = car.MonthlyPrice;
}
}
}
Assuming MonthlyPrice is of Int

We will do a bad thing: a linq expression with a side effect.
int? min = null;
int? max = null;
allFilteredCars
.SelectMany(car => car.LeasingPlans
.SelectMany(plan => plan.Durations))
.Select(a =>
{
if (min == null || a.MonthlyPrice < min.Value)
{
min = a.MonthlyPrice;
}
if (max == null || a.MonthlyPrice > max.Value)
{
max = a.MonthlyPrice;
}
return true;
}).All(x => x);
In the end you don't really seem to need the range result... You only want the Min() and Max(), so we calculate it, and force the whole IEnumerable<> to be elaborated through the use of All().
You have to replace int? with the type of MonthlyPrice!

I don't think you can get around the multiple SelectManys but an Ordering in the query will get rid of the Min/Max. This should be quicker as you are then only sorting once:
var range = allFilteredCars
.SelectMany(car => car.LeasingPlans
.SelectMany(plan => plan.Durations)
.Select(a => a.MonthlyPrice))
.OrderBy(a => a))
.ToList();
var min = range[0]; // get the first
var max = range[range.Count-1]; // get the last

Related

How to sort a loop order using linq query in C#?

Assume that I have a list of items from 1 - 3.
I could order them by 1,1,2,2,3,3.
But instead, I would like to order them by 1,2,3,1,2,3....
Is there an already exist function to achieve that?
This approach separates each number into groups, then iterates through the groups in order while conditionally adding them to a result list. There's probably ways to make this safer and more efficient, but this should give you a start. (It assumes that if there aren't equal counts of each number in the source array, it will skip those numbers as it runs out of them during the iteration phase.)
int[] arr = new[] { 1,1,1,2,2,2,3,3,3,4,4,4,5,5,5 };
var orderList = arr.OrderBy(x => x).Distinct().ToArray();
var refList = arr.GroupBy(x => x).ToDictionary(k => k.Key, v => v.Count());
var result = new List<int>();
int i = 0;
while (result.Count < arr.Length)
{
if (refList.Values.Sum() == 0)
break;
if (refList[orderList[i]] > 0)
{
result.Add(orderList[i]);
refList[orderList[i]]--;
}
i++;
if (i >= orderList.Length)
i = 0;
}
// Result: [1,2,3,4,5,1,2,3,4,5,1,2,3,4,5]

Is it possible to use LINQ to detect duplicate adjacent characters?

I want to verify that a string does not contain any duplicate characters (from a set of bad characters) in adjacent positions. Previous stack overflow answers on this subject seem to mostly be of the general form:
for(int i = 0; i < testString.Length-1; i++){
if(testString[i] == testString[i+1] && testString[i] == badChar){
//Handle rejection here
}
}
Is it possible to do this kind of verification/validation in LINQ? More generically: is it possible within LINQ to compare the value of each character in a string to the next character in a
testString.Any(c => /*test goes here*/) call?
Anytime you have a class that has Count (or equivalent) property and indexer, you can use Enumerable.Range as base for the LINQ query and perform inside an indexed access similar to the non LINQ code:
bool test = Enumerable.Range(0, testString.Length - 1).Any(i = >
testString[i] == testString[i + 1] && testString[i] == badChar)
You could use Pairwise from moreLINQ library:
if(testString.Pairwise((n, m) => new {n, m}).Any(x => x.n == x.m && x.n == badChar))
// do something
If you want to use pure LINQ you could hack it with Skip/Zip combination:
if(testString.Zip(testString.Skip(1), (n, m) => new {n, m})).Any(x => x.n == x.m && x.n == badChar))
// do something
But both these solutions will be much slower then for loop-based solution, so I'd advice against doing that.
How about the egregious misuse of the aggregate function? I like to think this answer is more of an example of what not to do, even if it is possible. A while and string.indexOf are probably the most appropriate to this problem.
var items = "ab^cdeef##gg";
var badChars = new[] {'^', '#', '~'};
var doesAdjacentDupeExist = false;
var meaninglessAggregate = items.Aggregate((last, current) =>
{
if (last == current && badChars.Contains(last))
{
doesAdjacentDupeExist = true;
};
return current;
});
This is not as clever, but it does work. It trades the setting of an outside variable inside the query (bad), for relying on index and elementAt (not great).
var items = "abcdefffghhijjk";
var badChars = new[] { 'f', 'h' };
var indexCieling = items.Count() - 1;
var badCharIndexes = items.Select((item, index) =>
{
if (index >= indexCieling)
{
return null as int?;
}
else
{
if (item == items.ElementAt(index + 1) && badChars.Contains(item))
{
return index as int?;
}
else
{
return null as int?;
}
}
});
var doesAdjacentDupeExist = badCharIndexes.Any(x => x.HasValue);

Find indexes in bool array changed from false to true?

I have two arrays:
bool[] oldValues = GetCurrentValuesFromSomewhere ();
ChangeCurrentValues ();
bool[] newValues = GetCurrentValuesFromSomewhere ();
List<int> whichIndexsHasBeenChangedFromFalseToTrue = /* linq */
Any idea? Instead of list, it can be bool[] array too.
You could do use something like this:
var changedValues =
(from i in Enumerable.Range(0, oldValues.Length)
where !oldValues[i] && newValues[i]
select i)
.ToList();
Or if you prefer fluent syntax:
var changedValues = Enumerable
.Range(0, oldValues.Length)
.Where(i => !oldValues[i] && newValues[i])
.ToList();
If you wanted a bool[] result, you can use this:
var changedValues =
(from i in Enumerable.Range(0, oldValues.Length)
select !oldValues[i] && newValues[i])
.ToArray();
Or in fluent syntax:
var changedValues = Enumerable
.Range(0, oldValues.Length)
.Select(i => !oldValues[i] && newValues[i])
.ToArray();
I would prefer using the lambda that gives you the index, so you do not have to generate the range:
var changed = newValues.
Select((value, index) => oldValues[index] == value ? -1 : index).
Where(i => i >= 0);
This should return a list of the indexes that have changed; .Count() will give you how many values have changed.
UPDATE: An alternative version
var changed = newValues.
Select((value, index) =>
value ? (oldValues[index] ? 0 : index + 1) : (oldValues[index] ? - (index + 1) : 0)).
Where(i => i != 0);
Will give you as index+1 those values that were false and are now true, and as -(index + 1) those values that were true and now are false. I am learning LINQ myself so I like to play with it quite a bit.
If there are always the same number of new and old, and you're just doing a diff, which is what you seem to be doing, I'd do something like this:
int index;
whichIndexsHasBeenChangedFromFalseToTrue = oldValues.Zip(newValues, (old, new) =>
{
int result = -1;
if(old != new) result = index;
index++;
return result;
}).Where(x => x != -1);
This is only for changed, but if you specifically want false to true, that's just a change to the if.
EDIT: Fixed a serious issue.

find the first available long in a List<long>

ok, this should be interesting.
lets assume i have the following code:
in this example, the first available number would be 2.
List<long> myList = new List<long>(){0,1,10,3};
in this example, the first available number would be '4'.
List<long> myList = new List<long>(){0,1,2,3};
any ideas?
So by "available" you mean "the lowest non-negative number which doesn't already exist in the list"?
I'd be tempted to write something like:
HashSet<long> existing = new HashSet<long>(list);
for (long x = 0; x < long.MaxValue; x++)
{
if (!existing.Contains(x))
{
return x;
}
}
throw new InvalidOperationException("Somehow the list is enormous...");
EDIT: Alternatively, you could order the list and then find the first value where the index isn't the same as the value...
var ordered = list.OrderBy(x => x);
var differences = ordered.Select((value, index) => new { value, index })
.Where(pair => pair.value != pair.index)
.Select(pair => (int?) pair.index);
var firstDifference = differences.FirstOrDefault();
long nextAvailable = firstDifference ?? list.Count;
The last line is to take care of the situation where the list is contiguous from 0. Another alternative would be:
var nextAvailable = list.Concat(new[] { long.MaxValue })
.OrderBy(x => x)
.Select((value, index) => new { value, index })
.Where(pair => pair.value != pair.index)
.Select(pair => pair.index)
.First();
This should be fine so long as the list doesn't contain long.MaxValue + 1 elements, which it can't in current versions of .NET. (That's a lot of memory...) To be honest, this will already have problems when it goes beyond int.MaxValue elements due to the Select part taking an int index...
list.Sort();
var range = Enumerable.Range( list.First(), list.Last()- list.First());
var number = range.Except(list).FirstOrDefault();

If I'm projecting with linq and not using a range variable what is the proper syntax?

I have a query that sums and aggregates alot of data something like this:
var anonType = from x in collection
let Cars = collection.Where(c=>c.Code == "Cars")
let Trucks = collection.Where(c=>c.Code == "Trucks")
select new {
Total = collection.Sum(v=>v.Amount),
CarValue = Cars.Sum(v=>v.Amout),
TruckValue = Trucks.Sum(v=>v.Amount),
CarCount = Cars.Count(),
TruckCount = Trucks.Count()
};
I find it really weird that I have to declare the range variable x, especially if I'm not using it. So, am I doing something wrong or is there a different format I should be following?
I could be wrong, but from your usage, I don't think you want to do a traditional query expression syntax query with your collection anyway, as it appears you are only looking for aggregates. The way you have it written, you would be pulling multiple copies of the aggregated data because you're doing it for each of the items in the collection. If you wished, you could split your query like this (sample properties thrown in)
var values = collection.Where(c => c.Code == "A");
var anonType = new
{
Sum = values.Sum(v => v.Amount),
MinimumStartDate = values.Min(v => v.StartDate),
Count = values.Count()
};
You declare a range variable no matter the looping construct:
foreach(var x in collection)
or
for(var index = 0; index < collection.Count; index++)
or
var index = 0;
while(index < collection.Count)
{
//...
index++;
}
Queries are no different. Just don't use the variable, it doesn't hurt anything.
So, am I doing something wrong?
Your query is not good. For each element in the collection, you are enumerating the collection 5 times (cost = 5*n^2).
Is there a different format I should be following?
You could get away with enumerating the collection 5 times (cost = 5n).
IEnumerable<X> cars = collection.Where(c => c.Code == "Cars");
IEnumerable<X> trucks = collection.Where(c => c.Code == "Trucks");
var myTotals = new
{
Total = collection.Sum(v => v.Amount),
CarValue = cars.Sum(v => v.Amount),
TruckValue = trucks.Sum(v => v.Amount,
CarCount = cars.Count(),
TruckCount = trucks.Count()
};

Categories

Resources