-2

I have a sparse array in Javascript

const sparseArray = [
{"id":3, "value":"banana"},
{"id":7, "value":"coconut"}
];
const denseArraySize=10;

I would like to get :
const denseArray = [
{"id":0, "value":undefined},
{"id":1, "value":undefined}
{"id":2, "value":undefined},
{"id":3, "value":"banana"},
{"id":4, "value":undefined}
{"id":5, "value":undefined},
{"id":6, "value":undefined}
{"id":7, "value":"coconut"},
{"id":8, "value":undefined},
{"id":9, "value":undefined}
];

How do I get denseArray from sparseArray in a functional way.

Notice that sparceArray is sorted by id.

In C++, I was able to do this in linear time, lazily, using ranges::set_union().

In SQL, I would use a LEFT JOIN and COALESCE.

In Javascript, how can it be done ?

Ludovic Aubert
  • 9,534
  • 4
  • 16
  • 28
  • how do you define the `lenght`? it always goes `from 0 to 9`? – Chris G Oct 01 '22 at 14:21
  • denseArraySize is an input – Ludovic Aubert Oct 01 '22 at 14:27
  • 2
    Short version: You write a loop. Your best bet here is to do your research, [search](/help/searching) for related topics on SO and elsewhere, and give it a go. ***If*** you get stuck and can't get unstuck after doing more research and searching, post a [mre] showing your attempt and say specifically where you're stuck. People will be glad to help. – T.J. Crowder Oct 01 '22 at 14:27
  • a loop is what I want to avoid – Ludovic Aubert Oct 01 '22 at 14:27
  • It can be done in C++20 and SQL without a loop. There has to be a way in Javascript. So far I haven't found it – Ludovic Aubert Oct 01 '22 at 14:30
  • 1
    I thought sparse arrays had empty slots like so: `[1, 2, , 4, 5]`. – zer00ne Oct 01 '22 at 14:31
  • so no `.map()` or other js methods to solve the issue? – Chris G Oct 01 '22 at 14:32
  • You may have a point there. Mine is sparse with respect to id. – Ludovic Aubert Oct 01 '22 at 14:33
  • 1
    @LudovicAubert - It can't be done without a loop, anywhere. It can be done in C++20 and SQL without ***you*** writing a loop, but there's still a loop involved. As far as I know, nothing in the JavaScript standard API will do this particular loop for you in a useful way. [`Array.from`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) with its mapping callback might get you close, either creating a new entry or grabbing it from the "sparse" array, but that's still a loop and frankly unnecessarily complicated vs. just writing one in the first place. – T.J. Crowder Oct 01 '22 at 14:34
  • I wanted to avoid writting a loop myself. Writting loops is becoming very vintage style these days, so I was hoping there was a better way. If not, at least it is good to know. – Ludovic Aubert Oct 01 '22 at 14:38
  • @LudovicAubert - *"Writting loop is becoming very vintage style these days..."* Couldn't disagree more. :-) I have noticed that people are misusing things a lot (like `reduce` and `map` only used for its side-effects) to avoid writing loops, but in most cases the loop would have been clearer, easier to read, and easier to debug. – T.J. Crowder Oct 01 '22 at 14:40
  • @hpaulj: You are right. – Ludovic Aubert Oct 01 '22 at 14:50
  • But also the key must match the position in the dense array – Ludovic Aubert Oct 01 '22 at 14:51
  • Why the extra 'id' layer? Just use position `arr[3]='banana'; arr[7]='coconut'` – hpaulj Oct 02 '22 at 06:14
  • @hpauli - sparseArray is in the actual use case a large sparse array in a large table like flat json, There is an additional dimension which wasn't mentioned to avoid making things more complex. The goal is to save space on a large dataset. But having stripped things down, this large dataset appears very tiny :-) – Ludovic Aubert Oct 02 '22 at 10:56

1 Answers1

1

You've said you want to avoid a loop. I assume you mean a loop you write, since there will always be a loop involved, whether in your code or in a function you call.

The tool in the standard API that comes closest, I think, is Array.from with its mapping callback (probably starting out by mapping your current array into a Map by id for sublinear access times on id):

const sparseMap = new Map(sparseArray.map((element) => [element.id, element]));
const denseArray = Array.from({length: denseArraySize}, (_, id) => {
    const element = sparseMap.get(id);
    return element ?? {id, value: undefined};
});

Live Example:

const sparseArray = [
    {"id":3, "value":"banana"},
    {"id":7, "value":"coconut"}
];
const denseArraySize=10;

const sparseMap = new Map(sparseArray.map((element) => [element.id, element]));
const denseArray = Array.from({length: denseArraySize}, (_, id) => {
    const element = sparseMap.get(id);
    return element ?? {id, value: undefined};
});

console.log(denseArray);
.as-console-wrapper {
    max-height: 100% !important;
}

That's still a loop, though (two — we also have a loop building the Map). :-)

These two lines:

const element = sparseMap.get(id);
return element ?? {id, value: undefined};

can be written as one if you like:

return sparseMap.get(id) ?? {id, value: undefined};

I prefer to keep them separate so it's easy when I'm debugging to see where the element came from.


I probably wouldn't do it that way, I'd probably just write a simple loop:

const sparseMap = new Map(sparseArray.map((element) => [element.id, element]));
const denseArray = new Array(denseArraySize);
for (let id = 0; id < denseArray.length; ++id) {
    const element = sparseMap.get(id);
    denseArray[id] = element ?? {id, value: undefined};
}

Live Example:

const sparseArray = [
    {"id":3, "value":"banana"},
    {"id":7, "value":"coconut"}
];
const denseArraySize=10;

const sparseMap = new Map(sparseArray.map((element) => [element.id, element]));
const denseArray = new Array(denseArraySize);
for (let id = 0; id < denseArray.length; ++id) {
    const element = sparseMap.get(id);
    denseArray[id] = element ?? {id, value: undefined};
}

console.log(denseArray);
.as-console-wrapper {
    max-height: 100% !important;
}

And again, these two lines:

const element = sparseMap.get(id);
denseArray[id] = element ?? {id, value: undefined};

can be combined if you like:

denseArray[id] = sparseMap.get(id) ?? {id, value: undefined};
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • In C++20, the pipeline was a combination of views::iota(0,10) and ranges::set_union() to act pretty much as a SQL LEFT JOIN and COALESCE. I know there are loops inside all these things, but they are hidden away. – Ludovic Aubert Oct 01 '22 at 14:56
  • ranges::set_union( ranges::equal_range(translation_ranges, id, {}, &TranslationRangeItem::id), views::iota(0,n) | views::transform([&](int i){return TranslationRangeItem{.id=id,.ri=i, .tr={.x=0,.y=0}}; }), back_inserter(ts), {}, &TranslationRangeItem::ri, &TranslationRangeItem::ri); – Ludovic Aubert Oct 01 '22 at 14:57
  • runs in linear time, that's what's nice about it – Ludovic Aubert Oct 01 '22 at 15:00
  • 1
    Um...okay (although I doubt it's actually linear on the number of existing items, but it doens't matter). Meanwhile, this is how you do it in JavaScript. JavaScript has basically no set-based operations at present. – T.J. Crowder Oct 01 '22 at 15:07
  • every language has its up and downsides. The score is +1 for C++ and -1 for JS on this aspect. On some other apects it is the other way around. – Ludovic Aubert Oct 01 '22 at 15:13
  • @LudovicAubert - :-) (And if you haven't already, check out [TypeScript](https://typescriptlang.org).) – T.J. Crowder Oct 01 '22 at 15:15