填充数组

时间:2018-01-19 11:46:51

标签: javascript ramda.js

我们说我有一个数组[0, null, null, 3, null, null, null, 11]

我想用基于前一个和下一个已知数字(和索引?)的数字填充空值,所以我得到[0, 1, 2, 3, 5, 7, 9, 11]。最有效的方法是什么?

我正在考虑可以计算两个已知数字之间的空值然后只获得一步的大小的东西。但这两个步骤之间的步骤会有所不同

我在图表上工作,某些值可能会丢失,所以我必须填写可能的值。

这是我尝试过的,但我认为它非常低效和混乱。我更喜欢使用ramda.js或一些功能方法。



const data = [0, null, null, 3, null, null, null, 11]

const getStep = (arr, lastKnown = 0, counter = 1) => {
    const val = arr[0];

    if (val !== null) {
        return (val - lastKnown) / counter
    } else {
        return getStep(arr.slice(1), lastKnown, ++counter)
    }
}

let lastKnown = null
let currentStep = null

const filledData = data.map((x, i) => {
    if (x !== null) {
        lastKnown = x
        currentStep = null
        return x
    }
  
    if (currentStep !== null) {
        lastKnown = lastKnown + currentStep
    } else {
        currentStep = getStep(data.slice(i), lastKnown)
    }

    return currentStep + lastKnown
})

console.log(filledData)




//更新:我已选择THIS ANSWER为正确,但如果您对解决方案感兴趣,请务必在此处查看所有答案。有一些非常有趣的想法。

6 个答案:

答案 0 :(得分:2)

使用for循环和计数的一种方法:

var skips = 0;
var last;

for (var i=0; i<arr.length; i++){
  var current = arr[i]

  if (current !== null) {

    // If there are skipped spots that need to be filled...
    if (skips > 0){

      // Calculate interval based on on skip count, and difference between current and last
      var interval = (current-arr[last])/(skips+1);

      // Fill in the missing spots in original array
      for (var j=1; j<=skips; j++){
        arr[last+j] = arr[last]+(interval*j)
      }

    }

    last = i; // update last valid index
    skips = 0; // reset skip count
  }
  // If null, just increment skip count
  else { 
    skips++
  }
}

答案 1 :(得分:2)

您可以迭代数组,如果找到null值,则会先查看下一个数字和间隙,直到采用线性方法填充数字。

var array = [0, null, null, 3, null, null, null, 11],
    i = 0, j, delta;

while (i < array.length) {
    if (array[i] !== null) {
        i++;
        continue;
    }
    j = i;
    while (array[++j] === null);
    delta = (array[j] - array[i - 1]) / (j - i + 1);
    do {
        array[i] = delta + array[i - 1];
        i++;
    } while (i < j)
}

console.log(array);

答案 2 :(得分:2)

另一种方法是将输入数组转换为&#34;段&#34;捕获每个段的起始值,结束值和大小。然后,您可以使用R.chain构建列表,并在每个段的起始值和结束值之间使用线性步长。

&#13;
&#13;
const input = [0, null, null, 3, null, null, null, 11]

// recursively convert the sparse list of numbers into a list of segments
const segmentNull = xs => {
  if (xs.length === 0) {
    return []
  } else {
    const [y, ...ys] = xs
    const count = R.takeWhile(R.isNil, ys).length + 1
    const next = R.dropWhile(R.isNil, ys)
    
    return next.length > 0
      ? R.prepend({ start: y, end: next[0], count }, segmentNull(next))
      : []
  }
}

// segmentNull(input)
//=> [{"count": 3, "end": 3, "start": 0}, {"count": 4, "end": 11, "start": 3}]

// produce a list of `count` values linearly between `start` and `end` values
const linearRange = (start, end, count) =>
  R.times(n => (end - start) * (n + 1) / count + start, count)

// linearRange(3, 11, 4)
//=> [5, 7, 9, 11]

// convert the list of segments into a list of linear values between segments
const buildListFromSegments = R.chain(({ start, end, count }) =>
  linearRange(start, end, count))

// buildListFromSegments(segmentNull(input))
//=> [1, 2, 3, 5, 7, 9, 11]
//    ^-- note the leading 0 is missing

// prepend the initial value to the result of `buildListFromSegments`
const fn = xs => R.prepend(xs[0], buildListFromSegments(segmentNull(xs)))

console.log(fn(input))
&#13;
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
&#13;
&#13;
&#13;

答案 3 :(得分:1)

