Как эффективно получить самые высокие и самые низкие значения из списка <double?>, А затем изменить их? - PullRequest
0 голосов
/ 15 июня 2010

Я должен получить сумму в списке двойников.Если сумма> 100, я должен уменьшать с наибольшего числа до 100. Если сумма <100, я должен увеличивать наименьшее число до = 100. Я могу сделать это, просматривая список, назначаязначения переменных-заполнителей и тестирование, которое выше или ниже, но мне интересно, может ли какой-нибудь гуру предложить супер крутой и эффективный способ сделать это?Приведенный ниже код в основном описывает то, что я пытаюсь достичь: </p>

var splitValues = new List<double?>();
splitValues.Add(Math.Round(assetSplit.EquityTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.PropertyTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.FixedInterestTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.CashTypeSplit() ?? 0));

var listSum = splitValues.Sum(split => split.Value);
if (listSum != 100)
{
    if (listSum > 100)
    {
        // how to get highest value and decrement by 1 until listSum == 100
        // then reassign back into the splitValues list?
        var highest = // ??
    }
    else
    {
        // how to get lowest where value is > 0, and increment by 1 until listSum == 100
        // then reassign back into the splitValues list?
        var lowest = // ??
    }
}

update: список должен оставаться в том же порядке, в котором добавляются элементы.

Ответы [ 9 ]

4 голосов
/ 15 июня 2010

Я думаю, что наиболее эффективная вещь, вероятно, состоит в том, чтобы не использовать метод List.Sum () и выполнить один цикл, который вычисляет сумму, наименьшую и наибольшую Также легко писать, отлаживать, читать и поддерживать, что я бы назвал супер-классным.

3 голосов
/ 15 июня 2010

Обновление : Гах, я не заметил, что у вас, кажется, есть только 4 элемента в списке.Ответ здесь для общего случая, и будет излишним для 4-элементной задачи.Продолжайте цикл.


Ну, лично я бы использовал структуру данных Heap, где каждый элемент - это значение каждого элемента + его позиция в исходном списке.

Вам нужновыследить хорошую реализацию кучи для C #, хотя.Вы можете использовать мой, но он является частью большой библиотеки классов, так что это может быть немного резиновый шарик, чтобы втянуть в ваш проект.Код здесь: Мой Mercurial Repository .

Ниже я приведу примеры того, как будет работать моя реализация.

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

Итак, я бы сделал следующее:

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

Затем, если сумма <0, возьмите первый элемент кучи (значение + позиция), увеличьте значение висходный список на 1, затем <em>заменить первый элемент кучи на (значение + 1), положение.Замена первого элемента кучи приводит к его удалению, а затем к чтению, что может переместить его в другое положение, чем первое, если оно больше не является самым высоким / самым низким значением.Например, предположим, у вас есть следующий список:

list: 1, 2, 3

Куча теперь выглядит так:

heap: (1, 0), (2, 1), (3, 2)  <-- second value is position, 0-based

т.е.Вы строите это так:

position:  0, 1, 2
list:      1, 2, 3
           |  |  +-------+
           |  |          |
         +-+  +--+       |
         |       |       |
       <-+>    <-+>    <-+>
heap: (1, 0), (2, 1), (3, 2)

Теперь, если сумма слишком мала, вы берете первый элемент lo-heap, который равен (1, 0), увеличиваете значение в позиции 0 вИсходный список на 1, затем замените первый элемент кучи (который по-прежнему (1, 0)) новым элементом, содержащим новое значение, в той же позиции.

После замены список и куча теперьвыглядит так:

list: 2, 2, 3
heap: (2, 0), (2, 1), (3, 1)

Допустим, сумма все еще мала, поэтому повторите.Теперь, при повторном добавлении (3, 0) вместо (2, 0), оно будет немного сдвинуто обратно в кучу, поэтому оно выглядит следующим образом:

list: 3, 2, 3
heap: (2, 1), (3, 0), (3, 1)

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

Итак, давайте посмотрим некоторый код.Я предполагаю, что у вас есть массив значений, подобных этому:

int[] values = new int[] { ... };

Таким образом, с моей реализацией кучи, следующее будет делать то, что вы хотите:

using System;
using System.Collections.Generic;
using System.Linq;
using LVK.DataStructures.Collections;

namespace SO3045604
{
    class LowestComparer : IComparer<Tuple<int, int>>
    {
        public int Compare(Tuple<int, int> x, Tuple<int, int> y)
        {
            return x.Item1.CompareTo(y.Item1);
        }
    }

    class HighestComparer : IComparer<Tuple<int, int>>
    {
        public int Compare(Tuple<int, int> x, Tuple<int, int> y)
        {
            return -x.Item1.CompareTo(y.Item1);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[] values = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            var valuesWithPositions = values
                .Select((value, index) => Tuple.Create(value, index));

            var loHeap = new Heap<Tuple<int, int>>(
                new LowestComparer(),
                valuesWithPositions);
            var hiHeap = new Heap<Tuple<int, int>>(
                new HighestComparer(),
                valuesWithPositions);

            int sum = values.Aggregate((a, b) => a + b);

            while (sum < 75)
            {
                var lowest = loHeap[0];
                values[lowest.Item2]++;
                loHeap.ReplaceAt(0, 
                    Tuple.Create(lowest.Item1 + 1, lowest.Item2));
                sum++;
            }
            while (sum > 55)
            {
                var highest = hiHeap[0];
                values[highest.Item2]--;
                hiHeap.ReplaceAt(0,
                    Tuple.Create(highest.Item1 - 1, highest.Item2));
                sum--;
            }

            // at this point, the sum of the values in the array is now 55
            // and the items have been modified as per your algorithm
        }
    }
}
2 голосов
/ 15 июня 2010

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

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

