LINQ Select Multiple Elements from a List<object[]> - c#

I have a List which is populated with data from a database.
The object array has say 10 elements when populated
I want to do a LINQ Select Statement that gets returns a List<object[]> with just 2 elements. How can I select these elements 1 and 2.
I have tried the following which work for element 0 but How can I get element 0 and element 1 ??
var resultDistinct = result.Select(p => p.GetValue(0)).Distinct();
var resultDistinct2 = result.Select(p => p.ElementAt(0)).Distinct();

You could use an anonymous object for this..
var items = result.Select(p => new { ValueA = p.GetValue(0), ValueB = p.GetValue(1) });
Then access each item
foreach(var item in items)
{
var valueA = item.ValueA;
var valueB = item.ValueB;
}

You can use the Take extension method:
items.Take(x);
This will return the first x items of a collection.
If you want to skip over some elements, you can use Skip(x) before calling Take. These two methods are very often used for paging.

If you want distinct and then 2 then,
result.Select(p => p).Distinct().Take(2);
If just 2 then,
result.Take(2);

private class Foo
{
public int Item1;
public int Item2;
public int Item3;
}
static void Main(string[] args)
{
List<Foo> foos = new List<Foo>
{
new Foo() { Item1 = 1, Item2 = 2, Item3 = 3 },
new Foo() { Item1 = 4, Item2 = 5, Item3 = 6 },
new Foo() { Item1 = 7, Item2 = 8, Item3 = 9 }
};
// Create a list of lists where each list has three elements corresponding to
// the values stored in Item1, Item2, and Item3. Then use SelectMany
// to flatten the list of lists.
var items = foos.Select(f => new List<int>() { f.Item1, f.Item2, f.Item3 }).SelectMany(item => item).Distinct();
foreach (int item in items)
Console.WriteLine(item.ToString());
Console.ReadLine();
}
refer to: https://nickstips.wordpress.com/2010/09/16/linq-selecting-multiple-properties-from-a-list-of-objects/

Related

Split List of int and keep reference to the original elements c# [duplicate]

This question already has answers here:
C# List of references to another list elements
(2 answers)
Closed 2 years ago.
I have a list of integer:
List<int> foo = new List<int>() {1, 2, 3, 4, 5, 6, 7, 8};
what i need to slice it based on the parity of the index so:
List<int> bar1 = foo.Where((i, v) => v % 2 == 0).ToList();
List<int> bar2 = foo.Where((i, v) => v % 2 == 1).ToList();
The issue is that if now i set
bar1[0] = -1
the original foo list remains unchanged.
Is there any work-around in order to change the value of bar1 and bar2 while also changing the values of the original foo list?
To keep the format you want and i do not recommend it you have to fill the collection with reference type object for example this would work
public class ReferenceInt
{
public int Value { get; set; } = 0;
}
now this would work :
List<ReferenceInt> foo = new List<ReferenceInt>()
{
new ReferenceInt(){ Value = 1 },
new ReferenceInt(){ Value = 2 },
new ReferenceInt(){ Value = 3 },
new ReferenceInt(){ Value = 4 },
new ReferenceInt(){ Value = 5 },
new ReferenceInt(){ Value = 6 },
new ReferenceInt(){ Value = 7 },
new ReferenceInt(){ Value = 8 }
};
now you have to filter per value inside the reference object
List<ReferenceInt> bar1 = foo.Where(v => v.Value % 2 == 0).ToList();
List<ReferenceInt> bar2 = foo.Where(v => v.Value % 2 == 1).ToList();
and this changed the value in all collections that has the ReferenceInt in it.
So both foo and bar1 will show the same thing
bar1[1].Value = 17;
You can create a proxy to work in-between of source list and filtered view.
public class Indexer<T> : IEnumerable<T>
{
public T this[int index]
{
get => All().ElementAt(index);
set
{
var i = 0;
foreach (var item in _source)
if (_filter(item))
{
if (i++ == index)
_source[i] = value;
break;
}
}
}
public IEnumerable<T> All() => _source.Where(o => _filter(o));
readonly IList<T> _source;
readonly Func<T, bool> _filter;
public Indexer(IList<T> source, Func<T, bool> filter)
{
_source = source;
_filter = filter;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator() => All().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => All().GetEnumerator();
}
This proxy will take care to handle indexes and simplify the usage:
List<int> foo = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8 };
var bar1 = new Indexer<int>(foo, o => o % 2 == 0);
Console.WriteLine(string.Join(",", bar1)); // 2,4,6,8
bar1[0] = -1;
Console.WriteLine(string.Join(",", bar1)); // 4,6,8
Console.WriteLine(string.Join(",", foo)); // 1,-1,3,4,5,6,7,8

