This commit is contained in:
gabe venberg 2021-05-03 18:26:49 -05:00
parent b34a4b51aa
commit a20e689ce8
14 changed files with 795 additions and 0 deletions

1
.gitignore vendored
View file

@ -49,3 +49,4 @@ hs_err_pid*
/Lab109/build/
/Lab110-VenbergGE/nbproject/private/
/Lab110-VenbergGE/build/
/Lab111-VenbergGE/build/

View file

@ -0,0 +1,181 @@
/*
* Copyright (C) 2021 Gabriel Venberg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* utility library for nicely formatted ascii tables.
* @author Gabriel Venberg
*/
public class ASCIITable {
/**
* generates an ASCII table based on a 2d data array. the top level array is an array of rows.
* @param data 2d array containing data to put in table
* @param padding how much padding to put on each side of entries
* @param tableHeader string to put in the table header (may cause problems if extremely long)
* @param columnHeaders array of strings to put at the top of each column.
* @return
*/
public static String render(Object data[][], int padding, String tableHeader, String[] columnHeaders) throws IllegalArgumentException {
int cols = calcNoCols(data);
if(cols!=columnHeaders.length){throw new IllegalArgumentException("must have equal number of column headers as columns!");}
int[] colWidths = calcColumnWidth(cols, data, columnHeaders);
//colWidths does not count padding or the | chars betwwen tables.
int width = sumOfArray(colWidths)+padding*cols*2+(cols-1);
String horizontalSpacer = assembleHorizontalSpacers(colWidths, padding, cols);
/*ok, so each cell will have the colwidth for the data, then padding for padding,
* then a | at the end. (plus 1 at the begginning of the table.
there will be 2 rows for each row of data (horizontal sep) plus a horizontal sep
at the end.
*/
String string = horizontalSpacer+'\n';
//print table header
string=string+tableHeader(tableHeader, width)+"\n";
string = string+horizontalSpacer+"\n";
//print coumn headers
string=string+columnHeaderString(colWidths, padding, columnHeaders)+'\n';
//got everything set up, build the table row by row.
for(int i=0; i<data.length; i++){
string = string+horizontalSpacer+"\n";
string = string+dataString(colWidths, padding, data[i])+'\n';
}
string = string+horizontalSpacer;
return string;
}
private static String tableHeader(String header, int width){
String string="|";
int halfPadding=(width-header.length())/2;
//front padding
for(int i=0; i<halfPadding; i++){string=string+" ";}
//if the total padding we need is odd, put it in front of the header
if((width-header.length())%2==1){string=string+" ";}
string=string+header;
//rear padding
for(int i=0; i<halfPadding; i++){string=string+" ";}
string=string+"|";
return string;
}
/**
* calcs the sum of all elements in an int array
* @param array array to be summed
* @return sum of array
*/
private static int sumOfArray(int[] array){
int sum=0;
for(int i=0; i<array.length; i++){
sum += array[i];
}
return sum;
}
/**
* calculates the maximum number of entries the rows in the data set have
* @param data 2D array of data
* @return needed number of rows in the final table.
*/
private static int calcNoCols(Object data[][]){
int rows = 0;
for(int i=0; i<data.length; i++){
rows = Math.max(rows, data[i].length);
}
return rows;
}
/**
* calculates the needed column widths for a data array without padding
* @param data the array of data
* @return an array of integers representing the needed width of each column
*/
private static int[] calcColumnWidth(int cols, Object data[][], String[] headers){
int[] maxWidths = new int[cols];
for(int i=0; i<cols; i++){
maxWidths[i]=headers[i].length();
for(int j=0; j<data.length; j++){
maxWidths[i]=Math.max(maxWidths[i], data[j][i].toString().length());
}
}
return maxWidths;
}
/**
* gives the horizontal spacer needed for the table
* @param colWidth width of each column;
* @param padding padding on each side of data.
* @param noOfCols number of columns;
* @return a string suitable to use as the horizontal spacer for the table.
*/
private static String assembleHorizontalSpacers(int[] colWidth, int padding, int noOfCols){
String string = "+";
for(int i=0; i<noOfCols; i++){
for(int j=0; j<colWidth[i]+2*padding; j++){
string = string+'-';
}
string = string+'+';
}
return string;
}
/**
* takes a single row of the data array and returns a row. Make sure your colWidth is accurate.
* @param colWidth width of each column
* @param padding min padding to have around each entry
* @param data 1D array of data to print
* @return a string containing the data
*/
private static String dataString(int[] colWidth, int padding, Object data[]){
String string ="|";
//for each entry in the row
for(int i=0; i<data.length; i++){
//only calc this once.
int length=data[i].toString().length();
// front padding. Also, I wish java had string multiplication.
for(int p=0; p<padding+(colWidth[i]-length); p++){string = string+" ";}
string = string+data[i].toString();
//rear padding
for(int p=0; p<padding; p++){string = string+" ";}
string = string+"|";
}
return string;
}
/**
* takes an array of strings (column headers) and outputs a single row of the column, center justified.
* @param colWidth width of each column
* @param padding min padding around each entry
* @param columnHeaders 1d array of strings containing col headers
* @return
*/
private static String columnHeaderString(int[] colWidth, int padding, String columnHeaders[]){
String string="|";
for(int i=0; i<columnHeaders.length; i++){
//calc this once.
int length=columnHeaders[i].length();
int sidePadding=(colWidth[i]-length+padding*2)/2;
//front padding
for(int p=0; p<sidePadding; p++){string=string+" ";}
//if we need an odd number of total padding, add the spare on the front
if((colWidth[i]-length)%2==1){string=string+" ";}
string=string+columnHeaders[i];
//rear padding
for(int p=0; p<sidePadding; p++){string=string+" ";}
string=string+"|";
}
return string;
}
}

