Сортировка массива целых чисел с помощью Ethereum

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

Кто-нибудь знает что-нибудь, что может помочь?

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

  function quickSort(uint[] arr, uint left, uint right) private returns(uint[] _arr){
      uint i = left;
      uint j = right;
      uint tmp;
      uint pivot = arr[(left + right) / 2];
      while (i <= j) {
            while (arr[i] < pivot)
                  i++;
            while (arr[j] > pivot)
                  j--;
            if (i <= j) {
                  tmp = arr[i];
                  arr[i] = arr[j];
                  arr[j] = tmp;
                  i++;
                  j--;
            }
      }
      if (left < j)
            quickSort(arr, left, j);
      if (i < right)
            quickSort(arr, i, right);
}

Есть у кого чем поделиться?

Часто возникает ошибка «ОШИБКА: неверный пункт назначения перехода (PUSH1)».

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

То же самое с:

function insertionSort(uint[] a){
 for (uint i = 1;i < a.length;i++){
  uint temp = a[i];
  uint j;
  for (j = i -1; j >= 0 && temp < a[j]; j--)
    a[j+1] = a[j];
  a[j + 1] = temp;
 }
}

изменить 2:

Исправлено также

  function insertionSort(uint[] a)internal {
   for (uint i = 1;i < a.length;i++){
    uint temp = a[i];
    uint j;
    for (j = i -1; j >= 0 && temp < a[j]; j--)
      a[j+1] = a[j];
    a[j + 1] = temp;
   }
  }

Ответы (6)

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

Следующий код работает. Используя ключевое слово storageв типе аргумента, вы можете передать ссылку на хранилище (работает только для внутренних функций и библиотек), иначе данные хранилища будут скопированы в память. В качестве оптимизации вы можете рассмотреть возможность копирования массива хранения в память, проверки того, отсортирован ли он, если нет, отсортируйте его и скопируйте обратно в хранилище. Еще одна потенциальная ловушка, связанная с надежностью браузера: вы должны вводить аргументы массива как [1,7,4,5].

Да, и лучшая оптимизация, конечно, состоит в том, чтобы сортировать массив вне цепочки и только проверять в цепочке, отсортирован он или нет.

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;


function quickSort(uint[] memory arr, int left, int right) pure {
    int i = left;
    int j = right;
    if (i == j) return;
    uint pivot = arr[uint(left + (right - left) / 2)];
    while (i <= j) {
        while (arr[uint(i)] < pivot) i++;
        while (pivot < arr[uint(j)]) j--;
        if (i <= j) {
            (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]);
            i++;
            j--;
        }
    }
    if (left < j)
        quickSort(arr, left, j);
    if (i < right)
        quickSort(arr, i, right);
}

contract QuickSort {
    function sort(uint[] memory data) public pure returns (uint[] memory) {
        quickSort(data, int(0), int(data.length - 1));
        return data;
    }
}

Редактировать (2020-10): проблема недостаточного заполнения исправлена ​​@subhodi и обновлена ​​​​до последней версии Solidity.

Значит, нет лучшего способа сортировки массивов int?
Я пытался, но как только я пытаюсь создать транзакцию для метода, который вызывает функцию, я получаю исключение Solidity (Bad Jump). Я также пробовал использовать ваш веб-инструмент и свою цепочку разработчиков, но я как бы застрял. Есть ли ограничение доступа к сохраненному массиву?
@ user697 Сколько газа вы отправляете в своей транзакции? Начните с 3 000 000 газа, пока все не заработает, чтобы избежать возможных проблем с газом во время разработки.
Не работает. Я не думаю, что это проблема с газом, хотя у меня никогда не кончается бензин. Я думаю, что я чего-то не понял о доступе к массиву в памяти. Кто-нибудь из вас успешно воспроизвёл? Я использую общедоступный массив из 5 несортированных чисел в качестве параметра.
с рекурсивной функцией у вас не будет проблемы с глубиной стека?
Быстрая сортировка - это O (log ^ 2 n). относительно размера. Поскольку размер стоит больше, чем вычисление, возможно, Heapsort будет лучше. В статье в Википедии отмечается, что пирамидальная сортировка используется в небольших встроенных системах, и вы можете считать EVM небольшой встроенной системой. Вы должны хотя бы попробовать
Похоже, в строке 21 есть ошибка: j--. Должен предшествовать, if (0 == j) break;иначе произойдет недополнение.

