Cross join of unknown number of string arrays using linq - c#

Is it possible to do a cross join using linq where the number of joins is not known in advance?
I have this:
var arrays = new List<string[]>();
If I know I have three lists I can do:
var oQuery = from x in arrays[0]
from y in arrays[1]
from z in arrays[2]
select new {x, y, z};
Is it be possible to join n string arrays using linq?

Try this solution, where each item from result will not be look like {x:"A", y:"B", ... }, because number of properties is not predicted, so instead, it will be like ["A", "B", ... ]:
public static List<List<string>> CrossJoin(List<string[]> arrays)
{
var data = arrays.Select(x => x.ToList()).ToList();
List<List<string>> result = data[0].Select(x => new List<string> { x }).ToList();
for (var i = 1; i < data.Count; i++)
result = (from a in result
from b in data[i]
select new { a, b })
.Select(x => x.a.Concat(new List<string> { x.b }).ToList())
.ToList();
return result;
}
Usage:
var arr1 = new[] { "A", "B", "C" };
var arr2 = new[] { "D", "E" };
var arr3 = new[] { "F", "G" };
var result = CrossJoin(new List<string[]> { arr1, arr2, arr3 });
for(var i = 0; i < result.Count; i++)
Console.WriteLine(string.Format("{0}: {1}", i + 1, string.Join(",", result[i])));

Please try below solution to get Cartesian product for given "N" number of string arrays.
static void Main(string[] args)
{
var arrays = new List<string[]>();
var arr1 = new string[] { "A", "B", "C" };
var arr2 = new string[] { "D", "E", "F" };
var arr3 = new string[] { "G", "H", "I" };
arrays.Add(arr1);
arrays.Add(arr2);
arrays.Add(arr3);
IEnumerable<Tuple<string,string>> oQuery1 = null;
int count = arrays.Count;
var k = arrays[0].AsEnumerable();
for (int i =1; i< count; i++)
{
var l1 = arrays[i];
oQuery1 = k.SelectMany((x) => l1, (x, y) => Tuple.Create( x, y ));
k = oQuery1.Select(x=>x.ToString());
}
}

[Fact]
public void Test1()
{
var listOfStringList = new List<List<string>>();
var list1 = new List<string> { "A", "B", "C" };
var list2 = new List<string> { "AA", "BB", "CC" };
var list3 = new List<string> { "AAA", "BBB", "CCC" };
listOfStringList.Add(list1);
listOfStringList.Add(list2);
listOfStringList.Add(list3);
var resultData = new List<List<string>>();
listOfStringList.ForEach((stringList) =>
{
if (resultData.Count == 0)
{
resultData = stringList.Select(u => new List<string> { u }).ToList();
return;
}
resultData = resultData.SelectMany(u => stringList
.Select(v =>
{
var list = new List<string>();
u.ForEach(sv =>
{
list.Add(sv);
});
list.Add(v);
return list;
}).ToList()).ToList();
});
// Ignore variable resultantData it is just for highlight
var resultantData = resultData;
}

Related

Using linq to group/aggregate by string[]

