Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access the index on map() operations #88

Open
Mistyputt opened this issue Mar 25, 2020 · 4 comments
Open

Access the index on map() operations #88

Mistyputt opened this issue Mar 25, 2020 · 4 comments

Comments

@Mistyputt
Copy link

Hi

My use case frequently involves mapping a list of raw data into a UI representation that offers the ability to modify elements. For instance mapping a list into table rows, where the user may modify or delete rows. I was thinking having the index available when mapping would allow me to efficiently update the list on that index.

One could use List.zipWith(f, list, List.range(0,List.length(list))) or avoid the index and use List.find but maybe there is a place for a dedicated mapWithIndex function as that should theoretically be more efficient?

Does anyone else have similar use-cases and what is your solution?

Finally, thanks for a great lib!

@Mistyputt
Copy link
Author

I've played around with a naive implementation:

function mapArrayWithIndex<A, B>(f: (a: A, i: number) => B, array: A[], offset: number): [B[], number] {
  const result = new Array(array.length);
  for (let i = 0; i < array.length; ++i) {
    result[i] = f(array[i], offset + i);
  }
  return [result, offset + array.length];
}

function mapNodeWithIndex<A, B>(f: (a: A, i: number) => B, node: Node, depth: number, offset: number, adjust: number): [Node, number] {
  if (depth !== 0) {
    const { array } = node;
    var innerOffset = offset;
    const result = new Array(array.length);
    for (let i = 0; i < array.length; ++i) {
      let [res, newOffset] = mapNodeWithIndex(f, array[i], depth - 1, innerOffset, adjust * 32);
      innerOffset = newOffset;
      result[i] = res
    }
    return [new Node(node.sizes, result), innerOffset];
  } else {
    let [res, newOffset] = mapArrayWithIndex(f, node.array, offset);
    return [new Node(undefined, res), newOffset];
  }
}

function mapPrefixWithIndex<A, B>(f: (a: A, i: number) => B, prefix: A[], length: number): B[] {
  const newPrefix = new Array(length);
  for (let i = length - 1; 0 <= i; --i) {
    newPrefix[i] = f(prefix[i], length - 1 - i);
  }
  return newPrefix;
}

function mapAffixWithIndex<A, B>(f: (a: A, i: number) => B, suffix: A[], length: number, totalLength: number): B[] {
  const priorLength = totalLength - length;
  const newSuffix = new Array(length);
  for (let i = 0; i < length; ++i) {
    newSuffix[i] = f(suffix[i], priorLength + i);
  }
  return newSuffix;
}

/**
 * Applies a function to each element in the given list, and its index, and returns a
 * new list of the values that the function return.
 *
 */
export function mapWithIndex<A, B>(f: (a: A, i: number) => B, l: List<A>): List<B> {
  return new List(
    l.bits,
    l.offset,
    l.length,
    mapPrefixWithIndex(f, l.prefix, getPrefixSize(l)),
    l.root === undefined ? undefined : mapNodeWithIndex(f, l.root, getDepth(l), getPrefixSize(l), 1)[0],
    mapAffixWithIndex(f, l.suffix, getSuffixSize(l), l.length)
  );
}

Maybe one could use the depth and branchingFactor instead but this seems performance-wise to already fare better than zipWith. Here are my rudimentary benchmark results:

List.map 10                   36818486.05 op/s ±  0.54%   (86 samples)
List.mapWithIndex 10          32894014.39 op/s ±  0.39%   (88 samples)
List.zipWith 10               4332197.10 op/s ±  0.63%   (84 samples)
List.map 30000                14337.09 op/s ±  0.71%   (88 samples)
List.mapWithIndex 30000       11587.69 op/s ±  0.54%   (88 samples)
List.zipWith 30000             1240.52 op/s ±  0.61%   (87 samples)

@carleryd
Copy link

@Mistyputt I just ran into the same issue where I want to know which position in a row an element was clicked. The row is created using map function.
I would like to do the following

