Группировать (предыдущие) элементы массива по количеству последующих отрицательных элементов

#c# #arrays #algorithm

#c# #массивы #алгоритм

Вопрос:

Я пытаюсь обработать (на C #) большой файл данных с некоторыми числовыми данными. Учитывая массив целых чисел, как его можно разделить / сгруппировать, чтобы предыдущие n элементов были сгруппированы, если следующие n (два или более) являются отрицательными числами. Например, в приведенном ниже массиве два или более последовательных отрицательных числа следует использовать в качестве подсказки для группировки одинакового количества предыдущих элементов:

 0 
1
4 
-99
-99
-99
1 
2 
7 
9 
-99
-99
3 
6 
8 
-99
-99
5 
  

Вывод:

 Group 1:
0 
1
4 
[3 negative numbers were next, so group previous 3]

Group 2:
1 

Group 3:
2 

Group 4:
7 
9 
[2 negative numbers were next, so group previous 2, but keep any prior positive numbers (1, 2) as separate groups]

Group 5:
3 

Group 6:
6 
8 
[2 negative numbers were next, so group previous 2, but keep any prior positive numbers (3) as separate groups]

Group 7:
5 
  

Каким будет самый быстрый и эффективный способ обработки такого массива данных в новую сгруппированную коллекцию?

Комментарии:

1. Вы точно можете загрузить весь файл в массив? Это может упростить задачу, но это будет менее эффективно расходовать память, чем алгоритм, который должен принимать только IEnumerable<int> , например.

2. Да, @JonSkeet, весь файл может быть загружен в массив (или любой другой перечислимый, если необходимо).

3. Правильно. Все равно пытаюсь написать потоковую версию, но потом могу адаптироваться 🙂

4. Может быть, проще обработать массив в обратном порядке?

5. Что нам делать, если мы видим только одно отрицательное число? например 1, 2, -99, 3, 4. Должно ли это выдавать {1}, {2}, {3}, {4}?

Ответ №1:

Я бы, вероятно, попытался передать все это в потоковом режиме, поддерживая буфер «текущих неотрицательных чисел» и количество отрицательных чисел. Вот некоторый код, который, похоже, работает… Я бы ожидал, что это будет, по крайней мере, довольно эффективно. Я бы начал с этого, и если это недостаточно эффективно для вас, посмотрите, почему.

(Если вы действительно хотите иметь весь массив в памяти, вам не нужно создавать numbersToEmit список, так как вместо этого вы можете просто поддерживать индексы. Но, учитывая, что один список используется повторно, я бы ожидал, что это будет нормально.)

 using System;
using System.Linq;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        int[] input = { 0, 1, 4, -99, -99, -99, 1, 
                2, 7, 9, -99, -99, 3, 6, 8, -99, -99, 5 };
        foreach (var group in GroupByNegativeCounts(input))
        {
            Console.WriteLine(string.Join(", ", group));
        }
    }

    static IEnumerable<int[]> GroupByNegativeCounts(IEnumerable<int> input)
    {
        List<int> numbersToEmit = new List<int>();
        int negativeCount = 0;
        foreach (var value in input)
        {
            // We never emit anything when we see a negative number
            if (value < 0)                
            {
                negativeCount  ;
            }
            else
            {
                // We emit numbers if we've previously seen negative
                // numbers, and then we see a non-negative one.
                if (negativeCount > 0)
                {
                    int singles = Math.Max(numbersToEmit.Count - negativeCount, 0);
                    foreach (var single in numbersToEmit.Take(singles))
                    {
                        yield return new[] { single };
                    }
                    if (singles != numbersToEmit.Count)
                    {
                        yield return numbersToEmit.Skip(singles).ToArray();
                    }
                    negativeCount = 0;
                    numbersToEmit.Clear();
                }
                numbersToEmit.Add(value);
            }
        }
        // Emit anything we've got left at the end.
        foreach (var single in numbersToEmit)
        {
            yield return new[] { single };
        }
    }
}
  

Комментарии:

1. Спасибо, Джон, я попробую и посмотрю, что из этого получится. Приветствия!

Ответ №2:

Вот мое решение, я постарался сделать его как можно более простым 🙂

     List<int> input = new List<int>() { 0, 1, 4, -99, -99, -99, 1, 2, 7, 9, -99, -99, 3, 6, 8, -99, -99, 5 };
    List<int> reverse_input = input.Reverse<int>().ToList();    //reverse list for easier reading
    List<List<int>> grouped_input = new List<List<int>>();      //output variable

    List<int> leading_positives;
    int leading_negative_count;

    while (reverse_input.Any())
    {
        //Get the amount of leading negatives and remove them from the reversed list
        leading_negative_count = reverse_input.TakeWhile(num => num < 0).Count();
        reverse_input = reverse_input.Skip(leading_negative_count).ToList();

        //get and store leading positives and remove them from the reversed list
        leading_positives = reverse_input.TakeWhile(num => num >= 0).ToList();
        reverse_input = reverse_input.Skip(leading_positives.Count).ToList();

        //take an amount of positives equal to the amount of previously found negatives and add them as a separate list to the output
        grouped_input.Add(leading_positives.Take(leading_negative_count).Reverse().ToList());

        //for each remaining positive add it as an individual into the output
        leading_positives.Skip(leading_negative_count).ToList().ForEach(num => grouped_input.Add(new List<int>() { num }));               
    }

//output display       
grouped_input.Reverse<List<int>>().ToList().ForEach(lst => Console.WriteLine(string.Join(",", lst)));