Алгоритм быстрой сортировки без каких-либо исключений VM: см. этот обзор https://gist.github.com/subhodi/b3b86cc13ad2636420963e692a4d896f

tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--; 

Когда j=0 и j-- приводит к большому целочисленному значению, хранящемуся в памяти j, это происходит потому, что j имеет тип uint(целое число без знака) и 0-1=-1 (j--), которые j не может хранить, поэтому значение j будет (2^256)-1. В следующем цикле, когда EVM читает arr[j], он считывает мусорное значение, приводящее к исключению.

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

j--;

к

if (j > 0)  j--; 

если вы хотите избежать целочисленного потери значимости.

Ответ Subhod выше хорош, но для разнообразия я написал версию, которая intполностью избегает s и сохраняет все uints. Я немного пинал шины, но вам следует провести дополнительные испытания, прежде чем развертывать их. Также, конечно, как заметил Ник Джонсон в Твиттере , в худшем случае быстрая сортировка медленная (O(n^2)), поэтому вы можете найти более быструю O(n log n).

function sort(uint[] memory arr) public pure {
    if (arr.length > 0)
        quickSort(arr, 0, arr.length - 1);
}

function quickSort(uint[] memory arr, uint left, uint right) public pure {
    if (left >= right)
        return;
    uint p = arr[(left + right) / 2];   // p = the pivot element
    uint i = left;
    uint j = right;
    while (i < j) {
        while (arr[i] < p) ++i;
        while (arr[j] > p) --j;         // arr[j] > p means p still to the left, so j > 0
        if (arr[i] > arr[j])
            (arr[i], arr[j]) = (arr[j], arr[i]);
        else
            ++i;
    }

    // Note --j was only done when a[j] > p.  So we know: a[j] == p, a[<j] <= p, a[>j] > p
    if (j > left)
        quickSort(arr, left, j - 1);    // j > left, so j > 0
    quickSort(arr, j + 1, right);
}

Проблема с вашим кодом заключается в том, что вы передаете in-memoryмассив рекурсивному вызову. Массив передается копией (разные экземпляры при каждом вызове) вместо ссылки (тот же массив).

Решение от @chriseth правильное, поскольку оно использует storageпереданное по ссылке. Этот подход, к сожалению, очень дорогостоящий, так как требует изменения контрактного хранилища, которое является самой дорогой операцией EVM.

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

function sort(uint[] storage data) {

    uint n = data.length;
    uint[] memory arr = new uint[](n);
    uint i;

    for(i=0; i<n; i++) {
      arr[i] = data[i];
    }

    uint[] memory stack = new uint[](n+2);

    //Push initial lower and higher bound
    uint top = 1;
    stack[top] = 0;
    top = top + 1;
    stack[top] = n-1;

    //Keep popping from stack while is not empty
    while (top > 0) {

      uint h = stack[top];
      top = top - 1;
      uint l = stack[top];
      top = top - 1;

      i = l;
      uint x = arr[h];

      for(uint j=l; j<h; j++){
        if  (arr[j] <= x) {
          //Move smaller element
          (arr[i], arr[j]) = (arr[j],arr[i]);
          i = i + 1;
        }
      }
      (arr[i], arr[h]) = (arr[h],arr[i]);
      uint p = i;

      //Push left side to stack
      if (p > l + 1) {
        top = top + 1;
        stack[top] = l;
        top = top + 1;
        stack[top] = p - 1;
      }

      //Push right side to stack
      if (p+1 < h) {
        top = top + 1;
        stack[top] = p + 1;
        top = top + 1;
        stack[top] = h;
      }
    }

    for(i=0; i<n; i++) {
      data[i] = arr[i];
    }
  }

Если вас интересуют gas consumptionразличные алгоритмы сортировки по плотности, вы можете взглянуть на этот репозиторий .

Только что протестировал ваш код, и он потребляет больше газа, чем пример с storageзаписью. Например, только начальное и окончательное копирование потребляет ~1,2 тыс. килобайт газа.
function sort_array(uint64[] arr_) returns (uint64 [] )
{
    uint256 l = arr_.length;
    uint64[] memory arr = new uint64[] (l);

    for(uint i=0;i<l;i++)
    {
        arr[i] = arr_[i];
    }

    for(i =0;i<l;i++)
    {
        for(uint j =i+1;j<l;j++)
        {
            if(arr[i]<arr[j])
            {
                uint64 temp= arr[j];
                arr[j]=arr[i];
                arr[i] = temp;

            }

        }
    }

    return arr;
}