Concat all strings inside a List<List<string>> using LINQ - c#

My question is almost same as this one, but the List dimension is n.
How to concat all strings inside a List<List<List...<string>> (n dimension list) using LINQ ?
NOTE: Interested for both cases, n is known or unknown

Since the linked question is tagged as c# so i add this answer with c# code.
If the number of nested lists are known You have to use SelectMany() over and over to unwrap all the nested lists to sequence of chars. then make string out of that sequence.
List<List<List<string>>> nestedList = new List<List<List<string>>>();
var result = new string(nestedList.SelectMany(x => x).SelectMany(x => x).SelectMany(x => x).ToArray());
If the number of nested lists are not known you have to use reflection, since the type is not known. I didnt used reflection directly but actually dynamic type does. The performance would be terrible here of course ;) but it does what you want.
using Microsoft.CSharp.RuntimeBinder;
//...
private static string ConcatAll<T>(T nestedList) where T : IList
{
dynamic templist = nestedList;
try
{
while (true)
{
List<dynamic> inner = new List<dynamic>(templist).SelectMany<dynamic, dynamic>(x => x).ToList();
templist = inner;
}
}
catch (RuntimeBinderException)
{
List<object> l = templist;
return l.Aggregate("", (a, b) => a + b);
}
}
Here is the test
private static void Main(string[] args)
{
List<List<List<string>>> nestedList = new List<List<List<string>>>
{
new List<List<string>> {new List<string> {"Hello "}, new List<string> {"World "}},
new List<List<string>> {new List<string> {"Goodbye "}, new List<string> {"World ", "End "}}
};
Console.WriteLine(ConcatAll(nestedList));
}
Outputs:
Hello World Goodbye World End
Update:
After a bit fiddling i ended up this implementation. maybe better without try catch.
private static string ConcatAll<T>(T nestedList) where T : IList
{
dynamic templist = nestedList;
while (templist.Count > 0 && !(templist[0] is char?))
{
List<dynamic> inner = new List<dynamic>(templist).SelectMany<dynamic, dynamic>(x =>
{
var s = x as string;
if (s != null)
{
return s.Cast<dynamic>();
}
return x;
}).ToList();
templist = inner;
}
return new string(((List<object>) templist).Cast<char>().ToArray());
}

Another solution could be using a recursive method to flatten all your lists:
static IEnumerable<string> Flatten(IEnumerable enumerable)
{
foreach (object el in enumerable)
{
if (enumerable is IEnumerable<string>)
{
yield return (string) el;
}
else
{
IEnumerable candidate = el as IEnumerable;
if (candidate != null)
{
foreach (string nested in Flatten(candidate))
{
yield return nested;
}
}
}
}
}
With this method you can concat all the strings this way:
List<List<List<string>>> nestedList = new List<List<List<string>>>
{
new List<List<string>> {new List<string> {"Hello "}, new List<string> {"World "}},
new List<List<string>> {new List<string> {"Goodbye "}, new List<string> {"World ", "End "}}
};
Console.WriteLine(String.Join(" ",Flatten(nestedList)));
This idea was taken from this post.

Related

Group list of strings with common prefixes

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

Using LINQ to search two lists for match and return boolean for every item

