Linq - how to map (select) deconstructed tuples? - c#

I am trying to achieve a very simple thing. I have an Enumerable of tuples and I want to map and deconstruct them at the same time (as using .Item1, .Item2 is ugly as hell).
Something like this:
List<string> stringList = new List<string>() { "one", "two" };
IEnumerable<(string, int)> tupleList =
stringList.Select(str => (str, 23));
// This works fine, but ugly as hell
tupleList.Select(a => a.Item1 + a.Item2.ToString());
// Doesn't work, as the whole tuple is in the `str`, and num is the index
tupleList.Select((str, num) => ...);
// Doesn't even compile
tupleList.Select(((a, b), num) => ...);

Option 1
var result = tupleList.Select(x=> { var (str,num)=x; return $"{str}{num}";})
Output
one23
two23
Option 2
If you are permitted to change the creation of tupleList, then you can do as following.
IEnumerable<(string str, int num)> tupleList = stringList.Select(str => (str, 23));
var result = tupleList.Select(x=>$"{x.str}{x.num}");
Option 2 eliminates the additional step required in Option 1.

You can have named tuple members:
List<string> stringList = new List<string>() { "one", "two" };
// use named tuple members
IEnumerable<(string literal, int numeral)> tupleList =
stringList.Select(str => (str, 23));
// now you have
tupleList.Select(a => a.literal + a.numeral.ToString());
// or
tupleList.Select(a => $"{a.literal}{a.numeral}");

Related

C# - Sort list of strings by instances of a specified character?

If i want to sort a List of strings (say 15 strings) by the amount of a specified character (lets say "b" in this case), how can I do this? The order of strings which does not contain the specified character does not matter.
Example: "amy", "bob", "lee", "bret", this should be
sorted like this:
"bob"
bret
amy
lee
My guess is that I'll have to create a new class inherited by the IComparer, but beyond that im not sure how to proceed. Any ideas?
Code so far, if it matters:
List<string> str = new List<string>();
for (int i = 0; i < 10; i++)
{
Console.Write("{0}: ", i + 1);
str.Add(Console.ReadLine());
}
Using a class is possible indeed but with linq you can ask yourself if it's really required
List<string> listOfNames = new List<string>();
listOfNames.Add("bob");
listOfNames.Add("bret");
listOfNames.Add("amy");
listOfNames.Add("lee");
// sort the string by the count of character of B or b
var sorted = listOfNames.OrderBy(name => name.Count(c => c == 'b' || c == 'B')).ToList();
The answer using Linq is the one that is most clear I think but you can also use a comparer. And using the List.Sort does avoid creating a new list.
You can create a custom comparer specifically for the case your after - counting chars. Or you can create a generic one that uses a lambda. The second is more fun so I'll show that one.
class AnonymousComparer<T> : IComparer<T>
{
Func<T, T, int> compare;
public AnonymousComparer(Func<T, T, int> compare)
{
this.compare = compare;
}
public int Compare([AllowNull] T x, [AllowNull] T y)
{
return this.compare(x, y);
}
public static implicit operator AnonymousComparer<T>(Func<T,T,int> compare)
=> new AnonymousComparer<T>(compare);
}
Once you have that then you can just supply a lambda that would do the comparison. This uses a simple local function to dry out the character comparison.
static void Main(string[] _)
{
List<string> myStrings = new List<string>(
new string[] { "one", "two", "three", "four", "five", });
bool IsMyLetter(char ch) => ch == 'e' || ch == 'E';
myStrings.ForEach((s) => Console.WriteLine(s));
Console.WriteLine("\n\nafter sort\n");
myStrings.Sort((lhs, rhs) => rhs.Count(x => IsMyLetter(x)) - lhs.Count(x => IsMyLetter(x)));
myStrings.ForEach((s) => Console.WriteLine(s));
}

List<List<string>> unique permutations using LINQ [duplicate]

