【问题标题】:How can I push an element into array at a sorted index position?如何将元素推入排序索引位置的数组中?
【发布时间】:2017-08-14 06:24:29
【问题描述】:

假设我已经有一个包含这些文章的数组,按最低到最高价格排序:

[
  { title: "Article 3", price: 1.49 },
  { title: "Article 1", price: 3.00 },
  { title: "Article 4", price: 5.99 },
  { title: "Article 2", price: 19.99 }
]

基本上我想在正确的位置(按价格)将另一篇文章推送到数组中。我该怎么做?

我推送的新文章可能有以下属性:

{ title: "Article 5", price: 12.00 }

我希望文章出现在索引 3(文章 4 和 2 之间)。

更新(解决方案)

我使用@klutt 的答案和二进制搜索算法创建了一个原型方法:

Array.prototype.pushSorted = function(el, compareFn) {
  this.splice((function(arr) {
    var m = 0;
    var n = arr.length - 1;

    while(m <= n) {
      var k = (n + m) >> 1;
      var cmp = compareFn(el, arr[k]);

      if(cmp > 0) m = k + 1;
        else if(cmp < 0) n = k - 1;
        else return k;
    }

    return -m - 1;
  })(this), 0, el);

  return this.length;
};

const sortByPrice = (a, b) => a.price > b.price;
theArray.pushSorted({ title: "Article 5", price: 12.00 }, sortByPrice);

【问题讨论】:

  • “不知道当前元素”是什么意思有点不清楚。我猜你可以访问列表,对吧?所以你可以阅读它吗?
  • 如果它是手动排列的数组,我可以阅读它。但是,假设我正在从 API 获取结果。我不知道我收到了什么物品。排序必须在我(作为开发人员)不知道推送哪些项目的情况下完成。
  • 但是您从 API 获取了一个数组,然后您可以毫无问题地读取该数组?您还可以从 API 中获得一个元素,您也可以毫无问题地阅读它吗?您的任务是创建一个新的排序数组,并在正确的位置插入该元素。我明白你的问题了吗?
  • @NordlingArt 仅供参考,当需要时,拼接函数无法将 el 插入数组的最后。应该返回 m 而不是 -m-1?
  • @NordlingArt 这个原型解决方案不起作用!使用一个简单的整数列表,我最终得到一个非常未排序的数组。仍在试图找出问题所在......

标签: javascript arrays


【解决方案1】:

如果它是一个很长的列表,你不想每次都对列表​​进行排序。使用浮点数进行排序最多为 O(n*log n)。如果你进行线性搜索,你将得到 O(n) 。

function search(a, v) {
    if(a[0]['price'] > v['price']) {
        return 0;
    }
    var i=1;
    while (i<a.length && !(a[i]['price'] > v['price'] && a[i-1]['price'] <= v['price'])) {
        i=i+1;
        }
    return i;
}

myArray.splice(search(myArray, newItem), 0, newItem)

但是,如果列表很长,最好进行二分搜索而不是线性搜索。这会将其降低到 O(log n)。二进制搜索非常简单。你从中间开始。如果元素大于您要搜索的元素,则将相同的算法应用于列表的上半部分,否则应用于下半部分。在网上很容易找到二分查找的代码示例。

这是一个例子:

function binarySearch(ar, el, compare_fn) {
    if (el.price < ar[0].price)
        return 0;
    if (el.price > ar[ar.length-1].price)
        return ar.length;
    var m = 0;
    var n = ar.length - 1;
    while (m <= n) {
        var k = (n + m) >> 1;
        var cmp = compare_fn(el, ar[k]);
        if (cmp > 0) {
            m = k + 1;
        } else if(cmp < 0) {
            n = k - 1;
        } else {
            return k;
        }
    }
    return -m - 1;
}

function comp(a, b) {
    return a['price']>b['price']
}

myArray.splice(binarySearch(myArray, element, comp), 0, element)

