1

It's pretty simple to move item in array via move, but unfortunately it's not suitable in my case as usual.

For example I need to move object with index 0 from group #31 to #33 and set the new index for object in the destination array to 1.

  • source_group_id = 31
  • source_object_index = 0
  • destination_group_id = 33
  • destination_object_index = 1

Data object model:

const stuff = {
  "31": [
    {------------------------------|
      "id": "11",                  |============|
      "title": "Just move me pls"  |           ||
    },-----------------------------|           ||
    {                                          ||
      "id": "12",                              ||
      "title": "Ramda 123"                     ||
    },                                         ||
  ],                                           ||
  "33": [                                      ||
    {                                          ||
      "id": "3",                               ||
      "title": "Ramda jedi"                    ||
    }                                          ||
     ◀==========================================|
  ],   
  "4321": [
    {
      "id": "1",
      "title": "Hello Ramda"
    }
  ]
}

Does anyone know how to solve this?

1
  • 1
    You can either use lenses that render JS objects persistent or you can use persistent data types like immutable.js. Commented Dec 1, 2019 at 9:30

4 Answers 4

2

You can use lenses to change the sub-objects, but you'll first need to get the item.

Start by using R.view with R.lensPath to get the item. Then use R.over with R.lenseProp to remove the item from sub-object at the source key and index (sk, si), and then to insert it to the target key and index (tk, ti).

Update: to add a target, if the key (tk) doesn't exist, use R.unless to check with R.has for the existence of tk, and if it doesn't add an empty array with R.assoc.

const { curry, view, lensPath, pipe, over, lensProp, remove, unless, has, assoc, insert } = R;

const fn = curry(({ key: sk, idx: si }, { key: tk, idx: ti }, obj) => {
  const item = view(lensPath([sk, si]), obj); // get the item
  
  return pipe(
    over(lensProp(sk), remove(si, 1)), // remove the item from the source
    unless(has(tk), assoc(tk, [])), // add target if it's missing
    over(lensProp(tk), insert(ti, item)), // move to the target
  )(obj);
});

const stuff = { 31: [{ id: "11", title: "just move me pls" }, { id: "12", title: "ramda 123" }], 33: [{ id: "3", title: "..." }], 4321: [{ id: "1", title: "hello Ramda" }] };

console.log(fn({ key: '31', idx: 0 }, { key: 33, idx: 1 }, stuff));
console.log(fn({ key: '31', idx: 0 }, { key: 555, idx: 1 }, stuff));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Sign up to request clarification or add additional context in comments.

1 Comment

Hi! Your code works great as always, but I'd like to supplement my question. Just imagine a condition, if tk doesn't exist in stuff can we create group with key tk? (I could open a new question)
2

In plain Javascript, Array#splice works well.

const stuff = { 31: [{ id: "11", title: "just move me pls" }, { id: "12", title: "ramda 123" }], 33: [{ id: "3", title: "..." }], 4321: [{ id: "1", title: "hello Ramda" }] };

stuff['33'].splice(1, 0, ...stuff['31'].splice(0, 1));

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

1 Comment

Hi, Nina! Yeah, I know about native solution, but I want to solve this with more functional way:) (with Ramda)
1

I'm one of the founders of Ramda, and a big fan. But Ramda was mostly built in the days before we could count on ES6+ techniques, and, while I still use it a lot, certain things are just as easy now with vanilla JS. Here is my approach, which still uses the insert and remove Ramda helper functions:

const moveItem = (
  srcKey, srcIdx, destKey, destIdx, obj,
  {[String (srcKey)]: src, [String (destKey)]: dest, ...rest} = obj
) => ({
  [srcKey]: remove (srcIdx, 1, src),
  [destKey]: insert (destIdx, src [srcIdx], dest || []),
  ...rest
})

const stuff = {"31": [{"id": "11", "title": "Just move me pls"}, {"id": "12", "title": "Ramda 123"}], "33": [{"id": "3", "title": "Ramda jedi"}], "4321": [{"id": "1", "title": "Hello Ramda"}]}

console.log (
  moveItem (31, 0, 33, 1, stuff)
)

console.log ( // destination doesn't exist
  moveItem (31, 0, 35, 1, stuff)
)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {remove, insert} = R                                  </script>

Note that dest || [] is to handle the case when the destination object does not exist.

I would never introduce Ramda just to get these helper functions, as it's easy enough to write the equivalent without them with only a little extra code:

const moveItem = (
  srcKey, srcIdx, destKey, destIdx, obj,
  {[String (srcKey)]: src, [String (destKey)]: dest, ...rest} = obj
) => ({
  [srcKey]: [... src .slice (0, srcIdx), ... src .slice (srcIdx + 1)],
  [destKey]: [...(dest || []) .slice (0, destIdx), src [srcIdx], ...(dest || []) .slice (destIdx)],
  ...rest
})

But, as I usually have Ramda included in my projects, I still tend to think of its functions right away.

I don't think of this as necessarily better than Ori Drori's lens-based solution. Reaching for lenses when you have to focus on part of a structure is probably a good habit. But it's not necessarily worse either. Writing code as simply as possible has some powerful benefits, and I do find this a bit simpler.


One other thing. Regarding this comment to Nina's answer:

I know about native solution, but I want to solve this with more functional way:) (with Ramda)

I dislike Nina's solution because of the inherent mutation, and that's a perfectly reasonable reason to reject it. But "with Ramda" should only be the goal if this is an exercise in learning Ramda. If your main unit of work is the function and your functions are pure and you don't mutate user data, then you are doing functional programming, regardless of any libraries you might be using.

2 Comments

Yes, I agree with you, all my questions concern the study of Ramda, I think it will be useful for understanding the basics of functional programming. In this case, it seems to me that your solution is better than that of Ori Drori in terms of performance, is that so? So, the FP pattern is needed to reduce code complexity and immutability, right?
My version will likely use less memory, simply because it creates fewer intermediate objects; as to speed, it's not clear to me without doing some testing. But speed versus simplicity is one of the major trade-offs commonly made in functional programming. Abandoning GOTO in favor of more structured idioms sacrificed speed, but it made code much more manageable and understandable. Abandoning mutation and variable (re-)assignment is similar.
0

Unless you need to perform this programmatically, you can simply move the object around using Javascript Array methods.

stuff[33][1] = stuff[31][0]
// index 1 at key 33 gets assigned index 0 at key 31

stuff[31].shift()
// remove the first array element and shift all others to a lower index

1 Comment

Your solution is too easy, but what about immutability?:D

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.