I am trying to check two lists say for example looks like this
list1 = {"br","je"};
list2 = {"banana", "bread", "jam", "brisket", "flakes", "jelly"};
should return
{false, true, false, true, false, true}
Using LINQ is it possible to achieve the above output. I tried something below like this
public static IEnumerable<bool> ContainsValues(List<string> list1, List<string> list2)
{
List<bool> res = new List<bool>();
foreach (string item in list1)
{
foreach (string sub in list2)
{
res.Add(item.ToLower().Contains(sub.ToLower()));
}
}
return res;
}
Yes, you can re-write your existing code in a single statement:
public static IEnumerable<bool> ContainsValues(List<string> list1, List<string> list2)
{
return
from item in list1
from sub in list2
select item.ToLower().Contains(sub.ToLower());
}
However, your existing code does not provide the sample results you want. To get the results you're looking for, I think you want something like this:
public static IEnumerable<bool> SecondContainsFirst(List<string> first,
List<string> second)
{
return second.Select(s => first.Any(f =>
s.IndexOf(f, StringComparison.OrdinalIgnoreCase)) > -1);
}
Here's sample usage:
private static void Main()
{
var list1 = new List<string> {"br", "je"};
var list2 = new List<string> {"banana", "bread", "jam", "brisket", "flakes", "jelly"};
var result = SecondContainsFirst(list1, list2);
Console.WriteLine($"{{{string.Join(", ", result)}}}");
Console.Write("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
Output
IEnumerable<bool> res = list2.Select(q => list1.Any(q.Contains));
Later and identical to then koryakinp's answer - took me time to make it a full fledged example - which his is not.
list2.Select(item => list1.Any(l1 => item.IndexOf(l1,StringComparison.OrdinalIgnoreCase) >= 0)); will produce the desired output:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main(string[] args)
{
var list1 = new List<string> { "br", "je" };
var list2 = new List<string> { "banana", "bread", "jam",
"brisket", "flakes", "jelly" };
var res = list2.Select(item =>
list1.Any(l1 => item.IndexOf(l1, StringComparison.OrdinalIgnoreCase) >= 0));
Console.WriteLine(string.Join(",", list1));
Console.WriteLine(string.Join(",", list2));
Console.WriteLine(string.Join(",", res));
Console.ReadLine();
}
}
Output:
br,je
banana, bread, jam, brisket, flakes, jelly
False,True,False,True,False,True // no idea why yours are lower case
Suggested edit:
thanks #Martino - String.Contains() suggest:
To determine whether a string contains a specified substring by using something other than ordinal comparison (such as culture-sensitive comparison, or ordinal case-insensitive comparison), you can create a custom method. The following example illustrates one such approach.
Followed by an example using IndexOf() + desired StringComparison.
If only because all 3 current answers used, IMHO, troublesome string operations:
public static void Main(string[] args)
{
var list1 = new List<string> { "br", "je" };
var list2 = new List<string> { "banana", "bread", "jam", "brisket", "flakes", "jelly" };
var result = string.Join(",",ContainsValues(list1, list2));
Console.WriteLine($"{string.Join(",",list1)}");
Console.WriteLine($"{string.Join(",", list2)}");
Console.WriteLine($"{result}");
Console.Read();
IEnumerable<bool> ContainsValues(List<string> lst1, List<string> lst2) =>
lst2.Select(l2 => lst1.Any(l1 => l2.IndexOf(l1, StringComparison.OrdinalIgnoreCase) >= 0));
}
`
`

Convert List<dynamic> to List using LINQ

I'm having a collection List<dynamic> dList. In that, it has string items and List<string> items. Now I need to organize all the values in a single List.
Just refer the List<dynamic> dList
Case: 1
List<dynamic> dList = new List<dynamic>()
{
"Selva",
new List<string>() {"Bala"},
new List<string>() {"Prayag", "Raj"},
"Pavithran"
};
Case: 2
List<object> bala = new List<dynamic>()
{
"Selva",
new List<object>() {"Bala"},
new List<object>() {"Prayag", "Raj"},
"Pavithran"
};
The Output of the above two List are
My Expected Output is
How could I achieve the expected result from the above List<dynamic>? The List is generated at run time and I cannot to change the structure.
This is a small part of a complex Linq query, so, I need to achieve this in Linq.
If order is important then you can convert every element to a List<string> and then flatten these:
List<dynamic> dList = new List<dynamic>()
{
"Selva",
new List<string>() {"Bala"},
new List<string>() {"Prayag", "Raj"},
"Pavithran"
};
var flattenedList = dList.SelectMany(d =>
{
if (d is string)
{
return new List<string>() { d };
}
else if (d is List<string>)
{
return (d as List<string>);
}
else
{
throw new Exception("Type not recognised");
}
});
Or, as a sexy one-liner with no type-checking (so...use at your own risk!)
dList.SelectMany(d => d as List<string> ?? new List<string>() { d })
Or, finally, in LINQ syntax:
var newList =
(from d in dList
from d2 in EnsureListOfString((object)d)
select d2
);
public List<string> EnsureListOfString(object arg)
{
List<string> rtn = arg as List<string>;
if (rtn == null)
{
if (arg is string)
{
rtn = new List<string>() { arg as string };
}
else
{
throw new Exception("Type not recognised.");
}
}
return rtn;
}
If the order of the elements is not important, you can do this:
dList.OfType<string>().Concat(dList.OfType<List<string>>().SelectMany(l => l));
This first selects all string elements from the list, then selects all List<string> elements and flattens them using SelectMany and finally concats all strings.

What is the most concise way to array a series of string variables that may be empty

I need to array an ad-hoc set of strings like this
string a = null;
string b = "include me";
string c = string.Empty;
string d = "me too!";
without including null or empty strings. I know I can use a child function and params:
private List<string> GetUniqueKeys(params string[] list)
{
var newList = new List<string>();
foreach (string s in list)
{
if (!string.IsNullOrWhiteSpace(s))
newList.Add(s);
}
return newList;
}
///
return this.GetUniqueKeys(a, b, c, d).ToArray();
but is there any simpler way to do this that I'm not seeing?
EDIT: Sorry about that, happy to vote up the first LINQer, but I should have specified that I was trying to get rid of the child method altogether, not simplify it.
If the input strings are enumerable, you can use linq.
var result = stringList.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
No Child Function
The shortest way you can do this without a child function is the following:
var a = new string[] { a, b, c, d }.Where(s => !string.IsNullOrWhiteSpace(s));
With Child Function
However, I would recommend using your child function:
private IEnumerable<string> GetUniqueKeys(params string[] list)
{
return list.Where(s => !string.IsNullOrWhitespace(s));
}
Extension Method
Alternatively, if you're really looking for other options... you could create an extension method:
public static List<string> AddIfNotEmpty(this List<string> list, params string[] items)
{
list.AddRange(items.Where(s => !string.IsNullOrEmpty(s)));
return list;
}
Then use it like such:
var list = new List<string>().AddIfNotEmpty(a, b, c, d);
And add other items later:
list.AddIfNotEmpty("new item", string.Empty);
private List<string> GetUniqueKeys(params string[] list)
{
var newList = new List<string>();
newList.AddRange(list.Where(str => !string.IsNullOrEmpty(str)));
return newList;
}
With Child method..
private List<string> GetUniqueKeys(params string[] list)
{
return list.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
}
Without Child method..
string a = null;
string b = "include me";
string c = string.Empty;
string d = "me too!";
string[] lst = { a, b, c, d };
var uniqueLst = lst.Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); //Or ToArray();
I recommend to use child method with params.

