join 2 lists and create one to many relationship [closed] - c#

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 11 months ago.
Improve this question
Is it possible to join 2 lists and create one to many relationship?
For example,
list 1:
{"High", "Med", "Low"}
list 2:
{"Submitted", "In Progress", "Draft", "Rejected"}
final list, I will have values like this:
{
{"High", "Submitted"}, {"High", "In Progress"},{"High", "Draft"}, {"High", "Rejected"},
{"Med", "Submitted"}, {"Med", "In Progress"},{"Med", "Draft"}, {"Med", "Rejected"},
{"Low", "Submitted"}, {"Low", "In Progress"},{"Low", "Draft"}, {"Low", "Rejected"}
}

Not sure if this is what you are looking for, but you can use Select and SelectMany to create the final List of KeyVaulePair.
var list1 = new List<string>() { "High", "Medium", "Low" };
var list2 = new List<string>() { "Submitted", "In-Progress", "Draft", "Rejected" };
var joined = list1.SelectMany(val1 => list2.Select(val2 => new KeyValuePair<string, string>(val1, val2)));
foreach(var res in joined)
{
Console.WriteLine(res.Key + "," + res.Value);
}
If you want to use imperative style of coding, then just create 2 for loops

It seems, that you are looking for a Cartesian Join, you can do it with a help of Linq:
var list1 = new List<string>() {
"High", "Medium", "Low" };
var list2 = new List<string>() {
"Submitted", "In-Progress", "Draft", "Rejected" };
// I've combined values as named tuple: (string first, string second)
// but you can use anonymous class, or just concat string strings
var joined = list1
.SelectMany(item1 => list2.Select(item2 => (first: item1, second: item2)));
.ToList();
Let's have a look:
Console.Write(string.Join(Environment.NewLine, joined));
Outcome:
(High, Submitted)
(High, In-Progress)
(High, Draft)
(High, Rejected)
(Medium, Submitted)
(Medium, In-Progress)
(Medium, Draft)
(Medium, Rejected)
(Low, Submitted)
(Low, In-Progress)
(Low, Draft)
(Low, Rejected)

LINQ method syntax for joins is pretty ugly and even counterintuitive, IMHO; query syntax looks much nicer/easier to read:
from s in list1
from t in list2
select new { s, t }
Or as a tuple:
from s in list1
from t in list2
select ( s, t )

If you want to do Cartesian product and want to store it in text-value pair then you can use List with SelectListItem. It will give you list with text-value pair.
var list1 = new List<string>() { "High", "Medium", "Low" };
var list2 = new List<string>() { "Submitted", "In-Progress", "Draft", "Rejected" };
List<SelectListItem> list3 = new List<SelectListItem>();
foreach (string str in list1)
{
foreach(string str2 in list2)
{
list3.Add(new SelectListItem()
{ Text = str, Value = str2 });
}
}

You can use the join method from LINQ:
List<(string,string)> joined = list1.Join(list2, x => true, y => true, (x,y) => (x,y)).ToList();
The second and third parameter are selectors for list1 and list2. If both are the same, a joined value will be created. If we give the same constant value for both selectors, the joining condition is always fulfilled and hence all possible pairs are created.
Online demo: https://dotnetfiddle.net/JaK6ff

Related

How to add add items near to next list

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();

How can I sort a list of numerical strings? Preferably using LINQ

I have a string in below manner:-
string[] things = new string[] { "1", "10", "2", "1_1", "2_1","3_1" };
The desired output is:
"1",
"1_1",
"2",
"2_1",
"3_1",
"10",
How I can achieve this using LINQ?
Yes there is, you can split each part by _ and convert the first string part to an integer. When sorting afterwards this will ensure that 10 is not before 2. Then in the second step you order it according to the last number
string[] things = new string[] { "5_3", "5_2", "1", "10", "2", "1_1", "2_1", "1_2", "3_1" };
string[] ordered = things.OrderBy(x=>Convert.ToInt32(x.Split('_').First())).
ThenBy(x=>Convert.ToInt32(x.Split('_').Last())).ToArray();
Output:
EDIT: Here is the link to the documentation of ThenBy for the sake of informativity ;)
What about this?
string[] strings = new string[] { "1", "10", "2", "1_1", "2_1", "3_1" };
var ordered = from str in strings
let weight = double.Parse(str.Replace("_", CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator))
let pair = new { Weight = weight, OriginalString = str }
orderby pair.Weight
select pair.OriginalString;
You can use one of the overloads of OrderBy that allows you to create your own key and maybe specify your own comparer. It looks like you want to treat _ as a decimal separator.
One quick & dirty way would be to replace _ with the decimal separator, eg :
var decSeparator=CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
var orderedThings=things.OrderBy(thing=>double.Parse( thing.Replace("_",decSeparator)) )
.ToArray();
This is quick & dirty because it assumes _ appears only once. If _ appeared multiple times, the replaced string wouldn't be a valid number
This will give you the desired output:
string[] things = new string[] { "1", "10", "2", "1_1", "2_1", "3_1" };
var list = from row in things select Decimal.Parse(row.Replace("_",CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator));
list = list.OrderBy(d=> d).ToList();
var StringList = from row in list select row.ToString("0.#").Replace(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, "_");
If that format is strict you could parse to Version and order by that:
string[] orderedThings = things
.Select(t => new { Thing = t, Numbers = (t + "_0").Split('_') })
.Where(x => x.Numbers.All(s => s.All(char.IsDigit)))
.Select(x => new { x.Thing, VersionStr = String.Join(".",x.Numbers.Take(4)) })
.OrderBy(x => new Version(x.VersionStr))
.ThenBy(x => x.Thing)
.Select(x => x.Thing)
.ToArray();
The t + "_0" trick was necessary to ensure that also single digits can be parsed. A version needs at least a major and aminor part. This "works" also if there are more than 4 tokens(major, minor, build, and revision). Then only the first 4 are taken to initialize the Version instance.

