DulcetAirman icon

Stalin-Sort using Divide&Conquer

DulcetAirman | PRO | 12/24/23 05:51:16 PM UTC (Edited) | 0 ⭐ | 1451 👁️ | Never ⏰ | []
Java |

9.81 KB

|

None

|

0 👍

/

0 👎

package stalin;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
 
/**
 * Stalin-Sort is an impractical algorithm for bringing data structures into order.
 * 
 * <p>
 * Idea by: <code>@[email protected]</code>.
 * 
 * <p>
 * <img src="https://i.redd.it/x9triplll1v11.jpg" width=360 height=198 alt="I
 * came up with a single pass O(n) sort algorithm I call Stalin-Sort. U iterate
 * down the list of elements checking if they are in order. Any element which is
 * out of order is eliminated. At the end u have a sorted list"/>
 * 
 * <p>
 * However, this does not specify how exactly the elements are chosen. For
 * example for the input <code>[42,1,2,3,4,5,6]</code> you just get
 * <code>[42]</code> instead of <code>[1,2,3,4,5,6]</code>. You can get a better
 * result if you use <i>divide and conquer</i>. And that is something Stalin
 * would do, don't you think? So this is the recursive solution, implemented in
 * Java. It tries all possible results, which is not a single pass and therefore
 * defeats the purpose of having a fast algorithm. And none of these are
 * in-place, so a new data structure has to be created. That's why I added an
 * implementation using a {@link LinkedList}.
 * <p>
 * There are 5 public static methods:
 * <ul>
 * <li>{@link #sort(int[])}: sort integers in <code>O(n)</code>
 * <li>{@link #sortDC(int[])} sort integers using <i>divide and conquer</i>
 * <li>{@link #sort(List)}: sort any List in <code>O(n)</code>
 * <li>{@link #sortDC(List)} sort any List using <i>divide and conquer</i>
 * <li>{@link #inplace(LinkedList)} implementation of the original idea</i>
 * </ul>
 *
 * @author Claude Martin
 *
 */
public class StalinSort {
  /**
   * Naive implementation of Stalin-Sort for integers. It works but it does not
   * return the longest possible array. It's not exactly in <code>O(n)</code>
   * because the resulting array has to be created.
   */
  public static int[] sort(final int[] integers) {
    var previous = Integer.MIN_VALUE;
    final var accepted = new boolean[integers.length];
    var newLen = 0;
    for (var i = 0; i < integers.length; i++) {
      final var value = integers[i];
      if (previous < value) {
        final var next = i + 1 < integers.length ? integers[i + 1] : Integer.MAX_VALUE;
        if (next > previous && next < value) {
          // Eliminate this one and keep the next one instead.
        } else {
          accepted[i] = true;
          newLen++;
          previous = value;
        }
      } // else: Eliminate (not accepted)
    }
    final var result = new int[newLen];
    for (int i = 0, j = 0; i < accepted.length; i++) {
      if (accepted[i])
        result[j++] = integers[i];
    }
    return result;
  }
 
  /**
   * Improved implementation of Stalin-Sort for integers to find the best
   * solution. Uses <i>divide and conquer</i> to solve this problem. This is not
   * in <code>O(n)</code>. More like <code>O(n log<sub>n</sub>)</code>.
   */
  public static int[] sortDC(final int[] input) {
    final boolean[] accepted = new boolean[input.length];
    final int newLen = _sortDC(input, 0, accepted, 0, Integer.MIN_VALUE);
    final var result = new int[newLen];
    for (int i = 0, j = 0; i < input.length; i++) {
      if (accepted[i])
        result[j++] = input[i];
    }
    return result;
  }
 
  /**
   * Recursive Stalin-Sort with <i>divide and conquer</i>.
   *
   * @param input
   *          the original input (never changes)
   * @param position
   *          the position of the value to be accepted or eliminated
   * @param accepted
   *          bit set of already accepted values, which will be updated.
   * @return the length of the result. the longer one always wins.
   */
  private static int _sortDC(final int[] input, final int position, final boolean[] accepted, final int count,
      final int lastAccepted) {
    final var len = input.length;
    // System.out.println(String.format("%s = %d", Arrays.toString(accepted),
    // count));
    if (len == position)
      return count;
 
    if (position > 0 && input[position] < lastAccepted) {
      // input[position] has to be eliminated.
      return _sortDC(input, 1 + position, accepted, count, lastAccepted);
    }
    // First try: eliminate the value
    final var accepted1 = Arrays.copyOf(accepted, len);
    // accepted1[position] = false;
    final int result1 = _sortDC(input, 1 + position, accepted1, count, lastAccepted);
    // Second try: accept the value
    final var accepted2 = Arrays.copyOf(accepted, len);
    accepted2[position] = true;
    final int result2 = _sortDC(input, 1 + position, accepted2, 1 + count, input[position]);
 
    // Which one is better?
    if (result2 > result1) {
      System.arraycopy(accepted2, 0, accepted, 0, len);
      return result2;
    } else {
      System.arraycopy(accepted1, 0, accepted, 0, len);
      return result1;
    }
  }
 
