Order by field with lambda expression - c#

I am trying to order a list of lists by one of the fields.
I have two lists in one grouped list. The first contains the Id, the second contains the count. I was successfully able to group the list of lists by Id and reformat it.
for (int i = 0; i < GroupedListofLists.Count; i++)
{
tempo_Id.Add(GroupedListofLists[i][0]);
tempo_count.Add(GroupedListofLists[i][1]);
}
GroupedListofLists.Clear();
GroupedListofLists.Add(tempo_Id);
GroupedListofLists.Add(tempo_count);
If I print out the GroupedListofLists I will have distinct Ids (GroupedListofLists[0]) each with their count in the second(GroupedListofLists [1]).
Now when I try to sort this list with lambda expression I have a problem. I tried these two methods:
GroupedlistofLists.Sort( (a, b) => Convert.ToDouble(a[idx]).CompareTo(Convert.ToDouble(b[idx])));
AND
GroupedlistofLists = GroupedlistofLists.OrderBy(x => Convert.ToDouble(x[idx])).ToList();
A problem arises.
In the first method no matter what values I used for the variable 'idx', a will be assigned the values of GroupedListofLists[0] and b the values of GroupedListofLists[1].
In the second, no matter what values I use for the variable 'idx' (0 or 1), x will always contain the values for GroupedListofLists[0]. Which are the Id value and I need to sort them by Count, so GroupedListofLists[1].
I hope I was clear.
Thank you in advance.

I don't exacly know what you're trying to do. But i'll do a guess:
// Build some List you want to sort
List<string> myList = new List<string>() { "3", "1", "2" };
// Sort the List
myList = myList.OrderBy(a => Convert.ToDouble(a));
The Lambda-Expression in OrderBy needs to select a value which will be ordered in default manner. The Lambda-Expression will be run for every item in your list. The results will be used as order-key. you don't need any index.
Sort can be used so sort complex types.
Better version should work on a List with correct class and type. A Convert within a lambda-expression should be avoided!
// Some example-class with multiple properties
public class MyItem
{
public string Name { get; set; }
public double Value { get; set; }
}
// Some test-data
List<MyItem> myList = new List<MyItem>()
{
new MyItem() { Name = "One", Value = 1 },
new MyItem() { Name = "Three", Value = 3 },
new MyItem() { Name = "Two", Value = 2 }
}
// select the value you want to use for ordering the list.
myList = myList.OrderBy(item => item.Value);
// Expected output: 1, 2, 3
foreach(MyItem item in myList)
{
Console.WriteLine(item.Value + ", " + item.Name);
}
// Expected output: 3, 2, 1
myList = myList.OrderBy(item => item.Name);
foreach(MyItem item in myList)
{
Console.WriteLine(item.Value + ", " + item.Name);
}
(I got no Visual Studio installed, so check for typos ;))

Related

List<T>.Sort using Lambda expression with 2 parameters

I'm reading C# In Depths to try and better understand the language. I've used simple lambda expressions before with a single parameter and have become familiar with them. The part I'm struggling with is films.Sort((f1, f2) => f1.Name.CompareTo(f2.Name)); to sort the list. From what I've been able to figure out the lambda expression evaluates to IComparer<Film> when I tried to add f3 to it. The method being called IComparer.Compare Method (T, T) determines the items order.
The second parameter makes me want to say that it's comparing the Nth and Nth+1 film in the list and doing that from 0 through films.Count-1. Is this correct? If not, what part am I mistaken on. I wan't to avoid assuming incorrectly and avoid unintended errors.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var films = GetFilms();
Action<Film> print = film => Console.WriteLine("Name={0}, Year={1}", film.Name, film.Year);
Console.WriteLine("All films");
films.ForEach(print);
Console.WriteLine();
Console.WriteLine("Old films");
films.FindAll(film => film.Year < 1960).ForEach(print);
Console.WriteLine();
Console.WriteLine("Sorted films");
films.Sort((f1, f2) => f1.Name.CompareTo(f2.Name));
films.ForEach(print);
}
class Film
{
public string Name { get; set; }
public int Year { get; set; }
}
static List<Film> GetFilms()
{
return new List<Film>
{
new Film { Name = "Jaws", Year = 1975 },
new Film { Name = "Singing in the Rain", Year = 1952 },
new Film { Name = "Some like it Hot", Year = 1959 },
new Film { Name = "The Wizard of Oz", Year = 1939 },
new Film { Name = "It's a Wonderful Life", Year = 1946 },
new Film { Name = "American Beauty", Year = 1999 },
new Film { Name = "High Fidelity", Year = 2000 },
new Film { Name = "The Usual Suspects", Year = 1995 }
};
}
}
You can't tell from the method signature how many times the argument will be used. I could write a method Foo(Func<string, string>) that never called its argument.
However, since this is a sorting method, it's not enough to iterate once over the list. There are lots of different sorting algorithms, but they tend to use O(n log(n)) comparisons. In other words, doubling the length of the list sorted will result in slightly more than double the calls to your lambda function.
If you want to see what actually happens when it's running, add some logging!
films.Sort((f1, f2) =>
{
Console.WriteLine("Comparing ${f1.Name} to ${f2.Name}");
return f1.Name.CompareTo(f2.Name);
});
Actually you can use LINQ for sorting elements on any type that implements IEnumerable<T> interface like List<T> type:
// This will order the films by their name.
var result1 =
films
.OrderBy(film => film.Name)
.ToList();
// This will order the films first by Year in descending order and
// then by Name in ascending order.
var result2 =
films
.OrderByDescending(film => film.Year)
.ThenBy(film => film.Name)
.ToList();
There is also a ThenByDescending() method you can use and also you can create a longer chain from these methods if needed.
The thing is that here you should select only one property at a time in the lambda expression.