Contains without order

I want to search a list of strings using a set of characters and want to find matches regardless of order. For example if my list contains
List<string> testList = new List<string>() { "can", "rock", "bird" };
I want to be able to search using "irb" and have it return bird. I have to do this many times so I am looking for the most efficient way of doing it.
var query = "irb";
List<string> testList = new List<string>() { "can", "rock", "bird" };
var result = testList.Where(i => query.All(q => i.Contains(q)));
For each item in the testList test to see if it contains all the letters in query
For your scenario, you need to check each character of word in another list of word.
For that, you can do like this :
// Checks whether all character in word is present in another word
Func<string, string, bool> isContain = (s1, s2) =>
{
int matchingLength = 0;
foreach (var c2 in s2.ToCharArray())
{
foreach (var c1 in s1.ToCharArray())
{
if (c1 == c2)
++matchingLength;
}
}
// if matched length is equal to word length given, it would be assumed as matched
return s2.Length == matchingLength;
};
List<string> testList = new List<string>() { "can", "rock", "bird" };
string name = "irb";
var fileredList = testList.Where(x => isContain(x, name));
If you don't care about matching duplicates than checking if all characters in a sequence you are searching for are contained in the word would do for predicate:
"irb".Except("bird").Count() == 0
And whole condition:
List<string> testList = new List<string>() { "can", "rock", "bird" };
var search = "irb";
var matches = testList.Where(word => !search.Except(word).Any());
Notes:
you need to normalize all words to lowercase if you need mixed case letters to match.
if performance of searching for different values is critical - convert search string to HashSet first and do except manually.
if you need to match different values against same list many times - convert list of strings to list of HashSet and use search.All(c => wordAsHashSet.Contains(c)) as condition.
You can use linq to achieve this
List<string> testList = new List<string>() { "can", "rock", "bird" };
var lst = testList.Where(x => x.ToUpperInvariant().Contains("IRD")).ToList();
Make sure you also compare the cases using ToUpper and the string you want to compare also make it UpperCase

Concatenating strings in two lists to create a third list

I have two lists of items, can you please guide me how I can concatenate values of both and add concatenated value into third list as a value.
For example if List<string> From has A,B,C and List<string> To has 1,2,3 then List<string> All should have A1,B2,C3. I'd preferably like to use a lambda expression.
Use Linq's Zip extension method:
using System.Linq;
...
var list1 = new List<string> { "A", "B", "C" };
var list2 = new List<string> { "1", "2", "3" };
var list3 = list1.Zip(list2, (x, y) => x + y).ToList(); // { "A1", "B2", "C3" }
That's not concatenation - that's matching two sequences pairwise. You do it with LINQ's Zip method:
Zip applies a specified function to the corresponding elements of two sequences, producing a sequence of the results.
var res = from.Zip(to, (a,b) => a + b).ToList();
If item's count are equal in both lists then you can do:
var list3 = list1.Select((item, index) => item + list2[index]).ToList();

is there a better way to order a IEnumerable to match an arbitrary ordering?

I came across this problem at work, and though I have a solution, I can't help feeling there is a more elegant way. The use of List.IndexOf() stands out as a bit hacky to me.
I have to sort a collection of BreakdownItems by credit rating. Credit ratings don't follow alphabetical order so I've treated them as just having some arbitrary, non logical order.
IEnumerable<BreakdownItem> unsortedCreditRatings = new List<BreakdownItem>
{
new BreakdownItem{ Name = "CCC", Weight=20d},
new BreakdownItem{ Name = "AA", Weight=20d},
new BreakdownItem{ Name = "AAA", Weight=10d},
new BreakdownItem{ Name = "B", Weight=50d},
};
var sortOrder = new List<string>
{ "AAA", "AA", "A", "BBB", "BB", "B", "CCC", "below CCC" };
var sortedRatingBreakdown = unsortedCreditRatings
.OrderBy(item => sortOrder.IndexOf(item.Name));
Can you make the credit rating an enum instead of a string? You could then assign those enum values the correct sort order.
As I alluded in my comment above, having the multiple credit ratings as a string can cause data integrity issues, I'd move those to an enum instead, such as:
public enum CreditRatings
{
AAA,
AA,
A,
BBB,
BB,
B,
CCC,
etc
}
And you instead store that in your BreakdownItem, you can do:
var sortedRatingBreakdown = unsortedCreditRatings.OrderBy(item => item.Rating);
If you must store them as a string, and can't use an enum you could consider using a Dictionary<string, int> or something like that to store your ordering so that you at least get O(1) lookup time:
var ratingOrders = new Dictionary<string,int>
{
{ "AAA", 1 },
{ "AA", 2 },
{ "A", 3 },
etc...
};
Then you can order by the results of the dictionary:
var sortedRatingBreakdown = unsortedCreditRatings.OrderBy(item => ratingOrders[item.Name]);
Enumerable.Join preserves the order of the first (or outer) sequence. If you are not keen on the enum approach, you can use this and without needing to do OrderBy explicitly.
var orderedRatings = from item in sortOrder
join rating in unsortedCreditRatings
on item equals rating.Name
select rating;

Categories