Markov
Markov

Reputation: 150

How to sort array of object by properties from left to right having numbers first when property is string?

I am trying to sort this array first by chromosome then by startPosition then by endPosition The issue here is that the properties are string type, and chromosome contains numbers between [1-22] or [x,y,m]

I have created this function base on some StackOverflow answers what it does is compares the value and sorts them that way, but the issue now is that it a string sort so it put the 10 before the 2

    const sortByThenBy = (value, sortSettings) => {
    const sortIt = () => {
        const sortOrderBy = (left, right, sortOrder) => {
            const sortMultiplier = sortOrder === 'asc' ? 1 : -1;
            if (left > right) {
                return +1 * sortMultiplier;
            }
            if (left < right) {
                return -1 * sortMultiplier;
            }
            return 0;
        };

        return (sortLeft, sortRight) => {
            const getValueByStr = (obj, path) => {
                let i;
                path = path.replace('[', '.');
                path = path.replace(']', '');
                path = path.split('.');
                for (i = 0; i < path.length; i++) {
                    if (!obj || typeof obj !== 'object') {
                        return obj;
                    }
                    obj = obj[path[i]];
                }
                return obj;
            };

            return sortSettings
                .map(property =>
                    sortOrderBy(
                        getValueByStr(sortLeft, property.prop),
                        getValueByStr(sortRight, property.prop),
                        property.sortOrder
                    )
                )
                .reduceRight((left, right) => right || left);
        };
    };

    return value.sort(sortIt());
};
console.log(
    sortByThenBy(item, [
        {
            prop: 'chromosome',
            numericSort: true,
            sortOrder: 'asc'
        },
        {
            prop: 'startPosition',
            sortOrder: 'asc'
        },
        {
            prop: 'endPosition',
            sortOrder: 'asc'
        }
    ])
);

https://jsbin.com/kumavupuqi/edit?js,console,output

Given

    const item = [
    { chromosome: '2', startPosition: '980000', endPosition: '989000' },
    { chromosome: '2', startPosition: '978000', endPosition: '979000' },
    { chromosome: '1', startPosition: '978000', endPosition: '979000' },
    { chromosome: '10', startPosition: '978000', endPosition: '979000' },
    { chromosome: 'x', startPosition: '978000', endPosition: '979000' }
];

Expected result

const item = [
{ chromosome: '1', startPosition: '978000', endPosition: '979000' },
{ chromosome: '2', startPosition: '978000', endPosition: '979000' },
{ chromosome: '2', startPosition: '980000', endPosition: '989000' },
{ chromosome: '10', startPosition: '978000', endPosition: '979000' },
{ chromosome: 'x', startPosition: '978000', endPosition: '979000' }
];

Upvotes: 3

Views: 75

Answers (2)

Ben Aston
Ben Aston

Reputation: 55739

This might help.

The number of digits needed for chromosome coordinates varies. I used information from here: https://www.biostars.org/p/4355/ to guess at a sensible value for POSITION_DIGITS.

This code uses string comparisons. JavaScript cannot represent arbitrarily large integers exactly.

const item = [{
    chromosome: '2',
    startPosition: '980000',
    endPosition: '989000'
}, {
    chromosome: '2',
    startPosition: '40',
    endPosition: '60'
}, {
    chromosome: '1',
    startPosition: '978000',
    endPosition: '979000'
}, {
    chromosome: '10',
    startPosition: '978000',
    endPosition: '979000'
}, {
    chromosome: 'x',
    startPosition: '978000',
    endPosition: '979000'
}];

const LETTERS = { x: '23', y: '24', m: '25' }
const CHROMOSOME_DIGITS = 2
const POSITION_DIGITS = 10

const toStr = ({chromosome, startPosition, endPosition}) => 
    ((LETTERS[chromosome] || chromosome.padStart(CHROMOSOME_DIGITS,'0')) + 
    startPosition.padStart(POSITION_DIGITS,'0') + 
    endPosition.padStart(POSITION_DIGITS,'0'))

console.log(item.sort((a,b) => toStr(a).localeCompare(toStr(b))))

Upvotes: 2

CertainPerformance
CertainPerformance

Reputation: 370809

In the .sort callback, sort by:

(1) The difference between the two item's chromosomes being 'y'

(2) The difference between the two item's chromosomes being 'x'

(3) The numeric difference between the two item's chromosomes

(4) The numeric difference between the two item's startPosition

(5) The numeric difference between the two item's endPosition

Where the first difference which is not zero is returned:

const arr = [
  { chromosome: '2', startPosition: '980000', endPosition: '989000' },
  { chromosome: '2', startPosition: '978000', endPosition: '979000' },
  { chromosome: '1', startPosition: '978000', endPosition: '979000' },
  { chromosome: '10', startPosition: '978000', endPosition: '979000' },
  { chromosome: 'x', startPosition: '978000', endPosition: '979000' },
  { chromosome: 'x', startPosition: '5', endPosition: '979000' },
  { chromosome: '1', startPosition: '978000', endPosition: '9999999' },
];

arr.sort((a, b) => (
  ((a.chromosome === 'y') - (b.chromosome === 'y')) ||
  ((a.chromosome === 'x') - (b.chromosome === 'x')) ||
  (a.chromosome - b.chromosome) ||
  (a.startPosition - b.startPosition) ||
  (a.endPosition - b.endPosition)
));
console.log(arr);

This is made very concise by the fact that - implicitly coerces both sides to numbers.

Another option is to make a function which, given a single object, can come up with a relative value for that object (eg, value each chromosome difference from 0 at 1e20, each startPosition difference from 0 at 1e10, and each endPosition difference from 0 at 1). Then pass both objects through that function and check the difference between their relative values:

const arr = [
  { chromosome: '2', startPosition: '980000', endPosition: '989000' },
  { chromosome: '2', startPosition: '978000', endPosition: '979000' },
  { chromosome: '1', startPosition: '978000', endPosition: '979000' },
  { chromosome: '10', startPosition: '978000', endPosition: '979000' },
  { chromosome: 'x', startPosition: '978000', endPosition: '979000' },
  { chromosome: 'x', startPosition: '5', endPosition: '979000' },
  { chromosome: '1', startPosition: '978000', endPosition: '9999999' },
];

const getVal = (obj) => {
  const chr = obj.chromosome;
  const chromVal = chr === 'y'
    ? 25
    : chr === 'x'
      ? 24
      : Number(chr);
  return (
    chromVal * 1e20 +
    obj.startPosition * 1e10 +
    Number(obj.endPosition)
  );
  return totalVal;
};

arr.sort((a, b) => getVal(a) - getVal(b));
console.log(arr);

Upvotes: 3

Related Questions