Thursday, January 28, 2016

Understanding lazy sequences

The book The Joy of Clojure, Second Edition shows the following quick-sort example for lazy sequences:
(defn sort-parts [work]
     (lazy-seq
      (loop [[part & parts] work]            ;; Pull apart work - note: work will be a list of lists.
        (if-let [[pivot & xs] (seq part)]    ;; This blows up unless work was a list of lists.
          (let [smaller? #(< % pivot)]       ;; define pivot comparison function.
            (recur (list*                    
                    (filter smaller? xs)     ;; work all < pivot
                    pivot                    ;; work pivot itself
                    (remove smaller? xs)     ;; work all > pivot
                    parts)))                 ;; concat parts
          (when-let [[x & parts] parts]      ;; sort rest if more parts
            (cons x (sort-parts parts)))))))
;; #'joy.q/sort-parts

(defn qsort [xs]
   (sort-parts (list xs)))    ;; The (list) function ensures that we pass sort-parts a list of lists.
In order to better understand, how lazy sequence works, I implemented an equivalent for myself in Java:

public interface NextValueProducer {
 Object getNextValue();
}


import java.util.ArrayList;
import java.util.List;

public class LazySequence {
 private NextValueProducer nvp;
 private List cache;
 
 public LazySequence(NextValueProducer nvp) {
  this.nvp = nvp;
  cache = new ArrayList<>();
 }
 
 public Object getNthElement(int n) {
  if (n < 0) {
   throw new IllegalArgumentException("n has to be positive");
  }
  for (int i = cache.size(); i <= n; ++i) {
   Object produced = nvp.getNextValue();
   if (produced == null) {
    break;
   }
   else {
    cache.add(produced);
   }
  }
  if (cache.size() > n) {
   return cache.get(n);
  }
  else {
   return null;
  }
 }
}


import java.util.ArrayList;
import java.util.List;

public class QuickSortProducer implements NextValueProducer {

 private List<Object> work;
 
 public QuickSortProducer(List nums) {
  work = new ArrayList<>();
  List temp = new ArrayList<>(nums);
  work.add(temp);
 }

 @Override
 public Object getNextValue() {

  List workCopy = new ArrayList<>(work);
  
  while (true) {
  
   List part = new ArrayList<>((List)workCopy.get(0));
   List parts = new ArrayList<>(workCopy.subList(1, workCopy.size()));
   
   if (part.size() > 0) {
    int pivot = (int)part.get(0);
    List xs = part.subList(1, part.size());
    
    List smallers = new ArrayList<>();
    List biggers = new ArrayList<>();
    
    for (Object xObj : xs) {
     int x = (int)xObj;
     if (x < pivot) {
      smallers.add(x);
     }
     else if (x > pivot) {
      biggers.add(x);
     }
    }
    
    workCopy = new ArrayList<>();
    workCopy.add(smallers);
    workCopy.add(pivot);
    workCopy.add(biggers);
    workCopy.addAll(parts);
   }
   else {
    if (parts.size() > 0) {
     work = new ArrayList<>(parts.subList(1, parts.size()));
     return parts.get(0);
    }
    else {
     return null;
    }
   }
  }  
 }

}




Unit tests:
import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

public class QuickSortProducerTest {

 @Test
 public void testQuickSortEmptyList() {
  List numsToSort = new ArrayList<>();
  LazySequence seq = new LazySequence(new QuickSortProducer(numsToSort));
  
  Object elem = seq.getNthElement(0);

  assertNull(elem);
 }

 @Test
 public void testQuickSortOneElementList() {
  List numsToSort = new ArrayList<>();
  numsToSort.add(2);
  
  LazySequence seq = new LazySequence(new QuickSortProducer(numsToSort));

  assertEquals(2, seq.getNthElement(0));
  assertEquals(null, seq.getNthElement(1));
 }
 
 @Test
 public void testQuickSortUnsortedList() {
  List numsToSort = new ArrayList<>();
  numsToSort.add(2);
  numsToSort.add(1);
  numsToSort.add(4);
  numsToSort.add(3);
  
  LazySequence seq = new LazySequence(new QuickSortProducer(numsToSort));

  assertEquals(1, seq.getNthElement(0));
  assertEquals(2, seq.getNthElement(1));
  assertEquals(3, seq.getNthElement(2));
  assertEquals(4, seq.getNthElement(3));
  assertEquals(null, seq.getNthElement(4));
 }

}

No comments:

Post a Comment