Search a List of string array to find a value in matching element and return another element in same array

So I have
List<string[]> listy = new List<string[]>();
listy.add('a','1','blue');
listy.add('b','2','yellow');
And i want to search through all of the list ti find the index where the array containing 'yellow' is, and return the first element value, in this case 'b'.
Is there a way to do this with built in functions or am i going to need to write my own search here?
Relatively new to c# and not aware of good practice or all the built in functions. Lists and arrays im ok with but lists of arrays baffles me somewhat.
Thanks in advance.
As others have already suggested, the easiest way to do this involves a very powerful C# feature called LINQ ("Language INtegrated Queries). It gives you a SQL-like syntax for querying collections of objects (or databases, or XML documents, or JSON documents).
To make LINQ work, you will need to add this at the top of your source code file:
using System.Linq;
Then you can write:
IEnumerable<string> yellowThings =
from stringArray in listy
where stringArray.Contains("yellow")
select stringArray[0];
Or equivalently:
IEnumerable<string> yellowThings =
listy.Where(strings => strings.Contains("yellow"))
.Select(strings => strings[0]);
At this point, yellowThings is an object containing a description of the query that you want to run. You can write other LINQ queries on top of it if you want, and it won't actually perform the search until you ask to see the results.
You now have several options...
Loop over the yellow things:
foreach(string thing in yellowThings)
{
// do something with thing...
}
(Don't do this more than once, otherwise the query will be evaluated repeatedly.)
Get a list or array :
List<string> listOfYellowThings = yellowThings.ToList();
string[] arrayOfYellowThings = yellowThings.ToArray();
If you expect to have exactly one yellow thing:
string result = yellowThings.Single();
// Will throw an exception if the number of matches is zero or greater than 1
If you expect to have either zero or one yellow things:
string result = yellowThings.SingleOrDefault();
// result will be null if there are no matches.
// An exception will be thrown if there is more than one match.
If you expect to have one or more yellow things, but only want the first one:
string result = yellowThings.First();
// Will throw an exception if there are no yellow things
If you expect to have zero or more yellow things, but only want the first one if it exists:
string result = yellowThings.FirstOrDefault();
// result will be null if there are no yellow things.
Based on the problem explanation provided by you following is the solution I can suggest.
List<string[]> listy = new List<string[]>();
listy.Add(new string[] { "a", "1", "blue"});
listy.Add(new string[] { "b", "2", "yellow"});
var target = listy.FirstOrDefault(item => item.Contains("yellow"));
if (target != null)
{
Console.WriteLine(target[0]);
}
This should solve your issue. Let me know if I am missing any use case here.
You might consider changing the data structure,
Have a class for your data as follows,
public class Myclas
{
public string name { get; set; }
public int id { get; set; }
public string color { get; set; }
}
And then,
static void Main(string[] args)
{
List<Myclas> listy = new List<Myclas>();
listy.Add(new Myclas { name = "a", id = 1, color = "blue" });
listy.Add(new Myclas { name = "b", id = 1, color = "yellow" });
var result = listy.FirstOrDefault(t => t.color == "yellow");
}
Your current situation is
List<string[]> listy = new List<string[]>();
listy.Add(new string[]{"a","1","blue"});
listy.Add(new string[]{"b","2","yellow"});
Now there are Linq methods, so this is what you're trying to do
var result = listy.FirstOrDefault(x => x.Contains("yellow"))?[0];

What means a combined All with Any LINQ query

I have a line of code:
bool true/false = array1.All(a => array2.Any(t => t.field == a.field));
I do not understand this combination of all + any.
Does it mean 'No field of array1 equals any field of array2' then return true ?
Would this not be the same as array1.Except(array2).Any(); ?
UPDATE
Accidently I put a "!" before the .Any() !!!
I think they are really different, it also depends on how you array is structured. If it has only field property or it has other properties as well.
Code inspection
array1.All(a => array2.Any(t => t.field == a.field));
Return true if For each element in array1 there's at least one element in array2 that has the
same value for the field property
array1.Except(array2).Any();
Return True if there's at least one element of array1 not present in
array2
Now given your context, if field is the only property of your structure it produces the same result, but it does not if there's other things going on.
For example
struct Test
{
public string int { get; set; }
public string int { get; set; }
}
//...
var array1 = new Test[]
{
new Test { Field = 0, OtherField = 1 },
new Test { Field = 1, OtherField = 2 }
}
var array2 = new Test[]
{
new Test { Field = 0, OtherField = 1 },
new Test { Field = 2, OtherField = 2 }
}
First case: is it true that for each element of array1 there's at least one element in array2 with same value in field property? False
Second case: is it true that at least one element of array1 is not present in array2? True
That means return true if there is no item in array2 which has the same value in field for all of items in array1.
Simpler version:
For all items in array1, there is no item in array2 with same value of field.
UPDATE:
Now the modified version is much simpler since it says return true if for all items in array1 there is an item in array2 with same value for field.
In summary, your first solution is to check if all the elements in array1 have the same field value in some element in array2, which could be also translated to:
var areContained =!array1.Select(e=>e.field)).Except(array2.Select(d=>d.field)).Any();
Another variant could be using a hash that could help to the efficiency if the superset if too big:
HashSet<int> hashSet = new HashSet<int>(array2.Select(d=>d.field));//Superset
bool contained = array1.Select(e=>e.field)).All(i => hashSet.Contains(i));
In your alternative you are comparing based on object instances of the arrays by using the default equality comparer, so that can produce a completely different result. Take a look the example in this link

