« get me outta code hell

data: withFilteredList, withMappedList, withSortedList - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-12-03 13:23:40 -0400
committer(quasar) nebula <qznebula@protonmail.com>2023-12-03 13:24:28 -0400
commitdb4698f9ce602227dc2c9aa79c409dd4ce508e7b (patch)
tree26838bdca100edb0b01b4007601c44c95cf8e9c2
parent810acb84c379b76d3b8290bfd7d5971438999939 (diff)
data: withFilteredList, withMappedList, withSortedList
God bless thine soul, these are not unit tested.
-rw-r--r--src/data/composite/data/excludeFromList.js7
-rw-r--r--src/data/composite/data/fillMissingListItems.js7
-rw-r--r--src/data/composite/data/index.js3
-rw-r--r--src/data/composite/data/withFilteredList.js50
-rw-r--r--src/data/composite/data/withFlattenedList.js6
-rw-r--r--src/data/composite/data/withMappedList.js39
-rw-r--r--src/data/composite/data/withPropertiesFromList.js4
-rw-r--r--src/data/composite/data/withPropertyFromList.js4
-rw-r--r--src/data/composite/data/withSortedList.js126
-rw-r--r--src/data/composite/data/withUnflattenedList.js10
10 files changed, 241 insertions, 15 deletions
diff --git a/src/data/composite/data/excludeFromList.js b/src/data/composite/data/excludeFromList.js
index 718f2294..d798dcdc 100644
--- a/src/data/composite/data/excludeFromList.js
+++ b/src/data/composite/data/excludeFromList.js
@@ -6,10 +6,9 @@
 //  - fillMissingListItems
 //
 // More list utilities:
-//  - withFlattenedList
-//  - withPropertyFromList
-//  - withPropertiesFromList
-//  - withUnflattenedList
+//  - withFilteredList, withMappedList, withSortedList
+//  - withFlattenedList, withUnflattenedList
+//  - withPropertyFromList, withPropertiesFromList
 //
 
 import {input, templateCompositeFrom} from '#composite';
diff --git a/src/data/composite/data/fillMissingListItems.js b/src/data/composite/data/fillMissingListItems.js
index c06eceda..4f818a79 100644
--- a/src/data/composite/data/fillMissingListItems.js
+++ b/src/data/composite/data/fillMissingListItems.js
@@ -5,10 +5,9 @@
 //  - excludeFromList
 //
 // More list utilities:
-//  - withFlattenedList
-//  - withPropertyFromList
-//  - withPropertiesFromList
-//  - withUnflattenedList
+//  - withFilteredList, withMappedList, withSortedList
+//  - withFlattenedList, withUnflattenedList
+//  - withPropertyFromList, withPropertiesFromList
 //
 
 import {input, templateCompositeFrom} from '#composite';
diff --git a/src/data/composite/data/index.js b/src/data/composite/data/index.js
index e2927afd..256c0490 100644
--- a/src/data/composite/data/index.js
+++ b/src/data/composite/data/index.js
@@ -5,10 +5,13 @@
 
 export {default as excludeFromList} from './excludeFromList.js';
 export {default as fillMissingListItems} from './fillMissingListItems.js';
+export {default as withFilteredList} from './withFilteredList.js';
 export {default as withFlattenedList} from './withFlattenedList.js';
+export {default as withMappedList} from './withMappedList.js';
 export {default as withPropertiesFromList} from './withPropertiesFromList.js';
 export {default as withPropertiesFromObject} from './withPropertiesFromObject.js';
 export {default as withPropertyFromList} from './withPropertyFromList.js';
 export {default as withPropertyFromObject} from './withPropertyFromObject.js';
+export {default as withSortedList} from './withSortedList.js';
 export {default as withUnflattenedList} from './withUnflattenedList.js';
 export {default as withUniqueItemsOnly} from './withUniqueItemsOnly.js';