How to use dictionary in c# to compare two lists

Currently, I have implemented two lists with a double for loop to find matches between the two lists so I can join on them.
I have a list A which contains an ID and some other columns. I have a list B which contains an ID and some other columns. I have currently implemented a for loop within a for loop in order to make the comparisons for all the IDs so that I can find the ones that match and then return the joined results. I know want to understand how to implement a dictionary in this case as that will be more efficient to fix this problem.
public IEnumerable<Details> GetDetails(string ID)
{
// there are two lists defined up here
for (var item in listA)
{
for (var item2 in listB)
{
if (item.ID == item2.ID)
{
item.Name = item2.name;
}
}
}
return results;
}
Instead of having this double for loop, which is very inefficient. I want to learn how to implement a dictionary to fix this problem.
The dictionary would use the ids as keys (or indexes) so
Dictionary<string, object> myListA = new Dictionary<string, object>();
Dictionary<string, object> myListB = new Dictionary<string, object>();
public object GetDetails(string ID)
{
object a = myListA[ID];
object b = myListB[ID];
// combine them here how you want
// object c = a + b;
return c;
}
How about using linq to achieve your actual requirement? Something like:
public IEnumerable<A> GetDetails(int ID)
{
var listA = new List<A>
{
new A(){ ID = 1, Name = 2 },
new A(){ ID = 3, Name = 4 },
new A(){ ID = 5, Name = 6 },
};
var listB = new List<B>
{
new B(){ X = 1, name = 0 },
new B(){ X = 3, name = 1 }
};
return listA.Join(listB, k => k.ID, k => k.ID, (item, item2) =>
{
item.Name = item2.name;
return item;
}).Where(w => w.ID == ID);
}
If you just want the common IDs in the two lists, you can achieve that like this:
var commonIds = listA.Select(o => o.ID).Intersect(listB.Select(o => o.ID));

Merge equal items with multiple lists but different length

I have 2 lists.
They are different in length but same type.
I want that an Item from List2 replaces an equal item in List1.
var item1 = new Item { Id = 1, Name = "Test1" };
var item2 = new Item { Id = 2, Name = "Test2" };
var item3 = new Item { Id = 3, Name = "Test3" };
var item4 = new Item { Id = 4, Name = "Test4" };
var item5 = new Item { Id = 5, Name = "Test5" };
var list1 = new List<Item> { item1, item2, item3, item4, item5 };
var list2 = new List<Item> { new Item { Id = 1, Name = "NewValue" } };
As a result I expect a list with 5 items where the item with Id = 1 has a value "NewValue".
How can I do that preferable with linq.
UPDATE
I extend my question:
How can the replacement of the replaced Item happen without copying all properties manually. Just imagine I have 100 properties...
This is one way to do it:
First define an equality comparer that depends only on the Id property of the Item class like this:
public class IdBasedItemEqualityComparer : IEqualityComparer<Item>
{
public bool Equals(Item x, Item y)
{
return x.Id == y.Id;
}
public int GetHashCode(Item obj)
{
return obj.Id.GetHashCode();
}
}
Then you can take items list1 that don't have corresponding items in list2 using the Except method and then you can concatenate that with list2 using the Concat method like this:
var result = list1.Except(list2, new IdBasedItemEqualityComparer()).Concat(list2).ToList();
Notice how I use the IdBasedItemEqualityComparer with the Except method, so that comparison is based only on Id.
Off the top of my head this is one solution
var list3 = new List<Item>();
foreach (var item in list1)
list3.Add(list2.FirstOrDefault(s => s.Id == item.Id) ?? item);
I think LEFT OUTER JOIN in Linq will be able to merge 2 lists regardless of number of properties(columns) like this:
List<Item> newItems =
(from l1 in list1
join l2 in list2 on l1.Id equals l2.Id into l12
from l2 in l12.DefaultIfEmpty()
select new { Item = (l2 == null) ? l1 : l2 }).Select(r => r.Item).ToList();

