I want to group a list that includes integer List<int>.
List<CNode> cNodes
and the CNode is
public class CNode
{
public List<int> Elements;
// ...
}
I can group the cNodes like that
var groups = cNodes.GroupBy(node => node.Elements[0]);
foreach (var group in groups )
{
// ...
}
but as you see the groupping is depends the first element, I want to group it by all elements
For example if node.Elements.Count == 5 expected grouping result should be the same as for:
var groups = cNodes.GroupBy(node => new
{
A = node.Elements[0],
B = node.Elements[1],
C = node.Elements[2],
D = node.Elements[3],
E = node.Elements[4]
});
I couldn't find the solution.
Thanks.
You can use something like node.Take(5) with a proper IEqualityComparer like this:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var cNodes = new List<CNode>
{
new CNode{Elements = new List<int>{ 0, 0, 1, 1, 1 } },
new CNode{Elements = new List<int>{ 0, 0, 0, 1, 1 } },
new CNode{Elements = new List<int>{ 0, 1, 1, 0 } },
new CNode{Elements = new List<int>{ 0, 1, 1, 0, 0 } },
new CNode{Elements = new List<int>{ 0, 0, 0, 0 } },
new CNode{Elements = new List<int>{ 0, 0, 0, 0 } }
};
Console.WriteLine("\tGroup by 2:");
foreach (var group in cNodes.GroupByElements(2))
Console.WriteLine($"{string.Join("\n", group)}\n");
Console.WriteLine("\tGroup by 3:");
foreach (var group in cNodes.GroupByElements(3))
Console.WriteLine($"{string.Join("\n", group)}\n");
Console.WriteLine("\tGroup by all:");
foreach (var group in cNodes.GroupByElements())
Console.WriteLine($"{string.Join("\n", group)}\n");
}
}
static class CNodeExtensions
{
public static IEnumerable<IGrouping<IEnumerable<int>, CNode>> GroupByElements(this IEnumerable<CNode> nodes) =>
nodes.GroupByElements(nodes.Min(node => node.Elements.Count));
public static IEnumerable<IGrouping<IEnumerable<int>, CNode>> GroupByElements(this IEnumerable<CNode> nodes, int count) =>
nodes.GroupBy(node => node.Elements.Take(count), new SequenceCompare());
private class SequenceCompare : IEqualityComparer<IEnumerable<int>>
{
public bool Equals(IEnumerable<int> x, IEnumerable<int> y) => x.SequenceEqual(y);
public int GetHashCode(IEnumerable<int> obj)
{
unchecked
{
var hash = 17;
foreach (var i in obj)
hash = hash * 23 + i.GetHashCode();
return hash;
}
}
}
}
internal class CNode
{
public List<int> Elements;
public override string ToString() => string.Join(", ", Elements);
}
}
Output is:
Group by 2:
0, 0, 1, 1, 1
0, 0, 0, 1, 1
0, 0, 0, 0
0, 0, 0, 0
0, 1, 1, 0
0, 1, 1, 0, 0
Group by 3:
0, 0, 1, 1, 1
0, 0, 0, 1, 1
0, 0, 0, 0
0, 0, 0, 0
0, 1, 1, 0
0, 1, 1, 0, 0
Group by all:
0, 0, 1, 1, 1
0, 0, 0, 1, 1
0, 1, 1, 0
0, 1, 1, 0, 0
0, 0, 0, 0
0, 0, 0, 0
You wrote:
I want to group it by all elements
The solution given by Alex will only group by a limited number of elements. You said you want to group it by all elements, even if you have a CNode with 100 elements. Besides: his solution also crashes if property Elements of one of the CNodes equals null.
So let's create a solution that meets your requirement.
The return value will be a sequence of groups, where every group has a Key, which is a sequence of CNodes. All elements in the group are all source CNodes that have a property Elements equal to the Key.
With equal you mean SequenceEqual. So Elements[0] == Key[0] and Elements[1] == Key[1], etc.
And of course, you want to decide when Elements[0] equals Key[0]: do you want to compare by reference (same object)? or are two CNodes equal if they have the same property values? Or do you want to specify a IEqualityComparer<CNode>, so that you can see they are equal if they have the same Name or Id?
// overload without IEqualityComparer, calls the overload with IEqualityComparer:
IEnumerable<IGrouping<IEnumerable<Cnode>, CNode>> GroupBy(
this IEnumerable<CNode> cNodes)
{
return GroupBy(cNodes, null);
}
// overload with IEqualityComparer; use default CNode comparer if paramer equals null
IEnumerable<IGrouping<IEnumerable<Cnode>, CNode>> GroupBy(
this IEnumerable<CNode> cNodes,
IEqualityComparer<CNode> cNodeComparer)
{
// TODO: check cNodes != null
if (cNodeComparer == null) cNodeComparer = EqualityComparer<CNode>.Default;
CNodeSequenceComparer nodeSequenceComparer = new CNodeSequenceComparer()
{
CNodeComparer = cNodeComparer,
}
return sequenceComparer.GroupBy(nodeSequenceComparer);
}
You've noticed I've transferred my problem to a new EqualityComparer: this compare takes two sequences of CNodes and declares them equal if they SequenceEqual, using the provided IEqualityComparer<CNode>:
class CNodeSequenceComparer : IEqualityComparer<IEnumerable<CNode>>
{
public IEqualityComparer<CNode> CNodeComparer {get; set;}
public bool Equals(IEnumerable<CNode> x, IEnumerable<CNode> y)
{
// returns true if same sequence, using CNodeComparer
// TODO: implement
}
}
One of the things we have to keep in mind, is that your property Elements might have a value null (after all, you didn't specify that this isn't the case)
public bool Equals(IEnumerable<CNode> x, IEnumerable<CNode> y)
{
if (x == null) return y == null; // true if both null
if (y == null) return false; // false because x not null
// optimizations: true if x and y are same object; false if different types
if (Object.ReferenceEquals(x, y) return true;
if (x.GetType() != y.GetType()) return false;
return x.SequenceEquals(y, this.CNodeComparer);
}
Related
In my code, I created a dictionary where the value is an array on integers. What I need to do is take one of those values within the array and set that as a value somewhere else. How would I do that? For reference, the dictionary looks like this:
public IDictionary<string, int[]> Statistics { get; set; } = new Dictionary<string, int[]>()
{
{ "STR", new int[] { 0, 0, 0 } },
{ "DEX", new int[] { 0, 0, 0 } },
{ "CON", new int[] { 0, 0, 0 } },
{ "INT", new int[] { 0, 0, 0 } },
{ "WIS", new int[] { 0, 0, 0 } },
{ "CHA", new int[] { 0, 0, 0 } }
};
So, I want to take the second integer in the array of whichever key I need, and then assign that value somewhere else.
I was able to figure it out. So, in the case of, say the key "STR", I would need to do the following
int x = Statistics["STR"][1];
I wan't to have an array result [1000, 0, 0, 500, 0, 0, 0, 0, 3210, 0] from 1 array and sql MySqlDataReader sdr.Read() result.
here are the arrays
ArrayList array1 = new ArrayList()[1,2,3,4,5,6,7,8,9,10];
ArrayList arrayresult = new ArrayList();
while (sdr.Read())
{
transactions.Add(new transaction_details
{
ID = Int32.Parse(sdr["ID"].ToString()),
Transdate = DateTime.Parse(sdr["Transdate"].ToString()),
Debit = Decimal.Parse(sdr["Debit"].ToString()),
TransactionName = sdr["TransactionName"].ToString()
});
}
//arrayresult.add() - what do I do with this ?
If ID in sdr["ID"].ToString() is in array1, then the result would be [ 1, 4, 9]. array1 has 10 index so for final result I want it to be [ 1, 0, 0, 4, 0, 0, 0, 0, 9, 0]. If ID does not exist, 0 will be inserted.
I need arrayresult to have this result.
var debit = [1000, 0, 0 ,500, 0, 0, 0, 0, 3210, 0];
var ID = [ 1, 0, 0, 4, 0, 0, 0, 0, 9, 0];
var date= [ "2020-01-01", 0, 0, "2020-01-02", 0, 0, 0, 0, "2020-01-03", 0];
Short linq pseudo code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<int> inp = new List<int>(){1,4,9};
List<int> array1 = new List<int>(){1,2,3,4,5,6,7,8,9,10};
var array1Index = array1.Select((i, index) => new { Index=index, i=i }).ToList();
List<int> arrayresult = new List<int>();
var query = from p in array1Index
select inp.Contains(p.i)==true?p.i:0;
arrayresult = query.ToList();
arrayresult.ForEach(x=>{
Console.Write(x+" ");
});
}
}
I have an Observable that returns integers like so:
1, 1, 1, 1, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, 0, 0, 0
How can I convert this Observable to return arrays of those integers splitting stream not by the time based windows but by value based ones?
Those integers are fingerId of Touch in Unity Update event. It's not so important for this task but to explain why I need it I have to provide these details. -1 is for no touch. This is the gap. I need to remove those -1 parts and split stream to buffers of fingerId's between «no touch» moments. I can describe this like this too:
Touch0, Touch0, Touch0, no Touch, no Touch, Touch1
It doesnt' matter if there is integer or another type. Just need to split stream into buffers removing «window values».
This is my code if it will be helpful:
var leftSideTouchStream = Observable.EveryUpdate()
.Scan(-1, (id, _) =>
{
if (id < 0)
{
var leftSideTouches = Input.touches
.Where(t =>
t.phase == TouchPhase.Began
&& t.position.x < Screen.width / 2
);
return leftSideTouches.Any() ? leftSideTouches.First().fingerId : -1;
}
else
{
var touchEnded = Input.touches
.Any(t =>
t.fingerId == id &&
(t.phase == TouchPhase.Ended || t.phase == TouchPhase.Canceled)
);
return touchEnded ? -1 : id;
}
})
.Select(id =>
{
return Input.touches
.Where(t => t.fingerId == id)
.Select(t => new Nullable<Touch>(t))
.FirstOrDefault();
});
I need exactly the same behaviour as Buffer function gives but as I said according to values not the time.
If I have this stream:
1, 1, 1, 1, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, 0, 0, 0
And the «window value» is -1, then the result is expected to be:
[1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0]
There might exist some better magic solutions but the most straight forward I guess would be somthing like
private static int[][] GetArrays(int[] intputArray)
{
// use lists for dynamically adding elements
var outputLists = new List<List<int>>();
// initialize with the ignored value
var lastValue = -1;
// iterate over the inputArray
foreach (var value in intputArray)
{
// skip -1 values
if (value < 0)
{
lastValue = -1;
continue;
}
// if a new value begin a new list
if (lastValue != value)
{
outputLists.Add(new List<int>());
}
// add the value to the current (= last) list
outputLists[outputLists.Count - 1].Add(value);
// update the lastValue
lastValue = value;
}
// convert to arrays
// you could as well directly return the List<List<int>> instead
// and access the values exactly the same way
// but since you speak of buffers I guess you wanted arrays explicitely
var outputArrays = new int[outputLists.Count][];
for (var i = 0; i < outputLists.Count; i++)
{
outputArrays[i] = outputLists[i].ToArray();
}
return outputArrays;
}
so calling
var arrays = GetArrays(new int[]{1, 1, 1, 1, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, 0, 0, 0});
should result in
arrays[0] => [1, 1, 1, 1]
arrays[1] => [0, 0, 0, 0]
arrays[2] => [0, 0, 0]
since you seem to rather want to add values one by one dynamically I wouldn't use arrays at all but instead use something like
private List<List<int>> arrays = new List<List<int>>();
private int lastValue;
private void AddValue(int value)
{
// skip -1 values
if (value < 0)
{
lastValue = -1;
return;
}
// if a new value begin a new list
if (lastValue != value)
{
arrays.Add(new List<int>());
}
// add the value to the current (= last) list
arrays[outputLists.Count - 1].Add(value);
// update the lastValue
lastValue = value;
}
I mean something you have to store somewhere
Unfortunately I didn't find any out-of-the-box solution so I came up with my own Observable implementation:
using System;
using System.Collections.Generic;
using UniRx;
public static class ObservableFunctions
{
public static IObservable<T[]> BufferWhen<T>(this IObservable<T> source, Predicate<T> predicate)
{
return Observable.Create<T[]>(observer =>
{
List<T> buffer = new List<T>();
source.Subscribe(
t =>
{
if (predicate(t))
{
buffer.Add(t);
}
else
{
if (buffer.Count > 0)
{
observer.OnNext(buffer.ToArray());
buffer = new List<T>();
}
}
},
e =>
{
observer.OnError(e);
},
() =>
{
observer.OnCompleted();
}
);
return Disposable.Empty;
});
}
}
Luckily it was very simple. Simplier than searching for the appropriate function in Google...
I have list of 1 and 0 like this:
var list = new List<int>{1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1}
between two items, can be only one zero.
How to split that list into sublists by 0?
Other words: if I have string like this: string myString = "111011011110111111011101" then it is easy to split it by 0 into few strings.
But how to do it with list? This example shoudl produce these sublists:
1,1,1
1,1
1,1,1,1
1,1,1,1,1,1
1,1,1
1
so is there better way then casting each element into string, joining them and doing what I show what can be done with string ?
You can solve your problem by transforming the input sequence into a sequence of sequences just like the LINQ GroupBy does. However, in your case you are grouping on a change in the input sequence. There is perhaps the possibility of combining existing LINQ operators like GroupBy, Zip and Skip into something that does what you want but I think it is easier (and performs better) to create an iterator block that looks at pairs of items in the input sequence:
static class EnumerableExtensions {
public static IEnumerable<IEnumerable<T>> GroupOnChange<T>(
this IEnumerable<T> source,
Func<T, T, Boolean> changePredicate
) {
if (source == null)
throw new ArgumentNullException("source");
if (changePredicate == null)
throw new ArgumentNullException("changePredicate");
using (var enumerator = source.GetEnumerator()) {
if (!enumerator.MoveNext())
yield break;
var firstValue = enumerator.Current;
var currentGroup = new List<T>();
currentGroup.Add(firstValue);
while (enumerator.MoveNext()) {
var secondValue = enumerator.Current;
var change = changePredicate(firstValue, secondValue);
if (change) {
yield return currentGroup;
currentGroup = new List<T>();
}
currentGroup.Add(secondValue);
firstValue = secondValue;
}
yield return currentGroup;
}
}
}
GroupOnChange will take the items in the input sequence and group them into a sequence of sequences. A new group is started when changePredicate is true.
You can use GroupOnChange to split your input sequence exactly as you want to. You then have to remove the groups that have zero as a value by using Where.
var groups = items
.GroupOnChange((first, second) => first != second)
.Where(group => group.First() != 0);
You can also use this approach if the input are class instances and you want to group by a property of that class. You then have to modify the predicate accordingly to compare the properties. (I know you need this because you asked a now deleted question that was slightly more complicated where the input sequence was not simply numbers but classes with a number property.)
You could write an extension method like this:
public static class Extensions
{
public static IEnumerable<IEnumerable<TSource>> Split<TSource>(this IEnumerable<TSource> source, TSource splitOn, IEqualityComparer<TSource> comparer = null)
{
if (source == null)
throw new ArgumentNullException("source");
return SplitIterator(source, splitOn, comparer);
}
private static IEnumerable<IEnumerable<TSource>> SplitIterator<TSource>(this IEnumerable<TSource> source, TSource splitOn, IEqualityComparer<TSource> comparer)
{
comparer = comparer ?? EqualityComparer<TSource>.Default;
var current = new List<TSource>();
foreach (var item in source)
{
if (comparer.Equals(item, splitOn))
{
if (current.Count > 0)
{
yield return current;
current = new List<TSource>();
}
}
else
{
current.Add(item);
}
}
if (current.Count > 0)
yield return current;
}
}
And use it like this:
var list = new List<int>{1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1};
var result = list.Split(0);
int c = 0;
var list = new List<int>{1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1};
var res = list
// split in groups and set their numbers
// c is a captured variable
.Select(x=>new {Item = x, Subgroup = x==1 ? c : c++})
// remove zeros
.Where(x=>x.Item!=0)
// create groups
.GroupBy(x=>x.Subgroup)
// convert to format List<List<int>>
.Select(gr=>gr.Select(w=>w.Item).ToList())
.ToList();
You can just group by the index of the next zero:
static void Main(string[] args)
{
var list = new List<int> { 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 };
var result = list.Select((e, i) => new { Element = e, Index = i })
.Where(e => e.Element == 1)
.GroupBy(e => list.IndexOf(0, e.Index));
}
Maybe something simpler:
IEnumerable<IEnumerable<int>> Split(IEnumerable<int> items)
{
List<int> result = new List<int>();
foreach (int item in items)
if (item == 0 && result.Any())
{
yield return result;
result = new List<int>();
}
else
result.Add(item);
if (result.Any())
yield return result;
}
The code below splits by iterating over the list and storing sub-sequences of '1' in a list of lists.
using System;
using System.Collections.Generic;
namespace SplitList
{
class Program
{
static void Main(string[] args)
{
var list = new List<int> { 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 };
List<List<int>> splitSequences = new List<List<int>>();
List<int> curSequence = new List<int>();
foreach (var item in list)
{
if (item == 1) {
curSequence.Add(item);
} else {
//Only push the current sequence onto the list of sequences if it is not empty,
//which could happen if multiple zeroes are encountered in a row
if (curSequence.Count > 0) {
splitSequences.Add(curSequence);
curSequence = new List<int>();
}
}
}
//push any final list
if (curSequence.Count > 0)
{
splitSequences.Add(curSequence);
}
foreach (var seq in splitSequences) {
String line = String.Join(",", seq);
Console.WriteLine(line);
}
Console.WriteLine("");
Console.WriteLine("Press any key to exit");
var discard = Console.ReadKey();
}
}
}
I did like ASh's solution most. I've used it with a slight change. There is no captured variable in "my" variant:
var list = new List<int> { 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 };
var res = list
// Step 1: associate every value with an index
.Select((x, i) => (Value: x, Index: i))
// Step 2: remove separator values
.Where(x => x.Value != 0)
// Step 3: adjacent items will have the same result
// subtracting real position from the index marked during the 1st step
.Select((tuple, realIndex) => (tuple.Value, GroupId: tuple.Index - realIndex))
// Step 4: group by groupId
.GroupBy(tuple => tuple.GroupId)
// Step 5: convert to List<List<int>>
.Select(group => group.Select(tuple => tuple.Value).ToList())
.ToList();
More on step 3:
Let's say, we have 5 items with indices:
[ 0 1 2 3 4 ]
{ 1, 1, 0, 1, 1 }
After filtration from the step 2, list of numbers looks next:
[ 0 1 3 4 ]
{ 1, 1, 1, 1 }
What does step 3:
real indices (ri): [ 0 1 2 3 ]
indices from the 1st step (i): [ 0 1 3 4 ]
numbers: { 1, 1, 1, 1 }
i - ri: [ 0, 0, 1, 1 ]
And step 4 just groups by result of the subtraction i - ri, so called GroupID.
Say i have a class that contains these items publicly accessible via properties:
class MyClass
{
int switch1; //0 or 1
int switch2; //0 or 1
int switch3; //0 or 1
}
This class represents switch states, and each time a switch state changes, i would like to add it to my transition list
I have a large sorted list that contains instances of this class and would like to use a query to capture only the entries in my list where the switch state for any switch changes.
Is this possible using a linq query?
try this:
Assuming your class looks like:
public class State
{
public int Id { get; set; }
public int Switch1 { get; set; }
public int Switch2 { get; set; }
public int Switch3 { get; set; }
public override bool Equals(object obj)
{
var other = obj as State;
if (other != null)
{
return Switch1 == other.Switch1 &&
Switch2 == other.Switch2 &&
Switch3 == other.Switch3;
}
return false;
}
}
I just added an Equals() to compare flags and my Id field is purely to demonstrate which items changed.
We can then craft a LINQ query like:
State previous = null;
var transitions = list.Where(s =>
{
bool result = !s.Equals(previous);
previous = s;
return result;
})
.ToList();
Not elegant, but it works, if you had this data set:
var list = new List<State>
{
new State { Id = 0, Switch1 = 0, Switch2 = 0, Switch3 = 0 },
new State { Id = 1, Switch1 = 0, Switch2 = 0, Switch3 = 0 },
new State { Id = 2, Switch1 = 1, Switch2 = 0, Switch3 = 0 },
new State { Id = 3, Switch1 = 0, Switch2 = 1, Switch3 = 0 },
new State { Id = 4, Switch1 = 0, Switch2 = 1, Switch3 = 0 },
new State { Id = 5, Switch1 = 0, Switch2 = 1, Switch3 = 0 },
new State { Id = 6, Switch1 = 1, Switch2 = 1, Switch3 = 0 },
new State { Id = 7, Switch1 = 0, Switch2 = 0, Switch3 = 1 },
new State { Id = 8, Switch1 = 0, Switch2 = 0, Switch3 = 1 },
new State { Id = 9, Switch1 = 0, Switch2 = 0, Switch3 = 0 },
};
And ran the query, the list would contain your state transitions at items: 0, 2, 3, 6, 7, 9
I would do as follow:
class MyClass
{
int ID; //needs for recognize the message
int switch1; //0 or 1
int switch2; //0 or 1
int switch3; //0 or 1
public int Pattern
{
get { return switch1 + switch2 << 1 + switch3 << 2; }
}
}
Then it must be declared a dictionary with the previous-state messages:
Dictionary<int, int> _prevStates;
each cell has for key the ID, and for value the "Pattern" of the message.
At this point, let's suppose that the new incoming message stream is a list of MyClass:
IEnumerable<MyClass> incoming = ...
var changed = from msg in incoming
where _prevStates.ContainsKey(msg.ID) //what to do?
where _prevStates[msg.ID].Pattern != msg.Pattern
select msg;
Finally, you must update the dictionary with the changed patterns.
Cheers