Here's a list, think of it as rows and columns where rows are going down and columns are side ways. the column count will always be the same for all rows.
var dataValues = new List<List<string>>()
{
//row 1
new List<string>(){"A","12","X","P8" },
//row 2
new List<string>(){"B","13","Y","P7" },
//row 3
new List<string>(){"C","12","Y","P6" },
//row 4
new List<string>(){"A","14","X","P5" },
//....
new List<string>(){"D","15","Z","P4" },
new List<string>(){"A","13","X","P3" },
new List<string>(){"B","14","Y","P2" },
new List<string>(){"C","13","Z","P1" },
};
The user providers a list of indexes to group by.
var userParam= new List<int>() { 0, 2 };
my question is how do i dynamically group dataValues by the userParam where user param is n amount of index. In the example above it will gorup by the first column and the 3rd. However the index can change and the amount of indexes can change aswell
example
var userParam2 = new List<int>() { 0, 2};
var userParam3 = new List<int>() { 0};
var userParam4 = new List<int>() { 0,1,2};
i know how to group by when i know how many indexes there will be (the the case below it's 2 index parameters), however when it's dynamic (x amount) then i do not know how to do this
var result = dataValues.GroupBy(e => new { G1 = e[userParam2 [0]], G2 = e[userParam2 [1]] });
You could use a Custom Comparer to achieve this :
1 - Declaration of GroupByComparer that inherit from IEqualityComparer :
public class GroupByComparer : IEqualityComparer<List<string>>
{
private static List<int> _intList;
public GroupByComparer(List<int> intList)
{
_intList = intList;
}
public bool Equals(List<string> x, List<string> y)
{
foreach (int item in _intList)
{
if (x[item] != y[item])
return false;
}
return true;
}
public int GetHashCode(List<string> obj)
{
int hashCode = 0;
foreach (int item in _intList)
{
hashCode ^= obj[item].GetHashCode() + item;
}
return hashCode;
}
}
2 - Call group by with EqualityComparer like :
var userParam = new List<int>() { 0, 2 };
var result = dataValues.GroupBy(e => e, new GroupByComparer(userParam));
I hope you find this helpful.
I believe i have something but this looks slow please let me know if there is anyway better of doing this.
var userParams = new List<int>() { 0, 2 };
var dataValues = new List<List<string>>()
{
new List<string>(){"A","12","X","P8" },
new List<string>(){"B","13","Y","P7" },
new List<string>(){"C","12","Y","P6" },
new List<string>(){"A","14","X","P5" },
new List<string>(){"D","15","Z","P4" },
new List<string>(){"A","13","X","P3" },
new List<string>(){"B","14","Y","P2" },
new List<string>(){"C","13","Z","P1" },
};
var result = new List<(List<string> Key, List<List<string>> Values)>();
result.Add((new List<string>(), dataValues));
for (int index = 0; index < userParams.Count; index++)
{
var currentResult = new List<(List<string> Key, List<List<string>> Values)>();
foreach (var item in result)
{
foreach (var newGroup in item.Values.GroupBy(e => e[userParams[index]]))
{
var newKey = item.Key.ToList();
newKey.Add(newGroup.Key);
currentResult.Add((newKey, newGroup.ToList()));
}
}
result = currentResult;
}
foreach(var res in result)
{
Console.WriteLine($"Key: {string.Join(#"\", res.Key)}, Values: {string.Join(" | ", res.Values.Select(e=> string.Join(",",e)))}");
}
final result
Key: A\X, Values: A,12,X,P8 | A,14,X,P5 | A,13,X,P3
Key: B\Y, Values: B,13,Y,P7 | B,14,Y,P2
Key: C\Y, Values: C,12,Y,P6
Key: C\Z, Values: C,13,Z,P1
Key: D\Z, Values: D,15,Z,P4
Related
I have a big list of objects and in this object there is a category ID something like:
var list = new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 1, Value = new { }},
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 3, Value = new { }}
// and so on
};
So I am looking for making this complicated list more organized like list of lists of unique elements
something like:
var result = new List<List<Example>>
{
new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 3, Value = new { }}
},
new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }}
},
new List<Example>
{
new Example {CatId = 1, Value = new { }}
}
}
Problem is I do not what to use, group by will not fix my case, so how to do this in most efficient way.
So this is about partitioning, it's the sort of thing that is easy to do in a database query, but in c# you need to create some key with a partition number that you can then use to .GroupBy.
The partitioning itself is a grouping
var projected = list.GroupBy(x => x.CatId)
.SelectMany( g => g.Select( ( x, i ) => new { Item = x, rn = i + 1 } ) );
This gives you records that look like:
{"Item":{"CatId":1,"Value":{}},"rn":1}
{"Item":{"CatId":1,"Value":{}},"rn":2}
{"Item":{"CatId":1,"Value":{}},"rn":3}
{"Item":{"CatId":2,"Value":{}},"rn":1}
{"Item":{"CatId":2,"Value":{}},"rn":2}
{"Item":{"CatId":3,"Value":{}},"rn":1}
As you can see that rn ("row number") value can be used to group by:
var result = projected.GroupBy(x => x.rn, x => x.Item);
This gives us:
[{"CatId":1,"Value":{}},{"CatId":2,"Value":{}},{"CatId":3,"Value":{}}]
[{"CatId":1,"Value":{}},{"CatId":2,"Value":{}}]
[{"CatId":1,"Value":{}}]
So, all in 1 go:
var result = list.GroupBy(x => x.CatId)
.SelectMany( g => g.Select( ( x, i ) => new { Item = x, rn = i + 1 } ) )
.GroupBy(x => x.rn, x => x.Item);
Live example: https://dotnetfiddle.net/AlTfk8
I'm facing an issue while displaying multiple lists the value in a single row column.
Here is an example of code.
public class Program
{
static void Main(string[] args)
{
Dictionary<string, List<object>> keyvalues = new Dictionary<string, List<object>>();
keyvalues.Add("Code", new List<object>() { 1, 2, 3, 4 });
keyvalues.Add("Name", new List<object>() { "A", "B", "C", "D" });
keyvalues.Add("Age", new List<object>() { 20, 30, 40, 50 });
var listData = keyvalues.Select(x => x.Value).Select((x, i) => new { obj = x, index = i });
var listData = keyvalues.Select((x, iparent) => x.Value.Select((z, i) => new { value = string.Concat(z, x.Value[i]) }).ToList()).ToList();
Console.ReadLine();
}
}
Expected output
1A20
2B30
3C40
4D50
If you are using .Net 6, you could make use of the new 3 way Zip extension.
var result = keyvalues["Code"].Zip(keyvalues["Name"], keyvalues["Age"])
.Select(x=> $"{x.First}{x.Second}{x.Third}");
Why make it so complicated?
for(int x = 0; x<keyValues["Code"].Count; x++)
Console.WriteLine(
keyValues["Code"][x]+
keyValues["Name"][x]+
keyValues["Age"][x]
);
LINQ's a hammer; not every problem is a nail.
ps if you have N keys, you can easily turn it into a
var keys = new[]{"Code","Name","Age","Foo","Bar"};
for(...)
foreach(var k in keys)
... //some concat here or use the values directly eg adding to your page
You could easily use Zip here. However, you could roll your own
public static IEnumerable<string> DoStuff<T, T2>(Dictionary<T, List<T2>> source)
{
var max = source.Values.Max(x => x?.Count ?? 0);
for (var i = 0; i < max; i++)
yield return string.Concat(source.Values.Select(x => x.ElementAtOrDefault(i)));
}
Usage
var results = DoStuff(keyvalues);
Console.WriteLine(string.Join(Environment.NewLine,results));
Output
1A20
2B30
3C40
4D50
or
public static IEnumerable<string> DoStuff<T>(List<T>[] source)
{
var max = source.Max(x => x?.Count ?? 0);
for (var i = 0; i < max; i++)
yield return string.Concat(source.Select(x => x.ElementAtOrDefault(i)));
}
...
var results = DoStuff(keyvalues.Values.ToArray());
Console.WriteLine(string.Join(Environment.NewLine,results));
This question already has answers here:
Is there a good LINQ way to do a cartesian product?
(3 answers)
Closed 4 years ago.
I'm looking to get the Cartesian Product of an arbitrary number of objects in c#. My situation is slightly unusual - my inputs are not lists of base types, but objects which have a property that's a list of base types.
My input and output objects are as follows:
public class Input
{
public string Label;
public List<int> Ids;
}
public class Result
{
public string Label;
public int Id;
}
Some sample input data:
var inputs = new List<Input>
{
new Input { Label = "List1", Ids = new List<int>{ 1, 2 } },
new Input { Label = "List2", Ids = new List<int>{ 2, 3 } },
new Input { Label = "List3", Ids = new List<int>{ 4 } }
};
And my expected output object:
var expectedResult = new List<List<Result>>
{
new List<Result>
{
new Result{Label = "List1", Id = 1},
new Result{Label = "List2", Id = 2},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 1},
new Result{Label = "List2", Id = 3},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 2},
new Result{Label = "List2", Id = 2},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 2},
new Result{Label = "List2", Id = 3},
new Result{Label = "List3", Id = 4}
}
};
If I knew the number of items in 'inputs' in advance I could do this:
var knownInputResult =
from id1 in inputs[0].Ids
from id2 in inputs[1].Ids
from id3 in inputs[2].Ids
select
new List<Result>
{
new Result { Id = id1, Label = inputs[0].Label },
new Result { Id = id2, Label = inputs[1].Label },
new Result { Id = id3, Label = inputs[2].Label },
};
I'm struggling to adapt this to an arbitrary number of inputs - is there a possible way to do this?
I consider this duplicate of question linked in comments, but since it was reopened and you struggle to adapt that question to your case, here is how.
First grab function by Eric Lippert from duplicate question as is (how it works is explained there):
public static class Extensions {
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] { item })
);
}
}
Then flatten your input. Basically just attach corresponding label to each id:
var flatten = inputs.Select(c => c.Ids.Select(r => new Result {Label = c.Label, Id = r}));
Then run cartesian product and done:
// your expected result
var result = flatten.CartesianProduct().Select(r => r.ToList()).ToList();
I'm not proud of the amount of time I spent messing with this, but it works.
It's basically black magic, and I would replace it the first chance you get.
public static List<List<Result>> Permutate(IEnumerable<Input> inputs)
{
List<List<Result>> results = new List<List<Result>>();
var size = inputs.Select(inp => factorial_WhileLoop(inp.Ids.Count)).Aggregate((item, carry) => item + carry) - 1;
for (int i = 0; i < size; i++) results.Add(new List<Result>());
foreach (var input in inputs)
{
for (int j = 0; j < input.Ids.Count; j++)
{
for (int i = 0; i < (size / input.Ids.Count); i++)
{
var x = new Result() { Label = input.Label, Id = input.Ids[j] };
results[(input.Ids.Count * i) + j].Add(x);
}
}
}
return results;
}
public static int factorial_WhileLoop(int number)
{
var result = 1;
while (number != 1)
{
result = result * number;
number = number - 1;
}
return result;
}
I have two lists like below in C#.
List 1 = [{Item="A",position =1},{Item="B",position =2},{Item="A",position =3}]
List 2 = [{Item="AA",position =1},{Item="BB",position =2},{Item="AC",position =3}]
Now i want to remove duplicate values in the List 1 and that position should be removed in the List 2.
Example o/p
List 1 = [{Item="A",position =1},{Item="B",position =2}]
List 2 = [{Item="AA",position =1},{Item="BB",position =2}]
Can any one help me. Thanks.
List<string> lst1 = new List<string> { "A", "B", "A" };
List<string> lst2 = new List<string> { "AA", "BB", "AC" };
HashSet<string> seen = new HashSet<string>();
for (int i = 0; i < lst1.Count; i++) {
if (!seen.Add(lst1[i])) {
lst1.RemoveAt(i);
lst2.RemoveAt(i);
i--;
}
}
I used a HashSet to "save" the "already seen" elements of lst1 and then simply cycle the lst1 and remove the duplicate elements. HashSet.Add returns true if the HashSet doesn't already have an element, false if it already has it.
It isn't exactly clear what you want/what you have, but here there is the solution for another possible use case:
public class MyObject {
public string Item;
public int Position;
}
List<MyObject> lst1 = new List<MyObject> {
new MyObject { Item = "A", Position = 1 },
new MyObject { Item = "B", Position = 2 },
new MyObject { Item = "A", Position = 3 },
};
List<MyObject> lst2 = new List<MyObject> {
new MyObject { Item = "AA", Position = 1 },
new MyObject { Item = "BB", Position = 2 },
new MyObject { Item = "AC", Position = 3 },
};
HashSet<string> seen = new HashSet<string>();
HashSet<int> toBeDeleted = new HashSet<int>();
for (int i = 0; i < lst1.Count; i++) {
if (!seen.Add(lst1[i].Item)) {
toBeDeleted.Add(lst1[i].Position);
lst1.RemoveAt(i);
i--;
}
}
if (toBeDeleted.Count > 0) {
for (int i = 0; i < lst2.Count; i++) {
if (toBeDeleted.Contains(lst2[i].Position)) {
lst2.RemoveAt(i);
i--;
}
}
// or equivalent and shorter, without the for cycle
//lst2.RemoveAll(x => toBeDeleted.Contains(x.Position));
}
In this case in a first pass on lst1 we remove the duplicate items (as seen in the first example) and "save" the Positions that need to be deleted in the HashSet<int> tobedeleted and then we do a second pass on lst2 to remove the elements that need deleting.
Much not clear what you want do, but I try with this:
var filteredList1 = list1.GroupBy(x => x.Item).Select(g => g.First()).ToList();
var removeElements = list2.Where(f => !filteredList1.Any(t => t.Position == f.Position)).ToList();
removeElements.ForEach(x => list2.Remove(x));
I have an array of elements where the element has a Flagged boolean value.
1 flagged
2 not flagged
3 not flagged
4 flagged
5 not flagged
6 not flagged
7 not flagged
8 flagged
9 not flagged
I want to break it into arrays based on the flagged indicator
output >
array 1 {1,2,3}
array 2 {4,5,6,7}
array 3 {8,9}
Linq doesn't have an operator for this, but I've written an extension method that you may be able to use (in the process of submitting it to MoreLinq, which you should also check out):
Using the operator below, you would write:
var result =
items.Segment( (item,prevItem,idx) => item.Flagged )
.Select( seq => seq.ToArray() ) // converts each sequence to an array
.ToList();
Here's the code of the extension method:
public static IEnumerable<IEnumerable<T>> Segment<T>(IEnumerable<T> sequence, Func<T, T, int, bool> newSegmentIdentifier)
{
var index = -1;
using (var iter = sequence.GetEnumerator())
{
var segment = new List<T>();
var prevItem = default(T);
// ensure that the first item is always part
// of the first segment. This is an intentional
// behavior. Segmentation always begins with
// the second element in the sequence.
if (iter.MoveNext())
{
++index;
segment.Add(iter.Current);
prevItem = iter.Current;
}
while (iter.MoveNext())
{
++index;
// check if the item represents the start of a new segment
var isNewSegment = newSegmentIdentifier(iter.Current, prevItem, index);
prevItem = iter.Current;
if (!isNewSegment)
{
// if not a new segment, append and continue
segment.Add(iter.Current);
continue;
}
yield return segment; // yield the completed segment
// start a new segment...
segment = new List<T> { iter.Current };
}
// handle the case of the sequence ending before new segment is detected
if (segment.Count > 0)
yield return segment;
}
}
I had a similar problem with this, and solved it using GroupBy and closure.
//sample data
var arrayOfElements = new[] {
new { Id = 1, Flagged = true },
new { Id = 2, Flagged = false },
new { Id = 3, Flagged = false },
new { Id = 4, Flagged = true },
new { Id = 5, Flagged = false },
new { Id = 6, Flagged = false },
new { Id = 7, Flagged = false },
new { Id = 8, Flagged = true },
new { Id = 9, Flagged = false }
};
//this is the closure which will increase each time I see a flagged
int flagCounter = 0;
var query =
arrayOfElements.GroupBy(e =>
{
if (e.Flagged)
flagCounter++;
return flagCounter;
});
What it does is grouping on an int (flagCounter), which is increased each time a Flagged element is found.
Please note this won't work with AsParallel().
Testing the results:
foreach(var group in query)
{
Console.Write("\r\nGroup: ");
foreach (var element in group)
Console.Write(element.Id);
}
Outputs:
Group: 123
Group: 4567
Group: 89
Considering:
var arrayOfElements = new[] {
new { Id = 1, Flagged = true },
new { Id = 2, Flagged = false },
new { Id = 3, Flagged = false },
new { Id = 4, Flagged = true },
new { Id = 5, Flagged = false },
new { Id = 6, Flagged = false },
new { Id = 7, Flagged = false },
new { Id = 8, Flagged = true },
new { Id = 9, Flagged = false }
};
You can write:
var grouped =
from i in arrayOfElements
where i.Flagged
select
(new[] { i.Id })
.Union(arrayOfElements.Where(i2 => i2.Id > i.Id).TakeWhile(i2 => !i2.Flagged).Select(i2 => i2.Id))
.ToArray();
This works if your elements are ordered by the Id attribute. If they don't, you'll have to inject a Sequence on your original array, that should be easy to do with linq as well, so you'll get a sequence.
Also, a better alternative should be:
// for each flagged element, slice the array,
// starting on the flagged element until the next flagged element
var grouped =
from i in arrayOfElements
where i.Flagged
select
arrayOfElements
.SkipWhile(i2 => i2 != i)
.TakeWhile(i2 => i2 == i || !i2.Flagged)
.Select(i2 => i2.Id)
.ToArray();
Note that those answers are using pure linq.
I don't think LINQ is the right tool for this task. What about this:
public static List<List<T>> PartitionData<T>(T[] arr, Func<T, bool> flagSelector){
List<List<T>> output = new List<List<T>>();
List<T> partition = null;
bool first = true;
foreach(T obj in arr){
if(flagSelector(obj) || first){
partition = new List<T>();
output.Add(partition);
first = false;
}
partition.Add(obj);
}
return output;
}
A small example, with the Data from Fábio Batistas post:
var arrayOfElements = new[] {
new { Id = 1, Flagged = true },
new { Id = 2, Flagged = false },
new { Id = 3, Flagged = false },
new { Id = 4, Flagged = true },
new { Id = 5, Flagged = false },
new { Id = 6, Flagged = false },
new { Id = 7, Flagged = false },
new { Id = 8, Flagged = true },
new { Id = 9, Flagged = false }
};
var partitioned = PartitionData(arrayOfElements, x => x.Flagged);
I don't think LINQ is suited for this very well. It could be done with Aggregate() but I think you'd be better of just looping with a foreach() building up the result.