# Combine neighboring parts of an array based on a condition

I have written this code which takes a one dimensional array, for example:

{"abc","ade","sss","fgw","asd","lka","o"}

and turns it into an array of arrays, combining the elements which do not meet the condition.

For the condition m=>!m.Contains("a") for example, the output will be:

{{"abc","ade"},{"sss"},{"fgw"},{"asd","lka"},{"o"}}

The code works, but i suspect there is a linq way to do it, and refrain from using temporary variables that i dislike.

    public static string[][] combineBy(this string[] inp,Func<string,bool> f)
{
List<string[]> holdElements = new List<string[]>();
while (inp.Length!=0)
{
if (f.Invoke(inp[0]))
{
inp = inp.Skip(1).ToArray();
}
else
{
var toAdd = inp.TakeWhile(n => !f.Invoke(n));
}
}
return holdElements.ToArray();
}

• You dislike temporary variables but you yourself are using them in var toAdd = ... instead of holdElements.Add(inp.TakeWhile(n => !f.Invoke(n))); ;-P which is inefficient anyway because you first enumerate the TakeWhile into an array and then you Count() instead of ToArray it in the first place and use the Length later. – t3chb0t Jul 26 '16 at 10:33
• Seems hypocritical but it was meant for debugging and readability reasons, I didn't succeed in boiling down the code more than that tho. – downrep_nation Jul 26 '16 at 10:35
• Just as a curiosity: I think this can be implemented in terms of codereview.stackexchange.com/questions/134363/…. The predicate, it seems, would be value => !value.contains("a"). In fact, perhaps, combineBy(list, predicate) is generally SplitBeforeIf(list, val => !predicate(val)). – Heman Gandhi Jul 26 '16 at 15:38
• Please don't edit your question to incorporate changes from answers. It breaks the Q+A nature of the site. – forsvarir Jul 26 '16 at 16:27
• I didnt incorporate changes from answers whatsoever. the code was simply not doing what i said it should because of a typo which is against the rules of this site – downrep_nation Jul 26 '16 at 16:29

• combineBy use PascalCase for method names
• f it's not a good parameter name, if it's a condition function they are usualy named predicate
• predicate.Invoke(...) - you can call it directly with predicate(...)
• values.TakeWhile(n => !predicate(n)) - do not enumerate it twice, call ToArray and use it in the subsequent lines
• if you like it the LINQ way you can write values.Any() instead of values.Length != 0 and values.First() instead of values[0] also this new string[] { inp[0] } could be turned into values.Take(1).ToArray()
• don't use abbreviations for variables like inp or f, use full names like values and predicate

x

public static string[][] CombineBy(this string[] values, Func<string, bool> predicate)
{
var result = new List<string[]>();
while (values.Any())
{
if (predicate(values.First()))
{
values = values.Skip(1).ToArray();
}
else
{
var toAdd = values.TakeWhile(n => !predicate(n)).ToArray();
}
}
return result.ToArray();
}


Unfortuantely your code doesn't seem to work and returns invalid results. This should do:

public static List<List<string>> CombineBy(this string[] values, Func<string, bool> predicate)
{
var result = new List<List<string>>();

foreach (var value in values)
{
if (predicate(value))
{
if (!result.Any() || !predicate(result.Last().Last()))
{
}
}
else
{
}
}

return result;
}


or even without loops:

public static List<List<string>> CombineBy3(this string[] values, Func<string, bool> predicate)
{
var result = values.Aggregate(new List<List<string>>(), (current, next) =>
{
if (predicate(next))
{
if (!current.Any() || !predicate(current.Last().Last()))
{
}
}
else
{
}
return current;
});
return result;
}

• If you're going this far you may as well make it generic. – Der Kommissar Jul 26 '16 at 16:58
• @EBrown right CombineBy<T> and string --> T + ;-) – t3chb0t Jul 26 '16 at 17:00
• You got it. :) Then it's much more robust and usable. – Der Kommissar Jul 26 '16 at 17:03

I hate to break it to you but your code doesn't work. For the input you've given, your code returns:

{ {"abc"}, {"ade"}, {"sss", "fgw"}, {"asd"}, {"lka"}, {"o"} }

Which is clearly incorrect. Your code is also needlessly complex - you should be able to do this with one iteration through the source array:

public static IEnumerable<IEnumerable<string>> CombineBy(this string[] input, Func<string, bool> predicate)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
return CombineByCore(input, predicate);
}

private static IEnumerable<IEnumerable<string>> CombineByCore(string[] input, Func<string, bool> predicate)
{
var buffer = new List<string>();
foreach (var inputString in input)
{
if (predicate(inputString))
{
}
else
{
if (buffer.Any())
{
yield return buffer;
buffer = new List<string>();
}
yield return new List<string>() { inputString };
}
}
if (buffer.Any())
{
yield return buffer;
}
}

• About the null check, see: stackoverflow.com/documentation/c%23/61/yield-keyword/279/… I made the same mistake :-( – Heslacher Jul 26 '16 at 11:03
• @Heslacher - every day is a school day! Thanks for that - will update my answer – RobH Jul 26 '16 at 11:05
• "every day is a school day" that what I thought some days ago as well. – Heslacher Jul 26 '16 at 11:05
• Didn't mean to remove the null check but have another method doing it and then call the method which is yielding – Heslacher Jul 26 '16 at 11:06
• I was being lazy :) – RobH Jul 26 '16 at 11:08

It's hard to find a "linq way" to accomplish that... However, you could use the GroupBy method with 2 helper variables:

public static IEnumerable<IEnumerable<string>> CombineBy(this IEnumerable<string> input, Func<string, bool> prediction)
{
bool? prevPredict = null;
int group = 0;
return input.GroupBy(i =>
{
var predict = prediction(i);
if (!predict || predict != prevPredict)
group++;
prevPredict = predict;
return group;
});
}

• This is nice ;-] – t3chb0t Jul 26 '16 at 15:08
• You call a string[] a list ... well, well, well... this needs a code review :-P – t3chb0t Jul 26 '16 at 15:11
• Shame on me! Improved the answer ;) – JanDotNet Jul 26 '16 at 15:17
• you got a typo, its accomplish. also it doesnt work because it groups variables that return true to the predicate – downrep_nation Jul 26 '16 at 16:18
• @downrep_nation: Ok... than it becomes even more complex (see updated answer). – JanDotNet Jul 26 '16 at 16:31