Find indexes in bool array changed from false to true? - c#

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.

Related

Is it possible to know what actions have been called on an IEnumerable?

I have several conditions that might affect what filters (.Where(...)) are used on a list. And at some point an exception is thrown, and I would like to know what actions have been called upon the list up until this point.
Is something like this possible?
var myList = new List<SomeClass>();
myList = myList.Where(item => item.property == value);
.
.
.
myList = myList.Where(item => item.otherProperty < otherValue);
Console.WriteLine(myList.ToActionsString());
It might print something like this:
list.Where(i => i.property == <the actual value>)
.Where(i => i.otherProperty < <the actual otherValue>)
Just calling toString() on the list does not exactly give any relevant information, and just listing the items in the list is not of interest.
Warning: there is overhead to this as the compiler has to create all of the Expression objects (which then have to be allocated and compiled at runtime). Use this sparingly.
You can do this using AsQueryable and relying on the ToString logic of the built in EnumerableQuery and Expression classes. The following extension method will convert your query to it's textual representation:
public static string GetText<T>(this IQueryable<T> query) {
retury query.Expression.ToString();
}
It can be used like:
var list = new List<int>();
var query = list.AsQueryable()
.Select((c, i) => c * (i + 1))
.Where(c => c > 5)
.Where(c => c < 10 && c != 7)
.Take(2)
.OrderBy(x => 1);
var text = query.GetText();
This results in the following:
System.Collections.Generic.List`1[System.Int32].Select((c, i) => (c * (i + 1))).Where(c => (c > 5)).Where(c => ((c < 10) AndAlso (c != 7))).Take(2).OrderBy(x => 1)
We can throw an anonymous type in the mix just to see how it looks:
var query = list.AsQueryable()
.Select((c, i) => c * (i + 1))
.Select(x => new { Value = x, ValueSquared = x * x });
var result = query.GetText();
Which will print:
System.Collections.Generic.List`1[System.Int32].Select((c, i) => (c * (i + 1))).Select(x => new <>f__AnonymousType0`2(Value = x, ValueSquared = (x * x)))
Through the use of Expression manipulation, we can make this method a little bit more robust. We can add in line breaks between the method calls and optionally strip off the name of the list's type.
public static string GetText<T>(this IQueryable<T> query, bool lineBreaks, bool noClassName)
{
var text = query.Expression.ToString();
if (!lineBreaks && !noClassName)
return text;
var expression = StripQuotes(query.Expression);
if (!(expression is MethodCallExpression mce))
return text;
if (lineBreaks)
{
var strings = new Stack<string>();
strings.Push(mce.ToString());
while (mce.Arguments.Count > 0 && mce.Arguments[0] is MethodCallExpression me)
{
strings.Push(me.ToString());
mce = me;
}
var sb = new StringBuilder(strings.Pop());
var len = sb.Length;
while (strings.TryPop(out var item))
{
sb.AppendLine().Append(item.Substring(len));
len = item.Length;
}
text = sb.ToString();
}
if (mce.Arguments.Count > 0 && mce.Arguments[0] is ConstantExpression ce)
{
var root = ce.Value.ToString();
if (root != null && text.StartsWith(root))
{
text = noClassName
? text.Substring(root.Length + 1)
: text.Insert(root.Length, Environment.NewLine);
}
}
return text;
}
// helper in case we get an actual Queryable in there
private static Expression StripQuotes(Expression e)
{
while (e.NodeType == ExpressionType.Quote)
e = ((UnaryExpression)e).Operand;
return e;
}
We can call this method as follows:
var list = new List<int>();
var query = list.AsQueryable()
.Select((c, i) => c * (i + 1))
.Where(c => c > 5)
.Where(c => c < 10 && c != 7)
.Take(2)
.OrderBy(x => 1);
var text = query.GetText(true, true);
Which will produce the following:
Select((c, i) => (c * (i + 1)))
.Where(c => (c > 5))
.Where(c => ((c < 10) AndAlso (c != 7)))
.Take(2)
.OrderBy(x => 1)
Note that this is very basic. It's not going to cover the case of closures (passing variables in) you'll get the <>DisplayClass objects written into your query. We can resolve that with an ExpressionVisitor that walks the expression and evaluates the ConstantExpressions representing the closures.
(Unfortunately I do not have time at the moment to provide that ExpressionVisitor solution, but stay tuned for an update)
No, it is not possible, at least not in a direct way as described in your question.
The List is not filtered step by step (i.e., apply Where(expr1) for all elements, then Where(expr2) for all remaining elements, ...), but in a deferred way:
If you request the first item of the resulting IEnumerable, LINQ evaluates the expr1 clause for each item until one item matches.
Then it checks whether this list item also matches expr2.
If it does, return it (or pass to further Where stages).
If it doesn't match, go back to step 1 and continue finding an item which matches expr1.
So logging by simply calling some ToActionsString() is difficult here. As already noted in the comments, it is probably much easier to just log when adding the Where-clauses, since you are in a known state then anyway:
if(condition1)
{
myList = myList.Where(item => item.Property == value);
Log($"Adding expression 1 with value '{value}'");
}
If your concern is that value could change before the IEnumerable is actually evaluated (captured variable), and you cannot restructure your control flow adequately, a workaround may be to create Func<T> objects which output the captured variables, and to evaluate those immediately before iterating the list:
List<Func<int>> values = new List<Func<int>>();
if(condition1)
{
myList = myList.Where(item => item.Property == value);
values.Add(() => value);
}
...
foreach(var v in values)
Log($"List will be filtered by {v()}");
var filteredList = myList.ToList();
Finally, you could call some logging function in your expressions, which logs the conditions and/or catches exceptions when evaluating those conditions:
myList.Where(item =>
{
Log(item, value);
return item.Property == value;
});

Split each two-word string in a list, compare if same and count using Linq

Is it possible to split each string (containing 2 words) in a list, then compare if both words are the same and count that occurrences using Linq? For example:
Let's say I have a list containing
list[0] = "bla bla";
list[1] = "bla heh";
list[2] = "heh heh";
The output of count should be 2 in this case.
my attempt so far:
var count = lst.Count(c => c.Split(.......)....
can't get past this.
Any help would be greatly appreciated.
list.Select(c => c.Split(' ')).Count(y => y.Length >= 2 && y[0] == y[1]);
You can utilise the Select clause then the Count like so:
int count = myList.Select(s => s.Split(' '))
.Count(a => a[0] == a[1]);
or you can use Count only like this:
int count = myList.Count(s => s.Substring(0, s.IndexOf(' ')) ==
s.Substring(s.IndexOf(' ') + 1));
With the new Span<T> value type (see this article) from nuget package 'System.Memory' you can do this without any unnecessary allocations:
int count = input.Count(x =>
{
int index = x.IndexOf(' ');
if (index < 1 || index == x.Length - 1) return false;
var span = x.AsSpan();
return span.Slice(start:0, length:index) // no allocation
.SequenceEqual(
span.Slice(start: index + 1)); // no allocation
});
Same as the others, but if you would have 3 or more words it would check them against eachother and only count the arrays that have the same words everywhere.
var result = test.Select(x => x.Split(' ')).Count(x => x.All(y => x[0] == y));

How to take last records from list

I have list as follows
static List<MessageDetail> CurrentMessage = new List<MessageDetail>();
Dynamically, values assigned to this list for example:
CurrentMessage.Add(new MessageDetail { UserName = 123,GroupName = somegrp, Message = somemsg });
Here, I want to take last 5 or so records.
// this returns first 5 result, dont want to user orderby clause either
CurrentMessagesForGroup = CurrentMessage
.Where(c => c.GroupName == groupName)
.Take(5).ToList();
Is there a way to implement TakeLast() attribute? Or any kind of help will be appreciated. Thanks for your time.
Use skip:
CurrentMessagesForGroup = CurrentMessage
.Where(c => c.GroupName == groupName).Skip(Math.Max(0, CurrentMessage.Count() - 5)).ToList();
EDIT: I also find this that I think it is more easier to use (MoreLinq):
using MoreLinq;
var CurrentMessagesForGroup2 = CurrentMessage.TakeLast(5);
Use an OrderBy (ASC or DESC) to get the records lined up correctly for your Take operation.
Ascending:
CurrentMessagesForGroup = CurrentMessage
.Where(c => c.GroupName == groupName)
.OrderBy(c => c.GroupName)
.Take(5)
.ToList();
or Descending:
CurrentMessagesForGroup = CurrentMessage
.Where(c => c.GroupName == groupName)
.OrderByDescending(c => c.GroupName)
.Take(5)
.ToList();
If anyone using DotNet Core 2 or above or DotNet Standard 2.1 or above then you can use Linq's built in .TakeLast()
Reference: Microsoft Documentation here
You could use Reverse(), which is slightly perverse.
CurrentMessagesForGroup = CurrentMessage
.Where(c => c.GroupName == groupName)
.Reverse()
.Take(5).ToList();
I use an extension method for this.
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int numElements)
{
return source.Skip(Math.Max(0, source.Count() - numElements));
}
And to use it:
CurrentMessagesForGroup = CurrentMessage.Where(c => c.GroupName == groupName).TakeLast(5).ToList();
Edit: Credit to Using Linq to get the last N elements of a collection?
I like this implementation, it uses a circular buffer.
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> input, int n)
{
if (n == 0)
yield break;
int tail = 0;
int head = 0;
int count = 0;
T[] buffer = new T[n];
foreach (T item in input)
{
buffer[tail] = item;
tail = (tail + 1) % n;
if (count < n)
count++;
else
head = (head + 1) % n;
}
for (int i = 0; i < count; i++)
yield return buffer[(head + i) % n];
}
If the MessageDetails class has numeric Id or Created date time we can use
var lastRecords= CurrentMessage.OrderByDescending(i=>i.Id).Where(p=>p.GroupName==groupName).Take(5).ToList();

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 multiple index in array

Say I have an array like this
string [] fruits = {"watermelon","apple","apple","kiwi","pear","banana"};
Is there an built in function that allows me to query all the index of "apple" ?
For example,
fruits.FindAllIndex("apple");
will return an array of 1 and 2
If there is not, how should I implement it?
Thanks!
LINQ version:
var indexes = fruits.Select((value, index) => new { value, index })
.Where(x => x.value == "apple")
.Select(x => x.index)
.ToList();
Non-LINQ version, using Array<T>.IndexOf() static method:
var indexes = new List<int>();
var lastIndex = 0;
while ((lastIndex = Array.IndexOf(fruits, "apple", lastIndex)) != -1)
{
indexes.Add(lastIndex);
lastIndex++;
}
One way would be to write like this:
var indices = fruits
.Select ((f, i) => new {f, i})
.Where (x => x.f == "apple")
.Select (x => x.i);
Or the traditional way:
var indices = new List<int>();
for (int i = 0; i < fruits.Length; i++)
if(fruits[i] == "apple")
indices.Add(i);
Pretty easy with an extension method.
var fruits = new[] { "watermelon","apple","apple","kiwi","pear","banana" };
var indexes = fruits.FindAllIndexes("apple");
public static class Extensions
{
public static int[] FindAllIndexes(this string[] array, string search) => array
.Select((x, i) => (x, i))
.Where(value => value.x == search)
.Select(value => value.i)
.ToArray();
}

Categories

Resources