Doing pivot with LINQ

I've got this problem..I have a CSV file in the following format (customer, bought item pair):
customer1 item1
customer1 item2
customer1 item3
customer2 item4
customer2 item2
customer3 item5
customer3 item1
customer3 item2
customer4 item1
customer4 item2
customer5 item5
customer5 item1
Now, I wish to show in query results:
item x; item y; how many customers have bought itemx and item together
For example:
item1 item2 3 (because cust1 and cust2 and cust3 bought item1 and item2 together)
item1 item5 1 (because cust5 and cust3 bought item1 and item5 together)
The query return all possible combinations of items that customers have bought in pairs. Also notice that Pair(x, y) is the same as Pair(y, x).
An SQL query would look like this:
SELECT a1.item_id, a2.item_id, COUNT(a1.cust_id) AS how_many_custs_bought_both
FROM data AS a1
INNER JOIN data AS a2
ON a2.cust_id=a1.cust_id AND a2.item_id<>a1.item_id AND a1.item_id<a2.item_id
GROUP BY a1.item_id, a2.item_id
How would you do that in C# 1) using regular for/foreach loops 2) using LINQ ?
I tried doing it in LINQ first but stuck when I noticed that LINQ doesn't support multiple equals keyword in join clause. Then I tried doing using normal loops, however, it became so unefficient that it could only process like 30 lines (of CSV file rows) per second.
Please advise!
Using LINQ (and following the first 5 lines from Tim's answer) combining the chained method syntax with the query syntax for the join part:
var custItems = new [] {
new { customer = 1, item = 1 },
new { customer = 1, item = 2 },
new { customer = 1, item = 3 },
new { customer = 2, item = 4 },
new { customer = 2, item = 2 },
new { customer = 3, item = 5 },
new { customer = 3, item = 1 },
new { customer = 3, item = 2 },
new { customer = 4, item = 1 },
new { customer = 4, item = 2 },
new { customer = 5, item = 5 },
new { customer = 5, item = 1 }
};
};
var pairs = custItems.GroupBy(x => x.customer)
.Where(g => g.Count() > 1)
.Select(x => (from a in x.Select( y => y.item )
from b in x.Select( y => y.item )
where a < b //If you want to avoid duplicate (a,b)+(b,a)
// or just: where a != b, if you want to keep the dupes.
select new { a, b}))
.SelectMany(x => x)
.GroupBy(x => x)
.Select(g => new { Pair = g.Key, Count = g.Count() })
.ToList();
pairs.ForEach(x => Console.WriteLine(x));
EDIT: Forgot that OP wanted pair ocurrence count, added another .GroupBy() magic.
EDIT: Completed the example to show what it would output:
{ Pair = { a = 1, b = 2 }, Count = 3 }
{ Pair = { a = 1, b = 3 }, Count = 1 }
{ Pair = { a = 2, b = 3 }, Count = 1 }
{ Pair = { a = 2, b = 4 }, Count = 1 }
{ Pair = { a = 1, b = 5 }, Count = 2 }
{ Pair = { a = 2, b = 5 }, Count = 1 }
EDIT: rolled back and changed strings to integers, as OP shows a dataset with integers as IDs, and that removes the need for .GetHashCode()
Perhaps:
var lines = File.ReadLines(csvFilePath);
var custItems = lines
.Select(l => new { split = l.Split() })
.Select(x => new { customer = x.split[0].Trim(), item = x.split[1].Trim() })
.ToList();
var groups = from ci1 in custItems
join ci2 in custItems
on ci1.customer equals ci2.customer
where ci1.item != ci2.item
group new { Item1 = ci1.item, Item2 = ci2.item } by new { Item1 = ci1.item, Item2 = ci2.item } into ItemGroup
select ItemGroup;
var result = groups.Select(g => new
{
g.Key.Item1,
g.Key.Item2,
how_many_custs_bought_both = g.Count()
});
Note that the materialization with ToList is important when the file is large because of the self-join.
{ Item1 = item1, Item2 = item2, how_many_custs_bought_both = 3 }
{ Item1 = item1, Item2 = item3, how_many_custs_bought_both = 1 }
{ Item1 = item2, Item2 = item1, how_many_custs_bought_both = 3 }
{ Item1 = item2, Item2 = item3, how_many_custs_bought_both = 1 }
{ Item1 = item3, Item2 = item1, how_many_custs_bought_both = 1 }
{ Item1 = item3, Item2 = item2, how_many_custs_bought_both = 1 }
{ Item1 = item4, Item2 = item2, how_many_custs_bought_both = 1 }
{ Item1 = item2, Item2 = item4, how_many_custs_bought_both = 1 }
{ Item1 = item5, Item2 = item1, how_many_custs_bought_both = 2 }
{ Item1 = item5, Item2 = item2, how_many_custs_bought_both = 1 }
{ Item1 = item1, Item2 = item5, how_many_custs_bought_both = 2 }
{ Item1 = item2, Item2 = item5, how_many_custs_bought_both = 1 }
You can write some like this:
IDictionary<int, int> pivotResult = customerItems.ToLookup(c => c.Customer)
.ToDictionary(x=>x.Key, y=>y.Count());
Working LINQ example, not too pretty!
using System;
using System.Collections.Generic;
using System.Linq;
class Data
{
public Data(int cust, int item)
{
item_id = item;
cust_id = cust;
}
public int item_id { get; set; }
public int cust_id { get; set; }
static void Main(string[] args)
{
var data = new List<Data>
{new Data(1,1),new Data(1,2),new Data(1,3),
new Data(2,4),new Data(2,2),new Data(3,5),
new Data(3,1),new Data(3,2),new Data(4,1),
new Data(4,2),new Data(5,5),new Data(5,1)};
(from a1 in data
from a2 in data
where a2.cust_id == a1.cust_id && a2.item_id != a1.item_id && a1.item_id < a2.item_id
group new {a1, a2} by new {item1 = a1.item_id, item2 = a2.item_id}
into g
select new {g.Key.item1, g.Key.item2, count = g.Count()})
.ToList()
.ForEach(x=>Console.WriteLine("{0} {1} {2}",x.item1,x.item2,x.count))
;
Console.Read();
}
}
Output:
1 2 3
1 3 1
2 3 1
2 4 1
1 5 2
2 5 1