  /**
   * Naive implementation of Stalin-Sort. This always creates a new list (not
   * in-place!). It works but it does not return the longest possible list. Works
   * great with linked lists. But no support for <code>null</code>.
   */
  public static <T extends Comparable<? super T>> List<T> sort(final List<T> input) {
    final int size = input.size();
    if (size < 2)
      return new ArrayList<>(input);
    T previous = input.get(0);
    final var result = new ArrayList<T>(size);
    result.add(previous);
    for (var i = 1; i < size; i++) {
      final var value = input.get(i);
      if (previous.compareTo(value) < 0) {
        result.add(previous = value);
      }
    }
    return result;
  }
 
  /**
   * Improved implementation of Stalin-Sort to find the best solution. Uses
   * <i>divide and conquer</i> to solve this problem. This is not in
   * <code>O(n)</code>. More like <code>O(n log<sub>n</sub>)</code>.
   */
  public static <T extends Comparable<? super T>> List<T> sortDC(final List<T> input) {
    final boolean[] accepted = new boolean[input.size()];
    final int newLen = _sortDC(input, 0, accepted, 0, null);
    final var result = new ArrayList<T>(newLen);
    for (int i = 0; i < input.size(); i++) {
      if (accepted[i])
        result.add(input.get(i));
    }
    return result;
  }
 
  /**
   * Recursive Stalin-Sort with <i>divide and conquer</i>. Does not support
   * <code>null</code>.
   *
   * @param input
   *          the original input (never changes)
   * @param position
   *          the position of the value to be accepted or eliminated
   * @param accepted
   *          bit set of already accepted values, which will be updated.
   * @return the length of the result. the longer one always wins.
   */
  private static <T extends Comparable<? super T>> int _sortDC(final List<T> input, final int position,
      final boolean[] accepted, final int count, final T lastAccepted) {
    final var len = input.size();
    if (len == position)
      return count;
    if (position > 0 && lastAccepted != null && input.get(position).compareTo(lastAccepted) < 0) {
      // input[position] has to be eliminated.
      return _sortDC(input, 1 + position, accepted, count, lastAccepted);
    }
    // First try: eliminate the value
    final var accepted1 = Arrays.copyOf(accepted, len);
    // accepted1[position] = false;
    final int result1 = _sortDC(input, 1 + position, accepted1, count, lastAccepted);
    // Second try: accept the value
    final var accepted2 = Arrays.copyOf(accepted, len);
    accepted2[position] = true;
    final int result2 = _sortDC(input, 1 + position, accepted2, 1 + count, input.get(position));
 
    // Which one is better?
    if (result2 > result1) {
      System.arraycopy(accepted2, position, accepted, position, len - position);
      return result2;
    } else {
      System.arraycopy(accepted1, position, accepted, position, len - position);
      return result1;
    }
  }
 
  /**
   * This is the Stalin-Sort as it should be. Use this for best performance. Does
   * not support <code>null</code>.
   */
  public static <T extends Comparable<? super T>> void inplace(LinkedList<T> input) {
    final var iterator = input.iterator();
    if (!iterator.hasNext())
      return;
    var previous = iterator.next();
    while (iterator.hasNext()) {
      var value = iterator.next();
      if (value.compareTo(previous) < 0)
        iterator.remove();
      else
        previous = value;
    }
  }
 
  /**
   * Entry point to show that and how it works.
   *
   * @param args
   *          you can give a list of strings to be sorted (optional)
   */
  public static void main(final String[] args) {
    if (args.length > 0) {
      System.out.println("Using your input to demonstrate Stalin-Sort.");
      final var list = new LinkedList<>(List.of(args));
      System.out.println("Input:\t\t" + list);
      System.out.println("Naive:\t\t" + sort(list));
      System.out.println("Div&Conq:\t" + sortDC(list));
      inplace(list);
      System.out.println("in-place:\t" + list);
    } else {
      final int[] array = { 0, 1, 2, -123, 6, 7, 8, 3, 0, 4, 5, 6 };
      System.out.println("Demonstration of Stalin-Sort using integers and Strings.");
      System.out.println("Input:\t\t" + Arrays.toString(array));
      System.out.println("Naive:\t\t" + Arrays.toString(sort(array)));
      System.out.println("Div&Conq:\t" + Arrays.toString(sortDC(array)));
      System.out.println("========================");
      final var list = new LinkedList<>(List.of("A", "B", "x", "Ab", "y", "A", "", "A", "A", "A", "A", "z", "A"));
      System.out.println("Input:\t\t" + list);
      System.out.println("Naive:\t\t" + sort(list));
      System.out.println("Div&Conq:\t" + sortDC(list));
      inplace(list);
      System.out.println("in-place:\t" + list);
    }
  }
}

Comments