简体   繁体   中英

How can I separate similar or alike items in an array of objects based on a specific key-value pair for each item?

I have been given an array of objects. Every object in the array has a key PlanType... I need to make sure that no objects with the same PlanType are next to each other...

How can I implement this even if at times it will be impossible to completely separate the like items.

 [{Item: 1,PlanType: A}, {Item: 2,PlanType: B}, {Item: 3,PlanType: C}, {Item: 4,PlanType: C}, {Item: 5,PlanType: A}, {Item: 6,PlanType: A}, {Item: 7,PlanType: B}, {Item: 8,PlanType: A}, {Item: 9,PlanType: C}, {Item: 10,PlanType: A}]

Expected Outcome...

 [{Item: 1,PlanType: A}, {Item: 2,PlanType: B}, {Item: 3,PlanType: C}, {Item: 5,PlanType: A}, {Item: 4,PlanType: C}, {Item: 6,PlanType: A}, {Item: 7,PlanType: B}, {Item: 8,PlanType: A}, {Item: 9,PlanType: C}, {Item: 10,PlanType: A}]

And here is code I have Tried...

//Sort the array into an object of arrays by PlanType
let plan_types = {};
original_array.forEach(item => {
  if(plan_types[item.plan_type] === undefined){
    Object.assign(plan_types, {
      [item.plan_types]: []
    })
  }
  plan_types[item.plan_types].push(item);
});
//Loop through the list of Plan types and try to evenly space out the items across a new array of the same size
let new_array = new Array(original_array.length).fill(null);
Object.keys(program_types).forEach((item,index) => {
  let new_array_index = 0;
  let item_index = 0;
  const frequency = Math.ceil(new_array.length / plan_types[item].length);
  while(new_array_index < new_array.length){
    if(new_array[new_array_index] !== null) new_array_index ++;
    else{
      new_array[new_array_index] = plan_types[item][item_index];
      new_array_index += frequency;
      item_index ++;
    }
  }
})

The issue there is you get all the way through it and it misses filling some items. Leaving Null spots and items left out.

You can do this it might not be the most efficient way but it may help you reason out your problem.

let A='A', B='B', C='C', D='D';
let f = [{ Item: 1, PlanType: A }, { Item: 2, PlanType: B }, { Item: 3, PlanType: C }, { Item: 4, PlanType: C }, { Item: 5, PlanType: A }, { Item: 6, PlanType: A }, { Item: 7, PlanType: B }, { Item: 8, PlanType: A }, { Item: 9, PlanType: C }, { Item: 10, PlanType: A } ];
let prevItem=f[0];
let currentItem;
for(let i=1; i<f.length; i++){
  currentItem = f[i];
  if(currentItem.PlanType === prevItem.PlanType){
    f.splice(i,1);
    f.push(currentItem);
    console.log("Item shifted");
  };
  prevItem = currentItem;
}
console.log(f);

Code example

I'm terrible at solving algorithms, but this is what I came up with. It also treat the case in which you have multiple objects with the same key, on after another.

Basically I've created another array from the one provided I just check to see if the item from the current iteration is the same as the from my new array. If it is, add it to a 'stack'. Whenever the first condition fails, it will proceed to insert the current item into the new array. After doing that I'm just checking if I can get an item from that stack and insert it into the new array, removing it from the duplicateItems array.

const items = [
  { Item: 1, PlanType: 'A' },
  { Item: 2, PlanType: 'B' },
  { Item: 3, PlanType: 'C' },
  { Item: 4, PlanType: 'C' },
  { Item: 5, PlanType: 'C' },
  { Item: 6, PlanType: 'A' },
  { Item: 7, PlanType: 'A' },
  { Item: 8, PlanType: 'B' },
  { Item: 9, PlanType: 'A' },
  { Item: 10, PlanType: 'C' },
  { Item: 11, PlanType: 'A' }
];

function normalizeList(items) {
  if (items.length < 1) return items;
  const result = [];
  const duplicateItems = [];
  items.forEach((item, index) => {
    if (result.length && item.PlanType === result[result.length - 1].PlanType) {
      if(index === items.length - 1) result.push(item); 
      else duplicateItems.push(item);
    }

    else {
      result.push(item);
      if(duplicateItems.length && duplicateItems[0].PlanType !== item.PlanType) {
        result.push(duplicateItems.shift());
      }
    }
  });
  return result;
}

Shifting neighboring items of equal value (or of equal child property value) reliably away from one another can, in my opinion, not be done by basic comparison. The approach that is going to be presented can not be called elegant for it is based on direct array mutation and on counting indices to a large extent. It was build to solve in a first iteration step tasks like ...

