-2

I have this array:

arr = [
  { name: 'This one', number: '67', codes:['B33', '45']},
  { name: 'Another', number: '003', codes: ['55', 'A47']},
  { name: 'Something', codes:['A33']},
  { name: 'One more', number: '003'},
  { name: 'Anything', number: '67', codes:['A33']},
];

I want to order it first by "number" and then by "codes" in in ascending order, but both are not required and "codes" is an array of string.

The result should be:

arr = [
  { name: 'Something', codes:['A33']},
  { name: 'One more', number: '003'},
  { name: 'Another', number: '003', codes: ['55', 'A47']},
  { name: 'Anything', number: '67', codes:['A33']},
  { name: 'This one', number: '67', codes:['B33', '45']}
];

Where the object that doesn't have number must be treated as number: '' and the object that doesn't have codes must be treated as codes : ''. codes is as an array of string. It should be compared like a unique string. For example: codes: ['55', 'A47'] must be compared like codes: '55, A47'

I tried to follow this code bellow, but it's not working for my case:

 function orderIt() {
     return arr.sort((a, b) => 
         a.number - b.number || a.codes - b.codes
     );
 }

(source)

Explaining why my question is different: In the other questions, both are required and there aren't an array of string inside of it.

Cline
  • 49
  • 1
  • 9
  • what do you mean with sorting by `codes`? should it be the first element by string? – Nina Scholz Feb 02 '22 at 20:50
  • @Nina Scholz. Yes, `codes` is string – Cline Feb 02 '22 at 20:51
  • "it's not working" ... why? In what cases does it return unexpected results? – Jonas Wilms Feb 02 '22 at 20:56
  • Also what do you expect `a.codes - b.codes` to evaluate to? Subtracting an array of strings will likely not produce the result you expect ... – Jonas Wilms Feb 02 '22 at 20:57
  • @JonasWilms this code it's only ordering by `numbers`...I don't know why... – Cline Feb 02 '22 at 20:59
  • 1
    @Caroline it is only ordering by numbers, because subtracting arbitrary strings will return `NaN`. Only when both strings are representing a number, you will get something meaningful by subtracting (in your case only when subtracting the `number`property). – Ma3x Feb 02 '22 at 21:01
  • @JonasWilms `a.codes - b.codes` must evaluate, for example, `a.codes = ['A33']` and `b.codes =['B33', '45']` . 'A33' must be the first and 'B33, 45' the second. – Cline Feb 02 '22 at 21:02
  • Does this answer your question? [sort Javascript array by two numeric fields](https://stackoverflow.com/questions/6129952/sort-javascript-array-by-two-numeric-fields) – Ayrton Pinheiro Feb 02 '22 at 21:06
  • @AyrtonPinheiro Thanks! But in that case, the property is not an array of strings... – Cline Feb 02 '22 at 21:09
  • How, exactly, should a missing property compare to values that are present? What are the rules for comparing two `codes` arrays? – outis Feb 03 '22 at 09:08
  • @outis the missing property is compare like "null" or ''. About the `codes`, see the comment that I give to @JonasWilms here. – Cline Feb 03 '22 at 12:15
  • Information relevant to the question should be edited into the question, rather than left in [comments](//stackoverflow.com/help/privileges/comment). A [total order](https://en.wikipedia.org/wiki/Total_order) needs to be defined; with current information, only a partial order is specified. – outis Feb 03 '22 at 20:54
  • @outis what the relevant information is not in the question? – Cline Feb 04 '22 at 04:30
  • As already mentioned, a clear definition of the total order. The order is only partially defined, and only in the comments. *Any* clarification that someone asks for in the comments should *not* be explained in the comments, but edited into the question. As for the partial definition, you still haven't defined the order on `code`, only given one example that doesn't even explain any edge cases. Examples can be helpful for clarifications, but are not enough to define something. – outis Feb 04 '22 at 08:47
  • @outis I edited my question. See if it`s clearer now. – Cline Feb 04 '22 at 12:33
  • Better, though the order on `codes` still isn't total. Consider: what if there were a `{name:'foo', number:'00', codes:['366']}` in the sample array; what position should it be sorted to? How should `['55', 'A47']` and `['55A', '47']` compare? `['55', 'A47']` and `['6', 'A47']`? Are the scalars guaranteed to be unaccented alphanumeric strings? One option to consider for `codes` is [componentwise](//en.wikipedia.org/wiki/Pointwise#Componentwise_operations) comparison, where `a.codes[i]` is compared to `b.codes[i]` in [natural order](//en.wikipedia.org/wiki/Natural_sort_order). – outis Feb 04 '22 at 20:43
  • A more expansive way of expressing how to compare missing properties is to have them be minimums, rather than simply comparing them as `''`. This will make a difference, for example, if `number`s might be negative. For `number`, the minimum could be `Number.NEGATIVE_INFINITY`; for `codes`, it's likely`[]`, an empty array. The empty string, `''`, is the minimum for strings. – outis Feb 04 '22 at 20:49
  • I should mention some alternative orderings. For strings, there's [lexicographic order](https://en.wikipedia.org/wiki/Alphabetical_order), when the strings are compared character-by-character (from a technical perspective, componentwise comparisons on arrays and other sequences are also lexicographic orders). – outis Feb 06 '22 at 01:00
  • … For ([homogeneous](https://techvidvan.com/tutorials/r-data-structures/#:~:text=Homogeneous%20data%20structures%20are%20ones,data%20at%20the%20same%20time.)) arrays, there's [fold](https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29)ing/[reduc](https://martinfowler.com/articles/collection-pipeline/reduce.html)ing the elements to a single value of the elements' type, then using the order on that type. … – outis Feb 06 '22 at 01:01
  • … Specifically, for the data in the question, that's using [`Array.join`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join) to convert each array to a string, then using a string comparison (as is currently done in fubar's answer, though that also folds in `number` at the start of `codes`, which is why it has different results than the desired results shown in the question). – outis Feb 06 '22 at 01:08

2 Answers2

3

Given that the number and codes properties are optional, first it’s necessary to check for their existence or revert to a default value.

Next, the values are converted into a single string so that two objects can be compared with one another.

The comparison is done using the localeCompare() function, which will evaluate the two values and return a value of 1, 0, or -1 based on the equality of the first string compared to the second.

const a = [
  { name: 'Something', codes:['A33']},
  { name: 'One more', number: '003'},
  { name: 'Another', number: '003', codes: ['55', 'A47']},
  { name: 'Anything',number: '67', codes:['A33']},
  { name: 'This one',number: '67', codes:['B33', '45']}
];


const b = a.sort((a, b) => {
  const an = a.number || '';
  const bn = b.number || '';
  const ac = (a.codes || []).join('');
  const bc = (b.codes || []).join('');
  

  return (an + ac).localeCompare(bn + bc);
});

console.log(b);
fubar
  • 16,918
  • 4
  • 37
  • 43
  • `"a" - "b"` is `NaN` ... Maybe you wanna use `.localeCompare` there? – Jonas Wilms Feb 02 '22 at 21:06
  • @JonasWilms Good call. For some reason I thought JavaScript would cast and compare the value of the strings. – fubar Feb 03 '22 at 00:01
  • Thanks! It's working perfectly! – Cline Feb 03 '22 at 02:24
  • Note this answer doesn't produce the sample desired result. It places the 'Something' object at the end of the sorted results, rather than the beginning. – outis Feb 04 '22 at 09:09
  • @outis It should works like in the example of result in my question. I don't know why, but it's working perfectly in the context of all my code with more rows...Sorry, I'm begginer – Cline Feb 04 '22 at 12:25
  • Update: to get the result as in my question, I changed `const an = a.number || '';` and `const bn = b.number || '';` to `const an = a.number || ' ';` and `const bn = b.number || ' ';` adding a space after the OR expression. – Cline Feb 07 '22 at 03:21
2

You could check if the property extist and take the delta to move this items to top.

const
    array = [{ name: 'This one', number: '67', codes: ['B33', '45'] }, { name: 'Another', number: '003', codes: ['55', 'A47'] }, { name: 'Something', codes: ['A33'] }, { name: 'One more', number: '003' }, { name: 'Anything', number: '67', codes: ['A33'] }];

array.sort((a, b) =>
    ('number' in a) - ('number' in b) ||
    ('codes' in a) - ('codes' in b) ||
    a.number - b.number ||
    (a?.codes?.[0] || '').toString().localeCompare(b?.codes?.[0] || '')
);

console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • I don't know why but in my code it's putting all objects that don't have `codes` first and the rest in the end. Like: `[ {number: 4}, {number:5}, {number: 4, codes: A}, {number: 5, codes: B}];` – Cline Feb 03 '22 at 02:23
  • 1
    please change the wanted result to your wanted. my code returns the result from the given dat. if you have more data to sort in some order please enhance the data set. actually, you have the following rules, if has no number, go first, if no codes go second the rest sort by number and then by codes. – Nina Scholz Feb 03 '22 at 07:53