1 голос
/ 15 июня 2010

Если я правильно прочитал этот код, в вашем List <> будет только 4 члена, верно?
Если это так, зацикливание не требуется или не рекомендуется.

Просто сохраните ваши данные в 4 переменных и разберите их с помощью if / then

0 голосов
/ 29 апреля 2011

Чтобы получить максимум ... очень просто:

// For type safety
List<double> list = new List<double>()
{ 1.2, 4.3, 7.8, 4.4, 9.3, 10.2, 55.6, 442.45, 21.32, 43.21 };

d.Sort();
list.Sort();

Console.WriteLine(d[d.Count - 1].ToString());
Console.WriteLine(list[list.Count - 1]);
0 голосов
/ 15 июня 2010

Похоже, я сначала неправильно понял вопрос.Очевидно, что цель состоит не в том, чтобы найти самый высокий / самый низкий и добавить +/- 1 к этому элементу, пока сумма не станет равной 100;цель состоит в том, чтобы найти новый максимум / минимум каждый раз, когда вы добавляете +/- 1.

Вот еще один ответ, основанный на Сани:

var splitValues = new List<double?>(); 
splitValues.Add(Math.Round(assetSplit.EquityTypeSplit() ?? 0)); 
splitValues.Add(Math.Round(assetSplit.PropertyTypeSplit() ?? 0)); 
splitValues.Add(Math.Round(assetSplit.FixedInterestTypeSplit() ?? 0)); 
splitValues.Add(Math.Round(assetSplit.CashTypeSplit() ?? 0)); 

var listSum = splitValues.Sum();
while (listSum != 100) 
{ 
    int increment = listSum > 100 ? -1 : 1;
    var value = listSum > 100 ? splitValues.Max() : splitValues.Min();
    splivValue[splitValues.IndexOf(value)] += increment;
    listSum += increment;
}
0 голосов
/ 15 июня 2010

Вот реализация, использующая метод Aggregate Линка (при условии, что list представляет собой список или массив значений типа double или что-либо, что реализует IList<double>):

var stats = list.Aggregate(
                     StatsAggregate.Default,
                     (a, v) => a.ProcessValue(v));

if (stats.Sum > 100.0)
{
    list[stats.MaxIndex] -= (stats.Sum - 100.0);
}
else if (stats.Sum < 100.0)
{
    list[stats.MinIndex] += (100.0 - stats.Sum);
}

...

struct StatsAggregate
{
    public static StatsAggregate Default
    {
        get
        {
            return new StatsAggregate
            {
                Sum = 0,
                Min = double.MaxValue,
                MinIndex = -1,
                Max = double.MinValue,
                MaxIndex = -1
            };
        }
    }

    private int currentIndex;

    public double Sum { get; private set; }
    public double Min { get; private set; }
    public double Max { get; private set; }
    public int MinIndex { get; private set; }
    public int MaxIndex { get; private set; }

    public StatsAggregate ProcessValue(double value)
    {
        return new StatsAggregate
        {
            Sum = this.Sum + value,
            Max = Math.Max(this.Max, value),
            MaxIndex = value > this.Max ? currentIndex : MaxIndex,
            Min = Math.Min(this.Min, value),
            MinIndex = value < this.Max ? currentIndex : MinIndex,
            currentIndex = currentIndex + 1
        };
    }
}

Преимущество этого метода заключается в том, чтоон повторяет список только один раз, и код более понятен, чем в цикле foreach (ИМХО ...)

0 голосов
/ 15 июня 2010

Не уверен, что понял ваш вопрос ... Как насчет этого?

        const double MIN_VALUE = 0.01;

        var values = new List<double>();
        var rand = new Random();
        for (int i = 0; i < 100; i++)
        {
            values.Add(rand.Next(0, 100) / 10);
        }

        double sum = 0, min = 0, max = 0;
        for (int i = 0; i < values.Count; i++)
        {
            var value = values[i];
            sum += value;
            min = Math.Min(value, min);
            max = Math.Max(value, max);
        }

        if (min == 0) min = MIN_VALUE;
        if (max == 0) max = MIN_VALUE;
        while (Math.Abs(sum - 100) > MIN_VALUE)
        {
            if (sum > 100)
                sum -= max;

            if (sum < 100)
                sum += min;
        }
0 голосов
/ 15 июня 2010

Я бы сделал это примерно так:

var splitValues = new List<double?>();
splitValues.Add(Math.Round(assetSplit.EquityTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.PropertyTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.FixedInterestTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.CashTypeSplit() ?? 0));

var listSum = splitValues.Sum(split => split.Value);
while (listSum != 100)
{
  var value = listSum > 100 ? splitValues.Max() : splitValues.Min();
  var idx = splitValues.IndexOf(value);
  splitValues.RemoveAt(idx);
  splitValues.Insert(idx, value + (listSum > 100 ? -1 : 1));
  listSum = splitValues.Sum(split => split.Value);
}

Примечание: Это решение будет работать для любого количества элементов в списке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...