This question already has answers here:
Generating all Possible Combinations
(12 answers)
Closed 4 years ago.
I need an algorithm that will take any number of List inside a List and generate a unique set of permutations. I prefer to find a LINQ solution.
I actually have a Javascript function that works well and I'm trying to recreate it in C# (see code at bottom)
C# (my attempt) - Visual Studio does not like my second Aggregate(). It says the arguments cannot be inferred from usage
public static void testit()
{
List<List<string>> master = new List<List<string>>();
List<string> voltages = new string[] { "208", "230", "460" }.ToList();
List<string> sysConfigs = new string[] { "10205", "10210", "10215", "10220" }.ToList();
master.Add(voltages);
master.Add(sysConfigs);
var amp = master.Aggregate(
(a, b) => a.Aggregate(
(r, v) => r.Concat(
b.Select(w => new List<string>().Concat(v, w))
), new List<string>()
)
);
}
The output of this new collection should look like this:
/*
OUTPUT (displayed as arrays - but will be lists):
[
["208", "10205"],
["208", "10210"],
["208", "10215"],
["208", "10220"],
["230", "10205"],
["230", "10210"],
["230", "10215"],
["230", "10220"],
["460", "10205"],
["460", "10210"],
["460", "10215"],
["460", "10220"]
];
Here's a Javascript function that works well that I'm trying to mimic in C#:
function getPermutations(arr) {
return arr.reduce(
(a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), [])
);
}
var voltages = ["208", "230", "460"];
var sysConfigs = ["10205", "10210", "10215", "10220"];
var master = [];
master.push(voltages);
master.push(sysConfigs);
var newArr = getPermutations(master);
console.log(newArr);
As noted in other questions, this is the Cartesian product, not a permutation.
Short version: Just go to my blog:
https://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
Long version:
Cartesian product of two lists is built-in in C# in the form of SelectMany or a query comprehension. Let's start with that.
Before we get into that though, please do not ever do this:
List<string> voltages = new string[] { "208", "230", "460" }.ToList()
Either do this:
IEnumerable<string> voltages = new string[] { "208", "230", "460" };
Or do this:
List<string> voltages = new List<string>() { "208", "230", "460" };
But do not make an array and then to-list it! Just make a list from the beginning.
OK, onward. We have two sequences:
IEnumerable<string> voltages = new string[] { "208", "230", "460" };
IEnumerable<string> sysConfigs = new string[] { "10205", "10210", "10215", "10220" };
We want their Cartesian product:
IEnumerable<IEnumerable<string>> master =
from v in voltages
from s in sysConfigs
select new List<string>() { v, s };
And we're done.
If you don't like "comprehension form" then you can use "fluent form":
IEnumerable<IEnumerable<string>> master =
voltages.SelectMany(
v => sysConfigs,
(s, v) => new List<string>() { v, s });
If you want a list of lists:
List<List<string>> master =
voltages.SelectMany(
v => sysConfigs,
(v, s) => new List<string>() { v, s })
.ToList();
Easy peasy.
The meaning of this operation should be clear, but if it is not: the general form is:
var zs =
from x in xs
from y in f(x) // f takes x and returns a collection of ys
g(x, y) // do something with every combination of x and y to make a z
In your case, f(x) is just "always produce the second collection", but it need not be; the collection could depend on x. The result is a sequence of z.
Now, what you need is the Cartesian product of arbitrarily many sequences.
Your intuition that this is an aggregation of concatenations is correct. We can solve it like this:
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}));
}
Notice how this combines the three operations: a select-many query, a concatenation, and an aggregation. Study this carefully to see how it works.
This is the correct version of your code
var amp = master.Aggregate(
new List<List<string>>(){new List<string>()},
(a, b) => a.Aggregate(
new List<List<string>>(new List<List<string>>()),
(r, v) => r.Concat(
b.Select(w => v.Concat(new List<string>{w}).ToList())
).ToList()));

Generate a map of list element indices using Linq

I want to take a List, and generate a Dictionary which maps each element to its index in the List. I can do this like so, for a List<string>:
var myList = new List<string>{ /* populate list */ };
var orderMap = new Dictionary<string, int>();
foreach (var element in myList)
{
orderMap[element] = myList.IndexOf(element);
}
Basically, I want to take a list like:
Apple
Banana
Orange
And return a map showing indices:
Apple -> 0
Banana -> 1
Orange -> 2
How can I do this with Linq? I think something like this should work:
orderMap = myList.Select( x => /* return a key value pair mapping x to myList.IndexOf(x) */ );
But I can't figure out the right syntax for it. Besides, can you refer to the list itself in the delegate used for Select?
While you can refer to the list within the delegate, it's not generally a good idea. You really want to use the overload of Select which provides the index as well as the value:
var dictionary = list.Select((value, index) => new { value, index })
.ToDictionary(p => p.value, p => p.index);
Note that this will throw an exception if you have any duplicate elements.
You could try the ToDictionary extension method:
int index = 0;
orderMap = myList.ToDictionary(x => x, x => index++);
Take a look at this overload of ToDictionary<TKey, TValue>(). It takes to functions to convert the input element into a Key and a Value.
e.g.
var myList = new List<string>{ /* populate list */ };
var orderMap = myList.ToDictionary(x => myList.IndexOf(x), x => x);
However, one problem with this is if the elements of myList aren't unique.

