Speaking of Linq single-line handlers)

In general, there is such a task:
There is an IEnumerable<string> containing some strings. And also there is an int[] containing positions for which you need to insert empty lines in the above collection.

Example:

 // IEnumerable<string> collection: "Some" "Strings" "To" "Test" "Method" // int[] mask: 1 2 4 // IEnumerable<string> result: "Some" "" "" "Strings" "" "To" "Test" "Method" 

Linq beauty is to convert the collection without any additional variables or explicit loops. However, I failed to solve the problem without introducing a new variable, so I would like to ask you: which solution will be more elegant?)

My implementations:

Head-on:

 int i = 0; List<string> result = new List<string>(); foreach (string x in collection) { while (mask.Contains(i)) { result.Add(string.Empty); ++i; } result.Add(x); ++i; } 

Same, but through SelectMany :

 int i = 0; IEnumerable<string> result = collection.SelectMany(x => { List<string> part = new List<string>(); while (mask.Contains(i)) { part.Add(string.Empty); ++i; } part.Add(x); ++i; return part; }); 

UPD:

This is usually important in such tasks, so I clarify: mask is an ordered array

  • In theory, you can pack in an intermediate state inside, if you use Aggregate. But the answer is not “lazy” - VladD
  • Given that in Linq, no one except OrderBy guarantees order, insertion by indexes looks suspicious. Plus, the processing judging by the example is also consistent, i.e. indices cannot be taken one-time, after insertion it is necessary to recompute positions. - Monk
  • If we take away the sequence {1, 2, 3, ...} from mask, we’ll get the sequence of indices of the original sequence, where we need to add elements using SelectMany - VladD
  • @VladD, an interesting idea! - Kir_Antipov
  • one
    @AndreyNOP, why not the answer?) The option is good - Kir_Antipov

2 answers 2

Another variation of the same idea with pre-processing.

Create a dictionary:

 var dict = mask.OrderBy(n => n) .Select((n, idx) => n - idx - 1) .GroupBy(n => n) .ToDictionary(g => g.Key, g => g.Count()); 

Having this, we get the following query:

 Enumerable.Repeat("", dict.TryGetValue(-1, out var k) ? k : 0).Concat( collection.SelectMany((s, idx) => Enumerable.Repeat("", dict.TryGetValue(idx, out var t) ? t : 0) .Prepend(s)) ); 

The first line is needed for the case when the mask has the index 0.

  • one
    You can get rid of the first line if you replace Prepend by Append - Andrey NOP
  • @AndreyNOP: It is possible, but then it will not work with the latest indices. - VladD
  • Yeah, got it, thanks. But on the other hand, the question does not say what to do in such situations, for example, there are only three lines in the source array, and { 10 } is in the mask - Andrey NOP
  • If you look at the author's decision "in the forehead", then it also writes nothing to the end - Andrey NOP
  • @ AndreiNOP, hmm. Perhaps this is my logical mistake. You cannot work with mask indexes that are clearly beyond the boundaries of the string collection, but why not expand it if the indexes are adjacent to the finite one? - Kir_Antipov

Actually, @VladD gave a great idea:

If we take away the sequence {0, 1, 2, 3, ...} from mask, we get just the sequence of indices of the original sequence, where we need to add elements with the help of SelectMany

Indeed, if we subtract from each i th element of mask N 0 i ( N 0 is a sequence of non-negative integers), we will get a collection of pre-calculated positions, which will need to be inserted empty lines

If it's simpler, we just need to subtract from each element of mask its position in the array

Having received a collection of positions where you need to insert empty lines, we already use the same SelectMany


Summary Code:

 // Определим данные IEnumerable<string> collection = new List<string> { "Some", "Strings", "To", "Test", "Method" }; IEnumerable<int> mask = new int[] { 1, 2, 4 }; // Просчитаем маску mask = mask.Select((x, i) => x - i); // Для каждого элемента выберем коллекцию, которая состоит из такого числа пустых строк, сколько раз индекс объекта // встречается в маске, а также из этого самого элемента IEnumerable<string> result = collection.SelectMany((x, i) => new List<string>(Enumerable.Repeat(string.Empty, mask.Count(index => index == i))) { x }); 

Result:

 // IEnumerable<string> result: "Some" "" "" "Strings" "" "To" "Test" "Method" 

Actually, we got exactly what we expected)

Thanks again for the good idea!

  • Just came to write the answer :) - VladD