Suppose I have a list with a Tuple.First as a string[] (i.e. my key):
var lst = new List<Tuple<string[], int>> {
new Tuple<string[], int>(new string[] { "A", "B" }, 5),
new Tuple<string[], int>(new string[] { "A", "B" }, 10),
new Tuple<string[], int>(new string[] { "C", "B" }, 10),
}
I would like to aggregate (e.g. Sum) by Tuple.First (i.e. string[]) so expecting an output as follows:
var output = new List<Tuple<string[], int>> {
new Tuple<string[], int>(new string[] { "A", "B" }, 15),
new Tuple<string[], int>(new string[] { "C", "B" }, 10),
}
I did it this way but there must be a cleaner way to do it instead of forcing a pipe concatenation:
var output = lst
.GroupBy(x => string.Join("|", x.First))
.Select(x => new Tuple<string[], int>(
x.Key.Split('|'),
Sum(x => x.Second)));
You need to implement IEqualityComparer for string[]. My example using touples (You don't say what Pair is):
static void Main(string[] args)
{
var lst = new List<(string[] arr, int num)> {
(new string[] { "A", "B" }, 5),
(new string[] { "A", "B" }, 10),
(new string[] { "C", "B" }, 10),
};
var grouped = lst.GroupBy(pair => pair.arr, new EnumerableComparer());
var sums = grouped.Select(g => (arr: g.Key, num: g.Sum(p => p.num)));
}
class EnumerableComparer<TRecord> : IEqualityComparer<IEnumerable<TRecord>>
{
public bool Equals(IEnumerable<TRecord> x, IEnumerable<TRecord> y)
{
return ReferenceEquals(x, y) || x != null && x.SequenceEqual(y);
}
public int GetHashCode(IEnumerable<TRecord> lst)
{
unchecked
{
int hash = 387;
foreach (var elem in lst)
{
hash = hash * 31 + elem.GetHashCode();
}
return hash;
}
}
}

How to get multiple combination of items from multiple list in c#

I have three list
List<string> firstList = new List<string> { "A", "B" };
List<string> secondList = new List<string> { "C", "D", "E" };
List<string> thirdList = new List<string> { "F", "G" };
And i want multiple combination among all of above three list like
ACF
ACG
ADF
ADG
...
I tried SelectMany and Zip but did't work.
Note: An help would be appreciate if I get my desired output by using lambda expression.
You can do this by using Join like
public class Program
{
static void Main(string[] args)
{
List<string> firstList = new List<string> { "A", "B" };
List<string> secondList = new List<string> { "C", "D", "E" };
List<string> thirdList = new List<string> { "F", "G" };
List<string> result = firstList
.Join(secondList, x => true, y => true, (m, n) => m + n)
.Join(thirdList, a => true, b => true, (a, b) => a + b)
.ToList();
result.ForEach(x => Console.WriteLine(x));
Console.ReadLine();
}
}
Output:
you need 3 loops:
List<string> combinations = new List<string>();
for(int i=0; i < firstList.Length; i++)
for(int j=0;j < secondList.Length; j++)
for(int k=0;k < thirdList.Length; k++)
combinations.Add(firstList[i]+secondList[j]+thirdList[k]);

How to compare two string array and find the index number of the common string array and apply it to the third string array?

I have three string arrays
string[] stringArray= {"AAA", "BBB", "CCC","DDD", "EEE","FFF","GGG","HHH" };
string[] stringArray1 = { "A", "B", "C","D", "E","F","G","H" };
string[] stringArray2 = { "BBB", "DDD","FFF","HHH" };
How could I compare stringArray2 with stringArray and find the index number stringArray that matches.
After finding the index ,I need to apply it to stringArray1
and display the results.
I have tried the following code ,but failed to get the match string
int j=0;
string line = null;
List<string> finalstring = new List<string>();
for (int i = 0; i < stringArray.Count; i++)
{
while(stringArray[i] == stringArray2[j])
{
line = stringArray2[i];
finalstring.Add(line);
j++;
}
}
I think you have to try like this:
var result = stringArray1.Where(c => // iterating stringArray1
stringArray2.Where(x => stringArray.Contains(x)) // filtering stringArray2 elements
.Any(y=>y.Contains(c))).ToList(); // collect the final result
A Working example for your reference
int j=0;
string line = null;
List<string> finalstring = new List<string>();
for (int i = 0; i < stringArray.Length; i++)
//for (int i = 0; i < stringArray.Count; i++) // <-- Count is not proper way to get total elements in an array
{
j = 0; // <-- you failed to reinitialize 'j'
//while(stringArray[i] == stringArray2[j]) // <-- while is not proper way to compare
if(stringArray[i] == stringArray2[j])
{
//line = stringArray2[i];
//if only first charracter is needed
finalstring.Add(new string(stringArray2[i][0], 1));
//if complete string is needed
//finalstring.Add(stringArray2[i]);
j++;
}
}
Above is not an optimized way to do the comparisons. You can try using HashSet class
string[] stringArray= {"AAA", "BBB", "CCC","DDD", "EEE","FFF","GGG","HHH" };
string[] stringArray1 = { "A", "B", "C","D", "E","F","G","H" };
string[] stringArray2 = { "BBB", "DDD","FFF","HHH" };
//convert string array to hashset
var hashSet = new HashSet<string>(stringArray);
int j=0;
string line = null;
List<string> finalstring = new List<string>();
for (int i = 0; i < stringArray2.Length; i++)
{
if(hashSet.Contains(stringArray2[i]))
{
//if only first charracter is needed
finalstring.Add(new string(stringArray2[i][0], 1));
}
}
List<string> finalList = new List<string>();
stringArray.Select((value,index) => new { value, index })
.Where(num => stringArray2.Contains(num.value)).ToList()
.ForEach(num => finalList.Add(stringArray1[num.index]));
you could do it with selecting the index via Linq (where >= 0 filters out all non matches):
string[] stringArray = { "AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "GGG", "HHH" };
string[] stringArray1 = { "A", "B", "C", "D", "E", "F", "G", "H" };
string[] stringArray2 = { "BBB", "DDD", "FFF", "HHH" };
var matches = stringArray2
.Select(s => Array.FindIndex(stringArray, s2 => s2 == s))
.Where(i => i >= 0).Select(i => stringArray1[i])
.ToList();
Here a dotnetfiddle
You are trying to compare the elements on same index but you have to search the element of stringArray2 in stringArray. You can use Array.IndexOf to find in Array the required element.
string[] stringArray= {"AAA", "BBB", "CCC","DDD", "EEE","FFF","GGG","HHH" };
string[] stringArray1 = { "A", "B", "C","D", "E","F","G","H" };
string[] stringArray2 = { "BBB", "DDD","FFF","HHH" };
int j=0;
string line = null;
List<string> finalstring = new List<string>();
for (int i = 0; i < stringArray2.Count(); i++)
{
if(Array.IndexOf(stringArray, stringArray2[i]) != -1)
{
int idx = Array.IndexOf(stringArray1, stringArray2[i][0].ToString());
if(idx!=-1)
finalstring.Add(stringArray1[idx]);
}
}
Edit using Linq
var result2 = stringArray1.Where(c=>
stringArray2.Intersect(stringArray)
.Any(i=>i.Contains(c)));

Sort by numbers first

I have a list as under
var x = new List<string>() { "a", "1", "b", "2" };
var sortedResultByNumeric = x
.Select(s => new { OriginalString = s,
ExtractNumbers = String.Join("", s.Where(Char.IsDigit)) })
.OrderBy(o => o.ExtractNumbers).ToList();
The output is
a
b
1
2
But expected is
1
2
a
b
How can I do so?
The output that you see and expected is based on OriginalString because ExtractNumbers is "","","1","2" then you should OrderBy OriginalString:
var x = new List<string>() { "a", "1", "b", "2" };
var sortedResultByNumeric = x
.Select(s => new { OriginalString = s, ExtractNumbers = String.Join("", s.Where(Char.IsDigit)) })
.OrderBy(o => o.OriginalString).ToList();
The output:
1
2
a
b
You can try this:
var myList = new List<string>() { "a", "1", "b", "2", "123", "cd", "12346", "657" };
var nonNumericItems = myList.Where(item => !item.Any(i => Char.IsDigit(i)))
.OrderBy(item => item);
var numericItems = myList
.Select(item => String.Join("", item.Where(Char.IsDigit)))
.Where(item => !String.IsNullOrEmpty(item))
.Select(item => Convert.ToInt32(item))
.OrderBy(item => item)
.Select(item => item.ToString());
var result = numericItems
.Union(nonNumericItems);
result.ToList()
.ForEach(Console.WriteLine);
The output is:
1, 2, 123, 657, 12346, a, b, cd
P.S: You didn't tell us any additional explanation about your logic here String.Join("", item.Where(Char.IsDigit). So, I didn't ask any additional question about that.
If your list contains only 1-char strings you can order them by their char numerical values:
var x = new List<string>() { "a", "1", "b", "2" };
var sorted = x.OrderBy(c => Convert.ToChar(c))
.ToList();
foreach (var c in sorted)
Console.WriteLine(c);
// 1
// 2
// a
// b
This solution may seem complicated but it will work with any Net built in sort method.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication63
{
class Program
{
const string FILENAME = #"c:\temp\test.csv";
static void Main(string[] args)
{
List<AlphaNumberic> aN = new List<AlphaNumberic>() { "a", "1", "b", "2" };
aN.Sort((x, y) => x.CompareTo(y));
}
}
public class AlphaNumberic
{
int results = 0;
public string alphaNumeric { get; set; }
public AlphaNumberic(string value)
{
this.alphaNumeric = value;
}
public static implicit operator string(AlphaNumberic d)
{
return d.alphaNumeric;
}
public static implicit operator AlphaNumberic(string d)
{
return new AlphaNumberic(d);
}
public int CompareTo(AlphaNumberic compareItem)
{
int thisNum;
int num;
if (int.TryParse(compareItem.alphaNumeric, out num))
{
if (int.TryParse(this.alphaNumeric, out thisNum))
{
//alphaNumeric and compareItem both integers;
results = thisNum.CompareTo(num);
}
else
{
//alphaNumeric not an integer and compareItem integers;
results = 1;
}
}
else
{
if (int.TryParse(this.alphaNumeric, out thisNum))
{
//alphaNumeric is an integer and compareItem not an integers;
results = -1;
}
else
{
//alphaNumeric not an integer and compareItem not an integers;
results = alphaNumeric.CompareTo(compareItem);
}
}
return results;
}
}
}

Generate all Combinations from Multiple (n) Lists

EDIT: I am completely redoing my questions as I have figured out the simplest way of asking it. Thanks to the commenters so far that got me thinking about the root problem.
public List<string> GetAllPossibleCombos(List<List<string>> strings)
{
List<string> PossibleCombos = new List<string>();
//????
{
string combo = string.Empty;
// ????
{
combo += ????
}
PossibleCombos.Add(combo);
}
return PossibleCombos;
}
I need to figure out how to recursively go through each List<string> and combine 1 string from each list into a combo string. Don't worry too much about formatting the string as the "live" code uses a custom object instead. Also, feel free to assume that every list will contain at least 1 string and that there are no null values.
Here is a simple non-recursive solution that just concatenates the elements of each combination:
public static List<string> GetAllPossibleCombos(List<List<string>> strings)
{
IEnumerable<string> combos = new [] { "" };
foreach (var inner in strings)
combos = from c in combos
from i in inner
select c + i;
return combos.ToList();
}
static void Main(string[] args)
{
var x = GetAllPossibleCombos(
new List<List<string>>{
new List<string> { "a", "b", "c" },
new List<string> { "x", "y" },
new List<string> { "1", "2", "3", "4" }});
}
You could generalize this to return an IEnumerable<IEnumerable<string>>, which allows the caller to apply any operation they like for transforming each combination into a string (such as the string.Join below). The combinations are enumerated using deferred execution.
public static IEnumerable<IEnumerable<string>> GetAllPossibleCombos(
IEnumerable<IEnumerable<string>> strings)
{
IEnumerable<IEnumerable<string>> combos = new string[][] { new string[0] };
foreach (var inner in strings)
combos = from c in combos
from i in inner
select c.Append(i);
return combos;
}
public static IEnumerable<TSource> Append<TSource>(
this IEnumerable<TSource> source, TSource item)
{
foreach (TSource element in source)
yield return element;
yield return item;
}
static void Main(string[] args)
{
var combos = GetAllPossibleCombos(
new List<List<string>>{
new List<string> { "a", "b", "c" },
new List<string> { "x", "y" },
new List<string> { "1", "2", "3", "4" }});
var result = combos.Select(c => string.Join(",", c)).ToList();
}
Hope this helps.
class NListBuilder
{
Dictionary<int, List<string>> tags = new Dictionary<int, List<string>>();
public NListBuilder()
{
tags.Add(1, new List<string>() { "A", "B", "C" });
tags.Add(2, new List<string>() { "+", "-", "*" });
tags.Add(3, new List<string>() { "1", "2", "3" });
}
public List<string> AllCombos
{
get
{
return GetCombos(tags);
}
}
List<string> GetCombos(IEnumerable<KeyValuePair<int, List<string>>> remainingTags)
{
if (remainingTags.Count() == 1)
{
return remainingTags.First().Value;
}
else
{
var current = remainingTags.First();
List<string> outputs = new List<string>();
List<string> combos = GetCombos(remainingTags.Where(tag => tag.Key != current.Key));
foreach (var tagPart in current.Value)
{
foreach (var combo in combos)
{
outputs.Add(tagPart + combo);
}
}
return outputs;
}
}
}
In case it helps anyone, here is the method syntax version for Douglas's GetAllPossibleCombos method.
public static List<string> GetAllPossibleCombos(List<List<string>> strings)
{
IEnumerable<string> combos = new[] { "" };
foreach (var inner in strings)
{
combos = combos.SelectMany(r => inner.Select(x => r + x));
}
return combos.ToList();
}
Here is a generic version that works with all object types:
public static List<List<T>> GetAllPossibleCombos<T>(List<List<T>> objects)
{
IEnumerable<List<T>> combos = new List<List<T>>() { new List<T>() };
foreach (var inner in objects)
{
combos = combos.SelectMany(r => inner
.Select(x => {
var n = r.DeepClone();
if (x != null)
{
n.Add(x);
}
return n;
}).ToList());
}
// Remove combinations were all items are empty
return combos.Where(c => c.Count > 0).ToList();
}
If you provide null values it will also give you empty combination. For example:
var list1 = new List<string>() { "1A", "1B", null };
var list2 = new List<string>() { "2A", "2B", null };
var output = GetAllPossibleCombos(allLists);
Will contain:
[["1A"], ["1B"], ["2A"], ["2B"], ["1A", "2A"], ["1A", "2B"], ["1B", "2A"], ["1B," "2B"]]
Rather than just:
var list1 = new List<string>() { "1A", "1B" };
var list2 = new List<string>() { "2A", "2B" };
var output = GetAllPossibleCombos(allLists);
[["1A", "2A"], ["1A", "2B"], ["1B", "2A"], ["1B," "2B"]]
Note: DeepClone is an extension method used for copying the list. This can be done in many ways
public static T DeepClone<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
Here is an answer that would work for any generic type, comes with a function to convert base-10 to base-n as well.
public static class IntExt
{
const string Symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static string ToString(this int value, int toBase)
{
switch (toBase)
{
case 2:
case 8:
case 10:
case 16:
return Convert.ToString(value, toBase);
case 64:
return Convert.ToBase64String(BitConverter.GetBytes(value));
default:
if (toBase < 2 || toBase > Symbols.Length)
throw new ArgumentOutOfRangeException(nameof(toBase));
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
int resultLength = 1 + (int)Math.Max(Math.Log(value, toBase), 0);
char[] results = new char[resultLength];
int num = value;
int index = resultLength - 1;
do
{
results[index--] = Symbols[num % toBase];
num /= toBase;
}
while (num != 0);
return new string(results);
}
}
}
public class UnitTest1
{
public static T[][] GetJoinCombinations<T>(T[][] arrs)
{
int maxLength = 0;
int total = 1;
for (int i = 0; i < arrs.Length; i++)
{
T[] arr = arrs[i];
maxLength = Math.Max(maxLength, arr.Length);
total *= arr.Length;
}
T[][] results = new T[total][];
int n = 0;
int count = (int)Math.Pow(maxLength, arrs.Length);
for (int i = 0; i < count; i++)
{
T[] combo = new T[arrs.Length];
string indices = i.ToString(maxLength).PadLeft(arrs.Length, '0');
bool skip = false;
for (int j = 0; j < indices.Length; j++)
{
T[] arr = arrs[j];
int index = int.Parse(indices[j].ToString());
if (index >= arr.Length)
{
skip = true;
break;
}
combo[j] = arr[index];
}
if (!skip)
results[n++] = combo;
}
return results;
}
[Fact]
public void Test1()
{
string[][] results = GetJoinCombinations(new string[][]
{
new string[] { "1", "2", "3" },
new string[] { "A", "B", "C" },
new string[] { "+", "-", "*", "/" },
});
}
}
public static IEnumerable<int[]> GetAllPossibleCombos(List<int[]> ints)
=> _getAllPossibleCombos(ints, 0, new List<int>());
private static IEnumerable<int[]> _getAllPossibleCombos(IReadOnlyList<int[]> ints, int index, IReadOnlyCollection<int> current)
{
return index == ints.Count - 1
? ints[index]
.Select(_ => new List<int>(current) {_}.ToArray())
: ints[index]
.SelectMany(_ => _getAllPossibleCombos(ints, index + 1, new List<int>(current) {_}));
}

Categories

Resources