Reputation: 5384
Say there are two arrays:
String[] title = { "One","Two","three","Four"};
String[] user = { "rob","","john",""};
I need to filter out the above array when the user
value is Empty then join or zip the two together. Final Output should be like:
{ "One:rob", "three:john" }
How can this be done using LINQ?
Upvotes: 12
Views: 10988
Reputation: 15968
As of .net Core 3.0 and up, the Zip
extension method is now built in.
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
foreach (var item in numbersAndWords)
Console.WriteLine(item);
// This code produces the following output:
// 1 one
// 2 two
// 3 three
See more examples at https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.zip?view=net-5.0
Upvotes: 1
Reputation: 31596
In looking at Marc's answer (and ultimately the Zip
method as of .Net 4), there is a significant amount of overhead to enumerate and join of rows where they are eventually thrown away; can that be done without that waste?
In looking at Jon's answer, creating a projection of dynamic entities to reference the existing data and then creating a new set of entities from that mirror could be a deterrent to using that method if the total row count were overly large.
The below snippet uses the references to the original data and the only wasted projected entitles created are the ones which have null string which are subsequently removed. Also the enumeration of the data is kept to a minimum.
String[] title = { "One","Two","three","Four"};
String[] user = { "rob","","john",""};
user.Select ((usr, index) => string.IsNullOrEmpty(usr)
? string.Empty
: string.Format("{0}:{1}", title[index], usr ))
.Where (cmb => string.IsNullOrEmpty(cmb) == false)
As an aside, this methodology could have user array which is smaller in size than the title array as a plus.
The Aggregate
function is overlooked, here it is in action:
int index = 0;
user.Aggregate (new List<string>(),
(result, usr) =>
{
if (string.IsNullOrEmpty(usr) == false)
result.Add(string.Format("{0}:{1}", title[index], usr));
++index;
return result;
} )
Upvotes: 0
Reputation: 116654
As another addition to the previous answers, Zip is usually defined and used in conjunction with a Tuple
type. This relieves the user of having to provide a resultSelector
function.
public class Tuple<TItem1, TItem2> // other definitions for higher arity
{
public TItem1 Item1 { get; private set; }
public TItem2 Item2 { get; private set; }
public Tuple(TItem1 item1, TItem2 item2)
{
Item1 = item1;
Item2 = item2;
}
}
And hence:
public static IEnumerable<Tuple<TFirst, TSecond>> Zip<TFirst, TSecond>
(this IEnumerable<TFirst> first, IEnumerable<TSecond> second)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext())
yield return new Tuple<TFirst, TSecond>(e1.Current, e2.Current);
}
}
I believe this is closer to what CLR 4.0 will have (though it may have the more flexible variety as well).
Upvotes: 0
Reputation: 164281
As a complement to the already posted answers, here is a solution without using the Zip method. This assumes that both arrays is of same length.
var pairs = from idx in Enumerable.Range(0, title.Length)
let pair = new {Title = title[idx], User = user[idx]}
where !String.IsNullOrEmpty(pair.User)
select String.Format("{0}:{1}", pair.Title, pair.User);
Upvotes: 5
Reputation: 1499940
For a start, you need a Zip
operator to zip the two arrays together. Here's an abbreviated version of the code from Eric Lippert's blog (no error checking in this version, just for brevity):
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>
(this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}
Note that Zip
will be in the standard libraries for .NET 4.0.
Then you need to just apply a filter and a projection. So we'd get:
var results = title.Zip(user, (Title, User) => new { Title, User })
.Where(x => x.Title != "")
.Select(x => x.Title + ":" + x.User);
Upvotes: 10
Reputation: 1062580
It sounds like you actually want to "zip" the data together (not join) - i.e. match pairwise; is that correct? If so, simply:
var qry = from row in title.Zip(user, (t, u) => new { Title = t, User = u })
where !string.IsNullOrEmpty(row.User)
select row.Title + ":" + row.User;
foreach (string s in qry) Console.WriteLine(s);
using the Zip
operation from here:
// http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>
(this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");
return ZipIterator(first, second, resultSelector);
}
private static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>
(IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}
Upvotes: 8