Reputation: 3616
Is there a way in Linq of setting a value for the first selected occurrence matching the condition?
I know I'm not explaining this well, but for example:
var mySelectedObjects = allMyObjects.OrderBy(o.Date).Select(o => new ObjectType
{
Element1 = o.Element1,
Element2 = o.Element2,
Date = o.Date,
BooleanElement = o.BooleanElement && ThisIsTheFirstTrue
};
So in the above example, the desired output result is that in mySelectedObjects
the BooleanElement
item would only be true for the first occurence of allMyObjects
(ordered by Date
) where it's true, and the rest would all be false regardless of their values in allMyObjects
.
i.e. I only want one (the earliest) occurrence with BooleanElement
= true in mySelectedObjects
. What should I replace ThisIsTheFirstTrue
with?
Edit
I realise this can be done with a for
loop after the select but I'm hoping to do it all within the Select.
Anyway just to clarify in response to suggestions. I want it to be true for the first occurrence where it's true in the input, not just the first occurrence, so for example if the input data is...
false
true
false
true
true
false
I want the output to be
false
true
false
false
false
false
Upvotes: 3
Views: 1946
Reputation: 2929
What you are asking for, that is, checking the index of an item from within Select
is not possible (actually is, by operating over a list and calling IndexOf
, but that's quadratic in terms of running costs, so I wouldn't suggest that). However, note that inside the select
, you don't necessarily have to write an object creation expression, you can write a code block as well. This way, the following will work:
bool found = false;
var mySelectedObjects = allMyObjects.OrderBy(o.Date).Select(o =>
{
var booleanElement = !found && o.BooleanElement;
found = booleanElement;
var result = new ObjectType
{
Element1 = o.Element1,
Element2 = o.Element2,
Date = o.Date,
BooleanElement = booleanElement
};
return result;
});
This is the most LINQish way of solving it that I can provide you with.
To address the comment of @TheJP, here is a way to go over the issue of parallelism:
Let's create a class like this:
class TrueOnlyOnFirstAccess
{
private bool val = true;
private object valLock = new object();
public bool Value
{
get
{
if (!val) return false;
lock (valLock)
{
if (!val) return false;
val = false;
return true;
}
}
}
}
What this does is when the value is first requested, it will return true
for the first time, and within a locking context, changes the value to false
. Since once the value is false, it should never change, we can safely return false if we find the backing field to be false, therefore avoiding unnecessary lockings. How to use it:
TrueOnlyOnFirstAccess switcher = new TrueOnlyOnFirstAccess();
items.Select(item => new
{
// other props
BooleanElement = item.BooleanElement && switcher .Value
})
Here we exploit the lazy nature of logical and
- that is, item.BooleanElement
must be the first to evaluate, if that is false, sw.Value
will not be called, and the backing field will therefore not be changed. Use it with caution in more complex expressions.
Upvotes: 1
Reputation: 460038
I would assign it in a loop, not elegant but working:
var mySelectedObjects = allMyObjects
.OrderBy(o.Date)
.Select(o => new ObjectType
{
Element1 = o.Element1,
Element2 = o.Element2,
Date = o.Date,
BooleanElement = o.BooleanElement
});
bool firstTrueFound = false;
foreach(ObjectType o in mySelectedObjects)
{
if(o.BooleanElement)
{
if(firstTrueFound)
o.BooleanElement = false;
else
firstTrueFound = true;
}
}
The only LINQ solution that comes into my mind isn't as efficient, look:
var boolLookup = allMyObjects.ToLookup(o => o.BooleanElement);
var mySelectedObjects = boolLookup[true]
.OrderBy(o => o.Date)
.Select((o, index) => new ObjectType
{
Element1 = o.Element1,
Element2 = o.Element2,
Date = o.Date,
BooleanElement = index == 0
})
.Concat(boolLookup[false])
.OrderBy(o => o.Date);
Upvotes: 2
Reputation: 5920
So using Linq
:
bool found = false;
var mySelectedObjects = objects
.OrderBy(o => o.Date)
.Select(o => new ObjectType
{
Element1 = o.Element1,
Element2 = o.Element2,
Date = o.Date,
BooleanElement = !found && o.BooleanElement ? (found = true) : false
}
);
Upvotes: 4