Insertion sort implementation in scala

守給你的承諾、 提交于 2019-12-05 13:43:59

The implementation given in the wikipedia article fits your bill. Here it is, copied, pasted, and converted to Scala syntax:

def insertionSort(a: Array[Int]): Array[Int] = {
  for (i <- 1 until a.length) {
    // A[ i ] is added in the sorted sequence A[0, .. i-1]
    // save A[i] to make a hole at index iHole
    val item = a(i)
    var iHole = i
    // keep moving the hole to next smaller index until A[iHole - 1] is <= item
    while (iHole > 0 && a(iHole - 1) > item) {
      // move hole to next smaller index
      a(iHole) = a(iHole - 1)
      iHole = iHole - 1
    }
    // put item in the hole
    a(iHole) = item
  }
  a
}

This is what I came up with, which seems to be functional, generic, tail-recursive (foldLeft is tail-recursive)...:

def insertionSort[A](la: List[A])(implicit ord: Ordering[A]): List[A] = {
  def insert(la: List[A], a: A) = {
    val (h, t) = la.span(ord.lt(_, a))
    h ::: (a :: t)
  }

  la.foldLeft(List[A]()) {(acc, a) => insert(acc, a)}
}

My version of the insertion sort procedure is the following. It consists of two functions, isort and insert which have the following signatures:

  • isort(xs: List[int]) : List[int]: Takes an input list of integers and outputs a list of integers.
  • insert(x: Int, xs: List[int]) : List[int] Takes a sorted input list and an integer x and adds x to the right position in the list to maintain the invariant that the list is sorted. Example: insert(3, [1, 2]) = [1, 2, 3]

First let us code up the insert function.

  1. If the list is empty, return a list of the integer to be added.
  2. Otherwise, there are two possible cases. The head of the list is greater or equal to the given input integer, in which case we simply append the input integer to the beginning of the list and return it. The other case is when the input integer is greater than the head of the input list. In this case, we recursively insert the input integer into the tail of the list.

    def insert(x : Int, xs : List[Int]) : List[Int] = {
    
    xs match{
        case Nil => List(x)
        case y :: xs1 =>
            if(y >= x) x :: xs
            else y :: insert(x,  xs1)
     }
    
    }
    

The second function is isort, which takes an input list and sorts it. The algorithm is as follows:

  1. If the input list is equal to Nil, return Nil.
  2. Otherwise, recursively sort the tail of the list, and add the head at the right position in the (sorted) tail (using the insert procedure)

     def isort(xs : List[Int]) : List[Int] = {
      xs match{
        case Nil => Nil
        case x :: xs1 => insert(x, isort(xs1))
    
       }
    
     } 
    

We can test this as isort(List(1, 6, 4, -2, 5, 12)) and we get the correct expected output.

The most elegant insertion sort algorithm implementation that I'we seen so far:

val rand = List(5,3,1,2)

def isort(xs: List[Int]): List[Int] =
  if (xs.isEmpty) Nil
  else insert(xs.head, isort(xs.tail))

def insert(x: Int, xs: List[Int]): List[Int] =
  if (xs.isEmpty || x <= xs.head) x :: xs
  else xs.head :: insert(x, xs.tail)

isort(rand) // Produces List(1,2,3,5)

Algorithm implementation is taken from this great book: https://www.artima.com/shop/programming_in_scala_3ed

Nested for loops are probably not the answer in Scala, whatever is the problem. They make sense in languages where for loops stand for "repeat until condition, changing this each iteration", which is not what for loops or for comprehensions are in Scala (a for comprehension is a for loop which yields something for each iteration).

In Scala, for loops and for comprehensions are iterations over the elements of a collection (or weirder things for non-collection monads), but for insertion sort you want to find a position, either swapping places up to that position, or inserting the element at that position. Finding stuff should not be done with for loops or for comprehensions.

Also, there's no pass-by-reference in Scala. And, in fact, Array is not even a Scala collection, but a Java thingy which has unique characteristics granted to it by the JVM that are not reproducible with classes, so it can't be replaced.

