Sorry, the diff of this file is not supported yet
| name: CI | ||
| on: [push, pull_request] | ||
| jobs: | ||
| test: | ||
| name: Test | ||
| runs-on: ubuntu-20.04 | ||
| timeout-minutes: 3 | ||
| steps: | ||
| - uses: actions/checkout@v2 | ||
| - uses: actions/setup-node@v1 | ||
| with: | ||
| node-version: 14 | ||
| - name: Dependencies | ||
| run: yarn install --frozen-lockfile | ||
| - name: Test with coverage | ||
| run: | | ||
| yarn check-coverage | ||
| yarn report-coverage | ||
| - uses: codecov/codecov-action@v2 | ||
| with: | ||
| token: ${{ secrets.CODECOV_TOKEN }} | ||
| files: ./coverage.lcov | ||
| name: Codecov | ||
| {"version":3,"file":"gridSort.js","sourceRoot":"","sources":["../src/gridSort.ts"],"names":[],"mappings":"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"} |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| import { test, suite } from "uvu"; | ||
| import * as assert from "uvu/assert"; | ||
| import { | ||
| buildGridFromBuckets, | ||
| getFillableItem, | ||
| getSortedKeys, | ||
| groupItemsByValue, | ||
| rowsAreEqual, | ||
| } from "../src/gridSort.js"; | ||
| test("sorts object keys numerically in descending order", () => { | ||
| const buckets = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getSortedKeys(buckets), [3, 2, 1].map(String)); | ||
| }); | ||
| test("gets best candidate for filling a column", () => { | ||
| const bucketsA = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsA, 4), "3"); | ||
| const bucketsB = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsB, 1), "1"); | ||
| const bucketsC = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsC, 2), "2"); | ||
| const bucketsD = { 1: [1, 1, 1], 2: [2, 2], 3: [] }; | ||
| assert.equal(getFillableItem(bucketsD, 4), "2"); | ||
| const bucketsE = { 1: [1, 1, 1], 2: [], 3: [] }; | ||
| assert.equal(getFillableItem(bucketsE, 4), "1"); | ||
| const bucketsF = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsF, 2), "2"); | ||
| const bucketsG = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsG, 1), "1"); | ||
| }); | ||
| test("rows equality", () => { | ||
| const [rowA, rowB] = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| ], | ||
| [ | ||
| { id: "8", ratio: 3 }, | ||
| { id: "5", ratio: 1 }, | ||
| ], | ||
| ]; | ||
| const accessor = (item: typeof rowA[number]) => item.ratio; | ||
| assert.ok(rowsAreEqual(rowA, rowB, accessor)); | ||
| assert.not.ok(rowsAreEqual(rowA, rowB.reverse(), accessor)); | ||
| }); | ||
| test("it sorts bucket of numbers", () => { | ||
| const buckets = groupItemsByValue([1, 1, 1, 1, 1, 2, 2, 3, 3]); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets, | ||
| columns: 4, | ||
| accessor: undefined, | ||
| }); | ||
| const expected = [ | ||
| [3, 1], | ||
| [1, 3], | ||
| [2, 2], | ||
| [1, 1, 1], | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| test("it builds buckets from array of items", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const itemsByValue = groupItemsByValue(items, (item) => item.ratio); | ||
| assert.equal(itemsByValue, { | ||
| 1: [ | ||
| { id: "2", ratio: 1 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| ], | ||
| 2: [ | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| ], | ||
| 3: [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| }); | ||
| }); | ||
| test("it sorts bucket of objects", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const itemsByValue = groupItemsByValue(items, accessor); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets: itemsByValue, | ||
| columns: 4, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| ], | ||
| [ | ||
| { id: "5", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| [ | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| ], | ||
| [{ id: "7", ratio: 1 }], | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| test.run(); | ||
| const ColumnSuite = suite('Columns Suite'); | ||
| ColumnSuite("it sorts bucket of objects with more columns", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const itemsByValue = groupItemsByValue(items, accessor); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets: itemsByValue, | ||
| columns: 5, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "3", ratio: 2 }, | ||
| ], | ||
| [ | ||
| { id: "4", ratio: 2 }, | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| [ | ||
| { id: "2", ratio: 1 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| ], | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| ColumnSuite("it sorts bucket of objects with less columns than items", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const itemsByValue = groupItemsByValue(items, accessor); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets: itemsByValue, | ||
| columns: 3, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| ], | ||
| [ | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| [ | ||
| { id: "3", ratio: 2 }, | ||
| { id: "2", ratio: 1 }, | ||
| ], | ||
| [ | ||
| { id: "5", ratio: 1 }, | ||
| { id: "4", ratio: 2 }, | ||
| ], | ||
| [ | ||
| { id: "7", ratio: 1 }, | ||
| ] | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| ColumnSuite("it sorts bucket of objects with less columns than biggest object", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const itemsByValue = groupItemsByValue(items, accessor); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets: itemsByValue, | ||
| columns: 2, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "3", ratio: 2 }, | ||
| ], | ||
| [ | ||
| { id: "4", ratio: 2 }, | ||
| ], | ||
| [ | ||
| { id: "2", ratio: 1 }, | ||
| { id: "5", ratio: 1 }, | ||
| ], | ||
| [ | ||
| { id: "7", ratio: 1 }, | ||
| ] | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| ColumnSuite("it sorts bucket of objects with a single column", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const itemsByValue = groupItemsByValue(items, accessor); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets: itemsByValue, | ||
| columns: 1, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "2", ratio: 1 }, | ||
| ], | ||
| [ | ||
| { id: "5", ratio: 1 }, | ||
| ], | ||
| [ | ||
| { id: "7", ratio: 1 }, | ||
| ] | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| ColumnSuite("it sorts bucket of objects with no columns", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const itemsByValue = groupItemsByValue(items, accessor); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets: itemsByValue, | ||
| columns: 0, | ||
| accessor, | ||
| }); | ||
| assert.equal(sorted, []); | ||
| }); | ||
| ColumnSuite.run(); |
| import { test } from "uvu"; | ||
| import * as assert from "uvu/assert"; | ||
| import { gridSort } from "../src/index.js"; | ||
| test("it sorts an array of items into a grid", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const sorted = gridSort({ | ||
| items, | ||
| columns: 5, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "3", ratio: 2 }, | ||
| ], | ||
| [ | ||
| { id: "4", ratio: 2 }, | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| [ | ||
| { id: "2", ratio: 1 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| ], | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| test.run(); |
@@ -7,3 +7,3 @@ export declare type Bucket<T> = T[]; | ||
| export declare function getSortedKeys<T>(buckets: Buckets<T>): number[]; | ||
| export declare function calculateItemsLeft<T>(buckets: Buckets<T>): number; | ||
| export declare function calculateItemsLeft<T>(buckets: Buckets<T>, columns: number, accessor?: T extends object ? Accessor<T> : undefined): number; | ||
| export declare function rowsAreEqual<T extends object | number>(rowA: T[], rowB: T[], accessor?: T extends object ? Accessor<T> : undefined): boolean; | ||
@@ -10,0 +10,0 @@ export declare function groupItemsByValue<T extends object | number>(items: T[], accessor?: T extends object ? Accessor<T> : undefined): Buckets<T>; |
+23
-18
@@ -8,6 +8,3 @@ const keysOf = (object) => Object.keys(object); | ||
| } | ||
| if (typeof item === "number") { | ||
| return acc + item; | ||
| } | ||
| return acc; | ||
| return acc + Number(item); | ||
| }, 0); | ||
@@ -17,4 +14,13 @@ export function getSortedKeys(buckets) { | ||
| } | ||
| export function calculateItemsLeft(buckets) { | ||
| return Object.values(buckets).reduce((acc, bucket) => acc + bucket.length, 0); | ||
| export function calculateItemsLeft(buckets, columns, accessor) { | ||
| return Object.values(buckets) | ||
| .reduce((acc, bucket) => { | ||
| const bucketQuantity = bucket.map(value => { | ||
| if (accessor && typeof value === 'object') { | ||
| return accessor(value); | ||
| } | ||
| return value; | ||
| }).filter(value => value <= columns).length; | ||
| return acc + bucketQuantity; | ||
| }, 0); | ||
| } | ||
@@ -38,7 +44,5 @@ export function rowsAreEqual(rowA, rowB, accessor) { | ||
| } | ||
| else if (typeof item === "number") { | ||
| key = item; | ||
| else { | ||
| key = Number(item); | ||
| } | ||
| if (key === undefined) | ||
| continue; | ||
| const group = groups[key]; | ||
@@ -67,5 +71,8 @@ if (group && Array.isArray(group)) { | ||
| export function buildGridFromBuckets({ buckets, columns, accessor, }) { | ||
| var _a; | ||
| const grid = []; | ||
| if (columns <= 0) | ||
| return grid; | ||
| let itemsLeft; | ||
| while ((itemsLeft = calculateItemsLeft(buckets)) > 0) { | ||
| while ((itemsLeft = calculateItemsLeft(buckets, columns, accessor)) > 0) { | ||
| const row = []; | ||
@@ -76,9 +83,6 @@ let key; | ||
| itemsLeft >= 1) { | ||
| const bucket = buckets[key]; | ||
| if (!bucket) | ||
| continue; | ||
| const item = bucket.shift(); | ||
| if (!item) | ||
| break; | ||
| row.push(item); | ||
| const item = (_a = buckets[key]) === null || _a === void 0 ? void 0 : _a.shift(); | ||
| if (item) { | ||
| row.push(item); | ||
| } | ||
| } | ||
@@ -98,1 +102,2 @@ // randomize current row if previous row has same layout | ||
| } | ||
| //# sourceMappingURL=gridSort.js.map |
+1
-0
| export { gridSort } from "./gridSort.js"; | ||
| //# sourceMappingURL=index.js.map |
+6
-2
| { | ||
| "name": "grid-sort", | ||
| "version": "2.0.2", | ||
| "version": "2.1.0", | ||
| "description": "Sort objects in a two dimensional array to compose grids based on a condition", | ||
@@ -12,2 +12,4 @@ "main": "./lib/index.js", | ||
| "devDependencies": { | ||
| "c8": "^7.8.0", | ||
| "source-map-support": "^0.5.19", | ||
| "ts-node": "^10.2.1", | ||
@@ -21,5 +23,7 @@ "typescript": "^4.3.5", | ||
| "scripts": { | ||
| "test": "node --experimental-loader ts-node/esm node_modules/uvu/bin.js tests", | ||
| "check-coverage": "c8 yarn test", | ||
| "report-coverage": "c8 report --reporter text-lcov > coverage.lcov", | ||
| "test": "node --loader ts-node/esm node_modules/uvu/bin.js ./tests", | ||
| "tsc": "tsc" | ||
| } | ||
| } |
+126
-10
@@ -1,16 +0,33 @@ | ||
| # Grid sort | ||
| <div align="center"> | ||
| <br /> | ||
| <img src="static/grid-sort.png" alt="grid sort" height="150"> | ||
| <h1>Grid sort</h1> | ||
| <p>Sort array of objects or numbers into a grid where items are sorted from biggest to lowest</p> | ||
| <a href="https://npmjs.org/package/grid-sort"> | ||
| <img src="https://badgen.net/npm/v/grid-sort" alt="version" /> | ||
| </a> | ||
| <a href="https://github.com/luisadame/grid-sort/tree/main/tests"> | ||
| <img src="https://img.shields.io/codecov/c/github/luisadame/grid-sort" alt="coverage" /> | ||
| </a> | ||
| <a href="https://packagephobia.now.sh/result?p=grid-sort"> | ||
| <img src="https://packagephobia.now.sh/badge?p=grid-sort" alt="install size" /> | ||
| </a> | ||
| </div> | ||
| Sort array of objects or numbers into a grid where items are sorted from biggest to lowest | ||
| ## Introduction | ||
| In CSS there are multiple ways to achieve stunning layouts, the most populare systems to achieve this are Grid and Flex layout systems. These systems provide you with a set of properties to define layouts, however they depend on the order of the elements in the dom. | ||
| In CSS there are multiple ways to achieve stunning layouts, the most popular systems to achieve this are Grid and Flex layout systems. These systems provide you with a set of properties to define layouts, however they depend on the order of the elements in the dom. | ||
| For simple binary choices this is great, however we may want a grid layout where elements fill the horizontal space based on items size. The ideal concept would be that grid or flex had properties to lay out items respecting their intrinsic size from greatest to lowest size, and filling out the gaps with items with smaller size. | ||
| I've tried to achieve this with only css but haven't come up with a way, so I decided to reach out to JavsScript and sort the items in that same manner, | ||
| For simple binary choices this is great, however we may want a grid layout where elements fill the horizontal space based on their item size. The ideal concept would be that grid or flex had properties to lay out items respecting their intrinsic size from greatest to lowest size, and filling out the gaps with items with smaller size. | ||
| I've tried to achieve this with only css but haven't come up with a way, so I decided to reach out to JavsScript and sort the items in that same manner. | ||
| ### Visualization | ||
| Given a list of numbers: | ||
| To better understand the case above let's introduce an example. | ||
| Imagine a list of numbers which represent the columns they would take in a grid of four columns: | ||
| <div align="center"> | ||
| <img alt="Unsorted list of rectangles that contain a number representing the column space" src="static/unsorted-grid.png" /> | ||
| </div> | ||
| ```javascript | ||
@@ -20,4 +37,14 @@ const list = [3, 2, 1, 2, 3, 1, 1, 1]; | ||
| In a 4 column grid I'd like to have them sorted like this: | ||
| The numbers are totally random, because they may come from any dataset you'd have and these may not be sorted for a grid, perhaps they are sorted by insertion order in your database, size, date created, or whatever. | ||
| Therefore to have them sorted for a grid the sorting algorithm should respect a few rules: | ||
| 1. Move largest objects to the top of the grid | ||
| 2. Fill the gaps they leave in the current row with whatever largest object we can take | ||
| 3. Always respect the number of columns | ||
| 4. If two rows are visually the same swap position of items to create a better space distribution | ||
| <div align="center"> | ||
| <img alt="Sorted list of rectangles that contain a number representing the column space they take on a grid, and they are layed out based on the algorithm above" src="static/sorted-grid.png"/> | ||
| </div> | ||
| ```javascript | ||
@@ -32,3 +59,92 @@ const sortedList = [ | ||
| Rows are filled with items that fill the 4 column rule, they are filled with biggest items first, and the space left with the next biggest item. | ||
| Rows compared themselves to the previous one to achieve a varied visual composition. | ||
| The sorted list is a two dimensional array that represents a grid where rows are filled using the rules above. The fourth rule can be seen on the second row, where if the second row didn't follow the fourth rule it would have the 3 column item first and the 1 column item last, but as we have an identical row before this one lets swap the items. | ||
| ## Installation | ||
| This module is distributed via [npm](https://www.npmjs.com/) and should be installed as one of your project's `dependencies`: | ||
| ```bash | ||
| # with yarn | ||
| yarn add grid-sort | ||
| # with npm | ||
| npm install grid-sort | ||
| ``` | ||
| ## Usage | ||
| With a list of primitive numbers | ||
| ```typescript | ||
| import { gridSort } from "grid-sort"; | ||
| const items = [1, 1, 1, 2, 3, 3]; | ||
| const sortedItems = gridSort({ items }); | ||
| /* | ||
| sortedItems -> [ | ||
| [3, 1], | ||
| [1, 3], | ||
| [2, 1] | ||
| ] | ||
| */ | ||
| ``` | ||
| With a list of objects we need to provide another option to our `gridSort` function. An `accessor` option is required for list of objects because we can compare objects we need you to provide a way to access a number within the objects that represents the column an object would take. | ||
| ```typescript | ||
| import { gridSort } from "grid-sort"; | ||
| const images = [ | ||
| { id: 1, columns: 3 }, | ||
| { id: 2, columns: 2 }, | ||
| { id: 3, columns: 1 }, | ||
| { id: 4, columns: 1 }, | ||
| { id: 5, columns: 1 }, | ||
| ]; | ||
| // define an accessor to get the property to check against | ||
| const accessor = (image: typeof items[number]) => image.columns; | ||
| const sortedItems = gridSort({ items, accessor }); | ||
| /* | ||
| sortedItems -> [ | ||
| [ | ||
| { id: 1, columns: 3 }, | ||
| { id: 3, columns: 1 } | ||
| ], | ||
| [ | ||
| { id: 2, columns: 2 }, | ||
| { id: 4, columns: 1 } | ||
| { id: 5, columns: 1 } | ||
| ] | ||
| ] | ||
| */ | ||
| ``` | ||
| ### Options | ||
| The exported function `gridSort` accepts an object that has three properties which represent required data or options. | ||
| #### items (required) | ||
| It represents the list of items you want to sort | ||
| #### accessor | ||
| If you give an array of objects as items you'll have to pass this option too, as I described above, this option is necessary to obtain a number that represents the number of columns an item in your list might take | ||
| #### columns | ||
| Defines the number of columns your grid would have | ||
| ## Contributions | ||
| You are more than welcome to report any issues or suggest new features. | ||
| Just create an issue with the bug or enhancement label and we'll discuss further actions. Thank you in advance for even considering contributing to this. | ||
| ## License | ||
| MIT |
+26
-19
@@ -23,7 +23,3 @@ export type Bucket<T> = T[]; | ||
| if (typeof item === "number") { | ||
| return acc + item; | ||
| } | ||
| return acc; | ||
| return acc + Number(item); | ||
| }, 0); | ||
@@ -35,4 +31,15 @@ | ||
| export function calculateItemsLeft<T>(buckets: Buckets<T>) { | ||
| return Object.values(buckets).reduce((acc, bucket) => acc + bucket.length, 0); | ||
| export function calculateItemsLeft<T>(buckets: Buckets<T>, columns: number, accessor?: T extends object ? Accessor<T> : undefined) { | ||
| return Object.values(buckets) | ||
| .reduce((acc, bucket) => { | ||
| const bucketQuantity = bucket.map(value => { | ||
| if (accessor && typeof value === 'object') { | ||
| return accessor(value); | ||
| } | ||
| return value; | ||
| }).filter(value => value <= columns).length; | ||
| return acc + bucketQuantity; | ||
| }, 0); | ||
| } | ||
@@ -63,12 +70,10 @@ | ||
| for (const item of items) { | ||
| let key: number | undefined; | ||
| let key: number; | ||
| if (accessor && typeof item === "object") { | ||
| key = accessor(item); | ||
| } else if (typeof item === "number") { | ||
| key = item; | ||
| } else { | ||
| key = Number(item); | ||
| } | ||
| if (key === undefined) continue; | ||
| const group = groups[key]; | ||
@@ -109,6 +114,9 @@ | ||
| }) { | ||
| const grid = []; | ||
| const grid: T[][] = []; | ||
| if (columns <= 0) return grid; | ||
| let itemsLeft: number; | ||
| while ((itemsLeft = calculateItemsLeft(buckets)) > 0) { | ||
| while ((itemsLeft = calculateItemsLeft(buckets, columns, accessor)) > 0) { | ||
| const row: T[] = []; | ||
@@ -122,7 +130,6 @@ let key: number; | ||
| ) { | ||
| const bucket = buckets[key]; | ||
| if (!bucket) continue; | ||
| const item = bucket.shift(); | ||
| if (!item) break; | ||
| row.push(item); | ||
| const item = buckets[key]?.shift(); | ||
| if (item) { | ||
| row.push(item); | ||
| } | ||
| } | ||
@@ -129,0 +136,0 @@ |
+1
-0
@@ -26,4 +26,5 @@ { | ||
| "target": "ES2015", | ||
| "sourceMap": true | ||
| }, | ||
| "include": ["src"] | ||
| } |
-209
| import { test } from "uvu"; | ||
| import * as assert from "uvu/assert"; | ||
| import type { Buckets } from "../lib/gridSort.js"; | ||
| import { | ||
| buildGridFromBuckets, | ||
| getFillableItem, | ||
| getSortedKeys, | ||
| gridSort, | ||
| groupItemsByValue, | ||
| rowsAreEqual, | ||
| } from "../lib/gridSort.js"; | ||
| test("sorts object keys numerically in descending order", () => { | ||
| const buckets = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getSortedKeys(buckets), [3, 2, 1].map(String)); | ||
| }); | ||
| test("gets best candidate for filling a column", () => { | ||
| const bucketsA = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsA, 4), "3"); | ||
| const bucketsB = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsB, 1), "1"); | ||
| const bucketsC = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsC, 2), "2"); | ||
| const bucketsD = { 1: [1, 1, 1], 2: [2, 2], 3: [] }; | ||
| assert.equal(getFillableItem(bucketsD, 4), "2"); | ||
| const bucketsE = { 1: [1, 1, 1], 2: [], 3: [] }; | ||
| assert.equal(getFillableItem(bucketsE, 4), "1"); | ||
| const bucketsF = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsF, 2), "2"); | ||
| const bucketsG = { 1: [1, 1, 1], 2: [2, 2], 3: [3, 3] }; | ||
| assert.equal(getFillableItem(bucketsG, 1), "1"); | ||
| }); | ||
| test("rows equality", () => { | ||
| const [rowA, rowB] = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| ], | ||
| [ | ||
| { id: "8", ratio: 3 }, | ||
| { id: "5", ratio: 1 }, | ||
| ], | ||
| ]; | ||
| const accessor = (item: typeof rowA[number]) => item.ratio; | ||
| assert.ok(rowsAreEqual(rowA, rowB, accessor)); | ||
| assert.not.ok(rowsAreEqual(rowA, rowB.reverse(), accessor)); | ||
| }); | ||
| test("it sorts bucket of numbers", () => { | ||
| const buckets = { | ||
| 1: [1, 1, 1, 1, 1], | ||
| 2: [2, 2], | ||
| 3: [3, 3], | ||
| } as Buckets<number>; | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets, | ||
| columns: 4, | ||
| accessor: undefined, | ||
| }); | ||
| const expected = [ | ||
| [3, 1], | ||
| [1, 3], | ||
| [2, 2], | ||
| [1, 1, 1], | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| test("it builds buckets from array of items", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const itemsByValue = groupItemsByValue(items, (item) => item.ratio); | ||
| assert.equal(itemsByValue, { | ||
| 1: [ | ||
| { id: "2", ratio: 1 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| ], | ||
| 2: [ | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| ], | ||
| 3: [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| }); | ||
| }); | ||
| test("it sorts bucket of objects", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const itemsByValue = groupItemsByValue(items, accessor); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets: itemsByValue, | ||
| columns: 4, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| ], | ||
| [ | ||
| { id: "5", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| [ | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| ], | ||
| [{ id: "7", ratio: 1 }], | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| test("it sorts bucket of objects with different columns", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const itemsByValue = groupItemsByValue(items, accessor); | ||
| const sorted = buildGridFromBuckets({ | ||
| buckets: itemsByValue, | ||
| columns: 5, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "3", ratio: 2 }, | ||
| ], | ||
| [ | ||
| { id: "4", ratio: 2 }, | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| [ | ||
| { id: "2", ratio: 1 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| ], | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| test("it sorts an array of items into a grid", () => { | ||
| const items = [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "2", ratio: 1 }, | ||
| { id: "3", ratio: 2 }, | ||
| { id: "4", ratio: 2 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| { id: "8", ratio: 3 }, | ||
| ]; | ||
| const accessor = (item: typeof items[number]) => item.ratio; | ||
| const sorted = gridSort({ | ||
| items, | ||
| columns: 5, | ||
| accessor, | ||
| }); | ||
| const expected = [ | ||
| [ | ||
| { id: "1", ratio: 3 }, | ||
| { id: "3", ratio: 2 }, | ||
| ], | ||
| [ | ||
| { id: "4", ratio: 2 }, | ||
| { id: "8", ratio: 3 }, | ||
| ], | ||
| [ | ||
| { id: "2", ratio: 1 }, | ||
| { id: "5", ratio: 1 }, | ||
| { id: "7", ratio: 1 }, | ||
| ], | ||
| ]; | ||
| assert.equal(sorted, expected); | ||
| }); | ||
| test.run(); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
221692
1289.92%18
80%593
28.35%149
351.52%5
66.67%1
Infinity%