0

I'm writing a helper function to get every nth element from an array in order to get all table cells for a specific column based on a column's index.

I've based on the answer from this thread.

My concern is that whenever I pass column index 0 it does not work. I am also not too sure what the delta should be changed to because it does not produce correct results.

Given the following array

const cells = [
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
];

and calling the function by getColumnCells(cells, 6) I should be receiving an array of 'button's.

const getColumnCells = (cells, columnIndex) => {
  const columnCells = [];
  const delta = Math.floor(cells.length / columnIndex);

  for (let i = columnIndex; i < cells.length; i = i + delta) {
    columnCells.push(cells[i]);
  }

  return columnCells;
};

getColumnCells(cells, 0) should return ['text', 'text', 'text] to get the index 0 of every row.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
LazioTibijczyk
  • 1,701
  • 21
  • 48

7 Answers7

1

You can achieve this using simply filter and % (modulo) operation. However you need to know the length of the row, cause looks like you want to treat 1d array as 2d.

Solution:

const cells = [
  'texta1', 'texta2', 'texta3', 'texta4', 'texta5', 'texta6', 'buttona',
  'textb1', 'textb2', 'textb3', 'textb4', 'textb5', 'textb6', 'buttonb',
  'textc1', 'textc2', 'textc3', 'textc4', 'textc5', 'textc6', 'buttonc',
];

const getEveryNth = (arr, rowLength, colIdx) => arr.filter(
  (_, i) => i % rowLength === colIdx
)

console.log(getEveryNth(cells, 7, 6))
console.log(getEveryNth(cells, 7, 0))
.as-console-wrapper { max-height: 100% !important; top: 0; } /* ignore this */
ulou
  • 5,542
  • 5
  • 37
  • 47
1

I would recommend to pass one more variable cols in the function.

You may add one more column one day in the future.

Then, you can just pass different argument of cols to the function to get the output.

const cells = [
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
];

function getColumnCells(arr, cols, index) {
  const output = [];
  if (cols > index) {
    for (let i = 0; i < arr.length; i += cols) {
      const row = arr.slice(i, i + cols);
      index > row.length ? output.push(null) : output.push(row[index]);
    }
  }
  return output;
}

console.log(getColumnCells(cells, 7, 6));
ikhvjs
  • 5,316
  • 2
  • 13
  • 36
1

Solution 1: You may assume you have 7 columns, & use it as an offset/delta.

    const cells = [
        'text', 'text', 'text', 'texts', 'text', 'text', 'button',
        'text', 'text', 'text', 'texts', 'text', 'text', 'button',
        'text', 'text', 'text', 'texts', 'text', 'text', 'button',
    ];

const getColumnCells = (cells, columnIndex) => {
    const columnCells = [];
    const numberOfColumns = 7
    const delta = numberOfColumns // or equals to number of columns
    for (let i = columnIndex; i < cells.length; i = i + delta) {
        columnCells.push(cells[i]);
    }

    return columnCells;
};
console.log(getColumnCells(cells, 3)) // ['texts','texts','texts']
console.log(getColumnCells(cells, 6)) // ['button','button','button']

Solution 2: Easy One An array with 3 rows & 7 columns.So,reshape your data

const newCells = [
    ['text0', 'text1', 'text2', 'text3', 'text4', 'text', 'button'],
    ['text0', 'text1', 'text2', 'text3', 'text4', 'text', 'button'],
    ['text0', 'text1', 'text2', 'text3', 'text4', 'text', 'button'],
];
const getColumnCellsTwo = (cells, columnIndex) => {
    const columnCells = [];
    for (let k = 0; k < cells.length; k++) {
        columnCells.push(cells[k][columnIndex]);
    }

    return columnCells;

};


console.log(getColumnCellsTwo(newCells, 0)) // ['text0','text0','text0']
console.log(getColumnCellsTwo(newCells, 6)) // ['button','button','button']

//Last comment is that: In your code, delta = Math.floor(cells.length / columnIndex) is Infinity when columnIndex = 0.Just to clarify.

Gere
  • 21
  • 2
0

const cells = ['text1', 'text2', 'text3', 'text4', 'text5', 'text6', 'button7'];

function breakInN(arr, chunk) {
  chunk--;
  return arr.filter((x, i) => i % chunk == 0 && (i+chunk != 0) || chunk == 0);
}

const output = breakInN(cells, 1);

console.log({
  output
})
Harsh Gundecha
  • 1,139
  • 9
  • 16
0
const cells = [
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
  'text', 'text', 'text', 'text', 'text', 'text', 'button',
];

const getColumnCells = (cells, columnIndex) => {
  const columnCells = [];
  const delta = columnIndex + 1;

  // Starting at -1 (ignoring -1) so we always move by 7 and push the 7th element `button`
  for (let i = -1; i < cells.length; i = i + delta) {
    if (i != -1) {
      columnCells.push(cells[i]);
    }
  }

  return columnCells;
};

console.log(getColumnCells(cells, 6));
Kevin Haxhi
  • 498
  • 2
  • 6
0

I suppose your goal is to be able to treat cells like a a matrix/table-like structure (basically a 2d array). You could mimic that behavior by converting your 1d array into an actual matrix before doing any filtering or selecting. It will make any work a lot easier afterwards.

I'm afraid there is no other way but passing some kind of column amount AND column index if you want to work with your 1d array the way you described in your question.

const cells = [
  'text0', 'text1', 'text2', 'text3', 'text4', 'text5', 'button',
  'text0', 'text1', 'text2', 'text3', 'text4', 'text5', 'button',
  'text0', 'text1', 'text2', 'text3', 'text4', 'text5', 'button',
];

const arrayToMatrix = (array, cols) =>
  Array(Math.floor(array.length / cols))
    .fill()
    .reduce((acc, _, i) => [...acc, [...array].splice(i * cols, cols)], []);

const getColumnCells = (cells, cols, colIndex) =>
  arrayToMatrix(cells, cols).map((e) => e[colIndex]);

console.log(getColumnCells(cells, 7, 6));
console.log(getColumnCells(cells, 7, 0));

The disadvantage with my approach yet is that you need to be quite careful what arguments you pass to getColumCell() since arrayToMatrix() could return an incomplete matrix and you would therefore end up with column values of undefined.

Behemoth
  • 5,389
  • 4
  • 16
  • 40
0

In case of allowing oneself enough time for a more generic solution, one could implement a prototypal Array.nthItem method which is inspired by the :nth-child() CSS pseudo-class but with a Zero/0 based index instead of 1 ...

const cells = [
  'texta1', 'texta2', 'texta3', 'texta4', 'texta5', 'texta6', 'buttona',
  'textb1', 'textb2', 'textb3', 'textb4', 'textb5', 'textb6', 'buttonb',
  'textc1', 'textc2', 'textc3', 'textc4', 'textc5', 'textc6', 'buttonc',
];

function processItem(nthItem, nthIndex, array) {
  console.log({ nthItem, nthIndex });
  
  // - any passed argument can be processed.
  // - this callback is aware of it's `this` context.
  // - the callback's return value will be mapped to the
  //   `nthItem`'s internal array of to be processed indices
  //   which also, entirely mapped, is the `nthItem`'s return value.

  return nthItem;
}
console.log(
  "cells.nthItem('7n-1', processItem) ...",
  cells.nthItem('7n-1', processItem)
);
console.log(
  "cells.nthItem('7n-5', processItem) ...",
  cells.nthItem('7n-5', processItem)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<!--

  https://gist.github.com/petsel/86d7ce468512fffdb051bf9e650d1e4f

//-->
<script>
const
  arrPrototype = Object.getPrototypeOf([]);

const {
  from: arrayFrom,
  isArray,

} = Array;

function isFunction(type) {
  return (
       (typeof type === 'function')
    && (typeof type.call === 'function')
    && (typeof type.apply === 'function')
  );
}

function getSanitizedTarget(type) {
  return (type ?? null);
}

function getNotationGroup(notation) {
  // - inspired by `:nth-child()` CSS pseudo-class ... [https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child]
  //
  // - an array's `nth-item` notation ... [https://regex101.com/r/tZmeZS/1/]
  //
  const regXNthItemNotation = (/^(?<nthBase>[+-]?\d+)(?:\*?(?<series>n)(?<crementer>[+-]\d+)?)?$/);
  const regXSanitizeNotation = (/\s+/g);

  return String(notation)
    .replace(regXSanitizeNotation, '')
    .match(regXNthItemNotation)
    ?.groups;
}

function getNthIndexSeriesDescending(getNthIndex, maximumIndex) {
  const nthIndexList = [];
  let nthIndex;
  let n = -1;
  while ((nthIndex = getNthIndex(++n)) >= 0) {
    if (nthIndex <= maximumIndex) {

      nthIndexList.push(nthIndex);
    }
  }
  return nthIndexList;
}
function getNthIndexSeriesAscending(getNthIndex, maximumIndex) {
  const nthIndexList = [];
  let nthIndex;
  let n = -1;
  while ((nthIndex = getNthIndex(++n)) <= maximumIndex) {
    if (nthIndex >= 0) {

      nthIndexList.push(nthIndex);
    }
  }
  return nthIndexList;
}

function getNthIndexSeriesList(notationGroup, maximumIndex) {
  const nthIndexList = [];
  let { series, nthBase, crementer } = notationGroup;

  nthBase = Number(nthBase);
  crementer = Number(crementer ?? 0);

  const getNthIndex = n => ((nthBase * n) + crementer);

  if (!!series) {
    if (nthBase !== 0) {

      const isDescending = (getNthIndex(0) > getNthIndex(1));
      nthIndexList.push(

        ...isDescending
        ? getNthIndexSeriesDescending(getNthIndex, maximumIndex)
        : getNthIndexSeriesAscending(getNthIndex, maximumIndex)
      );
    } else if (crementer >= 0) {

      // `crementer` in this case equals the result of the first nth-index computation.
      nthIndexList.push(crementer);
    }
  } else if ((nthBase >= 0) && (nthBase <= maximumIndex)) {

    // just the `nthBase` as the sole nth-index item.
    nthIndexList.push(nthBase);
  }
  return nthIndexList;
}

function nthItem(notation, callback, target) {
  let result = [];

  const notationGroup = getNotationGroup(notation);
  const arr = notationGroup && ((isArray(this) && this) || arrayFrom(this ?? []));

  // fail silently.
  const nthIndexList = arr && isFunction(callback) && getNthIndexSeriesList(notationGroup, (arr.length - 1));
  if (nthIndexList && nthIndexList.length) {

    target = getSanitizedTarget(target);
    result = nthIndexList.map(nthIndex =>

      callback.call(target, arr[nthIndex], nthIndex, arr)
    );
  }
  return result;
}

Object.defineProperty(arrPrototype, 'nthItem', {
  configurable: true,
  writable: true,
  value: nthItem
});
Object.defineProperty(arrPrototype.nthItem, 'toString', {
  value: () => 'function nthItem() { [custom code] }'
});

// export default Array;
</script>

Playground

The Seven of Nine approach

... nth(7).of(9) ... which is based on the index 1 ...

const cells = [
  'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9',
  'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9',
  'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9',
];

console.log(
  "cells.nth(7).of(9).map(({ item }) => item) ...",
  cells.nth(7).of(9).map(({ item }) => item)
);

console.log(
  "cells.nth(7).of(9) ...",
  cells.nth(7).of(9)
);

console.log(
  "cells.nth(3).of(6) ...",
  cells.nth(3).of(6)
);
console.log(
  "cells.nth(2).of(3) ...",
  cells.nth(2).of(3)
);

console.log(
  "cells.nth ...",
  cells.nth
);
console.log(
  "cells.nth(2) ...",
  cells.nth(2)
);
console.log(
  "cells.nth(2).of ...",
  cells.nth(2).of
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<!--

  https://gist.github.com/petsel/42d89ad83cc5e90dc0f61a4d030fc17d

//-->
<script> 
const
  arrPrototype = Object.getPrototypeOf([]);

const {
  from: arrayFrom,
  isArray,

} = Array;

function createListOfChunkLists(arr, chunkCount) {
  return arr.reduce((list, item, idx) => {
    if (idx % chunkCount === 0) {

      list.push([ item ]);
    } else {
      // list.at(-1).push(item);
      list[list.length - 1].push(item);
    }
    return list;
  }, []);
}

function getNthItemOfChunkCount(arr, nthIndex, chunkCount) {
  if (!isArray(arr)) {
    throw (new TypeError(
      'The "nth().of()" chain needs to operate a list like structure.'
    ));
  }
  if (Number.isNaN(chunkCount)) {
    throw (new TypeError(
      'The chunk count needs to be an integer value.'
    ));
  }
  if (chunkCount > arr.length) {
    throw (new RangeError(
      "The chunk count value has to be lower than or equal to the operated list structure's length."
    ));
  }
  if (
       Number.isNaN(nthIndex)
    || (nthIndex < 0)
  //|| ((nthIndex < 0) && ((nthIndex + chunkCount) <= 0))
    || ((nthIndex >= 0) && ((nthIndex + 1) >= chunkCount))
  ) {
    throw (new RangeError(
      "The nth index needs to be an integer value which has to be reasonable within the chunk count's context."
    ));
  }
  return createListOfChunkLists(arr, chunkCount)
    .map((chunkList, idx) => ({

      item: chunkList[nthIndex],
      idx: ((idx * chunkCount) + nthIndex),
    }));
}

class NthItemOfChunkCount {
  #arr;
  #idx;
  constructor(arr, idx) {
    this.#arr = arr;
    this.#idx = idx;
  }
  of(int) {
    return getNthItemOfChunkCount(this.#arr, this.#idx, parseInt(int, 10));
  }
}

function nthItemOfChunkCount(int) {
  const arr = (isArray(this) && this) || arrayFrom(this ?? []);
  const idx = parseInt(int, 10) - 1;

  return (new NthItemOfChunkCount(arr, idx));
}

Object.defineProperty(arrPrototype, 'nth', {
  configurable: true,
  writable: true,
  value: nthItemOfChunkCount
});
Object.defineProperty(arrPrototype.nth, 'toString', {
  value: () => 'function nth() { [custom code] }'
});

Object.defineProperty(NthItemOfChunkCount.prototype.of, 'toString', {
  value: () => 'function of() { [custom code] }'
});

// export default Array;
</script>
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37