How do I merge records using LINQ?

I'd like to merge two records using a condition for each column in the row. I'd give you a code sample but I don't know where to start.
class Foo
{
public int i {get;set;}
public int b{get;set;}
public string first{get;set;}
public string last{get;set;}
}
//...
var list = new List<Foo>() {
new Foo () { i=1, b=0, first="Vince", last="P"},
new Foo () { i=1, b=1, first="Vince", last="P"},
new Foo () { i=1, b=0, first="Bob", last="Z"},
new Foo () { i=0, b=1, first="Bob", last="Z"},
} ;
// This is how I'd like my result to look like
// Record 1 - i = 1, b = 1, first="Vince", last = "P"
// Record 2 - i = 1, b = 1, first="Bob", last = "Z"
You can group the result, then aggregate the fields from the items in the group:
var result = list.GroupBy(f => f.first).Select(
g => new Foo() {
b = g.Aggregate(0, (a, f) => a | f.b),
i = g.Aggregate(0, (a, f) => a | f.i),
first = g.Key,
last = g.First().last
}
);
You could use the Aggregate method in LINQ.
First add a method to Foo, say Merge that returns a new Foo based on your merging rules.
public Foo Merge (Foo other)
{
// Implement merge rules here ...
return new Foo {..., b=Math.Max(this.b, other,b), ...};
}
You could also, instead, create a helper method outside the Foo class that does the merging.
Now use Aggregate over your list, using the first element as the seed, merging each record with the current aggregate value as you go. Or, instead of using Aggregate (since it's a somewhat contrived use of LINQ in this case), just do:
Foo result = list.First();
foreach (var item in list.Skip(1)) result = result.Merge(item);
How are your merge rules specified?
I found a non-elegant solution that works
var result = list.GroupBy(i=>i.first);
foreach (IGrouping<string, Foo> grp in result)
{
grp.Aggregate ((f1, f2) => {
return new Foo() {
b = f1.b | f2.b,
i = f1.i | f2.i,
first = f1.first,
last = f1.last
};
});
}

Categories