L.map((x, i) => <Poster content={x} position={i} />, contentL)

I guess a workaround for now can be to use indexOf

@paldepind
Copy link
Member

Thanks for opening the issue and providing feedback. I definitely agree that it would be nice to offer an easy way to map with an index.

I think your mapWithIndex implementation looks great @Mistyputt. Would you like to add it in a PR? 😄

@Mistyputt
Copy link
Author

Absolutely!

I did work around a few issues building the project when trying out that implementation and would feel better if I had a "clean" project when submitting the PR. After cloning I ran npm install, then npm run build. That failed with errors on ts-toolbelt and devtoolsFormatters:

❯ npm run build

> [email protected] build /Users/thomas/git/github/funkia/list3
> npm run build-es6; npm run build-cmjs


> [email protected] build-es6 /Users/thomas/git/github/funkia/list3
> tsc -P ./tsconfig-build.json --outDir 'dist/es' --module es2015

node_modules/@types/ramda/index.d.ts:546:28 - error TS2307: Cannot find module 'ts-toolbelt'.

546 import { A, F, T, O } from "ts-toolbelt";
                               ~~~~~~~~~~~~~

src/devtools.ts:15:8 - error TS2339: Property 'devtoolsFormatters' does not exist on type '(Window & typeof globalThis) | Global'.
  Property 'devtoolsFormatters' does not exist on type 'Window & typeof globalThis'.

15 if (gw.devtoolsFormatters === undefined) {
          ~~~~~~~~~~~~~~~~~~

src/devtools.ts:16:6 - error TS2339: Property 'devtoolsFormatters' does not exist on type '(Window & typeof globalThis) | Global'.
  Property 'devtoolsFormatters' does not exist on type 'Window & typeof globalThis'.

16   gw.devtoolsFormatters = [];
        ~~~~~~~~~~~~~~~~~~

src/devtools.ts:26:4 - error TS2339: Property 'devtoolsFormatters' does not exist on type '(Window & typeof globalThis) | Global'.
  Property 'devtoolsFormatters' does not exist on type 'Window & typeof globalThis'.

26 gw.devtoolsFormatters.push({
      ~~~~~~~~~~~~~~~~~~


Found 4 errors.

npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] build-es6: `tsc -P ./tsconfig-build.json --outDir 'dist/es' --module es2015`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] build-es6 script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/thomas/.npm/_logs/2020-04-30T21_15_23_326Z-debug.log

> [email protected] build-cmjs /Users/thomas/git/github/funkia/list3
> tsc -P ./tsconfig-build.json

src/devtools.ts:15:8 - error TS2339: Property 'devtoolsFormatters' does not exist on type '(Window & typeof globalThis) | Global'.
  Property 'devtoolsFormatters' does not exist on type 'Window & typeof globalThis'.

15 if (gw.devtoolsFormatters === undefined) {
          ~~~~~~~~~~~~~~~~~~

src/devtools.ts:16:6 - error TS2339: Property 'devtoolsFormatters' does not exist on type '(Window & typeof globalThis) | Global'.
  Property 'devtoolsFormatters' does not exist on type 'Window & typeof globalThis'.

16   gw.devtoolsFormatters = [];
        ~~~~~~~~~~~~~~~~~~

src/devtools.ts:26:4 - error TS2339: Property 'devtoolsFormatters' does not exist on type '(Window & typeof globalThis) | Global'.
  Property 'devtoolsFormatters' does not exist on type 'Window & typeof globalThis'.

26 gw.devtoolsFormatters.push({
      ~~~~~~~~~~~~~~~~~~

As no one else has reported this I feel I'm missing something obvious. When I played around with the mapWithIndex implementation I just commented out src/devtools.ts and ran the build-cmjs only, not the es6 that additionally failed on ts-toolbelt.

I built with node v10.19.0. Any pointers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants