Tạo các phím sắp xếp khi sắp xếp lại các mục


11

Chúng tôi có một số mặt hàng mà người dùng cuối sẽ có thể sắp xếp theo thứ tự mong muốn. Tập hợp các mục không được sắp xếp, nhưng mỗi mục chứa một khóa sắp xếp có thể được sửa đổi.

Chúng tôi đang tìm kiếm một thuật toán cho phép tạo khóa sắp xếp mới cho một mục được thêm hoặc di chuyển thành mục đầu tiên, mục cuối hoặc giữa hai mục bất kỳ. Chúng tôi hy vọng chỉ phải sửa đổi khóa sắp xếp của mục được di chuyển.

Một thuật toán ví dụ sẽ có mỗi khóa sắp xếp là một số dấu phẩy động và khi đặt một mục giữa hai mục, đặt khóa sắp xếp thành trung bình của chúng. Đặt một mục đầu tiên hoặc cuối cùng sẽ có giá trị ngoài cùng + - 1.

Vấn đề ở đây là độ chính xác của dấu phẩy động có thể khiến việc sắp xếp thất bại. Sử dụng hai số nguyên để biểu diễn một số phân số có thể khiến các số trở nên lớn đến mức chúng không thể được biểu diễn chính xác theo các loại số thông thường (ví dụ: khi chuyển dưới dạng JSON). Chúng tôi sẽ không muốn sử dụng BigInts.

Có một thuật toán phù hợp cho việc này sẽ hoạt động, ví dụ, sử dụng các chuỗi, sẽ không bị ảnh hưởng bởi những thiếu sót này?

Chúng tôi không tìm cách hỗ trợ số lượng lớn các bước di chuyển, nhưng thuật toán được mô tả ở trên có thể thất bại với số điểm nổi chính xác kép sau khoảng 50 lần di chuyển.


Chuỗi là một lựa chọn rõ ràng, bởi vì bạn chỉ có thể tiếp tục thêm các ký tự vào cuối của chúng để phân chia. Điều đó nói rằng, tôi cảm thấy như có một cách tốt hơn để tiếp cận điều này.
Robert Harvey

Từ đỉnh đầu tôi không thấy cách làm cho nó hoạt động bằng cách sử dụng các chuỗi mà không sửa đổi các phím của các mục khác.
Sampo

3
Vấn đề bạn đang mô tả được gọi là Vấn đề bảo trì đơn hàng
Nathan Merrill

1
Tại sao bạn quan tâm đến việc không sửa đổi các mục khác trong danh sách?
GER

1
Cách bạn làm cho nó hoạt động với các chuỗi như thế này: A, B, C- A, AA, B, C- A, AA, AB, B, C- A, AA, AAA, AAB, AAC, AB, AC, B, C. Tất nhiên, có lẽ bạn sẽ muốn sắp xếp các chữ cái của mình nhiều hơn để các chuỗi không phát triển quá nhanh, nhưng nó có thể được thực hiện.
Robert Harvey

Câu trả lời:


4

Như một bản tóm tắt của tất cả các ý kiến ​​và câu trả lời:

TL; DR - Sử dụng các số dấu phẩy động có độ chính xác kép với thuật toán được đề xuất ban đầu là đủ cho hầu hết các nhu cầu thực tế (ít nhất là được đặt hàng thủ công). Duy trì một danh sách theo thứ tự riêng biệt của các yếu tố nên được xem xét là tốt. Các giải pháp chính khác sắp xếp có phần rườm rà.

Hai thao tác có vấn đề là chèn các mục ở đầu / cuối lặp đi lặp lại và liên tục chèn hoặc di chuyển các mục vào cùng một vị trí (ví dụ: với ba phần tử liên tục di chuyển phần tử thứ ba giữa hai phần tử đầu tiên hoặc liên tục thêm phần tử mới làm phần tử thứ hai thành phần).

Từ quan điểm lý thuyết (nghĩa là cho phép sắp xếp lại vô hạn), giải pháp duy nhất tôi có thể nghĩ đến là sử dụng hai số nguyên kích thước không giới hạn làm phân số a / b. Điều này cho phép độ chính xác vô hạn cho các phần chèn giữa, nhưng các con số có thể ngày càng lớn.

Các chuỗi có thể hỗ trợ một số lượng lớn các bản cập nhật (mặc dù tôi vẫn gặp khó khăn khi tìm ra thuật toán cho cả hai thao tác), nhưng không phải là vô hạn, vì bạn không thể thêm vô số nhiều ở vị trí đầu tiên (ít nhất là sử dụng sắp xếp chuỗi thông thường so sánh).

