アイテムを並べ替えるときに並べ替えキーを生成する


11

エンドユーザーが目的の順序に整理できるアイテムがいくつかあります。アイテムのセットは順不同ですが、各アイテムには変更可能な並べ替えキーが含まれています。

最初のアイテム、最後のアイテム、または2つのアイテムの間に追加または移動されたアイテムの新しい並べ替えキーを生成できるアルゴリズムを探しています。移動するアイテムの並べ替えキーを変更するだけで済みます。

アルゴリズムの例としては、各ソートキーを浮動小数点数にして、2つのアイテムの間にアイテムを配置するときに、ソートキーをそれらの平均に設定します。アイテムを最初または最後に配置すると、最も外側の値+-1になります。

ここでの問題は、浮動小数点の精度が原因でソートが失敗する可能性があることです。同様に、2つの整数を使用して小数を表すと、数値が非常に大きくなり、通常の数値型で正確に表すことができなくなる場合があります(JSONとして転送する場合など)。BigIntsを使用したくありません。

これらの欠点の影響を受けない文字列を使用するなど、機能する適切なアルゴリズムはありますか?

膨大な数の移動をサポートするつもりはありませんが、上記のアルゴリズムは、約50回の移動後に倍精度浮動小数点数で失敗する可能性があります。


文字列は、その最後に文字を追加し続けるだけで分岐できるため、明らかな選択です。とはいえ、これに取り組むにはもっと良い方法があるように思います。
Robert Harvey

頭の上からは、他のアイテムのキーを変更せずに文字列を使用して動作させる方法もわかりません。
サンポ2016年

3
あなたが説明している問題は、注文のメンテナンスの問題
ネイサンメリル

1
リストの他のアイテムを変更しないことにどうして関心があるのですか?
GER

1
あなたはそれが文字列で動作させる方法、このようなものです: A, B, C- - A, AA, B, C- 。A, AA, AB, B, C A, AA, AAA, AAB, AAC, AB, AC, B, Cもちろん、文字列の間隔が狭くなりすぎて文字列が急速に成長しないようにすることもできますが、可能です。
Robert Harvey、

回答:


4

すべてのコメントと回答の要約として:

TL; DR-最初に提案されたアルゴリズムで倍精度浮動小数点数を使用すると、ほとんどの実用的な(少なくとも手動で順序付けされた)ニーズに十分対応できます。要素の個別の順序付きリストを維持することも検討する必要があります。他のソートキーソリューションはやや面倒です。

2つの問題のある操作は、最初/最後にアイテムを何度も挿入することと、アイテムを同じ場所に繰り返し挿入または移動することです(たとえば、3つの要素で最初の2つの要素の間で3番目の要素を繰り返し移動するか、2番目の要素として新しい要素を繰り返し追加します)素子)。

理論的な観点から(つまり、無限の並べ替えを可能にする)、考えられる唯一の解決策は、2つの無制限のサイズの整数を小数a / bとして使用することです。これにより、中間挿入の精度が無限になりますが、数値はますます大きくなる可能性があります。

文字列は多数の更新をサポートすることができますが(両方の操作のアルゴリズムを理解するのにまだ問題があります)、最初の位置に無限に多くを追加できないため(少なくとも通常の文字列ソートを使用できないため)比較)。

整数では、ソートキーの初期間隔を選択する必要があります。これにより、実行できる途中挿入の数が制限されます。並べ替えキーの間隔を最初に1024にした場合、隣接する番号を取得する前に実行できるのは、最悪の場合の10の中間挿入のみです。大きな初期間隔を選択すると、実行できる最初/最後の挿入の数が制限されます。64ビット整数を使用すると、どちらの方法でも最大63の操作に制限されます。これは、中間挿入と最初/最後の挿入をアプリオリに分割する必要があるためです。

浮動小数点値を使用すると、アプリオリの間隔を選択する必要がなくなります。アルゴリズムは簡単です:

  1. 挿入された最初の要素にはソートキー0.0があります
  2. 最初または最後に挿入(または移動)された要素には、それぞれ最初の要素のソートキー-1.0または最後の要素+ 1.0があります。
  3. 2つの要素の間に挿入(または移動)された要素には、2つの要素の平均に等しいソートキーがあります。

