I have a collection of objects to be ordered by an object's field value. Current problem is that the order depends on a business logic.
public enum Order : byte {
a = 1,
b = 2,
c = 3
}
public class Foo{
public long A {get;set;}
public long B {get;set;}
public long C {get;set;}
}
public class Worker(){
private Foo[] orderFoos(Foo[] foos, Func<Order, long> sort){
return foos.OrderByDescending(f => sort(f)).ToArray(foos.Length);
}
public void Work(){
Foo[] foos = getFoos();
var orderByA = orderFoos(foos, f => f.A);
var orderByB = orderFoos(foos, f => f.B);
var orderByC = orderFoos(foos, f => f.C);
}
}
Compiler throws an error that Argument 1: cannot convert from 'Foo' to 'Order'. Are there any workarounds or solutions?
It seems what you wanted to achieve is sorting on different fields. You may not need to have Order enum if it's only for that purpose and replace:
private Foo[] orderFoos(Foo[] foos, Func<Order, long> sort){
return foos.OrderByDescending(f => sort(f)).ToArray(foos.Length);
}
into
private Foo[] orderFoos(Foo[] foos, Func<Foo, long> sort){
return foos.OrderByDescending(sort).ToArray(foos.Length);
}
NB: I'm not sure your intention with adding foos.Length in the ToArray method, but supposedly that's out of the scope of the question.
The below code seems to work. It is a small change to the orderFoos method, with some sample code for you to test the results.
using System;
using System.Linq;
public enum Order : byte
{
a = 1,
b = 2,
c = 3
}
public class Foo
{
public long A { get; set; }
public long B { get; set; }
public long C { get; set; }
}
public class Worker
{
private Foo[] orderFoos(Foo[] foos, Func<Foo, long> sort)
{
return foos.OrderByDescending(sort).ToArray();
}
public void Work()
{
Foo[] foos = { new Foo() { A = 1, B = 2, C = 3 }, new Foo() { A = 10, B = 1, C = 2 }, new Foo() { A = -1, B = 1, C = 10 } };
var orderByA = orderFoos(foos, f => f.A);
var orderByB = orderFoos(foos, f => f.B);
var orderByC = orderFoos(foos, f => f.C);
Console.WriteLine(orderByA.First().A); // I expect the second to be first here so 10
Console.WriteLine(orderByB.First().A); // I expect the first to be first here so 1
Console.WriteLine(orderByC.First().A); // I expect the third to be first here so -1
}
}
class Program
{
static void Main(string[] args)
{
var worker = new Worker();
worker.Work();
Console.ReadLine();
}
}
#hsoesanto gave a good solution but it doesn't work the way I expected it would be.
So I've created temporary workaround.
private Func<Foo, long> GetOrderFunction(Order orderType)
{
switch (orderType)
{
case Order.A:
return (f) => f.A;
case Order.B:
return (f) => f.B;
case Order.C:
return (f) => f.C;
}
}
private Foo[] orderFoos(Foo[] foos, Order order)
{
var orderFunction = GetOrderFunction(order);
return foos
.OrderByDescending(f => orderFunction (f))
.ToArray(foos.Length);
}
Related
I am kind of lost between Join and GroupJoin. Which way is the right way to do INNER JOIN? On one hand Join is doing the right job, but I have to call Distinct. On the other hand GroupJoin is grouping by itself, but gives me empty RHS.
Or there is a better way?
using System;
using System.Linq;
public class Foo
{
public string Name { get; set; }
public Foo(string name)
{
Name = name;
}
}
public class Bar
{
public Foo Foo { get; set; }
public string Name { get; set; }
public Bar(string name, Foo foo)
{
Foo = foo;
Name = name;
}
}
public class Program
{
public static Foo[] foos = new[] { new Foo("a"), new Foo("b"), new Foo("c"), new Foo("d") };
public static Bar[] bars = new[] { new Bar("1", foos[1]), new Bar("2", foos[1]) };
public static void Main(string[] args)
{
#if true
var res = foos.Join(
bars,
f => f,
b => b.Foo,
(f, b) => f
)
.Distinct();
#else
var res = foos.GroupJoin(
bars,
f => f,
b => b.Foo,
(f, b) => new { f, b }
)
.Where(t => t.b.Any())
.Select(t => t.f);
#endif
foreach (var r in res)
Console.WriteLine(r.Name);
}
}
Thanks!
The key to understanding this is to look at the types of the parameters for that last lambda you're passing in.
For Join, the b will be a single bar, and you will get a row for every bar that has a match.
While for GroupJoin, the b will be a collection of bar, and you will get a single row for every foo that has a match.
Both perform an inner join, but if you're looking for SQL's INNER JOIN, the Join method is what you want.
I have a list which I filtered in this way:
var items = myList.Select(c => c.foo == 1)
.Aggregate((decimal s, decimal c) => s.prop + a.prop);
the compiler say:
Select does not contains a definition for Aggregate
what I did wrong??
I need to sum of the value of the list that match with 1, if I use only Where I get list does not contain a definition for aggregate which is my question.
Here is an approach that works with Aggregate.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var myList = new List<SomeObj>() { new SomeObj(1, 10), new SomeObj(1, 20) };
var sum = myList
// filter for values of foo that equal 1
.Where(c => c.foo == 1)
// accumulate those values
.Aggregate(0m, (decimal acc, SomeObj next) => acc += next.prop);
Console.WriteLine(sum);
}
}
public class SomeObj
{
public int foo { get; set; } = 1;
public int prop { get; set; } = 10;
public SomeObj(int foo, int prop)
{
this.foo = foo;
this.prop = prop;
}
}
I have two list of objects. For e.g. say the objects are like below
class A
{
int ID;
int Value;
}
class B
{
int ID;
int Value;
}
I have two list of above objects like List<A> AList and List<B> BList. I want to find if any object in List<B> has matching Value from List<A>.
For now, what I do like is
foreach(var item in AList)
{
if(!BList.Any(x => x.Value == item.Value))
{
//Handle the error message
}
}
Is there any better way to do it by Linq?
You can do it this way. This will be true if there are any items in BList that have matching values in AList:
BList.Any(b => AList.Select(a => a.Value).Contains(b.Value))
Simply:
from a in AList
join b in BList on a.Value equals b.Value
select a
Try this:
BList.Any(b => AList.Any(a => a.Value == b.Value));
According to your current code, and if you just need to handle error when any item in AList doesn't have a matching item in BList, you can do as follows :
if (AList.Any(a => !BList.Any(b => b.Value == a.Value)))
{
//Handle error
}
Or if you need to take an action on every item in AList that doesn't have a matching item in BList :
foreach(var item in AList.Where(a => !BList.Any(b => b.Value == a.Value)))
{
//Handle error for current `item`
}
Anyways, the reason to prefer LINQ over conventional foreach loop is usually more for its readability (shorter, cleaner, easier to maintain, etc.) rather than performance. For reference : Is a LINQ statement faster than a 'foreach' loop?
This is what I've tried and it seems to work just fine:
class First
{
public int Id { get; set; }
public int Value { get; set; }
}
class Second
{
public int Id { get; set; }
public int Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
var firstList = new List<First>
{
new First { Id = 1, Value = 2 },
new First { Id = 1, Value = 10 },
new First { Id = 1, Value = 0 }
};
var secondList = new List<Second>
{
new Second { Id = 1, Value = 2 },
new Second { Id = 1, Value = 2 },
new Second { Id = 1, Value = 4 }
};
bool hasCommonValues = firstList.Select(f => f)
.Any(u => secondList.Select(x => x.Value)
.Contains(u.Value));
Console.WriteLine(hasCommonValues);
}
}
I've searched for a solution on SO but I couldn't find it, so I've made this new question.
I have 2 list of two different classes.
First class:
public class Class1
{
public int C1Property1 {get;set;}
public int C1Property2 {get;set;}
public int C1Property3 {get;set;}
}
And second class:
public class Class2
{
public int C2Property1 {get;set;}
public int C2Property2 {get;set;}
public int C2Property3 {get;set;}
}
And I have two list of those classes:
List<Class1> Class1List = new List<Class1>();
List<Class2> Class2List = new List<Class2>();
Now comes the hard part for me: Two of those properties in both classes have the same value, but with a different name: i.e. C1Property1 = C2Property1 and C1Property2 = C2Property2. The list Class1List has the property C1Property1 which is empty, and I need to populate it using the property from Class2List. I do this by using the following code:
foreach(var element1 in Class1List)
{
foreach(var element2 in Class2List)
{
if(element2.C2Property2 == element1.C1Property2)
{
element1.C1Property1 = element2.C2Property1;
}
}
}
This solution works how I intent it, but is very ugly and I have 2 foreach-loops which can be slow (list can contain over 10 000 elements). In the example classes I wrote only 3 properties to show how it works, but originally it has ~20 properties each, and only 2 of them are the same. Can I do this faster and more efficient? Some LinQ perhaps? I can't show more code, sorry. I hope that you will understand what I'm asking.
I need to take only one property from Class2List and place it on Class1List only when one of parameters in list are same.
In my second attempt i use something like that :
foreach (var element1 in Class1List)
{
foreach (var element2 in Class2List.Where(element2 => element2.C2Property2 == element1.C1Property2 ))
{
element2.C2Property2 = element1.C1Property2;
break;
}
}
This should be faster but still look ugly
So here I have three options:
Using LINQ
Class1List.ForEach(element1 =>
{
Class2 element2 = Class2List.FirstOrDefault(e2 => e2.C2Property2 == element1.C1Property2);
if (element2 != null) element1.C1Property1 = element2.C2Property1;
});
This took my machine 4.58s for 20000 elements in each list. And though the code looks (to me) a little better, this is effectivly the same as your code.
Using a dictionary
Using a dictionary to access the Class2 elements via a hash is really effective:
Dictionary<int, Class2> dictionary = Class2List.GroupBy(e2 => e2.C2Property2, e2 => e2).Select(elements => elements.First()).ToDictionary(e2 => e2.C2Property2, e2 => e2);
Class1List.ForEach(element1 =>
{
if (dictionary.ContainsKey(element1.C1Property2))
element1.C1Property1 = dictionary[element1.C1Property2].C2Property1;
});
This took my machine 0.00878s for 20000 elements in each list.
Parallel
If your data gets really really big, you might consider to use Parallel.ForEach
Dictionary<int, Class2> dictionary =
Class2List.GroupBy(e2 => e2.C2Property2, e2 => e2).Select(elements => elements.First()).ToDictionary(e2 => e2.C2Property2, e2 => e2);
Parallel.ForEach(Class1List, element1 =>
{
if (dictionary.ContainsKey(element1.C1Property2))
element1.C1Property1 = dictionary[element1.C1Property2].C2Property1;
});
But with only 20000 elements in each list this took my machine even longer (0.0197s) than the non-parallel version.
It was a pretty interesting thing to figure out, but I think something like this might work:
Class1List.ForEach(c1 =>
c1.C1Property1 = Class2List.Where(c2 => c2.C2Property2 == c1.C1Property2)
.Select(r => r.C2Property1)
.FirstOrDefault());
Here is a test class:
using System;
using System.Collections.Generic;
using System.Linq;
namespace SO_Test
{
public class ObjectA
{
public int Property1 { get; set; }
public int? Property2 { get; set; }
public override string ToString()
{
return String.Format("({0}, {1})", Property1, Property2);
}
}
public class ObjectB
{
public int Property1 { get; set; }
public int? Property2 { get; set; }
public override string ToString()
{
return String.Format("({0}, {1})", Property1, Property2);
}
}
class Program
{
static void Main()
{
var listA = new List<ObjectA>
{
new ObjectA { Property1 = 5, Property2 = null },
new ObjectA { Property1 = 16, Property2 = null },
new ObjectA { Property1 = 9, Property2 = null },
new ObjectA { Property1 = 38, Property2 = null }
};
var listB = new List<ObjectB>
{
new ObjectB { Property1 = 5, Property2 = 1 },
new ObjectB { Property1 = 9, Property2 = 2 },
new ObjectB { Property1 = 16, Property2 = 3 }
};
Console.WriteLine("BEFORE");
Console.WriteLine("ListA: {0}", String.Join(", ", listA));
Console.WriteLine("ListB: {0}", String.Join(", ", listB));
listA.ForEach(a =>
a.Property2 = listB.Where(b => b.Property1 == a.Property1)
.Select(r => r.Property2)
.FirstOrDefault());
Console.WriteLine("AFTER");
Console.WriteLine("ListA: {0}", String.Join(", ", listA));
Console.WriteLine("ListB: {0}", String.Join(", ", listB));
Console.ReadLine();
}
}
}
Output:
BEFORE
ListA: (5, ), (16, ), (9, ), (38, )
ListB: (5, 1), (9, 2), (16, 3)
AFTER
ListA: (5, 1), (16, 3), (9, 2), (38, )
ListB: (5, 1), (9, 2), (16, 3)
When I have 2 List<string> objects, then I can use Intersect and Except on them directly to get an output IEnumerable<string>. That's simple enough, but what if I want the intersection/disjuction on something more complex?
Example, trying to get a collection of ClassA objects which is the result of the intersect on ClassA object's AStr1 and ClassB object's BStr; :
public class ClassA {
public string AStr1 { get; set; }
public string AStr2 { get; set; }
public int AInt { get; set; }
}
public class ClassB {
public string BStr { get; set; }
public int BInt { get; set; }
}
public class Whatever {
public void xyz(List<ClassA> aObj, List<ClassB> bObj) {
// *** this line is horribly incorrect ***
IEnumberable<ClassA> result =
aObj.Intersect(bObj).Where(a, b => a.AStr1 == b.BStr);
}
}
How can I fix the noted line to achieve this intersection.
MoreLINQ has ExceptBy. It doesn't have IntersectBy yet, but you could easily write your own implementation, and possibly even contribute it to MoreLINQ afterwards :)
It would probably look something like this (omitting error checking):
public static IEnumerable<TSource> IntersectBy<TSource, TKey>(
this IEnumerable<TSource> first,
IEnumerable<TSource> second,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> keyComparer)
{
HashSet<TKey> keys = new HashSet<TKey>(first.Select(keySelector),
keyComparer);
foreach (var element in second)
{
TKey key = keySelector(element);
// Remove the key so we only yield once
if (keys.Remove(key))
{
yield return element;
}
}
}
If you wanted to perform an intersection on two completely different types which happened to have a common property type, you could make a more general method with three type parameters (one for first, one for second, and one for the common key type).
x ∈ A ∩ B if and only if x ∈ A and x ∈ B.
So, for each a in aObj, you can check if a.AStr1 is in the set of BStr values.
public void xyz(List<ClassA> aObj, List<ClassB> bObj)
{
HashSet<string> bstr = new HashSet<string>(bObj.Select(b => b.BStr));
IEnumerable<ClassA> result = aObj.Where(a => bstr.Contains(a.AStr1));
}
this code:
public IEnumerable<ClassA> xyz(List<ClassA> aObj, List<ClassB> bObj)
{
IEnumerable<string> bStrs = bObj.Select(b => b.BStr).Distinct();
return aObj.Join(bStrs, a => a.AStr1, b => b, (a, b) => a);
}
has passed the following test:
[TestMethod]
public void PropertyIntersectionBasedJoin()
{
List<ClassA> aObj = new List<ClassA>()
{
new ClassA() { AStr1 = "a" },
new ClassA() { AStr1 = "b" },
new ClassA() { AStr1 = "c" }
};
List<ClassB> bObj = new List<ClassB>()
{
new ClassB() { BStr = "b" },
new ClassB() { BStr = "b" },
new ClassB() { BStr = "c" },
new ClassB() { BStr = "d" }
};
var result = xyz(aObj, bObj);
Assert.AreEqual(2, result.Count());
Assert.IsFalse(result.Any(a => a.AStr1 == "a"));
Assert.IsTrue(result.Any(a => a.AStr1 == "b"));
Assert.IsTrue(result.Any(a => a.AStr1 == "c"));
}