Create a Tuple from 2 Lists in C# - c#

I have 2 lists below:
// From Below List I want to retrieve it Text of each plan like: foreach(var plan in AvailablePlanNames) and then use plan.Text property.
private IList<IWebElement> AvailablePlanNames =>
_webDriver.FindElementsWithWait(By.XPath("//div[#class='asc-checkbox-group']"));
// From Below list I am going to pull 2 properties like:
foreach(var planDetail in PlanDetails), fetch:
planDetail.GetAttribute("id") and planDetail.GetAttribute("checked")
private IList<IWebElement> PlansDetails => _webDriver.FindElementsWithWait(By.XPath("//div[#class='asc-checkbox-group']/input"));
So first list has: ["Plan A", "Plan B", "Plan C"]
Second list can be: [[Plan A ID , true], [Plan B ID, false], [Plan C ID, null]]
I am trying to make a single list of it like Tuple which will have:
Tuple((Plan A, Plan A ID, true), (Plan B, Plan B ID, false), (Plan C, Plan C ID, null))
I searched several posts and tried multiple solutions but did not get it working.
public IList<string> GetAvailablePlans()
{
var list = new List<(string Text, string, string)>();
foreach (var planName in AvailablePlanNames)
{
foreach (var planDetail in PlansDetails)
{
list.Add((planName.Text,
planDetail.GetAttribute("id"),
planDetail.GetAttribute("checked")));
}
}
return (IList<string>)list;
}

Something like this? This code assumes that PlansDetails[i] is corresponding to the AvailablePlanNames[i] for specified i. If this is not true, you will also need to find corresponding data in PlansDetails for each AvailablePlanNames[i].
The code also use correct return value (list of tuples instead of list of strings).
public List<(string Text, string, bool)> GetAvailablePlans()
{
var list = new List<(string Text, string, string)>();
for (var i; i<AvailablePlanNames.Length;i++)
{
list.Add((AvailablePlanNames[i].Text,
PlansDetails[i].GetAttribute("id"),
PlansDetails[i].GetAttribute("checked")));
}
return list;
}

Instead of looping, you can use LINQ's Zip to combine items from two IEnumerable<T>s. :
var results=AvailablePlanNames
.Zip(PlanDetails)
.Select((first,second)=>
( Text: first.Text,
Id: second.GetAttribute("id"),
Check:second.GetAttrbute("checked")
))
.ToList();

Related

Merge data from two arrays or something else