diff --git a/src/data/composite/data/withFilteredList.js b/src/data/composite/data/withFilteredList.js
new file mode 100644
index 00000000..82e56903
--- /dev/null
+++ b/src/data/composite/data/withFilteredList.js
@@ -0,0 +1,50 @@
+// Applies a filter - an array of truthy and falsy values - to the index-
+// corresponding items in a list. Items which correspond to a truthy value
+// are kept, and the rest are excluded from the output list.
+//
+// TODO: It would be neat to apply an availability check here, e.g. to allow
+// not providing a filter at all and performing the check on the contents of
+// the list (though on the filter, if present, is fine too). But that's best
+// done by some shmancy-fancy mapping support in composite.js, so a bit out
+// of reach for now (apart from proving uses built on top of a more boring
+// implementation).
+//
+// TODO: There should be two outputs - one for the items included according to
+// the filter, and one for the items excluded.
+//
+// See also:
+//  - withMappedList
+//  - withSortedList
+//
+// More list utilities:
+//  - excludeFromList
+//  - fillMissingListItems
+//  - withFlattenedList, withUnflattenedList
+//  - withPropertyFromList, withPropertiesFromList
+//
+
+import {input, templateCompositeFrom} from '#composite';
+
+export default templateCompositeFrom({
+  annotation: `withFilteredList`,
+
+  inputs: {
+    list: input({type: 'array'}),
+    filter: input({type: 'array'}),
+  },
+
+  outputs: ['#filteredList'],
+
+  steps: () => [
+    {
+      dependencies: [input('list'), input('filter')],
+      compute: (continuation, {
+        [input('list')]: list,
+        [input('filter')]: filter,
+      }) => continuation({
+        '#filteredList':
+          list.filter((item, index) => filter[index]),
+      }),
+    },
+  ],
+});
diff --git a/src/data/composite/data/withFlattenedList.js b/src/data/composite/data/withFlattenedList.js
index b08edb4e..edfa3403 100644
--- a/src/data/composite/data/withFlattenedList.js
+++ b/src/data/composite/data/withFlattenedList.js
@@ -3,13 +3,13 @@
 // successive source array.
 //
 // See also:
-//  - withFlattenedList
+//  - withUnflattenedList
 //
 // More list utilities:
 //  - excludeFromList
 //  - fillMissingListItems
-//  - withPropertyFromList
-//  - withPropertiesFromList
+//  - withFilteredList, withMappedList, withSortedList
+//  - withPropertyFromList, withPropertiesFromList
 //
 
 import {input, templateCompositeFrom} from '#composite';
diff --git a/src/data/composite/data/withMappedList.js b/src/data/composite/data/withMappedList.js
new file mode 100644
index 00000000..e0a700b2
--- /dev/null
+++ b/src/data/composite/data/withMappedList.js
@@ -0,0 +1,39 @@
+// Applies a map function to each item in a list, just like a normal JavaScript
+// map.
+//
+// See also:
+//  - withFilteredList
+//  - withSortedList
+//
+// More list utilities:
+//  - excludeFromList
+//  - fillMissingListItems
+//  - withFlattenedList, withUnflattenedList
+//  - withPropertyFromList, withPropertiesFromList
+//
+
+import {input, templateCompositeFrom} from '#composite';
+
+export default templateCompositeFrom({
+  annotation: `withMappedList`,
+
+  inputs: {
+    list: input({type: 'array'}),
+    map: input({type: 'function'}),
+  },
+
+  outputs: ['#mappedList'],
+
+  steps: () => [
+    {
+      dependencies: [input('list'), input('map')],
+      compute: (continuation, {
+        [input('list')]: list,
+        [input('map')]: mapFn,
+      }) => continuation({
+        ['#mappedList']:
+          list.map(mapFn),
+      }),
+    },
+  ],
+});
diff --git a/src/data/composite/data/withPropertiesFromList.js b/src/data/composite/data/withPropertiesFromList.js
index 76ba696c..08907bab 100644
--- a/src/data/composite/data/withPropertiesFromList.js
+++ b/src/data/composite/data/withPropertiesFromList.js
@@ -11,8 +11,8 @@
 // More list utilities:
 //  - excludeFromList
 //  - fillMissingListItems
-//  - withFlattenedList
-//  - withUnflattenedList
+//  - withFilteredList, withMappedList, withSortedList
+//  - withFlattenedList, withUnflattenedList
 //
 
 import {input, templateCompositeFrom} from '#composite';
diff --git a/src/data/composite/data/withPropertyFromList.js b/src/data/composite/data/withPropertyFromList.js
index 1983ebbc..a2c66d77 100644
--- a/src/data/composite/data/withPropertyFromList.js
+++ b/src/data/composite/data/withPropertyFromList.js
@@ -12,8 +12,8 @@
 // More list utilities:
 //  - excludeFromList
 //  - fillMissingListItems
-//  - withFlattenedList
-//  - withUnflattenedList
+//  - withFilteredList, withMappedList, withSortedList
+//  - withFlattenedList, withUnflattenedList
 //
 
 import {input, templateCompositeFrom} from '#composite';
