항목을 재정렬 할 때 정렬 키 생성 최종 사용자가 원하는 순서로 구성

최종 사용자가 원하는 순서로 구성 할 수있는 여러 항목이 있습니다. 항목 세트는 순서가 없지만 각 항목에는 수정할 수있는 정렬 키가 있습니다.

첫 번째 항목, 마지막 항목 또는 두 항목 사이에 추가되거나 이동 된 항목에 대해 새 정렬 키를 생성 할 수있는 알고리즘을 찾고 있습니다. 이동하는 항목의 정렬 키만 수정하기를 희망합니다.

알고리즘 예는 각 정렬 키를 부동 소수점 숫자로 만들고 두 항목 사이에 항목을 배치 할 때 정렬 키를 평균으로 설정하는 것입니다. 항목을 처음 또는 마지막에 배치하면 가장 바깥쪽에 +1이 사용됩니다.

여기서 문제는 부동 소수점 정밀도가 정렬에 실패 할 수 있다는 것입니다. 소수를 나타 내기 위해 두 개의 정수를 사용하면 마찬가지로 숫자가 너무 커져서 일반 숫자 유형으로 정확하게 표현할 수 없습니다 (예 : JSON으로 전송할 때). BigInts를 사용하고 싶지 않습니다.

예를 들어, 이러한 단점의 영향을받지 않는 문자열을 사용하는 것과 같이 작동하는 적절한 알고리즘이 있습니까?

우리는 많은 수의 움직임을 지원하지는 않지만 위에서 설명한 알고리즘은 약 50 움직임 후 배정 밀도 부동 소수점 수에서 실패 할 수 있습니다.



답변

모든 의견과 답변을 요약하면 다음과 같습니다.

TL; DR- 처음 제안 된 알고리즘에 배정 밀도 부동 소수점 숫자를 사용하면 대부분의 실제 (적어도 수동으로 정렬 된) 요구에 충분해야합니다. 별도의 정렬 된 요소 목록을 유지하는 것도 고려해야합니다. 다른 정렬 키 솔루션은 다소 번거 롭습니다.

두 가지 문제가있는 작업은 처음부터 끝까지 반복해서 항목을 삽입하고 같은 지점에 반복적으로 항목을 삽입하거나 이동하는 것입니다 (예 : 세 요소가 첫 번째 요소 사이에서 세 번째 요소를 반복적으로 이동하거나 두 번째 요소로 새 요소를 반복적으로 추가 함) 요소).

이론적 관점에서 (즉, 무한한 재정렬 허용), 내가 생각할 수있는 유일한 해결책은 두 개의 무제한 크기 정수를 분수 a / b로 사용하는 것입니다. 이를 통해 미드 인서트의 정밀도가 무한 해지지 만 숫자는 점점 커질 수 있습니다.

문자열은 많은 수의 업데이트를 지원할 수 있지만 (두 가지 작업 모두에 대한 알고리즘을 파악하는 데 여전히 어려움이 있지만) 첫 번째 위치에서 무한히 많은 것을 추가 할 수 없기 때문에 (최소한 일반 문자열 정렬 사용) 비교).

정수는 정렬 키의 초기 간격을 선택해야하므로 수행 할 수있는 중간 삽입 수를 제한합니다. 처음에 정렬 키의 간격을 1024로 나누면 인접한 숫자를 갖기 전에 최악의 중간 삽입을 10 번만 수행 할 수 있습니다. 더 큰 초기 간격을 선택하면 수행 할 수있는 첫 번째 / 마지막 인서트 수가 제한됩니다. 64 비트 정수를 사용하면 ~ 63 작업으로 제한되며, 중간 삽입과 첫 번째 / 마지막 삽입간에 우선적으로 분할해야합니다.

부동 소수점 값을 사용하면 간격을 미리 선택할 필요가 없습니다. 알고리즘은 간단합니다.

  1. 삽입 된 첫 번째 요소에는 정렬 키 0.0이 있습니다
  2. 첫 번째 또는 마지막에 삽입 (또는 이동) 된 요소는 각각 첫 번째 요소 (1.0 또는 마지막 요소 + 1.0)의 정렬 키를 갖습니다.
  3. 두 요소 사이에 삽입 (또는 이동) 된 요소는 두 요소의 평균과 동일한 정렬 키를 갖습니다.

배정도 플로트를 사용하면 52 개의 최악의 미드 인서트와 사실상 무한 (약 1e15)의 첫 번째 / 마지막 인서트가 가능합니다. 실제로 알고리즘을 중심으로 항목을 이동하는 경우 항목을 처음 또는 마지막으로 이동할 때마다 사용할 수있는 범위가 확장되므로 자체적으로 수정해야합니다.

배정도 플로트는 또한 모든 플랫폼에서 지원되며 실질적으로 모든 전송 형식 및 라이브러리에 의해 쉽게 저장 및 전송된다는 이점이 있습니다. 이것이 우리가 사용한 결과입니다.


답변

@Sampo의 요약을 기반으로 TypeScript에서 솔루션을 작성했습니다. 코드는 아래에서 찾을 수 있습니다.

그 과정에서 몇 가지 통찰력을 얻었습니다.

  • 기존의 두 정렬 키 사이 의 중간 에만 삽입 하면 새로운 정렬 키가 필요하며, 교체 (즉, 재 배열)로 인해 분할 (예 : 새로운 중간 점)이 발생 하지 않습니다 . 두 항목을 이동하고 그 중 하나만 터치하면 목록에서 두 요소가 변경된 위치에 대한 정보가 손실됩니다. 시작해야하더라도 좋은 아이디어인지 확인하십시오.

  • 1074 : 번째 중간 점 분할마다 부동 소수점 범위를 정규화해야합니다. 우리는 단순히 새로운 중간 점이 불변을 만족시키는 지 확인함으로써 이것을 감지합니다.

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

  • 정렬 키가 정규화되기 때문에 스케일링은 중요하지 않습니다. 정규화는 여전히 1074중간 지점 분할 마다 발생합니다 . 우리가 처음에 숫자를 더 널리 배포하면 상황이 개선되지 않을 것입니다.

  • 정렬 키 정규화는 매우 드 rare니다. 정규화가 눈에 띄지 않는 지점까지이 비용을 상각합니다. 그러나 1000 개가 넘는 요소가있는 경우이 접근법에주의를 기울일 것입니다.


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;
    })
  );
}

답변

그렇게 되었으면 다시해야 할 수도 있습니다. 문자열을 정렬 키로 사용하면 주어진 두 키 사이에있는 키를 항상 찾을 수 있습니다. 문자열이 취향에 비해 너무 길면 여러 또는 모든 정렬 키를 수정해야합니다.


답변

정수를 사용하고 초기 목록의 정렬 키를 500 * 항목 번호로 설정하십시오. 항목 사이에 삽입 할 때 평균을 사용할 수 있습니다. 이를 통해 많은 삽입을 시작할 수 있습니다