Given a list of several of the same object, group and combine them based on field value

Sorry for the incoherent title. I don't know how to concisely explain my problem, which is why I didn't really know how to look it up. I'll explain using an example...
Let's say I have a class:
public class cas
{
public string name { get; set; }
public int num { get; set; }
}
With that class, I make several objects and stick them into a list. For the sake of example, I will make 4:
var list = new List<cas>
{
new cas { name = "firstname", num = 1 },
new cas { name = "firstname", num = 2 },
new cas { name = "lastname", num = 3 },
new cas { name = "lastname", num = 4 }
};
Is there a way to take this List and combine any objects with the same name field?
So, the new list would be 1 object with:
name = "firstname", num = 3,
name = "lastname", num = 7
There's the obvious "long" way to do it, but it would be clunky and expensive (go through the list several times to find like-objects). I was wondering if I was missing any clean way of doing it. I intentionally made a simple example so that the answer would be a proof of concept rather than writing my code for me. My actual problem is more complex than this, but I can't figure out this one aspect of it.
Using Linq, you have a GroupBy Method and a Select Method:
list = list.GroupBy(x=> x.name)
.Select(x=> new cas() { name = x.Key, num = x.Sum(y=> y.num) }).ToList();
Or using Elegant query-syntax:
list = (from item in list
group item by item.name into grouping
select new cas()
{
name = grouping.Key,
num = grouping.Sum(x => x.num)
}).ToList();
Note that to use these methods, you have to add using System.Linq at the top of your source file.
You can use linq, you would have to group them on name property and then sum on the num property of each group like:
var result = list.GroupBy(x=>x.name)
.Select(g=> new cas
{
name = g.Key,
num = g.Sum(x=>x.num)
});

Querying a list of strings with a query string?

I have a dictionary:
<string,List<string>>
The key is the product code say "product1" then the list is a list of properties:
"Brand","10.40","64","red","S"
Then I 'can' have a list of rules/filters e.g.
var tmpFilter = new customfilters();
tmpFilter.Field = "2";
tmpFilter.Expression = ">";
tmpFilter.Filter = "10";
So for the above example this would pass because at index 2 (tmpFilter.Field) it is more than 10; then I have another object which defines which fields within the list I want to write to file. For that dictionary item I just want to write the product brand and price where the filters match.
At the moment without the filter I have:
var tmp = new custom();
tmp.Columns = "0,1";
tmp.Delimiter = ",";
tmp.Extention = ".csv";
tmp.CustomFilters = new List<customfilters>() {new customfilters(){ Field = "2", Expression = ">", Filter = "10"} };
public static void Custom(custom custom)
{
foreach (var x in Settings.Prods)
{
//Get Current Product Code
var curprod = Settings.ProductInformation[x];// the dictionary value
foreach (var column in custom.Columns)
{
var curVal = curprod[Convert.ToInt32(column)];
tsw.Write(curVal + custom.Delimiter);
}
Settings.Lines++;
tsw.WriteLine();
}
tsw.Close();
}
I only want to write the curprod if all the filters pass for that list of strings.
How I can do this?
There's a really nice Nuget package based on an example published by Microsoft, that they have decided to make really hard to find for some reason, that allows dynamic linq queries:
https://www.nuget.org/packages/System.Linq.Dynamic/1.0.2
Source:
https://github.com/kahanu/System.Linq.Dynamic
Using that you can do stuff like this very easily (note: I used strings here because the OP states they have a List<string>):
List<string> stuff = new List<string> { "10.40", "64", "5", "56", "99", "2" };
var selected = stuff.Select(s => new { d = double.Parse(s) }).Where("d > 10");
Console.WriteLine(string.Join(", ", selected.Select(s => s.d.ToString()).ToArray()));
Outputs:
10.4, 64, 56, 99
That may give you a place to start. One thing you are going to have to tackle is identifying which of your fields are numeric and should be converted to a numeric type before trying to apply your filter. Otherwise you are going to comparing as strings.

Categories