diff --git a/src/data/composite/data/withSortedList.js b/src/data/composite/data/withSortedList.js
new file mode 100644
index 00000000..882907f5
--- /dev/null
+++ b/src/data/composite/data/withSortedList.js
@@ -0,0 +1,126 @@
+// Applies a sort function across pairs of items in a list, just like a normal
+// JavaScript sort. Alongside the sorted results, so are outputted the indices
+// which each item in the unsorted list corresponds to in the sorted one,
+// allowing for the results of this sort to be composed in some more involved
+// operation. For example, using an alphabetical sort, the list ['banana',
+// 'apple', 'pterodactyl'] will output the expected alphabetical items, as well
+// as the indices list [1, 0, 2].
+//
+// If two items are equal (in the eyes of the sort operation), their placement
+// in the sorted list is arbitrary, though every input index will be present in
+// '#sortIndices' exactly once (and equal items will be bunched together).
+//
+// The '#sortIndices' output refers to the "true" index which each source item
+// occupies in the sorted list. This sacrifices information about equal items,
+// which can be obtained through '#unstableSortIndices' instead: each mapped
+// index may appear more than once, and rather than represent exact positions
+// in the sorted list, they represent relational values: if items A and B are
+// mapped to indices 3 and 5, then A certainly is positioned before B (and vice
+// versa); but there may be more than one item in-between. If items C and D are
+// both mapped to index 4, then their position relative to each other is
+// arbitrary - they are equal - but they both certainly appear after item A and
+// before item B.
+//
+// This implementation is based on the one used for sortMultipleArrays.
+//
+// See also:
+//  - withFilteredList
+//  - withMappedList
+//
+// More list utilities:
+//  - excludeFromList
+//  - fillMissingListItems
+//  - withFlattenedList, withUnflattenedList
+//  - withPropertyFromList, withPropertiesFromList
+//
+
+import {input, templateCompositeFrom} from '#composite';
+import {empty} from '#sugar';
+
+export default templateCompositeFrom({
+  annotation: `withSortedList`,
+
+  inputs: {
+    list: input({type: 'array'}),
+    sort: input({type: 'function'}),
+  },
+
+  outputs: ['#sortedList', '#sortIndices', '#unstableSortIndices'],
+
+  steps: () => [
+    {
+      dependencies: [input('list'), input('sort')],
+      compute(continuation, {
+        [input('list')]: list,
+        [input('sort')]: sortFn,
+      }) {
+        const symbols =
+          Array.from({length: list.length}, () => Symbol());
+
+        const equalSymbols =
+          new Map();
+
+        const indexMap =
+          new Map(Array.from(symbols,
+            (symbol, index) => [symbol, index]));
+
+        symbols.sort((symbol1, symbol2) => {
+          const comparison =
+            sortFn(
+              list[indexMap.get(symbol1)],
+              list[indexMap.get(symbol2)]);
+
+          if (comparison === 0) {
+            if (equalSymbols.has(symbol1)) {
+              equalSymbols.get(symbol1).add(symbol2);
+            } else {
+              equalSymbols.set(symbol1, new Set([symbol2]));
+            }
+
+            if (equalSymbols.has(symbol2)) {
+              equalSymbols.get(symbol2).add(symbol1);
+            } else {
+              equalSymbols.set(symbol2, new Set([symbol1]));
+            }
+          }
+
+          return comparison;
+        });
+
+        const sortIndices =
+          symbols.map(symbol => indexMap.get(symbol));
+
+        const sortedList =
+          sortIndices.map(index => list[index]);
+
+        const stableToUnstable =
+          symbols
+            .map((symbol, index) =>
+              index > 0 &&
+              equalSymbols.get(symbols[index - 1])?.has(symbol))
+            .reduce((accumulator, collapseEqual) => {
+              if (empty(accumulator)) {
+                accumulator.push(0);
+              } else {
+                const last = accumulator[accumulator.length - 1];
+                if (collapseEqual) {
+                  accumulator.push(last);
+                } else {
+                  accumulator.push(last + 1);
+                }
+              }
+              return accumulator;
+            }, []);
+
+        const unstableSortIndices =
+          sortIndices.map(stable => stableToUnstable[stable]);
+
+        return continuation({
+          ['#sortedList']: sortedList,
+          ['#sortIndices']: sortIndices,
+          ['#unstableSortIndices']: unstableSortIndices,
+        });
+      },
+    },
+  ],
+});
diff --git a/src/data/composite/data/withUnflattenedList.js b/src/data/composite/data/withUnflattenedList.js
index 3cfc247b..39a666dc 100644
--- a/src/data/composite/data/withUnflattenedList.js
+++ b/src/data/composite/data/withUnflattenedList.js
@@ -3,6 +3,16 @@
 // of filtering them out), this function allows for recombining them. It will
 // filter out null and undefined items by default (pass {filter: false} to
 // disable this).
+//
+// See also:
+//  - withFlattenedList
+//
+// More list utilities:
+//  - excludeFromList
+//  - fillMissingListItems
+//  - withFilteredList, withMappedList, withSortedList
+//  - withPropertyFromList, withPropertiesFromList
+//
 
 import {input, templateCompositeFrom} from '#composite';
 import {isWholeNumber, validateArrayItems} from '#validators';