here's another generic version of insertion sort:

  def less[T <: Comparable[T]](i: T, j: T) = i.compareTo(j) < 0

  def swap[T](xs: Array[T], i: Int, j: Int) { val tmp = xs(i); xs(i) = xs(j); xs(j) = tmp }

  def insertSort[T <: Comparable[T]](xs: Array[T]) {
    for { i <- 1 to xs.size
          j <- List.range(1, i).reverse
          if less(xs(j),xs(j - 1)) } swap(xs, j, j -1)
  }

What do you need it for?
If you just want a sorted Array I would prefer: def sortArray(a: Array[Int]): Array[Int] = a.sortWith(_ < _).

At the beginning don't forget to add:

import scala.util.control.Breaks._

This is my solution, using a list as an input

def insertionSortLoop(list :List[Int]) : List[Int] = {
      val intArray: Array[Int] = list.toArray
      for ( j <- 1 until intArray.length ) {
          breakable {
            for ( i <- (1 to j).reverse ) {
              if (intArray(i-1) < intArray(i)) {
                break
              } else {
                  val temp = intArray(i)
                  intArray(i) = intArray(i-1)
                  intArray(i-1) = temp
              }
            }
        }
      }
      intArray.toList
    }

I have the implementation here at github.

Here is a code sample written using foldLeft.

def insertionSort(input: List[Int]): List[Int] = {

  input.foldLeft(List[Int]())( (acc, element) => {

    val (firstHalf, secondHalf) = acc.span(_ < element)

    //inserting the element at the right place
    firstHalf ::: element :: secondHalf
  })
}

The example can be found in this github repo.

Even though, when coding Scala, I'm used to prefer functional programming style (via combinators or recursion) over imperative style (via variables and iterations), THIS TIME, for this specific problem, old school imperative nested loops result in simpler code for the reader. I don't think falling back to imperative style is a mistake for certain classes of problems (such as sorting algorithms which usually mutate a buffer of items passed as input rather than resulting to a new sorted collection)

Here it is my solution:

package bitspoke.algo

import scala.math.Ordered
import scala.collection.mutable.Buffer

abstract class Sorter[T <% Ordered[T]] {

  // algorithm provided by subclasses
  def sort(buffer : Buffer[T]) : Unit

  // check if the buffer is sorted
  def sorted(buffer : Buffer[T]) = buffer.isEmpty || buffer.view.zip(buffer.tail).forall { t => t._2 > t._1 }

  // swap elements in buffer
  def swap(buffer : Buffer[T], i:Int, j:Int) {
    val temp = buffer(i)
    buffer(i) = buffer(j)
    buffer(j) = temp
  }
}

class InsertionSorter[T <% Ordered[T]] extends Sorter[T] {
  def sort(buffer : Buffer[T]) : Unit = {
    for {
      i <-  1 until buffer.length
      j <-  i until 0 by -1
      if (buffer(j) < buffer(j - 1))
    }
    swap(buffer, j, j - 1)
  }
}

As you can see, rather than using java.lang.Comparable, I preferred scala.math.Ordered and Scala View Bounds rather than Upper Bounds. That's certainly works thanks to many Scala Implicit Conversions of primitive types to Rich Wrappers.

You can write a client program as follows:

import bitspoke.algo._
import scala.collection.mutable._

val sorter = new InsertionSorter[Int]
val buffer = ArrayBuffer(3, 0, 4, 2, 1)
sorter.sort(buffer)

assert(sorter.sorted(buffer))

Github link to Scala implementation

Pseudo Code:

Algorithm Insertion-Sort-Descending(A)
    for j <- 2 to length[A]
        key <- A[j]                 // element we wish to insert
        i <- j - 1                  // position to the left side
        while i > 0 and A[i] < key  // while has not found correct position <-- difference between Ascending and Descending
            A[i + i] = A[i]         // right shift element
            i = i - 1               // decrement i
        A[i + 1] = key              // found position, place key
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!