LINQ expression to find if there are any matches between two arrays of string

Suppose I have two lists of strings, List1 and list2, where List1 is a property of an object of type Foo in a list fooList.
I would like to remove a given Foo if no string in foo.List1 matches any string in list2 a la RemoveAll.
I can do this with nested for loops, but is there a way to do this with a single slick LINQ expression?
Long-winded code, building a new list rather than removing stuff from the existing list:
var newFooList = new List<Foo>
foreach (Foo f in fooList)
{
bool found = false;
foreach (string s in newFooList)
{
if (f.FooStringList.Contains(s))
{
found = true;
break;
}
}
if (found)
newFooList.Add(f);
}
Yes:
var list2 = new List<string> { "one", "two", "four" };
var fooList = new List<Foo> {
new Foo { List1 = new List<string> { "two", "three", "five" } },
new Foo { List1 = new List<string> { "red", "blue" } }
};
fooList.RemoveAll( x => !x.List1.Intersect( list2 ).Any() );
Console.WriteLine( fooList );
Basically all the magic happens in RemoveAll: this only removes entries where the intersection of the entry's List1 property and list2 (i.e., the overlap) is empty.
I personally find the !....Any() construct kind of hard to read, so I like to have the following extension method on hand:
public static class Extensions {
public static bool Empty<T>( this IEnumerable<T> l,
Func<T,bool> predicate=null ) {
return predicate==null ? !l.Any() : !l.Any( predicate );
}
}
Then I can re-write the magic line in a way that's a little clearer:
fooList.RemoveAll( x => x.List1.Intersect( list2 ).Empty() );

Categories

Resources