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()));
Related
I'm trying to make a program like wordlist generator.
I want to add the items on the 2nd list next to each item on the 1st list.
`
List<string> list1 = new List<string>() {"tomato", "ball", "icecream", "blue"};
List<string> list2 = new List<string>() { "123", "yellow", "green" };
`
//Values to be added to Listing 3: tomato123, tomatoyellow, tomatogreen, ball123, ballyellow, ballgreen bla bla bla
To solve your problem, we will iterate over one of the lists, and for every item in it, we will create all the possible combinations with words from the other list. With LINQ, it would look something like this:
var list3 = list1.Select(w1 => list2.Select(w2 => w1 + w2)).ToList();
The problem is that now list3 is of type List<IEnumerable<string>> because we have a list of combinations for every word in list1. To flatten the result, all we need is to change the Select projection to a SelectMany flattened projection:
var list3 = list1.SelectMany(w1 => list2.Select(w2 => w1 + w2)).ToList();
Based on your requirement it might be useful. Please have a look.
static void AddLists()
{
List<string> list1 = new List<string>() { "tomato", "ball", "icecream", "blue" };
List<string> list2 = new List<string>() { "123", "yellow", "green" };
var resultList = from l1 in list1
from l2 in list2
select string.Concat(l1, l2);
}
My advice would be to create an extension method, instead of a LINQ statement that is difficult to understand: readers will immediately see what it does, it is easier to test and easier to change.
See extension methods demystified
public static IEnumerable<string> ConcatCombinations(
this.IEnumerable<string> sequenceA,
IEnumerable<string> sequenceB)
{
// TODO: invent a proper name
foreach (string textA in sequenceA)
foreach (string textB in sequenceB)
yield return textA + textB;
}
This code is way simpler than any solution using LINQ methods. Anyone will immediately see what it does.
Usage:
List<string> list1 = ...
string[] array1 = ...
List<string> concatenated = list1.ConcatCombinations(array1).ToList();
If you want to make a more generic method, consider this:
public static IEnumerable<TResult> MakeCombinations<TA, TB, TResult>(
this IEnumerable<TA> itemsA,
IEnumerable<TB> itemsB,
Func<TA, TB, TResult> resultSelector)
{
foreach (TA itemA in itemsA)
foreach (TB itemB in itemsB)
{
TResult result = resultSelector(itemA, itemB);
yield return result;
}
}
Usage:
List<string> list1 = ...
List<string> list2 = ...
List<string> concatenated = list1.ConcatCombinations(list2,
// parameter ResultSelector: concat every a and b:
(a, b) => a+b)
.ToList();
Or just change your ConcatCombinations:
public static IEnumerable<string> ConcatCombinations(
this.IEnumerable<string> sequenceA,
IEnumerable<string> sequenceB)
{
return sequenceA.MakeCombinations(sequenceB,
(a, b) => a + b);
}
Another completely different example, that shows you the reusability of the code:
var maleBallroomDancers = ...
var femaleBallroomDancers = ...
var danceCombinations = maleBallroomDancers.MakeCombinations(femaleBallroomDancers,
(male, female) => new
{
Male = male,
Female = female,
})
.ToList();
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}");
I have a question I have a list let us say:
List<string> list = new List<string> { A, B, C, D, E, F, G}
And I need to combine specific two rows of that list to new new list for example:
List<string> rebuildList = new List<string> { A, B, CD, E, FG}
I already made a code that would work:
var joinDictionary = new Dictionary<int, int> { { 3, 4 }, { 7, 8 } };
foreach (var value in list)
{
var index = list.IndexOf(value);
if (joinDictionary.ContainsKey(index))
{
rebuildList.Add(string.Format("{0} {1}", value, list.ElementAt(index + 1)));
}
if (!joinDictionary.ContainsKey(index) && !joinDictionary.ContainsValue(index))
{
rebuildList.Add(value);
}
}
But is there more elegant way to do this? Sole linq lambda query maybe?
If you change your dictionary to be zero based, you can use something like:
list.
Select((str, ind) => joinDictionary.ContainsKey(ind) ? str + list[joinDictionary[ind]] : str).
Where((str, ind) => !joinDictionary.ContainsValue(ind)).
ToList();
It's a one-liner, but I'm not sure if that's more readable than your solution.
If you don't want to switch to zero based dictionary, you'll have to play with the indices in the LINQ expression.
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();
}
I have an interesting problem, and I can't seem to figure out the lambda expression to make this work.
I have the following code:
List<string[]> list = GetSomeData(); // Returns large number of string[]'s
List<string[]> list2 = GetSomeData2(); // similar data, but smaller subset
List<string[]> newList = list.FindAll(predicate(string[] line){
return (???);
});
I want to return only those records in list in which element 0 of each string[] is equal to one of the element 0's in list2.
list contains data like this:
"000", "Data", "more data", "etc..."
list2 contains data like this:
"000", "different data", "even more different data"
Fundamentally, i could write this code like this:
List<string[]> newList = new List<string[]>();
foreach(var e in list)
{
foreach(var e2 in list2)
{
if (e[0] == e2[0])
newList.Add(e);
}
}
return newList;
But, i'm trying to use generics and lambda's more, so i'm looking for a nice clean solution. This one is frustrating me though.. maybe a Find inside of a Find?
EDIT:
Marc's answer below lead me to experiment with a varation that looks like this:
var z = list.Where(x => list2.Select(y => y[0]).Contains(x[0])).ToList();
I'm not sure how efficent this is, but it works and is sufficiently succinct. Anyone else have any suggestions?
You could join? I'd use two steps myself, though:
var keys = new HashSet<string>(list2.Select(x => x[0]));
var data = list.Where(x => keys.Contains(x[0]));
If you only have .NET 2.0, then either install LINQBridge and use the above (or similar with a Dictionary<> if LINQBridge doesn't include HashSet<>), or perhaps use nested Find:
var data = list.FindAll(arr => list2.Find(arr2 => arr2[0] == arr[0]) != null);
note though that the Find approach is O(n*m), where-as the HashSet<> approach is O(n+m)...
You could use the Intersect extension method in System.Linq, but you would need to provide an IEqualityComparer to do the work.
static void Main(string[] args)
{
List<string[]> data1 = new List<string[]>();
List<string[]> data2 = new List<string[]>();
var result = data1.Intersect(data2, new Comparer());
}
class Comparer : IEqualityComparer<string[]>
{
#region IEqualityComparer<string[]> Members
bool IEqualityComparer<string[]>.Equals(string[] x, string[] y)
{
return x[0] == y[0];
}
int IEqualityComparer<string[]>.GetHashCode(string[] obj)
{
return obj.GetHashCode();
}
#endregion
}
Intersect may work for you.
Intersect finds all the items that are in both lists.
Ok re-read the question. Intersect doesn't take the order into account.
I have written a slightly more complex linq expression that will return a list of items that are in the same position (index) with the same value.
List<String> list1 = new List<String>() {"000","33", "22", "11", "111"};
List<String> list2 = new List<String>() {"000", "22", "33", "11"};
List<String> subList = list1.Select ((value, index) => new { Value = value, Index = index})
.Where(w => list2.Skip(w.Index).FirstOrDefault() == w.Value )
.Select (s => s.Value).ToList();
Result: {"000", "11"}
Explanation of the query:
Select a set of values and position of that value.
Filter that set where the item in the same position in the second list has the same value.
Select just the value (not the index as well).
Note I used:
list2.Skip(w.Index).FirstOrDefault()
//instead of
list2[w.Index]
So that it will handle lists of different lengths.
If you know the lists will be the same length or list1 will always be shorter then list2[w.Index] would probably a bit faster.