Intersect Two Lists in C#

I have two lists:
List<int> data1 = new List<int> {1,2,3,4,5};
List<string> data2 = new List<string>{"6","3"};
I want do to something like
var newData = data1.intersect(data2, lambda expression);
The lambda expression should return true if data1[index].ToString() == data2[index]
You need to first transform data1, in your case by calling ToString() on each element.
Use this if you want to return strings.
List<int> data1 = new List<int> {1,2,3,4,5};
List<string> data2 = new List<string>{"6","3"};
var newData = data1.Select(i => i.ToString()).Intersect(data2);
Use this if you want to return integers.
List<int> data1 = new List<int> {1,2,3,4,5};
List<string> data2 = new List<string>{"6","3"};
var newData = data1.Intersect(data2.Select(s => int.Parse(s));
Note that this will throw an exception if not all strings are numbers. So you could do the following first to check:
int temp;
if(data2.All(s => int.TryParse(s, out temp)))
{
// All data2 strings are int's
}
If you have objects, not structs (or strings), then you'll have to intersect their keys first, and then select objects by those keys:
var ids = list1.Select(x => x.Id).Intersect(list2.Select(x => x.Id));
var result = list1.Where(x => ids.Contains(x.Id));
From performance point of view if two lists contain number of elements that differ significantly, you can try such approach (using conditional operator ?:):
1.First you need to declare a converter:
Converter<string, int> del = delegate(string s) { return Int32.Parse(s); };
2.Then you use a conditional operator:
var r = data1.Count > data2.Count ?
data2.ConvertAll<int>(del).Intersect(data1) :
data1.Select(v => v.ToString()).Intersect(data2).ToList<string>().ConvertAll<int>(del);
You convert elements of shorter list to match the type of longer list. Imagine an execution speed if your first set contains 1000 elements and second only 10 (or opposite as it doesn't matter) ;-)
As you want to have a result as List, in a last line you convert the result (only result) back to int.
public static List<T> ListCompare<T>(List<T> List1 , List<T> List2 , string key )
{
return List1.Select(t => t.GetType().GetProperty(key).GetValue(t))
.Intersect(List2.Select(t => t.GetType().GetProperty(key).GetValue(t))).ToList();
}

How take each two items from IEnumerable as a pair?

I have IEnumerable<string> which looks like {"First", "1", "Second", "2", ... }.
I need to iterate through the list and create IEnumerable<Tuple<string, string>> where Tuples will look like:
"First", "1"
"Second", "2"
So I need to create pairs from a list I have to get pairs as mentioned above.
A lazy extension method to achieve this is:
public static IEnumerable<Tuple<T, T>> Tupelize<T>(this IEnumerable<T> source)
{
using (var enumerator = source.GetEnumerator())
while (enumerator.MoveNext())
{
var item1 = enumerator.Current;
if (!enumerator.MoveNext())
throw new ArgumentException();
var item2 = enumerator.Current;
yield return new Tuple<T, T>(item1, item2);
}
}
Note that if the number of elements happens to not be even this will throw. Another way would be to use this extensions method to split the source collection into chunks of 2:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> list, int batchSize)
{
var batch = new List<T>(batchSize);
foreach (var item in list)
{
batch.Add(item);
if (batch.Count == batchSize)
{
yield return batch;
batch = new List<T>(batchSize);
}
}
if (batch.Count > 0)
yield return batch;
}
Then you can do:
var tuples = items.Chunk(2)
.Select(x => new Tuple<string, string>(x.First(), x.Skip(1).First()))
.ToArray();
Finally, to use only existing extension methods:
var tuples = items.Where((x, i) => i % 2 == 0)
.Zip(items.Where((x, i) => i % 2 == 1),
(a, b) => new Tuple<string, string>(a, b))
.ToArray();
morelinq contains a Batch extension method which can do what you want:
var str = new string[] { "First", "1", "Second", "2", "Third", "3" };
var tuples = str.Batch(2, r => new Tuple<string, string>(r.FirstOrDefault(), r.LastOrDefault()));
You could do something like:
var pairs = source.Select((value, index) => new {Index = index, Value = value})
.GroupBy(x => x.Index / 2)
.Select(g => new Tuple<string, string>(g.ElementAt(0).Value,
g.ElementAt(1).Value));
This will get you an IEnumerable<Tuple<string, string>>. It works by grouping the elements by their odd/even positions and then expanding each group into a Tuple. The benefit of this approach over the Zip approach suggested by BrokenGlass is that it only enumerates the original enumerable once.
It is however hard for someone to understand at first glance, so I would either do it another way (ie. not using linq), or document its intention next to where it is used.
You can make this work using the LINQ .Zip() extension method:
IEnumerable<string> source = new List<string> { "First", "1", "Second", "2" };
var tupleList = source.Zip(source.Skip(1),
(a, b) => new Tuple<string, string>(a, b))
.Where((x, i) => i % 2 == 0)
.ToList();
Basically the approach is zipping up the source Enumerable with itself, skipping the first element so the second enumeration is one off - that will give you the pairs ("First, "1"), ("1", "Second"), ("Second", "2").
Then we are filtering the odd tuples since we don't want those and end up with the right tuple pairs ("First, "1"), ("Second", "2") and so on.
Edit:
I actually agree with the sentiment of the comments - this is what I would consider "clever" code - looks smart, but has obvious (and not so obvious) downsides:
Performance: the Enumerable has to
be traversed twice - for the same
reason it cannot be used on
Enumerables that consume their
source, i.e. data from network
streams.
Maintenance: It's not obvious what
the code does - if someone else is
tasked to maintain the code there
might be trouble ahead, especially
given point 1.
Having said that, I'd probably use a good old foreach loop myself given the choice, or with a list as source collection a for loop so I can use the index directly.
IEnumerable<T> items = ...;
using (var enumerator = items.GetEnumerator())
{
while (enumerator.MoveNext())
{
T first = enumerator.Current;
bool hasSecond = enumerator.MoveNext();
Trace.Assert(hasSecond, "Collection must have even number of elements.");
T second = enumerator.Current;
var tuple = new Tuple<T, T>(first, second);
//Now you have the tuple
}
}
Starting from NET 6.0, you can use
Enumerable.Chunk(IEnumerable, Int32)
var tuples = new[] {"First", "1", "Second", "2", "Incomplete" }
.Chunk(2)
.Where(chunk => chunk.Length == 2)
.Select(chunk => (chunk[0], chunk[1]));
If you are using .NET 4.0, then you can use tuple object (see http://mutelight.org/articles/finally-tuples-in-c-sharp.html). Together with LINQ it should give you what you need. If not, then you probably need to define your own tuples to do that or encode those strings like for example "First:1", "Second:2" and then decode it (also with LINQ).

Categories