View file

@ -0,0 +1,13 @@
public interface Comparable <K> {
//
//Used for a comparison based on the "natural ordering"
//
// return < 0 if this.a < b
// return 0 if this.a = b
// return > 0 if this.a > b
int compareTo( K b );
}

View file

@ -0,0 +1,5 @@
public interface Comparator <K> {
int compare( K a, K b );
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2021 Gabriel Venberg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*
* @author Gabriel Venberg
*/
public class DeptComparator implements Comparator<Employee>{
public int compare(Employee a, Employee b){
return a.getDept()-b.getDept();
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2021 Gabriel Venberg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* a simple container class for employees.
* @author Gabriel Venberg
*/
public class Employee {
private int SSN;
private String name;
private int dept;
private int hireDate;
public Employee(int SSN, int dept, int hireDate, String name) throws IllegalArgumentException{
if(SSN<0||SSN>99999999){throw new IllegalArgumentException("SSN must be between 0 and 99999999");}
if(dept<1||dept>5){throw new IllegalArgumentException("dept must be between 1 and 5");}
if(hireDate<1995||hireDate>2021){throw new IllegalArgumentException("hireDate must be between 1995 and current year.");}
this.SSN=SSN;
this.dept=dept;
this.hireDate=hireDate;
this.name=name;
}
public int getSSN(){return SSN;}
public int getDept(){return dept;}
public int getHireDate(){return hireDate;}
public String getName(){return name;}
public void setDept(int dept){
if(dept<1||dept>5){throw new IllegalArgumentException("dept must be between 1 and 5");}
this.dept=dept;
}
public void setName(String name){
this.name=name;
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 Gabriel Venberg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*
* @author Gabriel Venberg
*/
public class HireDateComparator implements Comparator<Employee>{
public int compare(Employee a, Employee b){
return a.getHireDate()-b.getHireDate();
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 Gabriel Venberg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*
* @author Gabriel Venberg
*/
public class IDComparator implements Comparator<Employee>{
public int compare(Employee a, Employee b){
return a.getSSN()-b.getSSN();
}
}

View file

@ -0,0 +1,21 @@
/*
* Data Structures & Algorithms 6th Edition
* Goodrich, Tamassia, Goldwasser
* Code Fragment 6.11
*
* An implementation of the LinkedQueue class
* */
/**
*
* @author Gabriel Venberg
*/
public class LinkedQueue<E> implements Queue<E>{
private SinglyLinkedList<E> list = new SinglyLinkedList(); //an empty list
public LinkedQueue(){} //new queue relies on initaly empty list
public int size(){return list.size();}
public boolean isEmpty(){return list.isEmpty();}
public void enqueue(E element){list.addLast(element);}
public E first(){return list.first();}
public E dequeue(){return list.removeFirst();}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2021 Gabriel Venberg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*
* @author Gabriel Venberg
*/
public class NameComparator implements Comparator<Employee>{
public int compare(Employee a, Employee b){
return a.getName().compareTo(b.getName());
}
}

View file

@ -0,0 +1,28 @@
/**
* Data Structures & Algorithms 6th Edition
* Goodrich, Tamassia, Goldwasser
* Code Fragment 6.9
*
* An implementation of the Queue interface
* */
/**
*
* @author Gabriel Venberg
*/
public interface Queue<E> {
/** returns the number of elements in the queue*/
int size();
/** tests whether the queue is empty*/
boolean isEmpty();
/**inserts an element at the rear of the queue*/
void enqueue(E e);
/**returns, but does not remove, the first element of the queue (null if empty). */
E first();
/** removes and returns the first element of the queue (null if empty)*/
E dequeue();
}

View file

@ -0,0 +1,78 @@
/**
*SinglyLinkedListClass
* Code Fragments 3.14, 3.15
* from
* Data Structures & Algorithms, 6th edition
* by Michael T. Goodrich, Roberto Tamassia & Michael H. Goldwasser
* Wiley 2014
* Transcribed by
* @author Gabe Venberg
*/
public class SinglyLinkedList<E> {
private static class Node<E> {
private E element; //refrence to element stored at this node
private Node<E> next; //refrence to subsequent node of list
public Node(E e, Node<E> n){
element = e;
next = n;
}
public E getElement() {return element;}
public Node<E> getNext() {return next;}
public void setNext(Node<E> n) {next = n;}
}
//instance variables of SinglyLinkedList
private Node<E> head = null;//head node of list
private Node<E> tail = null;//last node of list
private int size = 0;//number of nodes in list
public SinglyLinkedList(){}//constructs an initaly empty list
//access methods
public int size() {return size;}
public boolean isEmpty() {return size == 0;}
public E first(){//returns but does not remove the first element
if (size == 0) {return null;} //special case
return head.getElement();
}
public E last(){//returns but does not remove last elemnt
if (size ==0) {return null;}//special case
return tail.getElement();
}
//update methods
public void addFirst(E e){//adds element e to the front of the list
head = new Node<>(e, head);//create and link a new node
if (size == 0) {tail = head;}//special case, head becomes tail also
size++;
}
public void addLast(E e){//adds element to end of list
Node<E> newest = new Node<>(e, null);//create and link a new node
if(size == 0){//special case, previously empty list
head = newest;
}
else{
tail.setNext(newest);//new node after existing tail
}
tail = newest;//new node becomes tail
size++;
}
public E removeFirst(){//removes and returns the first element
if(size == 0){return null;}//nothing to remove
E answer = head.getElement();
head = head.getNext();//will become null if list had only one node.
size--;
if(size==0){tail = null;}// special case as list is now empty
return answer;
}
}

View file

@ -0,0 +1,205 @@
/*
* Copyright (C) 2021 Gabriel Venberg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Class containing a bunch of static sort methods.
* @author Gabriel Venberg
*/
public class Sort {
/**
* copies an array
* @param <K> type of array
* @param toCopy array to copy
* @return copy of array
*/
public static <K> K[] arrayCopyRange(K[] toCopy, int start, int end){
K[] output = (K[]) new Object[end-start];
for(int i=start; i<end; i++){
output[i-start]=toCopy[i];
}
return output;
}
private static <K> LinkedQueue<K> arrayToQueue(K[] toConvert){
LinkedQueue<K> output = new LinkedQueue<>();
for(int i=0; i<toConvert.length; i++){
output.enqueue(toConvert[i]);
}
return output;
}
private static <K> K[] queueToArray(LinkedQueue<K> toConvert){
K[] output = (K[]) new Object[toConvert.size()];
for(int i=0; i<output.length; i++){
output[i]=toConvert.dequeue();
}
return output;
}
/**
* sorts a list in place with a bubble sort. stable
* @param <K> type of array
* @param toSort array to sort
* @param comp comparator to use.
*/
public static <K> void badBubbleSort(K[] toSort, Comparator<K> comp){
//dont want to be constantly creating and destroying this if we dont have to.
K tmp;
for(int i=0; i<toSort.length; i++){
//start at one to avoid arrayIndexOutOfBounds
for(int j=1; j<toSort.length; j++){
if(comp.compare(toSort[j-1], toSort[j])>0){
//swapping
tmp=toSort[j-1];
toSort[j-1]=toSort[j];
toSort[j]=tmp;
}
}
}
}
/**
* sorts a list in place with an optimized bubble sort. stable
* @param <K> type of array
* @param toSort array to sort
* @param comp comparator to use.
*/
public static <K> void bubbleSort(K[] toSort, Comparator<K> comp){
K tmp;
int lastSwap;
int sortTo = toSort.length-1;
do{
//last place we did a swap
lastSwap=0;
//the last place we did a swap is the last place we need to look.
for(int i=1; i<sortTo; i++){
if(comp.compare(toSort[i-1], toSort[i])>0){
lastSwap=i;
//swapping
tmp=toSort[i-1];
toSort[i-1]=toSort[i];
toSort[i]=tmp;
}
}
//so we only compare along the array for as long as we need to.
sortTo=lastSwap;
} while(lastSwap<=1);
}
/**
* merges the sorted arrays A and B into array C
* @param <K> type of the arrays
* @param A first sorted array
* @param B second sorted array
* @param C array to merge A and B into.
* @param comp comparator to use.
*/
private static <K> void merge(K[] A, K[] B, K[] C, Comparator<K> comp){
//counters for arrays A and B
int a=0, b=0;
while(a+b<C.length){
//the first part is so that if we have 'emptied' out one list, the rest of the other list is rapidly copied across. the <=0 should make the mergesort stable.
if(b==B.length||(a<A.length&&comp.compare(A[a], B[b])<=0))
//put next element of a into c and increment a's counter.
C[a+b]=A[a++];
else{
//put next element of b into c and increment b's counter.
C[a+b]=B[b++];
}
}
}
/**
* sorts an array in place with a mergesort. stable
* @param <K> type of array
* @param toSort array to sort
* @param comp comparator to use
*/
public static <K> void mergeSort(K[] toSort, Comparator<K> comp){
//if array is trivially sorted. I think this could be made a bit more space efficent by sorting the array with 1 or 0 swaps when we hit a 2 long array...
if(toSort.length<2){return;}
//devide the arrays.
int mid=toSort.length/2;
K[] A = arrayCopyRange(toSort, 0, mid);
K[] B = arrayCopyRange(toSort, mid, toSort.length);
//recurse
mergeSort(A, comp);
mergeSort(B, comp);
//merge results
merge(A, B, toSort, comp);
}
/**
* sorts a queue in place with a quicksort. unstable
* @param <K> type of array
* @param toSort array to sort
* @param comp comparator to use
*/
public static <K> void quickSort(Queue<K> toSort, Comparator<K> comp){
//queue is trivially sorted
if(toSort.size()<2){return;}
//divide
K pivot=toSort.first();
Queue<K> less = new LinkedQueue<>();
Queue<K> equal = new LinkedQueue<>();
Queue<K> greater = new LinkedQueue<>();
while(!toSort.isEmpty()){
K element = toSort.dequeue();
int c = comp.compare(element, pivot);
if(c<0){less.enqueue(element);}
else if(c>0){greater.enqueue(element);}
else{equal.enqueue(element);}
}
//recurse
quickSort(less, comp);
quickSort(greater, comp);
//concatenate results
while(!less.isEmpty()){toSort.enqueue(less.dequeue());}
while(!equal.isEmpty()){toSort.enqueue(equal.dequeue());}
while(!greater.isEmpty()){toSort.enqueue(greater.dequeue());}
}
/**
* sorts an array with a quicksort. unstable
* @param <K> type of array
* @param toSort array to sort
* @param comp comparator to use
*/
public static <K> K[] quickSortArray(K[] toSort, Comparator<K> comp){
LinkedQueue<K> tmp = arrayToQueue(toSort);
quickSort(tmp, comp);
return queueToArray(tmp);
}
/**
* sorts an array based on multiple keys, from least
* @param <K> type of array to be sorted
* @param toSort array to sort
* @param comp array of comparators to sort by, with array going from most significant comparator at the start and ending with the least significant comparator.
*/
//in order to keep this generic, we cant use a bucket sort, as we dont have any assumptions about the range of the data. instead, this just applies a series of stable sorts, as described in the textbook and lecture. (latimer didnt get around to explaining the non-naive way to do a radix sort.
public static <K> void radixSort(K[] toSort, Comparator<K>[] comp) throws IllegalArgumentException{
if(comp.length==0){throw new IllegalArgumentException("must have at least 1 comparator in comp array.");}
for(int i=comp.length-1; i<=0; i++){
mergeSort(toSort, comp[i]);
}
}
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2021 Gabriel Venberg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*
* @author Gabriel Venberg
*/
import java.util.Random;
public class client {
public static void main(String[] args){
//initalization stuff
Employee[] employees = new Employee[50000];
long startTime;
long endTime;
String[][] data = new String[4][2];
Random rgen = new Random();
Comparator compareName=new NameComparator();
Comparator compareDept=new DeptComparator();
Comparator compareID=new IDComparator();
Comparator compareHireDate=new HireDateComparator();
Employee[] tmp = new Employee[employees.length];
for(int i=0; i<employees.length; i++){
employees[i]=new Employee(rgen.nextInt(100000000), rgen.nextInt(4)+1, rgen.nextInt(26)+1995, randomPrintableString());
}
//tried doing this with a method, did NOT want to work with me for some reason. Even tried using the arrayCopyRange method in employee, kept giving me:
//Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [LEmployee; ([Ljava.lang.Object; is in module java.base of loader 'bootstrap'; [LEmployee;
for(int i=0; i<employees.length; i++){
tmp[i]=employees[i];
}
startTime=System.nanoTime();
Sort.mergeSort(tmp, compareName);
endTime=System.nanoTime();
data[0][0]="merge sort by name";
data[0][1]=String.format("%,d", endTime-startTime);
for(int i=0; i<employees.length; i++){
tmp[i]=employees[i];
} startTime=System.nanoTime();
Sort.quickSortArray(tmp, compareDept);
endTime=System.nanoTime();
data[1][0]="quick sort by dept";
data[1][1]=String.format("%,d", endTime-startTime);
for(int i=0; i<employees.length; i++){
tmp[i]=employees[i];
}
startTime=System.nanoTime();
Sort.bubbleSort(tmp, compareID);
endTime=System.nanoTime();
data[2][0]="bubble sort by ID";
data[2][1]=String.format("%,d", endTime-startTime);
for(int i=0; i<employees.length; i++){
tmp[i]=employees[i];
}
Comparator[] radixComparator = {compareDept, compareHireDate, compareName};
startTime=System.nanoTime();
Sort.radixSort(tmp, radixComparator);
endTime=System.nanoTime();
data[3][0]="radix sort";
data[3][1]=String.format("%,d", endTime-startTime);
String[] colHeaders = {"sort", "nanoseconds taken"};
System.out.println(ASCIITable.render(data, 2, "time of sorts", colHeaders));
}
/**
* generates a random string made of lowercase letters, 5 to 10 chars long. not general purpouse.
* @return
*/
public static String randomPrintableString(){
Random rgen = new Random();
int length = rgen.nextInt(6)+5;
String output="";
String letters = "qwertyuiopasdfghjklzxcvbnm";
for(int i=0; i<length; i++){
output = output+letters.charAt(rgen.nextInt(letters.length()));
}
return output;
}
private static <K> K[] arrayCopy(K[] toCopy){
K[] output = (K[]) new Object[toCopy.length];
for(int i=0; i<toCopy.length; i++){
output[i]=toCopy[i];
}
return output;
}
}