Округление значения только до списка определенных значений в C#

#c# #.net #math

#c# #.net #математика

Вопрос:

У меня есть список двойных значений, я хочу округлить значение переменной только до этого списка чисел

Пример:

Содержимое списка таково: {12,15,23,94,35,48}

Значение переменной равно 17, поэтому оно будет округлено до 15

Если значение переменной меньше наименьшего числа, оно будет округлено до него, если его значение больше наибольшего числа, оно будет округлено до него.

Содержимое списка всегда меняется в зависимости от внешнего фактора, поэтому я не могу жестко определить значения, которые я хочу округлить в большую или меньшую сторону.

Как это можно сделать на C #?

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

1. Определите разницу между неокругленными значениями и возможными результатами. Сохраните эти различия в словаре с указанием возможного результата и выберите результат с наименьшей разницей.

2. Насколько большой может быть эта коллекция? В противном случае вам следует рассмотреть возможность использования отсортированной коллекции и использования двоичного поиска.

3. Как сказал Seven, особенно если ваш список статичен (не меняется)

Ответ №1:

Вот метод, использующий LINQ:

 var list = new[] { 12, 15, 23, 94, 35, 48 };
var input = 17;

var diffList = from number in list
               select new {
                   number,
                   difference = Math.Abs(number - input)
               };
var result = (from diffItem in diffList
              orderby diffItem.difference
              select diffItem).First().number;
 

РЕДАКТИРОВАТЬ: переименовал некоторые переменные, чтобы код был менее запутанным…

Редактировать:

list Переменная является неявно объявленным массивом int . Первый оператор LINQ diffList определяет анонимный тип, который содержит ваш исходный номер из списка ( number ), а также разницу между ним и вашим текущим значением ( input ) .

Второй оператор LINQ result упорядочивает эту коллекцию анонимных типов по разнице, что является вашим требованием «округления». Он принимает первый элемент в этом списке, поскольку он будет иметь наименьшее различие, а затем выбирает только оригинал .number из анонимного типа.

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

1. 1. Это более эффективно, чем мое решение, поскольку оно выполняется только values один раз.

2. Работает как шарм, хотя я не могу понять все это 🙂

3. @AndersAbel: На самом деле, скорее в два раза, если не больше. Сначала, чтобы получить различия. Затем по порядку.

4. @Steven Jeuris: Вы правы, особенно реализация сортировки OrderBy будет иметь большое влияние на производительность.

Ответ №2:

Предполагая, что массив отсортирован, вы можете выполнить двоичный поиск в массиве, сузив его до двух чисел, между которыми находится данное число.

Затем, когда у вас есть эти два числа, вы просто округляете до ближайшего из двух.

 static int RoundToArray(int value, int[] array) {
    int min = 0;
    if (array[min] >= value) return array[min];

    int max = array.Length - 1;
    if (array[max] <= value) return array[max];

    while (max - min > 1) {
        int mid = (max   min) / 2;

        if (array[mid] == value) {
            return array[mid];
        } else if (array[mid] < value) {
            min = mid;
        } else {
            max = mid;
        }
    }

    if (array[max] - value <= value - array[min]) {
        return array[max];
    } else {
        return array[min];
    }

}
 

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

1. Не только предполагая, что если это должно работать с большими коллекциями, отсортированная коллекция — это правильный путь.

2. На практике, конечно, это не имеет значения, но я отмечаю, что вы используете «опасный» способ нахождения середины двух целых чисел. Если min max переполняется до отрицательного, то половина этого числа является отрицательным числом. Смотрите Эту статью для интересного анализа проблемы и ее возможных решений: locklessinc.com/articles/binary_search

Ответ №3:

Использование linq:

 int value = 17;
var values =  new float[] { 12, 15, 23, 94, 35, 48 };
if(value < values.First()) return value.First();
if(value > values.Last()) return value.Last();

float below = values.Where(v => v <= value).Max();
float above = values.Where(v => v >= value).Min();

if(value - below < above - value)
  return below;
else
  return above;
 

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

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

1. Ошибка 1 ‘int’ не содержит определения для ‘First’, и не удалось найти метод расширения ‘First’, принимающий первый аргумент типа ‘int’ (вам не хватает директивы using или ссылки на сборку?)

Ответ №4:

Вы можете перебрать массив чисел и установить переменную roundedNum равной каждой переменной, если переменная delta меньше текущей наименьшей дельты. Некоторые вещи лучше всего описывать в коде.

 int roundedNum = myNum;
int delta = myArray[myArray.Length-1]   1;

for(int i=0; i<myArray.Length;   i) {
    if(Math.Abs(myNum - myArray[i]) < delta) {
        delta = Math.Abs(myNum - myArray[i]);
        roundedNum = myArray[i];
    }
}
 

Это должно сделать трюк довольно хорошо.

Ответ №5:

Сделайте что-то вроде этого:

 double distance = double.PositiveInfinity;
float roundedValue = float.NaN;
foreach (float f in list)
{
    double d = Math.Abs(d - f);
    if (d < distance)
    {
        distance = d;
        roundedValue = f;
    }
}
 

Ответ №6:

Простое округление в меньшую сторону в linq

     public decimal? RoundDownToList(decimal input, decimal[] list)
    {
        var result = (from number in list
                      where number <= input
                      orderby number descending
                      select number).FirstOrDefault();

        return resu<
    }