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