relocateEqualNeighboringItemValues(["A", "A", "B"]) => ["A", "B", "A"]
relocateEqualNeighboringItemValues(["A", "A", "B", "C", "C"]) => ["A", "C", "B", "A", "C"]

With a second iteration step one also can pass a getter that, for eg items of a more complex structure does target an item's specific property ...

relocateEqualNeighboringItemValues([

  { item: 1, planType: "A" },
  { item: 2, planType: "A" },
  { item: 3, planType: "B" }

], (item => item.planType)) => [

  { item: 1, planType: "A" },
  { item: 3, planType: "B" },
  { item: 2, planType: "A" }
]

The implementation looks like this ...

function relocateEqualNeighboringItemValues(arr, getItemValue) {
  if (Array.isArray(arr) && (arr.length >= 3)) {

    const getValue = ((

      (typeof getItemValue === 'function') &&
      (item => (item && getItemValue(item)))

    ) || (item => item)); // getter default.

    let isTerminateRelocation = false;
    let isRelocateItem;
    let offsetCount;
    let itemCount;
    let itemValue;
    let leftToRightIndex;
    let rightToLeftIndex = arr.length;

    while (!isTerminateRelocation && (--rightToLeftIndex >= 0)) {

      isRelocateItem = false;
      itemValue = getValue(arr[rightToLeftIndex]);

      // - Exercise repeatedly from right to left for each item a relocation lookup,
      //   and, if possible, try to relocated such an equal neighboring item (value).

      if (itemValue === getValue(arr[rightToLeftIndex - 1])) {

        offsetCount = 1;
        isRelocateItem = true;

        while (isRelocateItem && ((rightToLeftIndex - (++offsetCount)) >= 0)) {
          if (
            (itemValue !== getValue(arr[rightToLeftIndex - offsetCount])) &&
            (itemValue !== getValue(arr[rightToLeftIndex - offsetCount - 1]))
          ) {
            arr.splice((rightToLeftIndex - offsetCount), 0, arr.splice(rightToLeftIndex, 1)[0]);

            ++rightToLeftIndex; // reset ... start look-up from the former entering position.
            isRelocateItem = false;
          }
        }

        // - In case the right to left relocation for a specific item got stuck (reached array boundaries),
        //   change lookup direction exclusively for this item from left to right and try relocating it.
        // - Does the relocation attempt fail yet again, nothing can be done. The process will terminate.

        if (isRelocateItem && ((rightToLeftIndex - offsetCount) < 0)) {
          offsetCount = 1;

          itemCount = arr.length;
          leftToRightIndex = Math.max(0, (rightToLeftIndex - 1));

          itemValue = getValue(arr[leftToRightIndex]);
          isRelocateItem = (itemValue === getValue(arr[leftToRightIndex + 1]));

          while (isRelocateItem && ((leftToRightIndex + (++offsetCount)) < itemCount)) {
            if (
              (itemValue !== getValue(arr[leftToRightIndex + offsetCount])) &&
              (itemValue !== getValue(arr[leftToRightIndex + offsetCount + 1]))
            ) {
              arr.splice((leftToRightIndex + offsetCount), 0, arr.splice(leftToRightIndex, 1)[0]);

              isRelocateItem = false;
            }
          }
          if (isRelocateItem && ((leftToRightIndex + offsetCount) >= itemCount)) {

            isTerminateRelocation = true;
          }
        }
      }
    }
  }
  return arr;
}

Test cases

Basic

 function relocateEqualNeighboringItemValues(a,b){if(Array.isArray(a)&&3<=a.length){const c="function"==typeof b&&(a=>a&&b(a))||(a=>a);let d,e,f,g,h,i=!1,j=a.length;for(;!i&&0<=--j;)if(d=!1,g=c(a[j]),g===c(a[j-1])){for(e=1,d=!0;d&&0<=j-++e;)g!==c(a[je])&&g!==c(a[je-1])&&(a.splice(je,0,a.splice(j,1)[0]),++j,d=!1);if(d&&0>je){for(e=1,f=a.length,h=Math.max(0,j-1),g=c(a[h]),d=g===c(a[h+1]);d&&h+ ++e<f;)g!==c(a[h+e])&&g!==c(a[h+e+1])&&(a.splice(h+e,0,a.splice(h,1)[0]),d=!1);d&&h+e>=f&&(i=!0)}}}return a} console.log('["A", "A", "B"] => ', relocateEqualNeighboringItemValues(["A", "A", "B"])); console.log('["A", "B", "B"] => ', relocateEqualNeighboringItemValues(["A", "B", "B"])); console.log('["A", "A", "B", "C", "C"] => ', relocateEqualNeighboringItemValues(["A", "A", "B", "C", "C"])); console.log('["A", "A", "C", "B", "C", "C"] => ', relocateEqualNeighboringItemValues(["A", "A", "C", "B", "C", "C"])); console.log('["A", "A", "C", "C", "B", "C", "C"] => ', relocateEqualNeighboringItemValues(["A", "A", "C", "C", "B", "C", "C"]));
 .as-console-wrapper { max-height: 100%!important; top: 0; }