Các số nguyên sẽ yêu cầu chọn khoảng cách ban đầu cho các phím sắp xếp, giới hạn số lần chèn giữa bạn có thể thực hiện. Nếu ban đầu bạn sắp xếp các phím sắp xếp không gian 1024, bạn chỉ có thể thực hiện 10 lần chèn giữa trường hợp xấu nhất trước khi bạn có các số liền kề. Chọn khoảng cách ban đầu lớn hơn giới hạn số lần chèn đầu tiên / cuối bạn có thể thực hiện. Sử dụng số nguyên 64 bit, bạn bị giới hạn ở ~ 63 thao tác, bạn cần phải phân chia giữa các lần chèn giữa và lần đầu tiên / lần cuối chèn trước một tiên nghiệm.

Sử dụng các giá trị dấu phẩy động sẽ loại bỏ nhu cầu chọn khoảng cách một ưu tiên. Thuật toán rất đơn giản:

  1. Phần tử đầu tiên được chèn có khóa sắp xếp 0,0
  2. Một phần tử được chèn (hoặc di chuyển) đầu tiên hoặc cuối cùng có khóa sắp xếp của phần tử đầu tiên - 1.0 hoặc phần tử cuối cùng + 1.0, tương ứng.
  3. Một phần tử được chèn (hoặc di chuyển) giữa hai phần tử có khóa sắp xếp bằng với mức trung bình của hai phần tử.

Sử dụng phao có độ chính xác kép cho phép 52 lần chèn giữa trường hợp xấu nhất và hiệu quả vô hạn (khoảng 1e15) lần đầu tiên / lần chèn cuối cùng. Trong thực tế khi di chuyển các mục xung quanh thuật toán sẽ tự điều chỉnh chính nó, vì mỗi lần bạn di chuyển một mục trước hoặc cuối cùng, nó sẽ mở rộng phạm vi có thể được sử dụng.

Phao chính xác kép cũng có lợi ích là chúng được hỗ trợ bởi tất cả các nền tảng và dễ dàng được lưu trữ và vận chuyển bởi tất cả các định dạng vận chuyển và thư viện. Đây là những gì chúng tôi đã sử dụng.


0

Tôi đã viết ra một giải pháp trong TypeScript dựa trên tóm tắt của @ Sampo. Mã có thể được tìm thấy dưới đây.

Một vài hiểu biết thu được trên đường đi.

  • Chỉ chèn vào giữa hai khóa sắp xếp hiện tại cần tạo khóa sắp xếp mới, việc hoán đổi (nghĩa là sắp xếp lại) không gây ra sự phân tách (tức là điểm giữa mới). Nếu bạn di chuyển hai mục và bạn chỉ chạm vào một trong số đó, bạn sẽ mất thông tin về hai yếu tố thay đổi vị trí trong danh sách. Ngay cả khi đó là một yêu cầu để bắt đầu, hãy lưu ý rằng đó là một ý tưởng tốt

  • Cứ sau 1074: chia điểm giữa chúng ta cần bình thường hóa phạm vi dấu phẩy động. Chúng tôi phát hiện điều này bằng cách kiểm tra xem điểm giữa mới có thỏa mãn bất biến không

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

  • Chia tỷ lệ không thành vấn đề, vì các phím sắp xếp được chuẩn hóa, chuẩn hóa vẫn xảy ra mỗi lần 1074phân chia điểm giữa. Tình hình sẽ không được cải thiện nếu chúng tôi phân phối số lượng rộng rãi hơn để bắt đầu.

  • Sắp xếp khóa chuẩn hóa là cực kỳ hiếm. Bạn sẽ khấu hao chi phí này đến mức mà việc bình thường hóa không đáng chú ý. Mặc dù vậy, tôi sẽ cẩn thận với phương pháp này nếu bạn có hơn 1000 yếu tố.


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

Đã ở đó, làm điều đó, có thể phải làm điều đó một lần nữa. Sử dụng một chuỗi làm khóa sắp xếp, sau đó bạn luôn có thể tìm thấy một khóa nằm giữa hai khóa đã cho. Nếu chuỗi quá dài so với sở thích của bạn, bạn phải sửa đổi nhiều hoặc tất cả các phím sắp xếp.


Bạn không thể luôn luôn tìm thấy một khóa trước một khóa chuỗi khác.
Sampo

-1

Sử dụng số nguyên và đặt khóa sắp xếp cho danh sách ban đầu là số mục 500 *. Khi chèn giữa các mục bạn có thể sử dụng mức trung bình. Điều này sẽ cho phép nhiều phần chèn thêm bắt đầu bằng


2
Điều này thực sự tồi tệ hơn so với sử dụng phao. Khoảng cách ban đầu là 500 chỉ cho phép chèn 8-9 điểm giữa (2 ^ 9 = 512), trong khi khoảng cách gấp đôi cho phép khoảng 50, không có vấn đề gì khi chọn khoảng cách ban đầu.
Sampo

Sử dụng khoảng cách 500 nổi!
Rob Mulder

Khi sử dụng float, khoảng cách không tạo ra sự khác biệt, vì yếu tố giới hạn cho các lần chèn giữa là số bit trong ý nghĩa. Đó là lý do tại sao tôi đề xuất khoảng cách mặc định là 1.0 khi sử dụng phao.
Sampo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.