以下是我使用ramda的快速解决方案:

const xs = [0, null, null, 3, null, null, null, 11]
const scanWithIndex = R.addIndex(R.scan)
const notNil = R.complement(R.isNil)
const mapWithIndex = R.addIndex(R.map)
const zipArrays = R.zipWith(R.concat)

// number of cons nulls for nth element
const consNulls = R.drop(1, R.scan((acc, x) => R.isNil(x) ? (acc + 1) : 0, 0, xs))

// length of ongoing null sequence for each element
const consNullsSeqLens = R.drop(1, scanWithIndex((acc, x, ind) =>{
  if (x !== 0 && acc !== 0) return acc
  const rest = R.drop(ind, consNulls)
  return R.findIndex(R.equals(0), rest)
}, 0, consNulls))

// previous non null value for each el
const prevNonNulls = R.scan((acc, x) => R.isNil(x) ? acc : x, 0, xs)

// next non null value for each el
const nextNonNulls = mapWithIndex((x, ind) => {
  const rest = R.drop(ind, xs)
  return R.find(notNil, rest)
}, xs)

// function to calculate missing values based on zipped arrays
const calculateMissingValue = ([x, seqN, seqLen, next, prev]) =>
  R.isNil(x) ? prev + (next - prev) / (seqLen + 1) * seqN : x 

R.map(
  calculateMissingValue,
  // zips 5 lists together
  zipArrays(
    zipWith(R.append, consNullsSeqLens, R.zip(xs, consNulls)),
    R.zip(nextNonNulls,prevNonNulls)
 )
)

Repl link

答案 4 :(得分:1)

虽然来自the answer@bubulik42表明你可以使用Ramda来做这件事,但我不确定Ramda会给你带来多少帮助。 (免责声明:我是Ramda的作者之一。)

我的第一次传球看起来像这样:

const intersperseNulls = pipe(
  reduce(
    ({vals, prev, nilCount}, curr) => isNil(curr) 
       ? {vals: vals, prev: prev, nilCount: nilCount + 1} 
       : (nilCount < 1) 
         ? {vals: append(curr, vals), prev: curr, nilCount: 0}
         : {
             vals: append(curr, concat(vals, times(n => prev + (n + 1) * (curr - prev) / (nilCount + 1), nilCount))), 
              prev: curr, 
              nilCount: 0
           },
    {vals: [], prev: undefined, nilCount: 0},
  ),
  prop('vals')
);

这使用通常具有功能的reduce调用,但它有点奇怪的用法,选择通过所有迭代而不是简单的累加器传递状态。请注意,如果我删除Ramda基础架构,它看起来有多相似:

const steps = (b, e, c) => {
  const results = []
  for (let i = 0; i < c; i++) {results.push(b + (i + 1) * (e - b) / (c + 1));}
  return results;
}

const intersperseNulls = array => array.reduce(
  ({vals, prev, nilCount}, curr) => (curr == null) 
    ? {vals: vals, prev: prev, nilCount: nilCount + 1} 
    : (nilCount < 1) 
      ? {vals: vals.concat(curr), prev: curr, nilCount: 0}
      : {
          vals: vals.concat(steps(prev, curr, nilCount)).concat(curr), 
          prev: curr, 
          nilCount: 0
        },
  {vals: [], prev: undefined, nilCount: 0},
).vals

只有times很难替换。

但最后,我更喜欢non-Ramda solution中的@Nina Scholz。它更简单,更易于阅读,并且不会尝试任何技巧。

您可以在 Ramda REPL

中查看这些内容

答案 5 :(得分:1)

O(n * m)解,其中n是所有元素的计数,m是空值的计数。

算法假设在索引位置0和长度-1处始终存在有效的数字。

&#13;
&#13;
function fillInTheBlanks(a){
  var s, //step
      r = a.reduce(function([t,ns,r], e){  // [temp, nulls array, result accumulator]
                     e === null ? ns.push(e)
                                : t === void 0 ?  t = e
                                               : (s = (e-t)/(ns.length+1),
                                                  r.push(t,...ns.map((_,i) => t+(i+1)*s)),
                                                  ns = [],
                                                  t = e);
                     return [t,ns,r];
                   }, [void 0,[],[]]);
  return r[2].concat(r[0]);
}
var arr = [0, null, null, 3, null, null, null, 11],
    res = fillInTheBlanks(arr);


console.log(JSON.stringify(res));
&#13;
&#13;
&#13;