Complex, with getter function but resolvable

 function relocateEqualNeighboringItemValues(a,b){if(Array.isArray(a)&&3<=a.length){const c="function"==typeof b&&(a=>a&&b(a))||(a=>a);let d,e,f,g,h,i=!1,j=a.length;for(;!i&&0<=--j;)if(d=!1,g=c(a[j]),g===c(a[j-1])){for(e=1,d=!0;d&&0<=j-++e;)g!==c(a[je])&&g!==c(a[je-1])&&(a.splice(je,0,a.splice(j,1)[0]),++j,d=!1);if(d&&0>je){for(e=1,f=a.length,h=Math.max(0,j-1),g=c(a[h]),d=g===c(a[h+1]);d&&h+ ++e<f;)g!==c(a[h+e])&&g!==c(a[h+e+1])&&(a.splice(h+e,0,a.splice(h,1)[0]),d=!1);d&&h+e>=f&&(i=!0)}}}return a} const items = [ { Item: 0, PlanType: 'B' }, { Item: 1, PlanType: 'B' }, { Item: 2, PlanType: 'A' }, { Item: 3, PlanType: 'C' }, { Item: 4, PlanType: 'C' }, { Item: 5, PlanType: 'C' }, { Item: 6, PlanType: 'A' }, { Item: 7, PlanType: 'A' }, { Item: 8, PlanType: 'B' }, { Item: 9, PlanType: 'A' }, { Item: 10, PlanType: 'A' }, { Item: 11, PlanType: 'A' }, { Item: 12, PlanType: 'A' } // { Item: 13, PlanType: 'A' } ]; console.log(items, ' => ', relocateEqualNeighboringItemValues(Array.from(items).reverse(), (item => item.PlanType)).reverse()); console.log(items, ' => ', relocateEqualNeighboringItemValues(Array.from(items).reverse(), (item => item.PlanType))); console.log(items, ' => ', relocateEqualNeighboringItemValues(Array.from(items), (item => item.PlanType)).reverse()); console.log(items, ' => ', relocateEqualNeighboringItemValues(Array.from(items), (item => item.PlanType)));
 .as-console-wrapper { max-height: 100%!important; top: 0; }

Complex, with getter function but not anymore resolvable

 function relocateEqualNeighboringItemValues(a,b){if(Array.isArray(a)&&3<=a.length){const c="function"==typeof b&&(a=>a&&b(a))||(a=>a);let d,e,f,g,h,i=!1,j=a.length;for(;!i&&0<=--j;)if(d=!1,g=c(a[j]),g===c(a[j-1])){for(e=1,d=!0;d&&0<=j-++e;)g!==c(a[je])&&g!==c(a[je-1])&&(a.splice(je,0,a.splice(j,1)[0]),++j,d=!1);if(d&&0>je){for(e=1,f=a.length,h=Math.max(0,j-1),g=c(a[h]),d=g===c(a[h+1]);d&&h+ ++e<f;)g!==c(a[h+e])&&g!==c(a[h+e+1])&&(a.splice(h+e,0,a.splice(h,1)[0]),d=!1);d&&h+e>=f&&(i=!0)}}}return a} const items = [ { Item: 0, PlanType: 'B' }, { Item: 1, PlanType: 'B' }, { Item: 2, PlanType: 'A' }, { Item: 3, PlanType: 'C' }, { Item: 4, PlanType: 'C' }, { Item: 5, PlanType: 'C' }, { Item: 6, PlanType: 'A' }, { Item: 7, PlanType: 'A' }, { Item: 8, PlanType: 'B' }, { Item: 9, PlanType: 'A' }, { Item: 10, PlanType: 'A' }, { Item: 11, PlanType: 'A' }, { Item: 12, PlanType: 'A' }, { Item: 13, PlanType: 'A' } ]; console.log(items, ' => ', relocateEqualNeighboringItemValues(Array.from(items), (item => item.PlanType)));
 .as-console-wrapper { max-height: 100%!important; top: 0; }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM