List of list iteration(all possible one direction combination) - c#

I have a List<List<string>> representing a grid of rows and columns (the count of each is dynamic).
I need to iterate over the items and display all possible combinations in one direction.
If I have the following items, for example:
var items = new List<List<string>>
{
new List<string> {"A", "B", "C", "D"},
new List<string> {"E", "F", "G", "H"},
new List<string> {"I", "J", "K", "L"},
};
The output should be :
---> *ABCD-ABCH-ABCL-ABGD-ABGH-ABGL-ABKD-ABKH-ABKL-........... IJKL*.
How can I iterate over the list to achieve this result?

What you want is the Cartesian product of the transpose. So break it down. First let's take the transpose:
public static List<List<T>> Transpose(
this List<List<T>> sequences)
{
// TODO: throw if sequences is null
// TODO: throw if sequences contains any null
// TODO: throw if the sequences are not all the same length
return Enumerable.Range(0, sequences[0].Count)
.Select(i =>
Enumerable.Range(0, sequences.Count)
.Select(j => sequences[j][i])
.ToList())
.ToList();
}
We can take the Cartesian Product from my answer here: https://stackoverflow.com/a/3098381/88656
And now the answer to your question is straightforward.
IEnumerable<string> combinations = items
.Transpose()
.CartesianProduct()
.Select(sequence => string.Join("", sequence));
Remember, the way to solve these problems is to break down the problem into a workflow of more basic operations on sequences, and then compose extension methods together into the workflow.

Another approach to this problem, if you need a combination of N unique elements, is to flatten the matrix:
var elementArray = items.SelectMany(x => x).ToList();
Which turns {{'A', 'B', 'C'}, {'D', 'E', 'F'}} into {'A', 'B', 'C', 'D', 'E', 'F'}
Then use the following LINQ extension taken from another question (place it anywhere in your project):
public static class Ex
{
public static IEnumerable<IEnumerable<T>> DifferentCombinations<T> (this IEnumerable<T> elements, int k)
{
return k == 0 ? new[] { new T[0] } :
elements.SelectMany((e, i) =>
elements.Skip(i + 1).DifferentCombinations(k - 1).Select(c => (new[] { e }).Concat(c)));
}
}
Used as:
var combinations = elementArray.DifferentCombinations(4)
.Select(
l => string.Join("", l.ToArray())
).ToList();
In this case, it will combine up to a length of 4 (DifferentCombinations(4)).

Related

Count duplicates and combine them in a list

