HumanSort is a single page web application that helps sorting an array of pictures according to human preference.
Compare mode where you insert an unranked image to the array of ranked images through choosing the image you prefer:
Gallery mode where you can see all the ranked images and re-order them in case you don't fully agree with past decisions:
Three color schemes to choose from:
With algorithms such as MergeSort and QuickSort the entities being sorted have an inherent nature that facilitates their sorting. If you are sorting an array of numbers, strings or dates, each entity can be compared with each other and a predicate of "is A bigger than B" can easily be answered.
The use case for HumanSort is entities for which there is no inherent nature that facilitates their sorting, and thus the answer for "is A bigger than B" has to be handled by an external judge, in this case human preference.
To minimize amount of comparisons needed to obtain a sorted array, a strategy of binary insertion is used (similar to binary search). We keep an always sorted list of items, and since it is always sorted if an unranked item is said to be better than an item in position 5 it can also be said that it is better than all ranked items lower than 5. With a binary strategy we're able to reduce the amount of comparisons needed to get a sorted array.
Consider two groups, an "Unranked" array of entities whose sort order is irrelevant and a "Ranked" array of entities sorted from best to worst; that is, an item of index 0 is the best item, an item of index 1 is second best, an item of index 2 is third best, and so forth.
// First state, no ranking yet
const unranked = ['π', 'π', 'π', 'π', 'π', 'π', 'π'];
const ranked = [];
We select two entities at random and we present them to the human to be compared, obtaining our first comparison.
// In the frontend: Which one do you like more, π or π ?
// Human selects π
// New state:
const unranked = ['π', 'π', 'π', 'π', 'π'];
const ranked = ['π', 'π'];
After we have a list of ranked items, we iteratively:
- Select a random item from unranked list to be compared
- Find the pivot point of the ranked list, the pivot point being the element in the middle defined by
<T>(array: T[]): number => Math.ceil(array.length / 2) - 1
- Compare random unranked with pivot point, it will either be better or worse and based on this answer a new subgroup from ranked is selected
- A pivot point for this new subgroup is selected and a comparison is performed again, process repeated until the list is of only one item, in which case the new random item from unranked can be inserted in the ranked list since its position can be determined.
- Newly ranked item no longer belongs to unranked list.
And we keep doing these steps for the remainder of unranked entities until they are all ranked.
const unranked = ['π', 'π', 'π', 'π', 'π'];
const ranked = ['π', 'π'];
// New random item from unranked is selected: 'π'
// Pivot of ranked is 'π'
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// There is no ranked subgroup, so we can insert 'π'
// New state:
const unranked = ['π', 'π', 'π', 'π'];
const ranked = ['π', 'π', 'π'];
// New random item from unranked is selected: 'π',
// Pivot of ranked is 'π'
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// New subgroup from ranked to compare is ['π']
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// There is no ranked subgroup, so we can insert 'π'
// New state:
const unranked = ['π', 'π', 'π'];
const ranked = ['π', 'π', 'π', 'π'];
// New random item from unranked is selected: 'π',
// Pivot of ranked is 'π'
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// New subgroup from ranked to compare is ['π', 'π']
// Pivot of subgroup is 'π'
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// There is no ranked subgroup, so we can insert 'π'
// New state:
const unranked = ['π', 'π'];
const ranked = ['π', 'π', 'π', 'π', 'π'];
// New random item from unranked is selected: 'π',
// Pivot of ranked is 'π'
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// New subgroup from ranked to compare is ['π', 'π']
// Pivot of subgroup is 'π'
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// There is no ranked subgroup, so we can insert 'π'
// New state:
const unranked = ['π'];
const ranked = ['π', 'π', 'π', 'π', 'π', 'π'];
// New random item from unranked is selected: 'π',
// Pivot of ranked is 'π'
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// New subgroup from ranked to compare is ['π', 'π']
// Pivot of subgroup is 'π'
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// New subgroup from ranked to compare is ['π']
// In the frontend: Which one do you like more, 'π' or 'π'?
// Human selects 'π'
// There is no ranked subgroup, so we can insert 'π'
// New state:
const unranked = [];
const ranked = ['π', 'π', 'π', 'π', 'π', 'π', 'π'];
// No unranked left, ranking finished!
The backend consists of:
- SQLite database
- node-sqlite3 SQLite driver
- Express backend serving API
- TypeScript
You can see the database schema and API endpoints documentation at the shared folder in types.ts.
It is assumed you have node and npm installed.
To have a fully functioning webapp, you'll need to install both the backend and frontend. Run this on your terminal to create a folder called HumanSort
, install backend, populate with sample images and install frontend:
mkdir HumanSort
cd HumanSort/
git clone git@github.com:DrummerHead/HumanSort-backend.git
cd HumanSort-backend/
npm install
cp -r pics-sample pics
cd db/
node pollinate.js
cd ../../
git clone git@github.com:DrummerHead/HumanSort-frontend.git
cd HumanSort-frontend/
npm install
For now, this application is intended to be used locally on your machine, to sort already existing images you have in your harddrive. You should put these images inside of the /pics/
folder.
It is assumed you have node and npm installed.
This repo comes with some sample images we can use and a script to populate the database. The images of cats where AI generated using this online tool. Run this at project root to use the sample images and an initial database state:
npm install
cp -r pics-sample pics
cd db/
node pollinate.js
cd ..
And to run it:
npm run start
Which should run the server at http://localhost:7777/
If you already have your own images to sort, then put them in the /pics/
folder and run:
cd db/
node backup.js
rm comparo.db
node pollinate.js
cd ..
And same steps as above to run it.
Open a terminal window at the root of HumanSort-backend
and type:
npm run start
To run the backend server at http://localhost:7777/
Open anoter terminal window at the root of HumanSort-frontend
and type:
npm run start
To run the frontend server at http://localhost:3000/
Interact with the application at http://localhost:3000/
Find the code for the frontend at HumanSort-frontend