#c# #datatable #rounding
#c# #datatable #округление
Вопрос:
У меня есть предварительно заполненная таблица данных, каждая строка в этой таблице данных должна составлять до 100
Я пытаюсь использовать подход, согласно которому, если общее значение строки меньше 100, я перебираю значения в этой строке и сначала добавляю 1 единицу к наибольшему значению, проверяю, достигнуто ли 100, если нет, добавляю 1 единицу ко второму по величине значению в строке и так далее.
пример:
исходные данные
50.2, 40.6, 9.0 итого 99.8
после округления:
50.3, 40.7, 9.0 итого 100.0
Если общее общее значение для строки больше 100, то я хочу повторить этот процесс, но сначала удалить 1 единицу из наибольшего значения и т.д…
Я столкнулся с рядом камней преткновения, первая сортировка таблицы данных оказалась проблемой, так как я не знаю имен столбцов и т.д., поэтому я преобразовал ее в массив.
Основная проблема заключается в том, что я просто не могу заставить его выполнить окончательную настройку в строке for (int a = 0; a < temAdj; a ), поскольку к тому времени temAdj всегда равен нулю.
private DataTable RoundingAndScaling(DataTable dtInput, int startingCol, int decPlaces = 1)
{
int dtRows = dtInput.Rows.Count;
int dtCols = dtInput.Columns.Count;
double temAdj;
double tempRowTotal;
int tempSig;
DataTable dtOuput = new DataTable();
dtOuput = dtInput.Copy();
DataTable dtFinal = new DataTable();
dtFinal = dtInput.Copy();
double[,] outputArray = new double[dtOuput.Columns.Count, 2];
for (int r = 0; r < dtRows - 1; r )
{
tempRowTotal = 0;
for (int c = startingCol; c < dtCols; c )
{
if (dtInput.Rows[r][c] == DBNull.Value)
{
dtOuput.Rows[r][c] = 0.0;
}
else
{
dtOuput.Rows[r][c] = Math.Round((double)dtInput.Rows[r][c], decPlaces);
}
outputArray[c - startingCol, 0] = Convert.ToDouble(dtOuput.Rows[r][c]);
tempRowTotal = tempRowTotal Convert.ToDouble(dtOuput.Rows[r][c]);
}
//now check if that all the cells in that row = 100
if (tempRowTotal != 100)
{
tempSig = 1;
//Sort the data
double[,] arrayDb = new double[dtOuput.Columns.Count, 2];
arrayDb = SortArray(outputArray, dtCols);
//Find how many assets need to be adjusted by 1 unit
temAdj = (tempRowTotal - 1) * (10 ^ decPlaces); //10^
if (temAdj < 0)
{
temAdj = -1 * temAdj;
tempSig = -1;
}
//make the adjustment to the assets that have the largest holdings
for (int a = 0; a < temAdj; a )
{
dtOuput.Rows[(int)(arrayDb[dtCols, 1])][1] = (int)dtOuput.Rows[(int)(arrayDb[dtCols, 1])][1] - tempSig * 1 / (10 ^ decPlaces); //^10
}
//get the data back into the correct structure to return
// for (int xx = 0; xx < dtCols; xx )
// {
// dtFinal.Rows[dtRows][xx - 1] = dtOuput.Rows[xx][1];
// }
}
}
return dtOuput;
}
private double [,] SortArray(double [,] inData, int tempLen)
{
double temVal1;
double temVal2;
for (int i = 0; i < tempLen; i )
{
for (int j = i 1; j < tempLen; j )
{
if (inData[i,0] > inData [j,0])
{
temVal1 = inData[i, 0];
temVal2 = inData [i,1];
inData [i,0] = inData [j,0];
inData[i,1] = inData [j,1];
inData[j, 0] = temVal1;
inData[j, 1] = temVal2;
}
}
}
return inData;
}
Лично я совсем не доволен своим подходом и уверен, что есть гораздо более простой способ добиться того, что я пытаюсь сделать, я с радостью отброшу вышесказанное и перейду к более простому подходу 🙂 Любая помощь приветствуется.
Комментарии:
1. Это не решение вашей проблемы, но вы действительно уверены, что вам нужно, чтобы эти значения суммировались до 100? Ваша логика кажется немного странной в том смысле, что она почти произвольно добавляет значения, чтобы довести общее число до 100.
2. Это связано с тем, что я должен получить все значения с точностью до 1 знака после запятой. Когда я применяю округление, я могу получить итоговые значения, которые немного завышены, система не примет их, поэтому мне нужно скорректировать наибольшие значения, чтобы достичь этой цели. Это не идеальный подход, но принятый для моих текущих потребностей
3. Первая проблема — это работоспособность этого кода. Цикломатическая сложность вашего решения слишком высока. Во-вторых, алгоритм связан с объектом пользовательского интерфейса, что затрудняет модульное тестирование. Если бы в этом методе был модульный тест, шансы на то, что вы найдете решение самостоятельно, увеличились бы в 100 раз.
4. Согласен с цикломатической сложностью — вот почему я уверен, что есть более простой подход, на данный момент я начал испытывать некоторую слепоту к коду, и именно поэтому я задал этот вопрос — иногда старое серое вещество просто решает, что его достаточно
Ответ №1:
List<decimal> numlist = new List<decimal>();
numlist.Add(50.2m);
numlist.Add(40.6m);
numlist.Add(9.0m);
decimal diff = 100.0m - numlist.Sum();
//This is set because the value should be only 1 decimal place
int update = Convert.ToInt32(diff / .1m);
if (update > 0)
{
for (int x = 0; x < update; x )
{
numlist[x % numlist.Count()] = .1m;
}
}
else
{
for (int x = 0; x < Math.Abs(update); x )
{
numlist[x % numlist.Count()] -= .1m;
}
}
Я использовал десятичную дробь для большей точности, но вот несколько примеров данных, чтобы взять разницу и попытаться сделать ее равной 100%. (Я также добавляю код, если вы набрали более 100% и хотите также сократить его равномерно)
Комментарии:
1. Спасибо за это. Похоже, это приведет меня туда, где я должен быть. Мне просто нужно изменить его, чтобы он соответствовал моей модели — я опубликую окончательную версию здесь, как только закончу
Ответ №2:
Просто, если кто-то заинтересован в готовом коде (это может помочь другим) Я вставил его ниже.
Я использовал код, предоставленный Кевином Куком, и модифицировал его в соответствии со своими потребностями. В приведенном ниже примере вы передаете таблицу данных вместе с тем, какие строки и столбцы вы хотите просмотреть. Затем это записывается в список вместе с первоначальным порядком данных. Список отсортирован по количеству, которое ограничено. Затем выполняется код округления, и после завершения список упорядочивается обратно в исходном порядке. Затем это записывается обратно в таблицу данных и возвращается.
private DataTable RoundScale(DataTable dtInput, int startingCol, int startingRow)
{
int xOrder = 0;
//write datatable values into a list (easier to work with)
for (int j = startingRow; j < dtInput.Rows.Count; j )
{
List<SortingData> numlist = new List<SortingData>();
for (int iCol = startingCol; iCol < dtInput.Columns.Count; iCol )
{
if ((dtInput.Rows[j][iCol] != DBNull.Value) amp;amp; (Convert.ToDouble(dtInput.Rows[j][iCol]) != 0))
{
SortingData LSO = new SortingData
{
NumberToBeRounded = Convert.ToDecimal(Math.Round((double)dtInput.Rows[j][iCol], 1)),
OrderNum = xOrder
};
numlist.Add(LSO);
}
else
{
SortingData LSO_1 = new SortingData
{
NumberToBeRounded = Convert.ToDecimal(0.0),
OrderNum = xOrder
};
numlist.Add(LSO_1);
}
xOrder ;
}
//need to sort the list desc so unit is added to or removed from the largest item in the list first
numlist = numlist.OrderByDescending(o => o.NumberToBeRounded).ToList();
decimal numTotal = numlist.Sum(y => y.NumberToBeRounded);
if (numTotal != 0) //ignore if there are zero totals
{
decimal diff = 100.0m - numTotal;
//the value should be only 1 decimal place
int update = Convert.ToInt32(diff / .1m);
if (update > 0)
{
for (int x = 0; x < update; x )
{
var sortednumlist = numlist[x % numlist.Count()];
sortednumlist.NumberToBeRounded = .1m;
}
}
else
{
for (int x = 0; x < Math.Abs(update); x )
{
var sortednumlist = numlist[x % numlist.Count()];
sortednumlist.NumberToBeRounded -= .1m;
}
}
}
//change order of list back to original order
numlist = numlist.OrderBy(o => o.OrderNum).ToList();
//now write the list back into that datatable row
for (int i = startingCol; i < dtInput.Columns.Count; i )
{
var numlistout = numlist[i - startingCol];
dtInput.Rows[j][i] = numlistout.NumberToBeRounded;
}
}
return dtInput;
}
public class SortingData
{
public decimal NumberToBeRounded { get; set; }
public int OrderNum { get; set; }
}