I have a lookup like ths:
Lookup<String, pages> plsBase = (Lookup<String, pages>)(Query<pages>($#"Select ...").ToLookup(s => s.ip, o => o));
It is very fast when I access it by key, but the problem is that I need to access it with StartsWith().
When I use StartsWith() like below, the performance is comparable to a regular List
var pls = plsBase.Where(x => (x.Key.StartsWith(classBIp, StringComparison.Ordinal))).SelectMany(x => x).ToList();
The question is what can be done to improve the performance when using StartsWith()?
This answer assumes that classBIp is of a fixed length.
Option 1: Intermediate lookup [UPDATED]
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace LookupTest
{
public class Tests
{
[Test]
public void IntermediateLookupTest()
{
var pageInfos = new Dictionary<string, string>
{
{ "Home", "home.html" },
{ "About", "about.html" },
{ "Fineprint", "fineprint.html" },
{ "Finish", "finish.html" },
{ "Above", "above.html" }
};
// Corresponds to OP: plsBase = (Lookup<String, pages>)(Query<pages>($#"Select ...").ToLookup(s => s.ip, o => o));
Lookup<string, string> plsBase = (Lookup<string, string>)pageInfos.ToLookup(k => k.Key, v => v.Value);
Lookup<string, string> intermediateLookup = (Lookup<string, string>)pageInfos.ToLookup(k => k.Key.Substring(0, 3), v => v.Key);
var classBIp = "Abo";
var result = new List<string>();
foreach (var plsBaseKey in intermediateLookup[classBIp])
{
result.AddRange(plsBase[plsBaseKey]);
}
Assert.AreEqual(2, result.Count);
Assert.True(result.Contains("about.html"));
Assert.True(result.Contains("above.html"));
}
}
}
Option 2: Compare a substring
var bipLength = classBip.Length;
var pls = plsBase.Where(x =>
(x.Key
.Substring(0, bipLength)
.Equals(classBIp, StringComparison.Ordinal)))
.SelectMany(x => x)
.ToList();
You might want to time both options to see which one performs better.
Related
Suppose I have a list of strings [city01, city01002, state02, state03, city04, statebg, countryqw, countrypo]
How do I group them in a dictionary of <string, List<Strings>> like
city - [city01, city04, city01002]
state- [state02, state03, statebg]
country - [countrywq, countrypo]
If not code, can anyone please help with how to approach or proceed?
As shown in other answers you can use the GroupBy method from LINQ to create this grouping based on any condition you want. Before you can group your strings you need to know the conditions for how a string is grouped. It could be that it starts with one of a set of predefined prefixes, grouped by whats before the first digit or any random condition you can describe with code. In my code example the groupBy method calls another method for every string in your list and in that method you can place the code you need to group the strings as you want by returning the key to group the given string under. You can test this example online with dotnetfiddle: https://dotnetfiddle.net/UHNXvZ
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<string> ungroupedList = new List<string>() {"city01", "city01002", "state02", "state03", "city04", "statebg", "countryqw", "countrypo", "theFirstTown"};
var groupedStrings = ungroupedList.GroupBy(x => groupingCondition(x));
foreach (var a in groupedStrings) {
Console.WriteLine("key: " + a.Key);
foreach (var b in a) {
Console.WriteLine("value: " + b);
}
}
}
public static string groupingCondition(String s) {
if(s.StartsWith("city") || s.EndsWith("Town"))
return "city";
if(s.StartsWith("country"))
return "country";
if(s.StartsWith("state"))
return "state";
return "unknown";
}
}
You can use LINQ:
var input = new List<string>()
{ "city01", "city01002", "state02",
"state03", "city04", "statebg", "countryqw", "countrypo" };
var output = input.GroupBy(c => string.Join("", c.TakeWhile(d => !char.IsDigit(d))
.Take(4))).ToDictionary(c => c.Key, c => c.ToList());
i suppose you have a list of references you are searching in the list:
var list = new List<string>()
{ "city01", "city01002", "state02",
"state03", "city04", "statebg", "countryqw", "countrypo" };
var tofound = new List<string>() { "city", "state", "country" }; //references to found
var result = new Dictionary<string, List<string>>();
foreach (var f in tofound)
{
result.Add(f, list.FindAll(x => x.StartsWith(f)));
}
In the result, you have the dictionary wanted. If no value are founded for a reference key, the value of key is null
Warning: This answer has a combinatorial expansion and will fail if your original string set is large. For 65 words I gave up after running for a couple of hours.
Using some IEnumerable extension methods to find Distinct sets and to find all possible combinations of sets, you can generate a group of prefixes and then group the original strings by these.
public static class IEnumerableExt {
public static bool IsDistinct<T>(this IEnumerable<T> items) {
var hs = new HashSet<T>();
foreach (var item in items)
if (!hs.Add(item))
return false;
return true;
}
public static bool IsEmpty<T>(this IEnumerable<T> items) => !items.Any();
public static IEnumerable<IEnumerable<T>> AllCombinations<T>(this IEnumerable<T> start) {
IEnumerable<IEnumerable<T>> HelperCombinations(IEnumerable<T> items) {
if (items.IsEmpty())
yield return items;
else {
var head = items.First();
var tail = items.Skip(1);
foreach (var sequence in HelperCombinations(tail)) {
yield return sequence; // Without first
yield return sequence.Prepend(head);
}
}
}
return HelperCombinations(start).Skip(1); // don't return the empty set
}
}
var keys = Enumerable.Range(0, src.Count - 1)
.SelectMany(n1 => Enumerable.Range(n1 + 1, src.Count - n1 - 1).Select(n2 => new { n1, n2 }))
.Select(n1n2 => new { s1 = src[n1n2.n1], s2 = src[n1n2.n2], Dist = src[n1n2.n1].TakeWhile((ch, n) => n < src[n1n2.n2].Length && ch == src[n1n2.n2][n]).Count() })
.SelectMany(s1s2d => new[] { new { s = s1s2d.s1, s1s2d.Dist }, new { s = s1s2d.s2, s1s2d.Dist } })
.Where(sd => sd.Dist > 0)
.GroupBy(sd => sd.s.Substring(0, sd.Dist))
.Select(sdg => sdg.Distinct())
.AllCombinations()
.Where(sdgc => sdgc.Sum(sdg => sdg.Count()) == src.Count)
.Where(sdgc => sdgc.SelectMany(sdg => sdg.Select(sd => sd.s)).IsDistinct())
.OrderByDescending(sdgc => sdgc.Sum(sdg => sdg.First().Dist)).First()
.Select(sdg => sdg.First())
.Select(sd => sd.s.Substring(0, sd.Dist))
.ToList();
var groups = src.GroupBy(s => keys.First(k => s.StartsWith(k)));
I have two string list , first list contains Ids for corresponding second list each element. Definition of the list,
IdsList ["Id1","Id2"]
ShippingsNoList ["n1,n2..","t1,t2"]
means that n1,n2->Id1, t1,t2->Id2
IdsList format -> A-date-B
ShippingNumbersList format-> number1,number2,etc.
My purpose combine two list and return result as string. If I find ShippingNumber which equals another ShippingNumber(s) and their Id's date should also be matched, then I should take Shipping Number and related Ids. One shipping Number may be already assigned more than one Id's which date is same.
Example:
IdsList=["A-28.03.18-B",
"S-17.05.18-G",
"L-17.05.18-P",
"M-28.03.18-T",
"B-17.05.18-U"]
ShippingNumbersList=["100,200,300",
"100,900",
"200,300,100",
"100,900,300",
"100,300"]
Expected Result:
100-> A-28.03.18-B,M-28.03.18-T
300-> A-28.03.18-B,M-28.03.18-T
100-> S-17.05.18-G,L-17.05.18-P,B-17.05.18-U
300-> L-17.05.18-P, B-17.05.18-U
Try this LINQ "beauty".
var idsList = new string[]
{
"A-28.03.18-B",
"S-17.05.18-G",
"L-17.05.18-P",
"M-28.03.18-T",
"B-17.05.18-U"
};
var shippingNumbersList = new string[]
{
"100,200,300",
"100,900",
"200,300,100",
"100,900,300",
"100,300"
};
var data = idsList
.Zip(shippingNumbersList, (x, y) =>
{
//parse the entry of the idsList ('x') in a dateTime
var date = DateTime.Parse(x.Split("-")[1]); //<-- may need to use DateTime.ParseExact(x.Split('-')[1], "dd.MM.yy", CultureInfo.InvariantCulture) - depending on the culture you are using, this will now work on any machine
//parse the entry of the shippingNumbersList ('y') in a IEnumerable<int>
var numbers = y.Split(",").Select(int.Parse);
//make a IEnumerable of the two different data, consisting of (Id, Date, ShippingNumber) <- a single ShippingNumber, thats why we call numbers.Select
return numbers.Select(number => (Id: x, Date: date, ShippingNumber: number));
}) //<-- use ZIP to combine the two lists together
.SelectMany(x => x) //<-- use SELECTMANY to get a flat list of each "id" with the x number of "shippingNumberList"
.GroupBy(x => (Date: x.Date, ShippingNumber: x.ShippingNumber)) //<-- use GROUPBY for the Date and ShippingNumber
.Where(x => x.Count() > 1) //<-- use WHERE to filter those who only have 1 entry in a group consisting of Date+ShippingNumber
.Select(x => x.Key.ShippingNumber + "-> " + string.Join(",", x.Select(y => y.Id))) //<-- use SELECT to resolve the group to a string, there the Key is the combined Date + ShippingNumber and the Value is the flatList of that group
.ToList(); //<-- use TOLIST to make a List out of the IEnumerable
Had to fix some stuff for it to run on dotnetfiddle, but here you go:
https://dotnetfiddle.net/bKpUDz
Here is another solution which is tested :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
using System.Text.RegularExpressions;
namespace ConsoleApplication100
{
class Program
{
static void Main(string[] args)
{
List<string> IdsList = new List<string>() {
"A-28.03.18-B",
"S-17.05.18-G",
"L-17.05.18-P",
"M-28.03.18-T",
"B-17.05.18-U"
};
List<string> ShippingNumbersList = new List<string>() {
"100,200,300",
"100,900",
"200,300,100",
"100,900,300",
"100,300"
};
var results = Shipping.MergeList(IdsList, ShippingNumbersList);
}
}
public class Shipping
{
public static object MergeList(List<string> ids, List<string> numbers)
{
string pattern = #"\w-(?'day'[^\.]+)\.(?'month'[^\.]+)\.(?'year'[^-]+)";
List<KeyValuePair<DateTime, string>> idDates = new List<KeyValuePair<DateTime,string>>();
foreach(string id in ids)
{
Match match = Regex.Match(id,pattern);
idDates.Add(new KeyValuePair<DateTime, string>( new DateTime(2000 + int.Parse(match.Groups["year"].Value), int.Parse(match.Groups["month"].Value), int.Parse(match.Groups["day"].Value)), id));
}
var groups = idDates.SelectMany((x, i) => numbers[i].Split(new char[] {','}).Select(y => new { idDate = x, number = y })).ToList();
var groupDates = groups.GroupBy(x => new { date = x.idDate.Key, number = x.number }).ToList();
var results = groupDates.Select(x => new { number = x.Key.number, ids = x.Select(y => y.idDate.Value).ToList() }).ToList();
return results;
}
}
}
Data :
var IdsList =new string[] {"A-28.03.18-B",
"S-17.05.18-G",
"L-17.05.18-P",
"M-28.03.18-T",
"B-17.05.18-U" };
var ShippingNumbersList =new string[] {"100,200,300",
"100,900",
"200,300,100",
"100,900,300",
"100,300" };
Making resuts:
//normalizing data and make a list of joined columns
var normalizedlist = IdsList
.Select((Ids, index) => new { Ids = Ids, ShippingNumbers = ShippingNumbersList[index].Split(',') })
.ToList();
//for each distinct ShippingNumber find and write respective Id
foreach (var ShippingNumber in normalizedlist.SelectMany(x=>x.ShippingNumbers).Distinct())
{
//fitering and then grouping by date
var filtered = normalizedlist.Where(y => y.ShippingNumbers.Contains(ShippingNumber))
.GroupBy(y => y.Ids.Split('-')[1])
.Where(y => y.Count() > 1)
.Select(y => y.Select(z=>z.Ids));
foreach (var date in filtered)
{
Console.WriteLine($"{ShippingNumber}>>{string.Join(",",date.ToArray())}");
}
}
Output:
100>>A-28.03.18-B,M-28.03.18-T
100>>S-17.05.18-G,L-17.05.18-P,B-17.05.18-U
300>>A-28.03.18-B,M-28.03.18-T
300>>L-17.05.18-P,B-17.05.18-U
having some trouble writing the following code to some nicer/less lines :)
any one have the good solution?
//custom implementation for popular filters
var popularFilter = new Dictionary<string, int>();
foreach (var car in allFilteredCars)
{
foreach (var offering in car.Offerings)
{
if (popularFilter.ContainsKey(offering))
popularFilter[offering] = popularFilter[offering] + 1;
else
popularFilter.Add(offering, 1);
}
}
categories.Add(new Category
{
Name = "popular",
Code = "popular",
Values = popularFilter.Select(p => new Value
{
Code = p.Key,
Name = p.Key,
Count = p.Value
}).ToList()
});
If it is possible i want i directly to add it in the categories list.
car.offerings = list<string>
so basicly something like:
Categories.Add(allFilteredCars.SelectMany(
c => c.Offerings.Select(
o => new {
something magical here}
.Select(a =>
new Category{
code.. etc etc..}
));
It looks like you just want to do a SelectMany to get the offerings, then group them and select the Count.
categories.Add(new Category
{
Name = "popular",
Code = "popular",
Values = allFilteredCars.SelectMany(c => c.Offerings)
.GroupBy(o => o)
.Select(grp => new Value
{
Code = grp.Key,
Name = grp.Key,
Count = grp.Count()
}).ToList()
});
Your non linq code already looks quite fine.
You can create your dictionary with linq by using a GroupBy & ToDictionary:
var dictionary = offerings
.GroupBy(x => x)
.ToDictionary(x => x.Key, x => x.Count());
I am trying to loop through two concurrent dictionaries like the code below, however I want to use a lambda expression instead
foreach (var s in sb_eventdata)
{
foreach (var f in final_data)
{
if (s.Value.Car.Equals(f.Value.Car))
{
Console.Writeline("Found!");
}
}
}
var values = sb_eventdata.Where(k => k.Value.Hometeam.Contains( ???? );
I'm really not sure what to pass into contains, I assume another lambda expression but what?
The closest linq expression to your loops would be:
var sb_eventdata = new Dictionary<string, string>{ {"a", "a"}, {"b", "b"}};
var final_data = new Dictionary<string, string>{{"a", "a"}, {"b", "b"}, {"c","c"}};
var result =
// first loop
sb_eventdata.Select(s =>
// second loop
final_data.Where(f => s.Value.Equals(f.Value)))
// flatten results (returns results from the first dictionary)
.SelectMany(x => x);
You can use a linq Intersect function to find like items in a list.
Then display all like items.
var foo = sb_eventdata.Select(o => o.Value.Car).Intersect(final_data.Select(o => o.Value.Car));
foreach (var item in foo)
{
Console.Writeline("Found!");
}
I think your friend is the Join() method.
In "LinqPad style":
void Main()
{
var a = new[] {
new Car("Opel",200),
new Car("Volkswagen",300),
new Car("Audi", 500)
};
var b = new[] {
new Car("Peugeot", 180),
new Car("Seat", 300),
new Car("Volvo", 480)
};
var c = a.Join(b, ak => ak.Value, bk => bk.Value, (ak,bk) => new {A=ak.Name,B=bk.Name,ak.Value});
c.Dump();
}
// Define other methods and classes here
class Car {
public string Name;
public int Value;
public Car (string name, int value) {
Name = name;
Value = value;
}
}
If you just want to know if both dictionary share at least one value, you can use Any:
if(sb_eventdata.Any(s =>
final_data.Any(f => s.Value.Car.Equals(f.Value.Car))))
Console.WriteLine("Found!");
or with Contains:
if(sb_eventdata.Any(s => final_data.ContainsValue(s.Value)))
Console.WriteLine("Found!");
and if you want to count how many of sb_eventdata are in final_data:
sb_eventdata.Where(s => final_data.ContainsValue(s.Value)).Count();
Is there an elegant way of converting this string array:
string[] a = new[] {"name", "Fred", "colour", "green", "sport", "tennis"};
into a Dictionary such that every two successive elements of the array become one {key, value} pair of the dictionary (I mean {"name" -> "Fred", "colour" -> "green", "sport" -> "tennis"})?
I can do it easily with a loop, but is there a more elegant way, perhaps using LINQ?
var dict = a.Select((s, i) => new { s, i })
.GroupBy(x => x.i / 2)
.ToDictionary(g => g.First().s, g => g.Last().s);
Since it's an array I would do this:
var result = Enumerable.Range(0,a.Length/2)
.ToDictionary(x => a[2 * x], x => a[2 * x + 1]);
How about this ?
var q = a.Zip(a.Skip(1), (Key, Value) => new { Key, Value })
.Where((pair,index) => index % 2 == 0)
.ToDictionary(pair => pair.Key, pair => pair.Value);
I've made a simular method to handle this type of request. But since your array contains both keys and values i think you need to split this first.
Then you can use something like this to combine them
public static IDictionary<T, T2> ZipMyTwoListToDictionary<T, T2>(IEnumerable<T> listContainingKeys, IEnumerable<T2> listContainingValue)
{
return listContainingValue.Zip(listContainingKeys, (value, key) => new { value, key }).ToDictionary(i => i.key, i => i.value);
}
a.Select((input, index) = >new {index})
.Where(x=>x.index%2!=0)
.ToDictionary(x => a[x.index], x => a[x.index+1])
I would recommend using a for loop but I have answered as requested by you.. This is by no means neater/cleaner..
public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
{
bool shouldReturn = true;
foreach (T item in source)
{
if (shouldReturn)
yield return item;
shouldReturn = !shouldReturn;
}
}
public static Dictionary<T, T> MakeDictionary<T>(IEnumerable<T> source)
{
return source.EveryOther()
.Zip(source.Skip(1).EveryOther(), (a, b) => new { Key = a, Value = b })
.ToDictionary(pair => pair.Key, pair => pair.Value);
}
The way this is set up, and because of the way Zip works, if there are an odd number of items in the list the last item will be ignored, rather than generation some sort of exception.
Note: derived from this answer.
IEnumerable<string> strArray = new string[] { "name", "Fred", "colour", "green", "sport", "tennis" };
var even = strArray.ToList().Where((c, i) => (i % 2 == 0)).ToList();
var odd = strArray.ToList().Where((c, i) => (i % 2 != 0)).ToList();
Dictionary<string, string> dict = even.ToDictionary(x => x, x => odd[even.IndexOf(x)]);