How to combine Id from the list I get from file /test.json and id from list ourOrders[i].id?
Or if there is another way?
private RegionModel FilterByOurOrders(RegionModel region, List<OurOrderModel> ourOrders, MarketSettings market, bool byOurOrders)
{
var result = new RegionModel
{
updatedTs = region.updatedTs,
orders = new List<OrderModel>(region.orders.Count)
};
var json = File.ReadAllText("/test.json");
var otherBotOrders = JsonSerializer.Deserialize<OrdersTimesModel>(json);
OtherBotOrders = new Dictionary<string, OrderTimesInfoModel>();
foreach (var otherBotOrder in otherBotOrders.OrdersTimesInfo)
{
//OtherBotOrders.Add(otherBotOrder.Id, otherBotOrder);
BotController.WriteLine($"{otherBotOrder.Id}"); //Output ID orders to the console works
}
foreach (var order in region.orders)
{
if (ConvertToDecimal(order.price) < 1 || !byOurOrders)
{
int i = 0;
var isOurOrder = false;
while (i < ourOrders.Count && !isOurOrder)
{
if (ourOrders[i].id.Equals(order.id, StringComparison.InvariantCultureIgnoreCase))
{
isOurOrder = true;
}
++i;
}
if (!isOurOrder)
{
result.orders.Add(order);
}
}
}
return result;
}
OrdersTimesModel Looks like that:
public class OrdersTimesModel
{
public List<OrderTimesInfoModel> OrdersTimesInfo { get; set; }
}
test.json:
{"OrdersTimesInfo":[{"Id":"1"},{"Id":"2"}]}
Added:
I'll try to clarify the question:
There are three lists with ID:
First (all orders): region.orders, as order.id
Second (our orders): ourOrders, as ourOrders[i].id in a while loop
Third (our orders 2): from the /test.json file, as an array {"Orders":[{"Id":"12345..."...},{"Id":"12345..." ...}...]}
There is a foreach in which there is a while, where the First (all orders) list and the Second (our orders) list are compared. If the id's match, then these are our orders: isOurOrder = true;
Accordingly, those orders that isOurOrder = false; will be added to the result: result.orders.Add(order)
I need:
So that if (ourOrders[i].id.Equals(order.id, StringComparison.InvariantCultureIgnoreCase)) would include more Id's from the Third (our orders 2) list.
Or any other way to do it?
You should be able to completely avoid writing loops if you use LINQ (there will be loops running in the background, but it's way easier to read)
You can access some documentation here: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/introduction-to-linq-queries
and you have some pretty cool extension methods for arrays: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=net-6.0 (these are great to get your code easy to read)
Solution
unsing System.Linq;
private RegionModel FilterByOurOrders(RegionModel region, List<OurOrderModel> ourOrders, MarketSettings market, bool byOurOrders)
{
var result = new RegionModel
{
updatedTs = region.updatedTs,
orders = new List<OrderModel>(region.orders.Count)
};
var json = File.ReadAllText("/test.json");
var otherBotOrders = JsonSerializer.Deserialize<OrdersTimesModel>(json);
// This line should get you an array containing
// JUST the ids in the JSON file
var idsFromJsonFile = otherBotOrders.Select(x => x.Id);
// Here you'll get an array with the ids for your orders
var idsFromOurOrders = ourOrders.Select(x => x.id);
// Union will only take unique values,
// so you avoid repetition.
var mergedArrays = idsFromJsonFile.Union(idsFromOurOrders);
// Now we just need to query the region orders
// We'll get every element that has an id contained in the arrays we created earlier
var filteredRegionOrders = region.orders.Where(x => !mergedArrays.Contains(x.id));
result.orders.AddRange(filteredRegionOrders );
return result;
}
You can add conditions to any of those actions (like checking for order price or the boolean flag you get as a parameter), and of course you can do it without assigning so many variables, I did it that way just to make it easier to explain.

List function, how to get an average of scores for each name- c# console application

I have a list function on a console application on C#. This list function has different items where they look something like 'matt,5' 'matt,7' 'jack,4' 'jack,8' etc...
I want to be able to combine all of the names where I only see their name written once but the number after them are averaged out so it would be like 'jack,5+7/2' which would then display as 'jack,6'.
So far I have this...
currentFileReader = new StreamReader(file);
List<string> AverageList = new List<string>();
while (!currentFileReader.EndOfStream)
{
string text = currentFileReader.ReadLine();
AverageList.Add(text.ToString());
}
AverageList.GroupBy(n => n).Any(c => c.Count() > 1);
Not really sure where to go from here.
What you need is to Split your each string item on , and then group by first element of the returned array and average second element of the array (after parsing it to int) something like:
List<string> AverageList = new List<string> { "matt,5", "matt,7", "jack,4", "jack,8" };
var query = AverageList.Select(s => s.Split(','))
.GroupBy(sp => sp[0])
.Select(grp =>
new
{
Name = grp.Key,
Avg = grp.Average(t=> int.Parse(t[1])),
});
foreach (var item in query)
{
Console.WriteLine("Name: {0}, Avg: {1}", item.Name, item.Avg);
}
and it will give you:
Name: matt, Avg: 6
Name: jack, Avg: 6
But, a better option would be to use a class with Name and Score properties instead of comma separated string values.
(The code above doesn't check for invalid input values).
Firstly you will want to populate your unformatted data into a List, as you can see I called it rawScores. You could then Split each line by the comma delimiting them. You can then check to see if an existing person is in your Dictionary and add his score to it, or if not create a new person.
After that you would simply have to generate the Average of the List.
Hope this helps!
var scores = new Dictionary<string, List<int>>();
var rawScores = new List<string>();
rawScores.ForEach(raw =>
{
var split = raw.Split(',');
if (scores.Keys.All(a => a != split[0]))
{
scores.Add(split[0], new List<int> {Convert.ToInt32(split[1])});
}
else
{
var existing = scores.FirstOrDefault(f => f.Key == split[0]);
existing.Value.Add(Convert.ToInt32(split[1]));
}
});

get unique values from query to build Dictionary

I want to build a combobox with key->postal and value->city to use as filter for my accomodations.
To limit the number of items in the list I only use the postals I have used when filling up the table tblAccomodations.
For now I do not use a relational table with postals and city's although I'm thinking about an update later on.
Here I build my dictionary:
public static Dictionary<int, string> getPostals()
{
Dictionary<int, string> oPostals = new Dictionary<int, string>();
using (DBReservationDataContext oReservation = new DBReservationDataContext())
{
var oAllPostals = (from oAccomodation in oReservation.tblAccomodations
orderby oAccomodation.Name ascending
select oAccomodation);
foreach (tblAccomodation item in oAllPostals)
{
oPostals.Add(int.Parse(item.Postal), item.City);
}
}
return oPostals;
}
As expected I got an error: some Accomodations are located in the same city, so there are double values for the key. So how can I get a list of unique cities and postals (as key)?
I tried to use
select oAccomodation.Postal.Distinct()
but that didn't work either.
UPDATE: I have found the main problem. There are multiple cities with the same postal ("Subcity"). So I'm gonna filter on "City" and not on "Postal".
I think your looking for 'Distinct'. Gather your list of all postals and then return myPostals.Distinct().
Hope than helps.
change
foreach (tblAccomodation item in oAllPostals)
{
oPostals.Add(int.Parse(item.Postal), item.City);
}
to
foreach (tblAccomodation item in oAllPostals.Distinct(x=>x..Postal)
{
if(!oPostals.ContainsKey(int.Parse(item.Postal)))
oPostals.Add(int.Parse(item.Postal), item.City);
}
BTW, if you have multiple cities in one postal (I am not sure if it is possible in your domain), which one you want to see?
If any of cities will do, then it is easy to just get the first one per postal:
var oAllPostals = oReservation.tblAccomodations
.OrderBy(x=>x.Name)
.ToLookup(x=>x.Postal, x=>x.City)
.ToDictionary(x=>x.Key, x.First());
In the same example if you do .ToList() or even .Distinct().ToList() instead of .First() you will have all of cities in the dictionary of Dictionary<Postal, List<City>>.
Assuming the combination of postal + city is unique you could do the following:
public static Dictionary<int, string> getPostals()
{
Dictionary<int, string> oPostals = new Dictionary<int, string>();
using (DBReservationDataContext oReservation = new DBReservationDataContext())
{
var oAllPostals = (from oAccomodation in oReservation.tblAccomodations
orderby oAccomodation.Name ascending
select oAccomodation);
foreach (tblAccomodation item in oAllPostals)
{
oPostals.Add((item.Postal + item.City).GetHashCode(), item.Postal + " " + item.City);
}
}
return oPostals;
}
Edit:
If you want to use the selected value from the drop box then you can use the following:
public static Dictionary<int, Tuple<string, string>> getPostals()
{
Dictionary<int, string> oPostals = new Dictionary<int, string>();
using (DBReservationDataContext oReservation = new DBReservationDataContext())
{
var oAllPostals = (from oAccomodation in oReservation.tblAccomodations
orderby oAccomodation.Name ascending
select oAccomodation);
foreach (tblAccomodation item in oAllPostals)
{
oPostals.Add((item.Postal + item.City).GetHashCode(), new Tuple<string, string>(item.Postal, item.City));
}
}
return oPostals;
}
The way you bind the following depends on whether you're using asp.net, winforms etc. Here's an example for winforms.
Using .containkey will exclude [1 (postal key) to n (cities relation)]. i.e since Key already exists next city (with the same postal key ) will not get into your dictionary.
However, if you want to map your postal to list of cities, you can represent a dictionary that can contain a collection of values like the following:
Dictionary < String[Postal]> , List < Cities>>
This way you'll have a dictionary that can have multiple values.

Update One List of Objects Attribute with Other List using LINQ

I have a scenario as think
class a
{
String Username;
String val;
}
List<a> lst = new List<a>();
List<a> lstnew = new List<a>();
What i required is to that in lstnew i have some updated values in val Attribute (Only in Several Objects) , what i required is to update the lst with updated values in lstnew as the Username Attribute using LINQ
You can join the two lists on UserName, and then update the Values in the first list with those in the second.
For example, given this class and lists:
public class a
{
public string UserName { get; set; }
public string Value { get; set; }
}
List<a> list = new List<a>
{
new a { UserName = "Perry", Value = "A" },
new a { UserName = "Ferb", Value = "B" },
new a { UserName = "Phineas", Value = "C" }
};
List<a> newList = new List<a>
{
new a { UserName = "Phineas", Value = "X" },
new a { UserName = "Ferb", Value = "Y" },
new a { UserName = "Candace", Value = "Z" }
};
You can join to get the elements with common UserNames:
var common = from a1 in list
join a2 in newList on a1.UserName equals a2.UserName
select new { A1 = a1, A2 = a2 };
At this point, if I understand you correctly, you want to update the elements from the original list:
foreach(var c in common)
{
c.A1.Value = c.A2.Value;
}
at which point the elements in list look like:
UserName Value
-----------------
Perry A
Ferb Y
Phineas X
It sounds like you have two lists. One of which is named lst and contains a full list of usernames and a second one named lstnew that contains a list of usernames who have had their val property updated. I suggest unioning the untouched usernames with the ones that have been updated. This represents the most LINQ-friendly solution I can think of.
var updatedList = Enumerable.Union(
lst.Where(x => !lstnew.Any(y => y.Username == x.Username)),
lstnew).ToList();
you should be able to use the .Zip() method to execute this.
lst.Zip(lstNew, (orig, new) => {
orig.Username = new.Username;
return orig;
});
the idea that you are getting each pair together, then instead of returning a new one, changing the orig.Username value and return the orig.
This should also do the trick. Zip method, propsed by Alastair Pitts assumes that both collections have the same order of elements and each element from first list has correspondent element in second list. My approach is more generic, it simply looks for corresponding element by comparing Username property. Still it assumes that for each element in lstNew there is corresponding element in lst.
lstNew.ForEach(new => lst.First(orig => orig.Username == new.Username).val = new.val);
I know this is an old question but a more elegant solution that I have developed, which is a slight improvement over the one given by #JeffOgata would be:
var newList= lst.GroupJoin(lstnew ,
i => i.UserName ,
j => j.UserName ,
(i, j) => j.FirstOrDefault()?? i );
Where lst is the original list and lstnew is the new list.
This will just replace the entire object in the first list with the corresponding object in the second list (the join) if one exists.
It is a slight improvement over the answer given by #JeffOgata
The result is the same.
If you have complex objects then iterating through each object then going through all the properties was a problem, simply replacing the old object with the new one was quicker.
This hopefully will help someone.

C# dedupe List based on split

I'm having a hard time deduping a list based on a specific delimiter.
For example I have 4 strings like below:
apple|pear|fruit|basket
orange|mango|fruit|turtle
purple|red|black|green
hero|thor|ironman|hulk
In this example I should want my list to only have unique values in column 3, so it would result in an List that looks like this,
apple|pear|fruit|basket
purple|red|black|green
hero|thor|ironman|hulk
In the above example I would have gotten rid of line 2 because line 1 had the same result in column 3. Any help would be awesome, deduping is tough in C#.
how i'm testing this:
static void Main(string[] args)
{
BeginListSet = new List<string>();
startHashSet();
}
public static List<string> BeginListSet { get; set; }
public static void startHashSet()
{
string[] BeginFileLine = File.ReadAllLines(#"C:\testit.txt");
foreach (string begLine in BeginFileLine)
{
BeginListSet.Add(begLine);
}
}
public static IEnumerable<string> Dedupe(IEnumerable<string> list, char seperator, int keyIndex)
{
var hashset = new HashSet<string>();
foreach (string item in list)
{
var array = item.Split(seperator);
if (hashset.Add(array[keyIndex]))
yield return item;
}
}
Something like this should work for you
static IEnumerable<string> Dedupe(this IEnumerable<string> input, char seperator, int keyIndex)
{
var hashset = new HashSet<string>();
foreach (string item in input)
{
var array = item.Split(seperator);
if (hashset.Add(array[keyIndex]))
yield return item;
}
}
...
var list = new string[]
{
"apple|pear|fruit|basket",
"orange|mango|fruit|turtle",
"purple|red|black|green",
"hero|thor|ironman|hulk"
};
foreach (string item in list.Dedupe('|', 2))
Console.WriteLine(item);
Edit: In the linked question Distinct() with Lambda, Jon Skeet presents the idea in a much better fashion, in the form of a DistinctBy custom method. While similar, his is far more reusable than the idea presented here.
Using his method, you could write
var deduped = list.DistinctBy(item => item.Split('|')[2]);
And you could later reuse the same method to "dedupe" another list of objects of a different type by a key of possibly yet another type.
Try this:
var list = new string[]
{
"apple|pear|fruit|basket",
"orange|mango|fruit|turtle",
"purple|red|black|green",
"hero|thor|ironman|hulk "
};
var dedup = new List<string>();
var filtered = new List<string>();
foreach (var s in list)
{
var filter = s.Split('|')[2];
if (dedup.Contains(filter)) continue;
filtered.Add(s);
dedup.Add(filter);
}
// Console.WriteLine(filtered);
Can you use a HashSet instead? That will eliminate dupes automatically for you as they are added.
May be you can sort the words with delimited | on alphabetical order. Then store them onto grid (columns). Then when you try to insert, just check if there is column having a word which starting with this char.
If LINQ is an option, you can do something like this:
// assume strings is a collection of strings
List<string> list = strings.Select(a => a.Split('|')) // split each line by '|'
.GroupBy(a => a[2]) // group by third column
.Select(a => a.First()) // select first line from each group
.Select(a => string.Join("|", a))
.ToList(); // convert to list of strings
Edit (per Jeff Mercado's comment), this can be simplified further:
List<string> list =
strings.GroupBy(a => a.split('|')[2]) // group by third column
.Select(a => a.First()) // select first line from each group
.ToList(); // convert to list of strings

Categories

Resources