I have a list like this:
var list = new List<string>() { "a", "b", "c", "a" };
As you can see I have one duplicate inside this list.
The desired output should look like this:
a (2)
b (1)
c (1)
The code which I already have looks like this:
list.GroupBy(x => x).Select(x => x.Key + "(" + x.Count() + ")").ToList().ForEach(x => list2.Add(x));
But as you can see I need a second list to get the desired output. My question now is how can I get the desired output with using only one list.
Why don't we just add the items into existing list2?
var list2 = new List<string>();
...
list2.AddRange(list
.GroupBy(item => item)
.Select(group => $"{group.Key} ({group.Count()})"));
Or create list2 from scratch:
var list2 = list
.GroupBy(item => item)
.Select(group => $"{group.Key} ({group.Count()})")
.ToList();
This does what you need:
var list = new List<string>() { "a", "b", "c", "a" };
foreach (var group in list.GroupBy(b => b).OrderBy(g => g.Key))
{
Console.WriteLine($"{group.Key} ({group.Count()})");
}
Consequentially, if one wishes to dedup this list, one could do:
list = list.GroupBy(b => b).Select(g => g.Key).ToList();
The result would be a deduped list with "a", "b" and "c" in it.
You can do this in one statement (I've split it over multiple lines for readability):
var list = new List<string> { "a", "b", "c", "a" };
Console.WriteLine(
string.Join("\n",
list.GroupBy(_=>_).Select(g => $"{g.Key} ({g.Count()})")));
Output:
a (2)
b (1)
c (1)
You can use the Distinct method to remove duplicates.
This returns a IEnumerable and in order to use the ForEach method you have to apply the ToList method first.
The Foreach iterates over the items {"a", "b", "c"}
and prints the current item and the count of this item in the list with dulicates.
list.Distinct().ToList().ForEach(v1 => Console.WriteLine($"{v1} ({list.Count(v2 => v1 == v2)})"))

Dictionary of List to List of KeyValuePair using LINQ

I'm trying to find the most "elegant" way to deal with the transformation between two collections using LINQ. The source container type is Dictionary<int, List<string>> and I need to convert it to List<KeyValuePair<string, string>>. Basically I need to duplicate the key in the dictionary for every element in its corresponding list into a flattened list. Below shows my 2 attempts to solve the issue.
Dictionary<int, List<string>> source = new() {
{100, new() {"a", "b", "c"}}
};
List<KeyValuePair<string, string>> target = new();
// Solution 1
foreach (var (score, terms) in source)
{
foreach (var term in terms)
{
target.Add(new KeyValuePair<string, string>(score.ToString(), term));
}
}
// Solution 2
target = source.SelectMany(kvp =>
{
var(score, terms) = kvp;
return terms.Select(t => new KeyValuePair<string, string>(score.ToString(), t));
}).ToList();
Runnable sample here on .NET Fiddle.
I'm using .NET Core 5 and C# 9 in a console application.
Both of these work, I think, but I want to see if I can make this cleaner. It's pretty difficult to find out how to use LINQ to solve complex transformations like this. One thing I tried to do was "deconstruct" the KeyValuePair in SelectMany(), like:
source.SelectMany((score, terms) => ...)
But this didn't work. I'm not sure if there's a way to make that kind of deconstruction possible. Little things like this I think could go a long way to making this cleaner.
Maybe
var results = source
.SelectMany(x => x.Value
.Select(y => new KeyValuePair<string, string>(x.Key.ToString(), y)))
.ToList();
Or if you are happy with Value Tuples (which have a few benefits)
var results = source
.SelectMany(x => x.Value
.Select(y => (x.Key.ToString(), y)))
.ToList();
If you want to pattern match it in a single expression ("that kind of deconstruction"), you can abuse (?) the switch expression:
target = source.SelectMany(kvp => kvp switch { var (score, terms) =>
terms.Select(t => new KeyValuePair<string, string>(score.ToString(), t))
}).ToList();
You can use the overload of SelectMany that has a parameter resultSelector.
var results = source.SelectMany(sourceItem => sourceItem.Value,
// parameter resultSelector:
(sourceItem, listItem) => ...
In baby steps:
Source is a sequence of KeyValuePair<int, List<string>>. So every sourceItem is one KeyValuePair<int, List<string>>. sourceItem.Value is the List<string> that belongs to the int sourceItem.Key. In the parameter resultSelector you get a combination of the complete sourceItem, with one item from the list:
(sourceItem, listItem) =>
So if your source is:
{ 1, {"A", "B"} }
{ 2, {"C", "D" "E"} }
{ 3, empty list }
You get:
( { 1, {"A", "B"} }, "A")
( { 1, {"A", "B"} }, "B")
( { 2, {"C", "D" "E"} }, "C")
( { 2, {"C", "D" "E"} }, "D")
( { 2, {"C", "D" "E"} }, "E")
No items for 3, the list is empty
Now, from every sourceItem you want sourceItem.Key.ToString(), from every listItem you want the complete listItem. So your SelectMany will be:
var results = source.SelectMany(sourceItem => sourceItem.Value,
(sourceItem, listItem) => new KeyValuePair<string, string>
(sourceItem.Key.ToString, listItem))
.ToList();

Return the indexes of the elements after finding the difference in two lists

I would like to find the difference between two lists of strings, and return the index (in the original list) of the remaining elements in list1.
For example, if list1 contains "Orange", "Blue", "Yellow", and list2 contains "Blue", I can easily get the difference using the Except method. However, I want to return the indexes - 1 and 3 in this case.
Pardon the laziness of the list creation:
var x = new[] { "Orange", "Blue", "Yellow" }.ToList();
var y = new[] { "Blue" }.ToList();
var indices = x.Except(y).Select(z => x.IndexOf(z));
This is not at all very efficient, but it solves the issue. If this is actually for something useful and not just a mental exercise, I would re-evaluate why you are doing this.
list1.Select((e,idx) => new { e, idx })
.Where(x => !list2.Contains(x.e)).Select(x => x.idx);
You can use ExceptBy to perform an except based on a projection of the given items, allowing you to first project each collection into an item containing its index, and then perform the except based on the item itself:
var query = first.Select((index, item) => new { index, item })
.ExceptBy(second.Select((index, item) => new { index, item }),
pair => pair.item);
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(
this IEnumerable<TSource> source,
IEnumerable<TSource> other,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer = null)
{
comparer = comparer ?? EqualityComparer<TKey>.Default;
var keys = new HashSet<TKey>(other.Select(keySelector), comparer);
foreach (var item in source)
if (keys.Add(keySelector(item)))
yield return item;
}
Additional way to solve the problem:
var x = new[] { "Orange", "Blue", "Yellow" };
var y = new HashSet<string>(new[] { "Blue" });
var indices = Enumerable.Range(0, x.Length)
.Where(i => !y.Contains(x[i]))
.ToArray();

get a List of Max values across a list of lists

I have a List<List<double>> and I need to find a List MyList where MyList[0], for instance, is the Max of all the first elements of the List.
Example, just to be clear:
First list contains (3,5,1), second contains (5,1,8), third contains (3,3,3), fourt contains (2,0,4).
I need to find a list with (5, 5, 8).
I do NOT need the list (5,8,3,4).
Of course i know how to do it with nested for cycles.
I'd like to know if there's a linq way and believe me i don't know where to start from.
var source = new List<List<int>> {
new List<int> { 3, 5, 1 },
new List<int> { 5, 1, 8 },
new List<int> { 3, 3, 3 },
new List<int> { 2, 0, 4 }
};
var maxes = source.SelectMany(x => x.Select((v, i) => new { v, i }))
.GroupBy(x => x.i, x => x.v)
.OrderBy(g => g.Key)
.Select(g => g.Max())
.ToList();
Returns { 5, 5, 8}, which is what you need. And will work when source lists have different number of elements too.
Bonus
If you need version for Min too, and want to prevent code duplication, you can go a little bit functional:
private static IEnumerable<TSource> GetByIndex<TSource>(IEnumerable<IEnumerable<TSource>> source, Func<IEnumerable<TSource>, TSource> selector)
{
return source.SelectMany(x => x.Select((v, i) => new { v, i }))
.GroupBy(x => x.i, x => x.v)
.OrderBy(g => g.Key)
.Select(g => selector(g));
}
public static IEnumerable<TSource> GetMaxByIndex<TSource>(IEnumerable<IEnumerable<TSource>> source)
{
return GetByIndex(source, Enumerable.Max);
}
public static IEnumerable<TSource> GetMinByIndex<TSource>(IEnumerable<IEnumerable<TSource>> source)
{
return GetByIndex(source, Enumerable.Min);
}
Try this one:
// Here I declare your initial list.
List<List<double>> list = new List<List<double>>()
{
new List<double>(){3,5,1},
new List<double>(){5,1,8},
new List<double>(){3,3,3},
new List<double>(){2,0,4},
};
// That would be the list, which will hold the maxs.
List<double> result = new List<double>();
// Find the maximum for the i-st element of all the lists in the list and add it
// to the result.
for (int i = 0; i < list[0].Count-1; i++)
{
result.Add(list.Select(x => x[i]).Max());
}
Note: this solution works only, when all the lists that are contained in the list have the same number of elements.
Even if this topic is answered long time ago, I'd like to put here another solution I've made up with Linq, shorter than this other solution :
List<List<int>> mylist; //initial list of list
List<List<int>> mins_list = mylist.Aggregate(
(x, cur) => cur.Zip(x, (a, b) => (a.Value > b.Value) ? a : b).ToList()
).ToList();
This very simple code is just aggregating every sub-list into a list of minima. Note that the internal ToList is mandatory as Zip is deferred.
You can encapsulate the code in an extension method, and do the same trick as MarcinJuraszek to generate other similar computations (min, max, mean, std, ...).
If always you know how many elements present in your lists,you can use this approach:
var result = new[]
{
list.Select(a => a[0]).Max(),
list.Select(a => a[1]).Max(),
list.Select(a => a[2]).Max()
};

Compare 2 List<string> with a LIKE clause

I have 2 lists, one a list of file names, the second a list of file name stubs, i would like to select everything from the first list where the file name is like the file name stub.
List<string> files = new List<string>() {"a.txt", "b.txt", "c.txt"};
List<string> fileStub = new List<string>() {"a", "b", "c"};
The query would return all records from the first list.
Thanks in advance.
var results = files.Where(x => fileStub.Any(y => x.Contains(y))).ToList();
if the order is important (of course, with this sample, the size of the two lists is important, if you don't want IndexOutOfRange exceptions)
var res = files.Where((file, index) => file.Contains(fileStub[index]));
if you don't mind order (than list sizes are not important)
var res = files.Where(file => fileStub.Any(fs => file.Contains(fs)));
var result = files.Where(item => fileStub.Any(stub => item.Contains(stub))).ToList();
Use the Any and Contains methods.
List<string> files = new List<string>() {"a.txt", "b.txt", "c.txt"};
List<string> fileStub = new List<string>() {"a", "c"};
var result = files.Where(x => fileStub.Any(y => x.Contains(y))).ToList();

Categories

Resources