倍精度の浮動小数点を使用すると、52のワーストケースの中間挿入が可能になり、事実上無限(約1e15)の最初/最後の挿入が可能になります。実際には、アイテムを最初から最後に移動するたびに使用できる範囲が拡大するため、アイテムをアルゴリズム内で移動すると自己修正する必要があります。

倍精度浮動小数点数には、すべてのプラットフォームでサポートされており、事実上すべてのトランスポート形式とライブラリで簡単に格納および転送できるという利点もあります。これが最終的に使用したものです。


0

@Sampoの概要に基づいて、TypeScriptでソリューションを作成しました。コードは以下にあります。

その過程で得られたいくつかの洞察。

  • 2つの既存の並べ替えキーの真ん中に挿入するだけで、新しい並べ替えキーを生成する必要があります。スワップ(つまり、再配置)によって分割(つまり、新しい中点)発生しませ2つのアイテムを移動し、そのうちの1つだけをタッチした場合、リスト内で2つの要素がどの位置を変更したかに関する情報が失われます。そもそもそれが要件だったとしても、それが良い考えであることを確認してください

  • 1074:番目の中点分割ごとに、浮動小数点範囲を正規化する必要があります。これを検出するには、新しい中点が不変式を満たすかどうかを確認します

    a.sortKey < m && m < b.sortKey

  • 並べ替えキーが正規化されているため、スケーリングは重要ではありません1074。正規化は、すべての中間点分割でも発生します。そもそも数値をもっと広く分布させても状況は改善しないでしょう。

  • ソートキーの正規化は非常にまれです。このコストは、正規化が目立たなくなるまで償却します。ただし、要素が数千以上ある場合は、このアプローチに注意します。


export interface HasSortKey {
  sortKey: number;
}

function normalizeList<T extends HasSortKey>(list: Array<T>) {
  const normalized = new Array<T>(list.length);
  for (let i = 0; i < list.length; i++) {
    normalized[i] = { ...list[i], sortKey: i };
  }
  return normalized;
}

function insertItem<T extends HasSortKey>(
  list: Array<T>,
  index: number,
  item: Partial<T>
): Array<T> {
  if (list.length === 0) {
    list.push({ ...item, sortKey: 0 } as T);
  } else {
    // list is non-empty

    if (index === 0) {
      list.splice(0, 0, { ...item, sortKey: list[0].sortKey - 1 } as T);
    } else if (index < list.length) {
      // midpoint, index is non-zero and less than length

      const a = list[index - 1];
      const b = list[index];

      const m = (a.sortKey + b.sortKey) / 2;

      if (!(a.sortKey < m && m < b.sortKey)) {
        return insertItem(normalizeList(list), index, item);
      }

      list.splice(index, 0, { ...item, sortKey: m } as T);
    } else if (index === list.length) {
      list.push({ ...item, sortKey: list[list.length - 1].sortKey + 1 } as T);
    }
  }
  return list;
}

export function main() {
  const normalized: Array<number> = [];

  let list: Array<{ n: number } & HasSortKey> = [];

  list = insertItem(list, 0, { n: 0 });

  for (let n = 1; n < 10 * 1000; n++) {
    const list2 = insertItem(list, 1, { n });
    if (list2 !== list) {
      normalized.push(n);
    }
    list = list2;
  }

  let m = normalized[0];

  console.log(
    normalized.slice(1).map(n => {
      const k = n - m;
      m = n;
      return k;
    })
  );
}

0

そこに行って、それをやったら、もう一度やらなければならないかもしれません。文字列をソートキーとして使用すると、常に2つの指定されたキーの間にあるキーを見つけることができます。文字列が長すぎて好みに合わない場合は、複数またはすべての並べ替えキーを変更する必要があります。


ただし、別の文字列キーの前にあるキーを常に見つけることができるとは限りません。
Sampo

-1

整数を使用し、初期リストのソートキーを500 * item numberに設定します。アイテム間に挿入するときは、平均を使用できます。これにより、多くの挿入が最初から可能になります


2
これは実際にはフロートを使用するよりも悪いです。500の初期間隔では8-9中間点挿入(2 ^ 9 = 512)のみが許可されますが、ダブルフロートでは約50が許可され、最初に間隔を選択する問題はありません。
Sampo

500のギャップフロートを使用してください!
Rob Mulder

フロートを使用する場合、中間挿入の制限要因は仮数部のビット数であるため、ギャップは違いがありません。そのため、フロートを使用するときにデフォルトのギャップ1.0を提案しました。
Sampo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.