Linq expression to join a list and a dictionary - c#

I have a list of part numbers:
var parts = new List<string> {"part1", "part2", "part3"};
I also have a dictionary of quantities for these part numbers:
var quantities = new Dictionary<string, int> {{"part1", 45}, {"part3", 25}};
Given a delimiter of |, I need to arrange these values in a flat file like so:
SalesRep|part1|part2|part3
Mr. Foo|45||25
What I'd like to do is define a string that no matter what values are in parts and quantities, I can tack this on to the sales rep name to resemble the example above.
It seems like I should be able to do this with a string.Join() on an enumerable LINQ operation, but I can't figure out what statement will get me the IEnumerable<string> result from joining parts and quantities. It thought that would be a .Join(), but the signature doesn't seem right. Can someone enlighten me?

Something like this perhaps?
var partValues = parts.Select(x => quantities.ContainsKey(x) ? quantities[x] : 0);
Basically for each item in the parts list you either pick the value from your dictionary, or if it doesn't exist 0.
To make this a little more interesting you could define a generic extension method on IDictionary<T,U> that makes this a little more readable:
public static class DictionaryExtensions
{
public static U GetValueOrDefault<T,U>(this IDictionary<T, U> dict, T key)
{
if(dict.ContainsKey(key))
{
return dict[key];
}
return default(U);
}
}
Then you can simply write:
var partValues = parts.Select(quantities.GetValueOrDefault);

var parts = new List<string> { "part1", "part2", "part3" };
var quantities = new Dictionary<string, int> { { "part1", 45 }, { "part3", 25 } };
var result = string.Join("|",
from p in parts select quantities.ContainsKey(p)
? quantities[p].ToString() : "");

Related

How to use Linq and lambda expressions on a Dictionary<string, List<T>> to a specify its order based on Key

So I have a Dictionary with string keys, and the value is a List of objects. Each object in each List has a property value which is equal to that List's associated key. In other words, the Dictionary is grouping objects by property value via the key. For example, let's say we have,
Dictionary<string, List<Animal>> GroupedByClass = new Dictionary<string, Animal>();
Where Animal is an object which contains a string property named "ClassType" which has valid options of "Mammal", "Reptile", or "Amphibian", etc.
The Animal class could also have string property named "Species" which more uniquely defines each object in the Dictionary.
A pseudocode description of the contents of the Dictionary could be:
<"Mammal", List<Animal>>
where the Animals in the list have Species "Dog", "Cat"
<"Reptile", List>
where the Animals in the list have Species "Snake", "Lizard", and "Turtle"
<"Amphibian", List>
where the Animals in the list have Species "Salamander" and "Frog"
I want to rearrange this Dictionary by the key value such that the values with a key of "Reptile" are first, then "Amphibian", and then finally "Mammal". Note that I do not want to sort this based on alphabetical order, I want to specify my own order.
I know I could solve this problem by simply iterating through the Dictionary a few times, extracting only the items with the right key. For example, I could do it like this,
Dictionary<string, List<Animal>> temp = new Dictionary<string, List<Animal>>();
foreach(KeyValuePair<string, List<Animal>> item in GroupedByClass)
{
if(item.Key == "Reptile")
{
temp.Add(item.Key, item.Value);
}
}
foreach(KeyValuePair<string, List<Animal>> item in GroupedByClass)
{
if(item.Key == "Amphibian")
{
temp.Add(item.Key, item.Value);
}
}
foreach(KeyValuePair<string, List<Animal>> item in GroupedByClass)
{
if(item.Key == "Mammal")
{
temp.Add(item.Key, item.Value);
}
}
return temp;
However, this seems inelegant, and I was wondering if there was a better answer for this problem using Linq queries and lambda expressions.
This should be close (with some syntax errors):
var order = new [] {"Reptile","Amphibian","Mammal"};
var elems = dict.OrderBy(x=>order.IndexOf(x.Key));
if you want to flatten the results, then you can use SelectMany:
var order = new [] {"Reptile","Amphibian","Mammal"};
var elems = dict.SelectMany(x=>x).OrderBy(x=>order.IndexOf(x.Species));
Dictionary<string, List<Animal>> temp = new Dictionary<string,List<Animal>>();
(new List<string>() {"Reptile","Amphibian","Mammal"}).ForEach(x => temp.Add(x, GroupedByClass[x]));
You could define an enumeration and have it be your defining order.
enum Animal : int
{
Reptile = 0,
Amphibian = 1,
Mammal = 2
}
Note, since you have simple strings this works and is straight forward. If however you end up with strings that have spaces you can use the DescriptionAttribute of the enumeration and go between it and the actual enumeration.
Enumeration with Display String
By using an enumeration over the string you can do many things, but of course order by it using the integers you assiged.

