First of all sorry for my mistakes in English its not my primary language
i have a problem , i have a array like following
string[] arr1 = new string[] {
"Pakistan:4,India:3,USA:2,Iran:1,UK:0",
"Pakistan:4,India:3,USA:2,Iran:1,UK:0",
"India:4,USA:3,Iran:2,UK:1,Pakistan:0"
};
now i just want to know that how many times Pakistan comes with 1 ,
how many times with 2 , 3 , 4
and i need to know this about all India , USA , Iran , UK
Thanks in advance , you guys are my last hope .
This linq will convert the array into a Dictionary>, where the outer dictionary contains the countries names, and inner dictionaries will contain the ocurrence number (the number after ':') and the count for each ocurrence.
string[] arr1 = new string[]
{
"Pakistan:4,India:3,USA:2,Iran:1,UK:0",
"Pakistan:4,India:3,USA:2,Iran:1,UK:0",
"India:4,USA:3,Iran:2,UK:1,Pakistan:0"
};
var count = arr1
.SelectMany(s => s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
.GroupBy(s => s.Split(':')[0], s => s.Split(':')[1])
.ToDictionary(g => g.Key,
g =>
{
var items = g.Distinct();
var result = new Dictionary<String, int>();
foreach (var item in items)
result[item] = g.Count(gitem => gitem == item);
return result;
});
// print the result
foreach(var country in count.Keys)
{
foreach(var ocurrence in count[country].Keys)
{
Console.WriteLine("{0} : {1} = {2}", country, ocurrence, count[country][ocurrence]);
}
}
I would use the String.Split(char[]) method and the String.SubString(int, int) method to inspect every 'country' inside your array and to get the number postfix of each country.
Try the following:
(The following code is now compiled and tested.)
Use a simple data structure to facilitate the task of holding the result of your operation.
public struct Result {
string Country { get; set; }
int Number { get; set; }
int Occurrences { get; set; }
}
// define what countries you are dealing with
string[] countries = new string[] { "Pakistan", "India", "USA", "Iran", "UK", }
Method to provide the overall result:
public static Result[] IterateOverAllCountries () {
// range of numbers forming the postfix of your country strings
int numbersToLookFor = 4;
// provide an array that stores all the local results
// numbersToLookFor + 1 to respect that numbers are starting with 0
Result[] result = new Result[countries.Length * (numbersToLookFor + 1)];
string currentCountry;
int c = 0;
// iterate over all countries
for (int i = 0; i < countries.Length; i++) {
currentCountry = countries[i];
int j = 0;
// do that for every number beginning with 0
// (according to your question)
int localResult;
while (j <= numbersToLookFor) {
localResult = FindCountryPosition(currentCountry, j);
// add another result to the array of all results
result[c] = new Result() { Country = currentCountry, Number = j, Occurrences = localResult };
j++;
c++;
}
}
return result;
}
Method to provide a local result:
// iterate over the whole array and search the
// occurrences of one particular country with one postfix number
public static int FindCountryPosition (string country, int number) {
int result = 0;
string[] subArray;
for (int i = 0; i < arr1.Length; i++) {
subArray = arr1[i].Split(',');
string current;
for (int j = 0; j < subArray.Length; j++) {
current = subArray[j];
if (
current.Equals(country + ":" + number) &&
current.Substring(current.Length - 1, 1).Equals(number + "")
)
result++;
}
}
return result;
}
The following should enable you to run the algorithm
// define what countries you are dealing with
static string[] countries = new string[] { "Pakistan", "India", "USA", "Iran", "UK", };
static string[] arr1 = new string[] {
"Pakistan:4,India:3,USA:2,Iran:1,UK:0",
"Pakistan:4,India:3,USA:2,Iran:1,UK:0",
"India:4,USA:3,Iran:2,UK:1,Pakistan:0"
};
static void Main (string[] args) {
Result[] r = IterateOverAllCountries();
}
The data structure you are using is not rich enough to provide you with that information. Hence you need to parse your string and create a new data structure to be able to provide (sring[][]):
string[] arr1 = new string[] {
"Pakistan,India,USA,Iran,UK",
"Pakistan,India,USA,Iran,UK",
"India,USA,Iran,UK,Pakistan"
};
string[][] richerArray = arr1.Select(x=> x.Split('\'')).ToArray();
var countPakistanIsFirst = richerArray.Select(x=>x[0] == "Pakistan").Count();
UPDATE
You seem to have changed your question. The answer applies to the original question.
Related
I have an array list of List<string> that contains values in the following order ["1m", "1cm", "4km","2cm"] (Centimeters, meters and kilometers)
When I want to sort this array, I get a wrong answer. I use OrderBy:
List<string> data = new List<string> { "1m", "1cm", "4km","2cm" };
var result= data.OrderBy(x => x).ToList();
the result is:
{ "1cm", "1m", "2cm", "4km"}
But I want the answer to be this order-: { "1cm", "2cm", "1m", "4km"}
You have sorted the data alphabetically. First the first character is compared. Then the second character and...
You need to normalize the data based on cm(or m) and then sort.
List<string> data = new List<string> { "1m", "1cm", "4km","2cm" };
var result = data.OrderBy(x => lenghtCM(x));
public int lenghtCM(string lenghtStr)
{
if (lenghtStr.Contains("cm"))
{
string num = lenghtStr.Split("cm")[0];
return int.Parse(num);
}
else if (lenghtStr.Contains("km"))
{
string num = lenghtStr.Split("km")[0];
return int.Parse(num) * 100*1000;
}
else if (lenghtStr.Contains("m"))
{
string num = lenghtStr.Split('m')[0];
return int.Parse(num) * 100;
}
return 0;
}
then the result:
{ "1cm", "2cm", "1m", "4km"}
private string[] normalaizeArray(string[] inputArray)
{
for (int i= 0 ; i < inputArray.Length; i++)
{
if(inputArray[i].Contains('m'))
{
inputArray[i] = (float.Parse(inputArray[i].Split('k')[0]) * 100).ToString();
} else if(inputArray[i].Contains('km'))
{
inputArray[i] = (float.Parse(inputArray[i].Split('k')[0]) * 100*1000).ToString();
}
else
{
inputArray[i] = inputArray[i].Replace("cm", "");
}
}
inputArray = inputArray.OrderBy(x => int.Parse(x)).ToArray();
for (int i = 0; i < inputArray.Length; i++)
{
if(int.Parse(inputArray[i])>1000*100)
inputArray[i] = (float.Parse(inputArray[i])/1000).ToString() + "km";
else if(int.Parse(inputArray[i])>100)
inputArray[i] = (float.Parse(inputArray[i])/100).ToString() + "m";
else
inputArray[i] = inputArray[i] + 'cm';
}
return inputArray;
}
If you can, parse the strings first:
enum Unit { cm, m, km }
record Measurment(int Length, Unit Unit)
{
public override string ToString() => $"{Length}{Enum.GetName(typeof(Unit), Unit)}";
public double NormalizedLength => Unit switch
{
Unit.cm => Length * 0.001,
Unit.m => Length * 1.0,
Unit.km => Length * 1000.0,
_ => throw new NotImplementedException()
};
public static Measurment Parse(string source)
{
var digits = source.TakeWhile(char.IsDigit).Count();
var length = int.Parse(source.AsSpan(0, digits));
// switches with source.AsSpan(digits) in preview
var measure = source[..digits] switch
{
"cm" => Unit.cm,
"m" => Unit.m,
"km" => Unit.km,
_ => throw new NotImplementedException(),
};
return new Measurment(length, measure);
}
}
.
var result = data.Select(Measurment.Parse).OrderBy(x => x.NormalizedLength).ToList();
This lets you sort your measurments by NormalizedLength and ToString gets back the original string. Should be very fast, simple to extend with new units and you can make it fault-tolerant if you turn Parse into the TryParse pattern.
There's a NuGet package to manage parsing and manipulating SI units called UnitsNet.
If you install that package (via Add | NuGet Package, search for and select UnitsNet and install it), then you can write the following code:
(You'll need to add using UnitsNet; at the top of the code file first)
This also works with nm etc.
List<string> data = new List<string> { "1m", "1cm", "4km", "2cm" };
var result = data.OrderBy(Length.Parse).ToList();
Console.WriteLine(string.Join(", ", result));
This will output "1cm, 2cm, 1m, 4km"
You need custom sort using IComparable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication49
{
class Program
{
static void Main(string[] args)
{
List<string> data = new List<string> { "1m", "1cm", "4km", "2cm" };
List<string> results = data.Select(x => new SortDistance(x)).OrderBy(x => x).Select(x => x.value).ToList();
}
}
public class SortDistance : IComparable<SortDistance>
{
const string pattern = #"(?'number'\d+)(?'multiplier'.*)";
List<string> distanceOrder = new List<string>() { "cm", "m", "km" };
public string value { get; set; }
public int distance { get; set; }
public string multiplier { get; set; }
public SortDistance(string value)
{
this.value = value;
Match match = Regex.Match(value, pattern);
this.distance = int.Parse(match.Groups["number"].Value);
this.multiplier = match.Groups["multiplier"].Value;
}
public int CompareTo(SortDistance other)
{
if (this.multiplier == other.multiplier)
return this.distance.CompareTo(other.distance);
else
return distanceOrder.IndexOf(this.multiplier).CompareTo(distanceOrder.IndexOf(other.multiplier));
}
}
}
you can not sort using OrderBy.
You have to define the conversion first from all units to the smallest unit. for example m to cm, km to cm.....
so 1m euqals to 100 cm
then you have to iterate through your list and check each item's unit, get its equivalent to the smallest unit.
Create another list.
you can implement insertion sort to sort the items and add keep on inserting the item based on the comparison.
Need suggestions on how to order the strings in order in a linq query.
Example of strings in db [1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.10 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.18 1.19 1.20 2.1a(i) 2.1a(ii) 2.1a(iii) 2.1a(iv) 2.1a(v) 2.1a(vi) 2.1a(vii) , ....]
In order to solve the issue for a job case.
I have written a linq query which suppose to order the jobs based on band level
var GetOrder =(from a in db.Details
join b in db.Information on a.InfromationId equals b.Id
where c.JobId == JobId
select new {a.Name, a.LastName, b.BandLevel}
//.OrderBy(b=>b.BandLevel)
.ToList();
I added the below query for it can order and sort for strings.
GetOrder.Sort((a, b) => a.BandLevel.CompareTo(b.BandLevel));
This query is suppose to sort the stings in order but it fails to set for some strings.
Instead it orders in this format using the above query.
1.1 , 1.10, 1.19 ,1.2, 2.1a(i) ,2.21(v)
It should be in this desired list .
1.1 ,1.2, 1.10, 1.19 , 2.1a(i) ,2.21(v)
Any suggestions on how to sort it in the suggested order for linq queries.
Well, p.s.w.g has a great answer, but since I worked on this a little I figured I would post mine as well.
My suggestion is to create a class that encapsulates the data from the string, which can only be instantiated from a static Parse method. This Parse method takes in a string and then parses it, setting the properties of the class as it does, and then returns the instance of the class.
The class also implements IComparable, so we can use the Sort and OrderBy methods on a list of these items.
I also used the same answer for parsing roman numerals that was used above (it's the first result when searching for "roman numeral comparer").
Here's the class:
public class BandLevelComponent : IComparable<BandLevelComponent>
{
public int Major { get; private set; }
public int Minor { get; private set; }
public string Revision { get; private set; }
public string RomanNumeral { get; private set; }
private static Dictionary<char, int> _romanMap = new Dictionary<char, int>
{
{'I', 1},
{'V', 5},
{'X', 10},
{'L', 50},
{'C', 100},
{'D', 500},
{'M', 1000}
};
private BandLevelComponent()
{
}
public static BandLevelComponent Parse(string input)
{
if (string.IsNullOrWhiteSpace(input)) return null;
BandLevelComponent result = new BandLevelComponent();
int temp;
var parts = input.Split('.');
int.TryParse(parts[0], out temp);
result.Major = temp;
if (parts.Length > 1)
{
var minor = string.Concat(parts[1].TakeWhile(char.IsNumber));
int.TryParse(minor, out temp);
result.Minor = temp;
if (parts[1].Length > minor.Length)
{
var remaining = parts[1].Substring(minor.Length);
var openParen = remaining.IndexOf("(");
if (openParen > 0) result.Revision = remaining.Substring(0, openParen);
if (openParen > -1)
result.RomanNumeral = remaining
.Split(new[] {'(', ')'}, StringSplitOptions.RemoveEmptyEntries)
.Last();
}
}
return result;
}
public int CompareTo(BandLevelComponent other)
{
if (other == null) return 1;
if (Major != other.Major) return Major.CompareTo(other.Major);
if (Minor != other.Minor) return Minor.CompareTo(other.Minor);
if (Revision != other.Revision) return Revision.CompareTo(other.Revision);
return RomanNumeral != other.RomanNumeral
? RomanToInteger(RomanNumeral).CompareTo(RomanToInteger(other.RomanNumeral))
: 0;
}
public override string ToString()
{
var revision = Revision ?? "";
var roman = RomanNumeral == null ? "" : $"({RomanNumeral})";
return $"{Major}.{Minor}{revision}{roman}";
}
private static int RomanToInteger(string romanNumeral)
{
var roman = romanNumeral?.ToUpper();
var number = 0;
for (var i = 0; i < roman?.Length; i++)
{
if (i + 1 < roman.Length && _romanMap[roman[i]] < _romanMap[roman[i + 1]])
{
number -= _romanMap[roman[i]];
}
else
{
number += _romanMap[roman[i]];
}
}
return number;
}
}
And here's a sample usage:
private static void Main()
{
var dbStrings = new[]
{
"1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11",
"1.12", "1.13", "1.14", "1.15", "1.16", "1.17", "1.18", "1.19", "1.20", "2.1a(i)",
"2.1a(ii)", "2.1a(iii)", "2.1a(iv)", "2.1a(v)", "2.1a(vi)", "2.1a(vii)"
};
// Custom extension method for shuffling
dbStrings.Shuffle();
// Select each string into our custom class
var bandLevels = dbStrings.Select(BandLevelComponent.Parse).ToList();
Console.WriteLine("\nShuffled List");
Console.WriteLine(string.Join(", ", bandLevels));
// Sort the list
bandLevels.Sort();
Console.WriteLine("\nSorted List");
Console.WriteLine(string.Join(", ", bandLevels));
// Order the list descending (largest first)
bandLevels = bandLevels.OrderByDescending(b => b).ToList();
Console.WriteLine("\nOrderByDescending List");
Console.WriteLine(string.Join(", ", bandLevels));
GetKeyFromUser("\nDone! Press any key to exit...");
}
Output
Here's my stab at it. First split the string into various parts, e.g. 2.11a(ii) will become 2, 11, a, and ii. The first two parts can be parsed as regular integers. The second part is parsed as a integer with a=1, b=2 and so on. The third part is parsed as a Roman numeral (I used a modified version of the algorithm presented in this answer). You collect these parts as an array of integers (I call them indexes), and compare the array from one item to the next such that if the first index of each is equal, the items are equal, and so on until an index is unequal.
public static int CustomComparison(string x, string y)
{
var xIndexes = StringToIndexes(x);
var yIndexes = StringToIndexes(y);
for (int i = 0; i < 4; i++)
{
if (xIndexes[i] < yIndexes[i])
{
return -1;
}
if (xIndexes[i] > yIndexes[i])
{
return 1;
}
}
return 0;
}
private static int[] StringToIndexes(string input) {
var match = Regex.Match(input, #"^(\d+)\.(\d+)([a-z]+)?(?:\(([ivx]+)\))?$");
if (!match.Success)
{
return new[] { 0, 0, 0, 0 };
}
return new[] {
int.Parse(match.Groups[1].Value),
int.Parse(match.Groups[2].Value),
AlphabeticToInteger(match.Groups[3].Value),
RomanToInteger(match.Groups[4].Value),
};
}
private static int AlphabeticToInteger(string alpha)
{
return alpha.Aggregate(0, (n, c) => n * 26 + (int)(c - 'a' + 1));
}
private static Dictionary<char, int> RomanMap = new Dictionary<char, int>()
{
{'i', 1},
{'v', 5},
{'x', 10},
};
public static int RomanToInteger(string roman)
{
int number = 0;
for (int i = 0; i < roman.Length; i++)
{
if (i + 1 < roman.Length && RomanMap[roman[i]] < RomanMap[roman[i + 1]])
{
number -= RomanMap[roman[i]];
}
else
{
number += RomanMap[roman[i]];
}
}
return number;
}
String Array 1: (In this format: <MENU>|<Not Served?>|<Alternate item served>)
Burger|True|Sandwich
Pizza|True|Hot Dog
String Array 2: (Contains Menu)
Burger
Pizza
Grill Chicken
Pasta
I need the menu is served or any alternate item served for that particular item.
Code:
for(int i = 0; i < strArr2.Length; i++)
{
if(strArr2.Any(_r => _r.Split('|').Any(_rS => _rS.Contains(strArr1[i]))))
{
var menu = strArr2[i];
var alternate = ? // need to get alternate item
}
}
As I commented in the code, how to get the alternate item in that string array? Please help, thanks in advance.
P.S: Any help to trim if condition is also gladly welcome.
Instead of any, you may use Where to get the value matching.
#Markus is having the detailed answer, I am just using your code to find a quick fix for you.
for(int i = 0; i < strArr2.Length; i++)
{
if(strArr2.Any(_r => _r.Split('|').Any(_rS => _rS.Contains(strArr1[i]))))
{
var menu = strArr2[i];
var alternate = strArr2.Where(_rs => _rs.Split('|').Any(_rS => _rS.Contains(strArr1[i]))).First().Split('|').Last();
}
}
In order to simplify your code, it is a good idea to better separate the tasks. For instance, it will be much easier to handle the contents of string array 1 after you have converted the contents into objects, e.g.
class NotServedMenu
{
public string Menu { get; set; }
public bool NotServed { get; set; }
public string AlternateMenu { get; set; }
}
Instead of having an array of strings, you can read the strings to a list first:
private IEnumerable<NotServedMenu> NotServedMenusFromStrings(IEnumerable<string> strings)
{
return (from x in strings select ParseNotServedMenuFromString(x)).ToArray();
}
private NotServedMenu ParseNotServedMenuFromString(string str)
{
var parts = str.Split('|');
// Validate
if (parts.Length != 3)
throw new ArgumentException(string.Format("Unable to parse \"{0}\" to an object of type {1}", str, typeof(NotServedMenu).FullName));
bool notServedVal;
if (!bool.TryParse(parts[1], out notServedVal))
throw new ArgumentException(string.Format("Unable to read bool value from \"{0}\" in string \"{1}\".", parts[1], str));
// Create object
return new NotServedMenu() { Menu = parts[0],
NotServed = notServedVal,
AlternateMenu = parts[2] };
}
Once you can use the objects, the subsequent code will be much cleaner to read:
var notServedMenusStr = new[]
{
"Burger|True|Sandwich",
"Pizza|True|Hot Dog"
};
var notServedMenus = NotServedMenusFromStrings(notServedMenusStr);
var menus = new[]
{
"Burger",
"Pizza",
"Grill Chicken",
"Pasta"
};
var alternateMenus = (from m in menus join n in notServedMenus on m equals n.Menu select n);
foreach(var m in alternateMenus)
Console.WriteLine("{0}, {1}, {2}", m.Menu, m.NotServed, m.AlternateMenu);
In this sample, I've used a Linq join to find the matching items.
You could do something like that
string[] strArr1 = { "Burger|True|Sandwich", "Pizza|True|Hot Dog" };
string[] strArr2 = { "Burger", "Pizza", "Grill Chicken", "Pasta" };
foreach (string str2 in strArr2)
{
string str1 = strArr1.FirstOrDefault(str => str.Contains(str2));
if (str1 != null)
{
string[] splited = str1.Split('|');
string first = splited[0];
bool condition = Convert.ToBoolean(splited[1]);
string second = splited[2];
}
}
I have my code like,
string firstLineOfRecord = "front images,Currency Code,Date,BackImages,Domination";
string[] fieldArrayRecord = firstLineOfRecord.Split(',');
string fields = "FrontImages,BackImages,Domination,CurrencyCode,SerialNumber";
string[] fieldArrayList = fields.Split(',');
List<int> mappedList = new List<int>();
for (int i = 0; i< fieldArrayList.Count(); i++)
{
for (int j = 0; j < fieldArrayRecord.Count(); j++)
{
if (fieldArrayList[i] == fieldArrayRecord[j])
{
mappedList.Add(j);
}
}
}
How can i map, the "front Images" with "FrontImages".
As iam the beginner i dont know how to solve this.Kindly tell me how to achieve this.
For such a fuzzy match, you first need to identify the valid identifiers to ignore (in this case a space).
You could do something like this: You strip out all those identifiers. Then compare case and culture insensitive.
string normalizedHeaderString = "FrontImages";
string normalizedInputString = "front images";
foreach (string c in new[] { " " }) /* the strings to strip out */
{
normalizedHeaderString = normalizedHeaderString.Replace(c, null);
normalizedInputString = normalizedInputString.Replace(c, null);
}
if (string.Equals( normalizedHeaderString
, normalizedInputString
, StringComparison.OrdinalIgnoreCase
)
)
{ /* do your logic, like saving the index, etc */ }
This is a little hacky, but you get the idea. You'd better use a custom implementation of a StringComparer that just ignores the characters to strip out.
As I understand from your question, your problems are spaces and case sensitivity
so you can use
fieldArrayList[i].Replace(" ","").ToLower() ==
fieldArrayRecord[j].Replace(" ","").ToLower()
class Program
{
static void Main(string[] args)
{
string firstLineOfRecord = "front images,Currency Code,Date,BackImages,Domination";
string[] fieldArrayRecord = firstLineOfRecord.Split(',');
string fields = "FrontImages,BackImages,Domination,CurrencyCode,SerialNumber";
string[] fieldArrayList = fields.Split(',');
List<int> mappedList = new List<int>();
for (int i = 0; i < fieldArrayRecord.Length; i++)
{
if (fieldArrayList.Any(s => string.Equals( fieldArrayRecord[i].Replace(" ", string.Empty), s, StringComparison.OrdinalIgnoreCase)))
{
mappedList.Add(i);
}
}
foreach (int index in mappedList)
{
Console.WriteLine(index);
}
}
}
Output:
0
1
3
4
Or using a dictionary:
class Program
{
static void Main(string[] args)
{
string firstLineOfRecord = "front images,Currency Code,Date,BackImages,Domination";
string fields = "FrontImages,BackImages,Domination,CurrencyCode,SerialNumber";
var dataFields = firstLineOfRecord.Split(',').Select((x, index) => new { FieldName = x.Replace(" ", string.Empty), Index = index });
var tableFields = fields.Split(',').Select((x, index) => new { FieldName = x, Index = index });
Dictionary<int, int> mapping = (from dataField in dataFields
let tableField = tableFields.SingleOrDefault(x => string.Equals(dataField.FieldName, x.FieldName, StringComparison.OrdinalIgnoreCase))
where tableField != null
select new { DF = dataField.Index, TF = tableField.Index })
.ToDictionary(c => c.DF, c => c.TF);
// Test:
string[] dataFieldsArray = firstLineOfRecord.Split(',');
string[] tableFieldsArray = fields.Split(',');
foreach (KeyValuePair<int,int> pair in mapping)
{
Console.WriteLine(
"TableField '{0}' Index {1} has to be mapped to DataField '{2}' Index {3}",
tableFieldsArray[pair.Value], pair.Value, dataFieldsArray[pair.Key],pair.Key);
}
}
}
Output:
TableField 'FrontImages' Index 0 has to be mapped to DataField 'front images' Index 0
TableField 'CurrencyCode' Index 3 has to be mapped to DataField 'Currency Code' Index 1
TableField 'BackImages' Index 1 has to be mapped to DataField 'BackImages' Index 3
TableField 'Domination' Index 2 has to be mapped to DataField 'Domination' Index 4
Here's a way to do it with a LINQ query:
string firstLineOfRecord = "front images,Currency Code,Date,BackImages,Domination";
string[] fieldArrayRecord = firstLineOfRecord.Split(',')
.Select(x => x.Replace(" ", string.Empty))
.ToArray();
// Test it, prints True.
fieldArrayRecord.Contains("FrontImages", StringComparer.OrdinalIgnoreCase)
Note this will replace any white space between those letters and will alter the given fieldArrayRecord.
I'm looking to combine the contents of two string arrays, into a new list that has the contents of both joined together.
string[] days = { "Mon", "Tue", "Wed" };
string[] months = { "Jan", "Feb", "Mar" };
// I want the output to be a list with the contents
// "Mon Jan", "Mon Feb", "Mon Mar", "Tue Jan", "Tue Feb" etc...
How can I do it ? For when it's only two arrays, the following works and is easy enough:
List<string> CombineWords(string[] wordsOne, string[] wordsTwo)
{
var combinedWords = new List<string>();
foreach (var wordOne in wordsOne)
{
foreach (string wordTwo in wordsTwo)
{
combinedWords.Add(wordOne + " " + wordTwo);
}
}
return combinedWords;
}
But I'd like to be able to pass varying numbers of arrays in (i.e. to have a method with the signature below) and have it still work.
List<string> CombineWords(params string[][] arraysOfWords)
{
// what needs to go here ?
}
Or some other solution would be great. If it's possible to do this simply with Linq, even better!
What you want to do is actually a cartesian product of all the arrays of words, then join the words with spaces. Eric Lippert has a simple implementation of a Linq cartesian product here. You can use it to implement CombineWords:
List<string> CombineWords(params string[][] arraysOfWords)
{
return CartesianProduct(arraysOfWords)
.Select(x => string.Join(" ", x))
.ToList();
}
To cross join on any amount of arrays of strings:
// Define other methods and classes here
List<string> CombineWords(params string[][] arraysOfWords)
{
if (arraysOfWords.Length == 0)
return new List<string>();
IEnumerable<string> result = arraysOfWords[0];
foreach( string[] words in arraysOfWords.Skip(1) )
{
var tempWords = words;
result = from r in result
from w in tempWords
select string.Concat(r, " ", w);
}
return result.ToList();
}
Code below works for any number of arrays (and uses linq to some degree):
List<string> CombineWords(params string[][] wordsToCombine)
{
if (wordsToCombine.Length == 0)
return new List<string>();
IEnumerable<string> combinedWords = wordsToCombine[0].ToList();
for (int i = 1; i < wordsToCombine.Length; ++i)
{
var temp = i;
combinedWords = (from x in combinedWords from y in wordsToCombine[temp]
select x + " " + y);
}
return combinedWords.ToList();
}
public static List<string> CombineWords(params string[][] arraysOfWords)
{
var strings = new List<string>();
if (arraysOfWords.Length == 0)
{
return strings;
}
Action<string, int> combineWordsInternal = null;
combineWordsInternal = (baseString, index) =>
{
foreach (var str in arraysOfWords[index])
{
string str2 = baseString + " " + str;
if (index + 1 < arraysOfWords.Length)
{
combineWordsInternal(str2, index + 1);
}
else
{
strings.Add(str2);
}
}
};
combineWordsInternal(string.Empty, 0);
return strings;
}
Second try... I'm not able to do it in LINQ... A little too much complex to linquize correctly :-)
I'm using a local anonymous function (and showing that it's quite complex to recurse on anonymous functions, because you have to declare them separately)
This is a non-recursive solution which buffers strings as it progresses, to reduce the number of concatenations. Therefore it should also be usable for more arrays.
It also preserves your desired order - the items in the first array will always be at the beginning of the resulting string.
var s1 = new string[] { "A", "B", "C" };
var s2 = new string[] { "1", "2", "3", "4" };
var s3 = new string[] { "-", "!", "?" };
var res = Combine(s1, s2, s3);
And the function in question:
private List<string> Combine(params string[][] arrays)
{
if (arrays.Length == 1)
{
// The trivial case - exit.
return new List<string>(arrays[0]);
}
IEnumerable<string> last = arrays[arrays.Length - 1];
// Build from the last array, progress forward
for (int i = arrays.Length - 2; i >= 0; i--)
{
var buffer = new List<string>();
var current = arrays[i];
foreach (var head in current)
{
foreach (var tail in last)
{
// Concatenate with the desired space.
buffer.Add(head + " " + tail);
}
}
last = buffer;
}
return (List<string>)last;
}
Could you try this method ?
static List<string> CombineWords(string[] wordsOne, string[] wordsTwo)
{
var combinedWords = new List<string>();
for(int x = 0; (x <= wordsOne.Length - 1); ++x)
{
for(int y = 0; (x <= wordsTwo.Length - 1); ++y)
{
combinedWords.Add(string.Format("{0} {1}", wordsOne[x], wordsTwo[y]));
}
}
return combinedWords;
}
Kris