(从这里偷Binary Search in Javascript

但要总结一下。添加元素然后排序通常是一个坏主意。最好的情况是没关系,但既然你知道列表是排序的,为什么不至少做一次线性搜索呢?

如果列表很小,这无关紧要,但如果列表有数百万个元素,差异将非常明显。

编辑:

我做了一个快速而原始的基准测试。

            10,000   100,000  1000,000   10,000,000  
Sort            80       900     13000          N/A
Linear           2         2        25         5000
Binary           2         2         5           21

我测量了在四种不同尺寸上运行这三种算法所需的时间。我不想等待排序在一千万个元素上结束。因此不适用。时间以毫秒为单位。请注意,该基准非常原始,但它提供了一个关于尺寸增长时它会影响多少的概念。

【讨论】:

  • 就是这样,谢谢!那么你会推荐哪一个?在这种情况下是线性搜索还是二分搜索?
  • 在实践中,如果我只需要完成工作并且我知道列表很小并且性能永远不会成为问题,我会选择我能生成的最短和最易读的代码,这可能会附加元素然后对其进行排序。推荐一个用于通用目的是不可能的。原始性能事实是二进制搜索最快,线性介于两者之间,然后排序最慢。如果列表的元素少于 10 个,我几乎可以保证没关系。
  • 知道了。好吧,实际上这是针对显示文章的应用程序提要。文章的数量可能会有所不同,但可以将列表视为 Instagram 列表,这意味着可能会有大量的项目。因此我采用二分搜索方法。
  • 我刚刚意识到这实际上不起作用。请check this fiddle 看看有什么问题吗?如果推送值最低,则它出现在不正确的索引处。与最高相同。只有在其他值之间,索引顺序才是正确的。
  • @NordlingArt 完成。还添加了一些基准比较。
【解决方案2】:

为避免每次都对数组进行排序,您可以遍历数组直到找到价格更高的元素,然后使用array.splice(index, 0, element) 将您的元素插入到正确的位置:

var array = [
  { title: "Article 3", price: 1.49 },
  { title: "Article 1", price: 3.00 },
  { title: "Article 4", price: 5.99 },
  { title: "Article 2", price: 19.99 }
]

function insertSorted (array, element, comparator) {
  for (var i = 0; i < array.length && comparator(array[i], element) < 0; i++) {}
  array.splice(i, 0, element)
}

function compareByPrice (a, b) { return a.price - b.price }

insertSorted(array, { title: "Article 5", price: 12.00 }, compareByPrice)

console.log(array)

【讨论】:

    【解决方案3】:

    Update (Solution) 仍然不能很好地工作。应该放在开头或结尾的项目被放置在错误的位置,请参阅基于相同代码的修改后的解决方案:

    Array.prototype.pushSorted = function(el, compareFn) {
      let index = (function(arr) {
        var m = 0;
        var n = arr.length - 1;
    
        while(m <= n) {
          var k = (n + m) >> 1;
          var cmp = compareFn(el, arr[k]);
    
          if(cmp > 0) m = k + 1;
            else if(cmp < 0) n = k - 1;
            else {
              console.log(`m=${m} k=${k}`)
              return k;
            };
        }
    
    
        return -m - 1;
      })(this);
    
      if (index >= 0)
        this.splice(index, 0, el);
      else if (index < 0)
        this.splice((index * -1) - 1, 0, el);
    
      return this.length;
    };
    

    示例用法:

    a = [1, 2, 3, 4, 5]
    a.pushSorted(2.5, (a,b) => a - b);
    a.pushSorted(8, (a,b) => a - b);
    a.pushSorted(0.5, (a,b) => a - b);
    

    【讨论】:

    • 这段代码似乎仍然不起作用。这是输出: [1, 2, 8, 0.5, 2.5, 3, 4, 5]
    • 为我工作。确定您不是在测试另一个实现?
    • 似乎对我有用
    【解决方案4】:

    我通过调整代码在 Angular 中实现了这一点。有人会发现它很有用。

    Array.prototype.sortedInsert = sortedInsert;
    
    interface Array<T> {
        sortedInsert(element: T, comparator: any): number;
    }
    
    /**
     *  This function takes an element, and a comparator function.
     *  It returns position of the inserted record.
     */
    
    function sortedInsert<T>(element: T, comparatorFn: any): number {
        const insertionIndex: number = findInsertionIndex(this, element, comparatorFn);
        this.splice(insertionIndex, 0, element);
        return insertionIndex;
    }
    
    function findInsertionIndex<T>(arr: Array<T>, element: T, comparatorFn: any): number {
        if (arr.length === 0) {
            return 0;
        }
    
        let low: number = 0;
        let high: number = arr.length - 1;
    
        while (low <= high) {
            const mid: number = Math.floor(low + (high - low) / 2);
            const comparison: number = comparatorFn(element, arr[mid]);
            if (comparison === 0) {
                return mid;
            } else if (comparison < 0) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return low;
    }
    

    要使用它,您必须将此文件导入app.module.ts
    使用示例:

    const arr = [];
    const comp = (a: number, b: number) => a - b;
    arr.sortedInsert(2, comp);
    arr.sortedInsert(1, comp);
    arr.sortedInsert(3, comp);
    console.log(arr);
    

    【讨论】:

      【解决方案5】:

      这是我当前的打字稿实现:

      enum SortDirection {
        Ascending,
        Descending
      }
      
      const propertyComparer = <T>(
        a: T,
        b: T,
        property: keyof T,
        direction: SortDirection
      ): number => {
        if (a[property] < b[property]) {
          return direction === SortDirection.Ascending ? 1 : -1;
        }
        if (a[property] > b[property]) {
          return direction === SortDirection.Ascending ? -1 : 1;
        }
        return 0;
      };
      
      const findAndInsertByProperty = <T>(
        arr: T[],
        item: T,
        property: keyof T,
        direction: SortDirection
      ): number => {
        let start = 0;
        let end = arr.length;
      
        while (start < end) {
          const mid = (start + end) >> 1;
          const result = propertyComparer<T>(item, arr[mid], property, direction);
      
          if (result === 0) {
            return mid;
          } else if (result < 0) {
            end = mid;
          } else {
            start = mid + 1;
          }
        }
      
        return end;
      };
      
      const myArray = [
        { title: "Article 3", price: 1.49 },
        { title: "Article 1", price: 3.00 },
        { title: "Article 4", price: 5.99 },
        { title: "Article 2", price: 19.99 }
      ];
      const value = { title: "Article 5", price: 12.00 };
      const valueLowest = { title: "Article 6", price: 0.49 };
      const valueHighest = { title: "Article 7", price: 20.00 };
      
      myArray.splice(findAndInsertByProperty(myArray, value, 'price', SortDirection.Descending), 0, value);
      myArray.splice(findAndInsertByProperty(myArray, valueLowest, 'price', SortDirection.Descending), 0, valueLowest);
      myArray.splice(findAndInsertByProperty(myArray, valueHighest, 'price', SortDirection.Descending), 0, valueHighest);
      
      console.log(myArray);
      

      【讨论】:

      • 非常好的一个,如果你能编辑你的答案并添加一个 JS 版本将非常感激
      【解决方案6】:

      只需推送您的项目

      var myArray = [
        { title: "Article 3", price: 1.49 },
        { title: "Article 1", price: 3.00 },
        { title: "Article 4", price: 5.99 },
        { title: "Article 2", price: 19.99 }
      ]
      
      myArray.push({ title: "Article 5", price: 12.00 })
      

      并对数组进行排序:

      myArray.sort(function(a, b) {
          return parseFloat(a.price) - parseFloat(b.price);
      });
      

      【讨论】:

      • 我不赞成这个,因为它没有利用数组从一开始就排序的事实。
      【解决方案7】:

      考虑我的解决方案如下;

      var articles=[
        { title: "Article 3", price: 1.49 },
        { title: "Article 1", price: 3.00 },
        { title: "Article 4", price: 5.99 },
        { title: "Article 2", price: 19.99 }
      ]
      

      现在这样写一个函数;

      function sortByKey(array, key) {
          return array.sort(function(a, b) {
              var x = a[key]; var y = b[key];
             return ((x < y) ? -1 : ((x > y) ? 1 : 0));
         });
      }
      

      现在插入一个元素;

      articles.push({ title: "Article 5", price: 12.00 });
      

      最后排序;

      articles=sortByKey(articles, 'price');
      

      【讨论】:

      • 我不赞成这个,因为它没有利用数组从一开始就排序的事实。
      【解决方案8】:

      不确定这是否最佳,但您可以将其推送到另一个(重复的)数组中,然后对其进行排序,然后检查其索引以便将其推送到正确的索引位置:

      Array.prototype.pushSorted = function(value, compareFunction) {
        const arrCopy = this.map(element => element);
      
        arrCopy.push(value);
        arrCopy.sort(compareFunction);
        this.splice(arrCopy.indexOf(value), 0, value);
      
        return this.length;
      };
      

      首先我们创建一个数组实例的副本。然后将元素推送到此数组副本中。然后对数组副本进行排序。然后将元素推送(通过splice 方法)到真正的数组中,同时检查它在数组副本中的索引。然后我们可以返回数组长度(就像普通的push)。

      例子:

      const sortByPrice = (a, b) => a > b ? 1 : -1;
      const newArticle = { title: "Article 5", price: 12.00 };
      
      theArray.pushSorted(newArticle, sortByPrice);
      

      【讨论】:

      • 我对此表示反对,因为它没有利用数组从一开始就排序的事实。
      • 看看我的回答,看看有什么问题。如果您知道数组已排序,则不需要对其进行排序。您只需搜索正确的位置并插入元素。
      • 它确实有效,但在问题中明确指出原始数组已排序。这只是一个关于优化的问题,这是真的。但是既然你说列表是排序的,一个好的答案应该利用这一点,特别是因为在这种情况下这样做并不复杂。
      • 不建议修改对象的原型。 stackoverflow.com/questions/6223449/…
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-27
      • 2022-11-13
      • 1970-01-01
      • 1970-01-01
      • 2016-06-03
      相关资源
      最近更新 更多