merge several lists but keep only values that are found on all the list

If I have the arrays:
var list1 = new int[] { 1,2,3,4,5};
var list2 = new int[] { 1,2,3};
var list3 = new int[] { 2,3};
what method can help me keep only value found on all the lists. In this example I will like to end up with {2,3} because those two values are found on all the lists.
Use Intersect -> Produces the set intersection of two sequences by using the default equality comparer to compare values. (MSDN: http://msdn.microsoft.com/en-us/library/bb460136.aspx)
var list = list1.Intersect(list2).Intersect(list3);
You can use this method to get the intersection of any number of sequences:
public static IEnumerable<T> IntersectAll<T>(params IEnumerable<T>[] sequences)
{
if (!sequences.Any())
return Enumerable.Empty<T>();
var set = new HashSet<T>(sequences.First());
foreach (var sequence in sequences.Skip(1))
{
set.IntersectWith(sequence);
}
return set;
}
Note that unlike repeated calls to the LINQ Intersect method, this won't be repeatedly rebuilding an intermediate HashSet. It'll re-use the same one throughout.
You can use the Intersect method that's a part of LINQ like this:
var result = list1.Intersect(list2).Intersect(list3);
If you want a method that you can pass an arbitrary number of lists you can use this:
public static int[] Process(params int[][] values)
{
int[] result = values[0];
foreach (int[] value in values)
{
result = result.Intersect(value).ToArray();
}
return result;
}
You can call it like this:
var result = Process(list1, list2, list3);
Other has already proposed good and working solutions. Based on their answer I propose this one:
public static class IEnumerableExtension
{
public static IEnumerable<T> Intersect<T>(this IEnumerable<T> one, params IEnumerable<T>[] others)
{
var result = one;
foreach (var other in others)
result = result.Intersect(other);
return result;
}
}
And the usage will be like this:
var result = list1.Intersect(list2,list3,...continued to...listn);

BinarySearch in two dimensional list

I have dimensional list:
List<List<string>> index_en_bg = new List<List<string>>();
index_en_bg.Add(new List<string>() { word1, translation1 });
index_en_bg.Add(new List<string>() { word2, translation2 });
index_en_bg.Add(new List<string>() { word3, translation3 });
I would do binary search by the first column (words), something like this:
int row = index_en_bg.BinarySearch(searchingstr);
but it works only for a one-dimensional list. How would I extend it to work for two-dimensional lists in my case? I don't want to use Dictionary class.
In this case you need to provide your own customer IComparer-implementing comparator
public class Comparer: IComparer<IList<string>>
{
public int Compare(IList<string> x, IList<string> y)
{
// base the comparison result on the first element in the respective lists
// eg basically
return x[0].CompareTo(y[0]);
}
And you'll call it like this, offering a List where only the field you're searching is filled in.
int row = index_en_bg.BinarySearch(new List<string>() {searchingstr},new Comparer());
Well as far as I understand you should use Dictionary<K,V> instead, this way:
// 1 creating the dictionary
var dic = new Dictionary<string, string>();
dic["word1"] = "translation1";
dic["word2"] = "translation2";
dic["word3"] = "translation3";
// 2 finding a translation
var trans = dic["word1"];
And Dictionary<K,V> is really performant.
But if you insist on using BinarySearch you can implement IComparer<List<string>> and pass it to the function.
As you always search using the first item of the list you could use dictionary too.
var d = Dictionary<string, List<string>>();
as answered previously it's preforms much better than List.

Linq with a Dictionary inside the class

i have the next class:
public class Example
{
String name;
Dictionary<String, decimal> data;
public Example()
{
data = new Dictionary<String, decimal>();
}
}
Then, using Linq i need to retrieve all distinct String keys in the data field.
For example:
e1: 1 - [["a", 2m],["b",3m])
e2: 2 - [["b", 2m],["c",3m])
I'll need a list with: ["a","b","c"]
I hope I was clear enough.
Thanks.
PD: One thing i was missing, i have a List of Examples.
Assuming you mean you have a collection of Examples (e1, e2...):
var keys = examples.SelectMany(example => example.data.Keys)
.Distinct();
var keys =
(from ex in examples
from key in ex.Data.Keys
select key).Distinct();

Concat all strings inside a List<string> using LINQ

Is there any easy LINQ expression to concatenate my entire List<string> collection items to a single string with a delimiter character?
What if the collection is of custom objects instead of string? Imagine I need to concatenate on object.Name.
string result = String.Join(delimiter, list);
is sufficient.
Warning - Serious Performance Issues
Though this answer does produce the desired result, it suffers from poor performance compared to other answers here. Be very careful about deciding to use it
By using LINQ, this should work;
string delimiter = ",";
List<string> items = new List<string>() { "foo", "boo", "john", "doe" };
Console.WriteLine(items.Aggregate((i, j) => i + delimiter + j));
class description:
public class Foo
{
public string Boo { get; set; }
}
Usage:
class Program
{
static void Main(string[] args)
{
string delimiter = ",";
List<Foo> items = new List<Foo>() { new Foo { Boo = "ABC" }, new Foo { Boo = "DEF" },
new Foo { Boo = "GHI" }, new Foo { Boo = "JKL" } };
Console.WriteLine(items.Aggregate((i, j) => new Foo{Boo = (i.Boo + delimiter + j.Boo)}).Boo);
Console.ReadKey();
}
}
And here is my best :)
items.Select(i => i.Boo).Aggregate((i, j) => i + delimiter + j)
Note: This answer does not use LINQ to generate the concatenated string. Using LINQ to turn enumerables into delimited strings can cause serious performance problems
Modern .NET (since .NET 4)
This is for an array, list or any type that implements IEnumerable:
string.Join(delimiter, enumerable);
And this is for an enumerable of custom objects:
string.Join(delimiter, enumerable.Select(i => i.Boo));
Old .NET (before .NET 4)
This is for a string array:
string.Join(delimiter, array);
This is for a List<string>:
string.Join(delimiter, list.ToArray());
And this is for a list of custom objects:
string.Join(delimiter, list.Select(i => i.Boo).ToArray());
using System.Linq;
public class Person
{
string FirstName { get; set; }
string LastName { get; set; }
}
List<Person> persons = new List<Person>();
string listOfPersons = string.Join(",", persons.Select(p => p.FirstName));
Good question. I've been using
List<string> myStrings = new List<string>{ "ours", "mine", "yours"};
string joinedString = string.Join(", ", myStrings.ToArray());
It's not LINQ, but it works.
You can simply use:
List<string> items = new List<string>() { "foo", "boo", "john", "doe" };
Console.WriteLine(string.Join(",", items));
Happy coding!
I think that if you define the logic in an extension method the code will be much more readable:
public static class EnumerableExtensions {
public static string Join<T>(this IEnumerable<T> self, string separator) {
return String.Join(separator, self.Select(e => e.ToString()).ToArray());
}
}
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString() {
return string.Format("{0} {1}", FirstName, LastName);
}
}
// ...
List<Person> people = new List<Person>();
// ...
string fullNames = people.Join(", ");
string lastNames = people.Select(p => p.LastName).Join(", ");
List<string> strings = new List<string>() { "ABC", "DEF", "GHI" };
string s = strings.Aggregate((a, b) => a + ',' + b);
I have done this using LINQ:
var oCSP = (from P in db.Products select new { P.ProductName });
string joinedString = string.Join(",", oCSP.Select(p => p.ProductName));
Put String.Join into an extension method. Here is the version I use, which is less verbose than Jordaos version.
returns empty string "" when list is empty. Aggregate would throw exception instead.
probably better performance than Aggregate
is easier to read when combined with other LINQ methods than a pure String.Join()
Usage
var myStrings = new List<string>() { "a", "b", "c" };
var joinedStrings = myStrings.Join(","); // "a,b,c"
Extensionmethods class
public static class ExtensionMethods
{
public static string Join(this IEnumerable<string> texts, string separator)
{
return String.Join(separator, texts);
}
}
This answer aims to extend and improve some mentions of LINQ-based solutions. It is not an example of a "good" way to solve this per se. Just use string.Join as suggested when it fits your needs.
Context
This answer is prompted by the second part of the question (a generic approach) and some comments expressing a deep affinity for LINQ.
The currently accepted answer does not seem to work with empty or singleton sequences. It also suffers from a performance issue.
The currently most upvoted answer does not explicitly address the generic string conversion requirement, when ToString does not yield the desired result. (This can be remedied by adding a call to Select.)
Another answer includes a note that may lead some to believe that the performance issue is inherent to LINQ. ("Using LINQ to turn enumerables into delimited strings can cause serious performance problems.")
I noticed this comment about sending the query to the database.
Given that there is no answer matching all these requirements, I propose an implementation that is based on LINQ, running in linear time, works with enumerations of arbitrary length, and supports generic conversions to string for the elements.
So, LINQ or bust? Okay.
static string Serialize<T>(IEnumerable<T> enumerable, char delim, Func<T, string> toString)
{
return enumerable.Aggregate(
new StringBuilder(),
(sb, t) => sb.Append(toString(t)).Append(delim),
sb =>
{
if (sb.Length > 0)
{
sb.Length--;
}
return sb.ToString();
});
}
This implementation is more involved than many alternatives, predominantly because we need to manage the boundary conditions for the delimiter (separator) in our own code.
It should run in linear time, traversing the elements at most twice.
Once for generating all the strings to be appended in the first place, and zero to one time while generating the final result during the final ToString call. This is because the latter may be able to just return the buffer that happened to be large enough to contain all the appended strings from the get go, or it has to regenerate the full thing (unlikely), or something in between. See e.g. What is the Complexity of the StringBuilder.ToString() on SO for more information.
Final Words
Just use string.Join as suggested if it fits your needs, adding a Select when you need to massage the sequence first.
This answer's main intent is to illustrate that it is possible to keep the performance in check using LINQ. The result is (probably) too verbose to recommend, but it exists.
You can use Aggregate, to concatenate the strings into a single, character separated string but will throw an Invalid Operation Exception if the collection is empty.
You can use Aggregate function with a seed string.
var seed = string.Empty;
var seperator = ",";
var cars = new List<string>() { "Ford", "McLaren Senna", "Aston Martin Vanquish"};
var carAggregate = cars.Aggregate(seed,
(partialPhrase, word) => $"{partialPhrase}{seperator}{word}").TrimStart(',');
you can use string.Join doesn’t care if you pass it an empty collection.
var seperator = ",";
var cars = new List<string>() { "Ford", "McLaren Senna", "Aston Martin Vanquish"};
var carJoin = string.Join(seperator, cars);

Categories

Resources