最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to symmetrically split a string in JavaScript? - Stack Overflow

programmeradmin2浏览0评论

I am trying to symmetrically split a string in JavaScript. By that I mean, if it is 8 characters, then divide it into chunks of 2. If it is 9 characters, into chunks of 3. If it is n characters, divide it into chunks of 2 and 3 so that it forms a mirror image in the center.

const segmentNumber = (value) => {
  let array = value.split('')
  if (array.length % 3 === 0) {
    return chunkArray(array, 3).join('·')
  } else if (array.length % 2 === 0) {
    return chunkArray(array, 2).join('·')
  } else {
    let reversed = array.slice().reverse()
    let a = 0
    let ar = []
    let zr = []
    while (true) {
      ar.push(array.slice(a, a + 2).join(''))
      zr.push(reversed.slice(a, a + 2).join(''))
      array = array.slice(a + 2)
      reversed = reversed.slice(a + 2)
      a += 2
      let modulus
      if (array.length % 3 === 0) {
        modulus = 3
      } else if (array.length % 2 === 0) {
        modulus = 2
      }
      if (modulus) {
        return ar.concat(chunkArray(array, modulus)).concat(zr.reverse())
      }
    }
  }

  return
}

function chunkArray(arr, len) {

  var chunks = [],
      i = 0,
      n = arr.length;

  while (i < n) {
    chunks.push(arr.slice(i, i += len));
  }

  return chunks;
}

I can chunk the string but not sure how to make it symmetrically chunked. For example:

  • 10: 2-3-3-2
  • 11: 2-2-3-2-2
  • 12: 3-3-3-3
  • 13: 2-3-3-3-2

How do you generate this sort of pattern, where the smaller numbers are on the outside, and the bigger number 3 is toward the center, so it forms a mirror image?

How do I improve what I am sort of implementing?

I am trying to symmetrically split a string in JavaScript. By that I mean, if it is 8 characters, then divide it into chunks of 2. If it is 9 characters, into chunks of 3. If it is n characters, divide it into chunks of 2 and 3 so that it forms a mirror image in the center.

const segmentNumber = (value) => {
  let array = value.split('')
  if (array.length % 3 === 0) {
    return chunkArray(array, 3).join('·')
  } else if (array.length % 2 === 0) {
    return chunkArray(array, 2).join('·')
  } else {
    let reversed = array.slice().reverse()
    let a = 0
    let ar = []
    let zr = []
    while (true) {
      ar.push(array.slice(a, a + 2).join(''))
      zr.push(reversed.slice(a, a + 2).join(''))
      array = array.slice(a + 2)
      reversed = reversed.slice(a + 2)
      a += 2
      let modulus
      if (array.length % 3 === 0) {
        modulus = 3
      } else if (array.length % 2 === 0) {
        modulus = 2
      }
      if (modulus) {
        return ar.concat(chunkArray(array, modulus)).concat(zr.reverse())
      }
    }
  }

  return
}

function chunkArray(arr, len) {

  var chunks = [],
      i = 0,
      n = arr.length;

  while (i < n) {
    chunks.push(arr.slice(i, i += len));
  }

  return chunks;
}

I can chunk the string but not sure how to make it symmetrically chunked. For example:

  • 10: 2-3-3-2
  • 11: 2-2-3-2-2
  • 12: 3-3-3-3
  • 13: 2-3-3-3-2

How do you generate this sort of pattern, where the smaller numbers are on the outside, and the bigger number 3 is toward the center, so it forms a mirror image?

How do I improve what I am sort of implementing?

Share Improve this question edited May 4, 2022 at 2:28 Lance Pollard asked May 4, 2022 at 2:19 Lance PollardLance Pollard 79.7k98 gold badges333 silver badges611 bronze badges 10
  • Chunk into pairs (2) then rematch as many of them as possible so that you've got the desired output. – k.tten Commented May 4, 2022 at 2:27
  • 1 is it only ever going to be 3's and 2's? – Bravo Commented May 4, 2022 at 2:28
  • This is a quite vague question. It's not clear what the input can be. – Ram Commented May 4, 2022 at 2:32
  • are there no restrictions like the most number of 3 should be chosen? as a certain length might have different binations of symmetrical chunks. like 18 which can be 333333 and 22233222. And based on your samples above, it can be possible that it can produce only 3s – NightEye Commented May 4, 2022 at 2:32
  • 1 @Bravo, that may be the case i believe, optimizing the 3s inside then pad 2 outside if possible. – NightEye Commented May 4, 2022 at 2:34
 |  Show 5 more ments

5 Answers 5

Reset to default 9

a solver that solves by calling itself

Recursion and generators are the natural solution to this problem. We can use inductive reasoning to write segment(t) for any number, t -

  1. If the input t is less than zero, there are no valid segments. Stop iteration
  2. (inductive) t is non-negative. If t is zero, yield the empty segment, []
  3. (inductive) t is positive. Yield the singleton segment, [t], and for each i of the range 1..t, for each segment s of the sub-problem t - i * 2, symmetrically prepend and append i to the segment and yield

function* segment(t) {
  if (t < 0) return               // 1
  if (t == 0) return yield []     // 2
  yield [t]                       // 3
  for (let i = 1; i < t; i++)
    for (const s of segment(t - i * 2))
      yield [i, ...s, i]
}

console.log("== 7 ==")
for (const s of segment(7))
  console.log(s.join(" · "))
  
console.log("== 8 ==")
for (const s of segment(8))
  console.log(s.join(" · "))
.as-console-wrapper { min-height: 100%; top: 0; }

== 7 ==
7
1 · 5 · 1
1 · 1 · 3 · 1 · 1
1 · 1 · 1 · 1 · 1 · 1 · 1
1 · 2 · 1 · 2 · 1
2 · 3 · 2
2 · 1 · 1 · 1 · 2
3 · 1 · 3
== 8 ==
8
1 · 6 · 1
1 · 1 · 4 · 1 · 1
1 · 1 · 1 · 2 · 1 · 1 · 1
1 · 1 · 1 · 1 · 1 · 1 · 1 · 1
1 · 1 · 2 · 2 · 1 · 1
1 · 2 · 2 · 2 · 1
1 · 2 · 1 · 1 · 2 · 1
1 · 3 · 3 · 1
2 · 4 · 2
2 · 1 · 2 · 1 · 2
2 · 1 · 1 · 1 · 1 · 2
2 · 2 · 2 · 2
3 · 2 · 3
3 · 1 · 1 · 3
4 · 4

why t - i * 2?

The sub-problem is t - i * 2, or t minus two is. The subsequent yield expression is adding two i to the result, so they must be subtracted from the sub-problem in order to balance the equation -

for (const s of segment(t - i * 2)) // subtract two i from t
  yield [i, ...s, i]                // add two i to the segment

why recursive technique?

The advantages to this approach are numerous -

static, arbitrary split sizes
only 2's and 3's
modulus arithmetic
conditional variable assignments
array .slice or .reverse
hard-coded .join
string-to-number conversion
parseInt
division and rounding
every bination found
two local variables
two simple if conditions
pause, resume, or cancel

visualization

Given segment(4) -

[4]                                  // t = 4

[1,            , 1]
    \        /
      ...[2]                         // t = 2

[1,                           , 1]
    \                       /
      ...[1,           , 1]
             \       /
               ...[]                 // t = 0

[2,           , 2]
    \       /
      ...[]                          // t = 0

[3,           , 3]
    \       /
      ...❌                         // t = -2
[4]
[1,2,1]
[1,1,1,1]
[2,2]

change output ordering

This does not require a surgical rewiring of the algorithm or adjustment in the way you think about the problem. By changing the order of the yield expressions, you change the order of the output -

function* segment(t) {
  if (t < 0) return
  if (t == 0) return yield []
  for (let i = 1; i < t; i++)
    for (const s of segment(t - i * 2))
      yield [i, ...s, i]
  yield [t]   // ✅ yield singleton segment last
}

console.log("== 7 ==")
for (const s of segment(7))
  console.log(s.join(" · "))
.as-console-wrapper { min-height: 100%; top: 0; }

== 7 ==
1 · 1 · 1 · 1 · 1 · 1 · 1
1 · 1 · 3 · 1 · 1
1 · 2 · 1 · 2 · 1
1 · 5 · 1
2 · 1 · 1 · 1 · 2
2 · 3 · 2
3 · 1 · 3
7

minimum segment length

Perhaps you want the smallest segment to be 2 or 3. By adding a min parameter, this can be decided by the caller instead of hard coding it into the segment function -

function* segment(t, min = 0) { // ✅ add min parameter
  if (t < min) return              // ✅ t < min
  if (t == 0) return yield []
  for (let i = Math.max(1, min); i < t; i++) // ✅ max(1, min)
    for (const s of segment(t - i * 2, min)) // ✅ pass min
      yield [i, ...s, i]
  yield [t]
}

console.log("== 18 ==")
for (const s of segment(18, 3)) // ✅ segments of 3 or greater
  console.log(s.join(" · "))
.as-console-wrapper { min-height: 100%; top: 0; }

maximum segment length

In a similar fashion, a max parameter can be added to control the maximum segment length. Avoid hard-coding this in the function to retain increased flexibility for the caller -

function* segment(t, min = 0, max = t) { // ✅ add max parameter
  if (t < min) return
  if (t == 0) return yield []
  for (let i = Math.max(1, min); i < t; i++)
    for (const s of segment(t - i * 2, min, max)) // ✅ pass max
      yield [i, ...s, i]
  if (t <= max) yield [t] // ✅ if (t <= max)
}

console.log("== 18 ==")
for (const s of segment(18, 3, 8)) // ✅ segments between 3 and 8
  console.log(s.join(" · "))
.as-console-wrapper { min-height: 100%; top: 0; }

== 18 ==
3 · 3 · 6 · 3 · 3
3 · 4 · 4 · 4 · 3
4 · 3 · 4 · 3 · 4
5 · 8 · 5
6 · 6 · 6
7 · 4 · 7

increasing toward the center

If you would like the numbers to increase toward the center, that is a matter of adding yet another configurable parameter, init. As you can see, each nuanced criteria can be carefully added with only minor adjustments to the original algorithm -

function* segment(t, min = 0, max = t, init = 1) { // ✅ init = 1
  if (t < min || t < init) return // ✅ || t < init
  if (t == 0) return yield []
  for (let i = Math.max(init, min); i < t; i++) // ✅ init
    for (const s of segment(t - i * 2, min, max, i + 1)) // ✅ i + 1
      yield [i, ...s, i]
  if (t <= max) yield [t]
}

console.log("== 36 ==")
for (const s of segment(36, 2, 9))
  console.log(s.join(" · "))
.as-console-wrapper { min-height: 100%; top: 0; }

== 36 ==
2 · 3 · 4 · 5 · 8 · 5 · 4 · 3 · 2
2 · 5 · 7 · 8 · 7 · 5 · 2
3 · 4 · 7 · 8 · 7 · 4 · 3
3 · 5 · 6 · 8 · 6 · 5 · 3

split the string

To split a string we can write split which takes a string and a value returned by segment -

const split = (t, [s, ...segments]) =>
  s == null
    ? []
    : [t.substring(0, s), ...split(t.substring(s), segments)]

function* segment(t) {
  if (t < 0) return
  if (t == 0) return yield []
  for (let i = 1; i < t; i++)
    for (const s of segment(t - i * 2))
      yield [i, ...s, i]
  yield [t]
}
    
const word = "function"
for (const s of segment(word.length)) // ✅ segment word.length
  console.log(split(word, s).join(" · ")) // ✅ split word using s
.as-console-wrapper { min-height: 100%; top: 0; }

f · u · n · c · t · i · o · n
f · u · n · ct · i · o · n
f · u · nc · ti · o · n
f · u · ncti · o · n
f · un · c · t · io · n
f · un · ct · io · n
f · unc · tio · n
f · unctio · n
fu · n · c · t · i · on
fu · n · ct · i · on
fu · nc · ti · on
fu · ncti · on
fun · c · t · ion
fun · ct · ion
func · tion
function

optimization

You could speed up the algorithm by eliminating the check for some binations. The outer for loop goes from 1..t but could be shortened to 1..Math.floor(t/2). This improves the performance of segment but adds some plexity. For sake of clarity this was left out and remains an update for the reader.

without generators

Although generators are a great fit for binatorics problems, they are not required. We can lift the entire program into an Array context and have segment return an array of results instead of an iterator. Notice the ergonomics are not as good and the data nesting level has forcibly been increased by one. It does however follow the same inductive reasoning as the original algorithm -

function segment(t) {
  if (t < 0) return []            // if (t < 0) return 
  if (t == 0) return [[]]         // if (t == 0) return yield []
  return [                        //   
    [t],                          // yield [t]
    ...range(1, t).flatMap(i =>   // for (let i = 0; i<t; i++)
      segment(t - i * 2).map(s => //   for (const s of segment(t - i * 2))
        [[i, ...s, i]]            //     yield [i, ...s, i]
      )
    )
  ]
}

function range(a, b) {
  return Array.from(Array(b - a), (_, n) => n + a)
}

console.log("== 7 ==")
for (const s of segment(7))
  console.log(s.join(" · "))

== 7 ==
7
1,5,1
1,1,3,1,1
1,1,1,1,1,1,1
1,2,1,2,1
2,3,2

This will produce pattern for numbers greater than 6, and always have at least one '2' on the outside (except 9)

const splitter = n => {
  if (n < 7) {
    throw 'bad'
  }
  let twos = 0;
  let threes = Math.ceil(n / 3);
  // 9 is an edge case
  if (n !== 9) {
    let remain;
    do {
      --threes;
      remain = n - threes * 3;
    } while (remain % 4);
    if (threes < 0) {
      threes = n / 3;
      remain = 0;
    }
    twos = remain / 4;
  }
  return `${'2'.repeat(twos)}${'3'.repeat(threes)}${'2'.repeat(twos)}`.split('');
}
for (let i = 7; i < 50; ++i) console.log(i, splitter(i).join('-'))

hmmm, I see you don't have 2's in all your cases

Simple change ...

const splitter = n => {
  if (n < 7) {
    throw 'bad'
  }
  let twos = 0;
  let threes = Math.floor(n / 3) + 1;
  // 9 is an edge case
  let remain;
  do {
    --threes;
    remain = n - threes * 3;
  } while (remain % 4);
  if (threes < 0) {
    threes = n / 3;
    remain = 0;
  }
  twos = remain / 4;
  return `${'2'.repeat(twos)}${'3'.repeat(threes)}${'2'.repeat(twos)}`.split('');
}
for (let i = 7; i < 50; ++i) console.log(i, splitter(i).join('-'))

function segmentNumber(value)
{
  for (let i = 0; i <= value; i++)
  {
    d = (value - 3 * i)/4
    if (d >= 0 && d == parseInt(d))
    {
      return Array(d).fill(2).concat(Array(i).fill(3), Array(d).fill(2))
    }
  }
}

Does this meet your output? I am not sure with what your inputs are so I have only tested it on the sample values you provided instead.

function segmentNumber(length) {
  let outside = 0;
  if (length < 6)
    return 'Only 6 and greater';

  if(length % 3 == 0) 
    return new Array(length / 3).fill(3).join('-');
  else {
    while(length % 3 != 0 && length > 6) {
      outside++;
      length = length - 4;
    }
    if(length == 4) 
      return new Array(Math.floor(++outside * 2)).fill(2).join('-');
    else
      return [...new Array(outside).fill(2), 
              ...new Array(Math.floor(length / 3)).fill(3), 
              ...new Array(outside).fill(2)].join('-');
  }
}

console.log(segmentNumber(10))
console.log(segmentNumber(11))
console.log(segmentNumber(12))
console.log(segmentNumber(13))

Criteria

From what I gathered from the OP and ments, the objective is return a symmetrical series of 2s and 3s from a given integer. So here's a few criteria I tried to follow:

  • From the start and end of the result are the 2s then the 3s are positioned up to the center:

    10 => 2 3 3 2
    
  • Numbers that shouldn't be accepted are numbers lower than 4 and 5 as well. So a limit will be set for anything less than 6 (4 is symmetrical but I didn't want to write extra code to include it)

  • If the given number is odd, it will have a 3 in the center.

    11 => 2 2 3 2 2
    
  • A symmetrical pattern only needs one half of a pattern to be built so divide the number by 2 (if odd parity subtract 3 before dividing by 2).

    if (int < 6) return "Must be an integer greater than 5";
    let parity = int & 1 ? 'odd' : 'even';
    if (parity === 'odd') {
      int = int - 3;
    }
    side = int / 2;
    
  • Find how many iterations are needed to divide the number by 3 and then the remaining (if any) by 2.

  • The remaining number can only be a 4, 2, or 0 in order to maintain symmetry. If the remaining number is 1, then the total number of iterations to divide by 3 should be one less and the remaining number 1 will be a 4.

  • Two separate loops to run division by 3 and division by 2.

    let temp = Math.floor(side / 3);
    let rem = side % 3;
    if (rem === 1) {
      temp = temp - 1;
      rem = 4;
    }
    for (let i = 0; i < temp; i++) {
      result.push(3);
    }
    for (let i = 0; i < rem / 2; i++) {
      result.push(2);
    }
    
  • Next, make a copy of the result array with .slice(0) and .reverse() it. If parity was "odd" then unshift(3) to the front of result. Finally .concat() the reversed array in front of the result array, and then .join(' ') it into a string.

    const left = result.slice(0).reverse();
    if (parity === 'odd') {
      result.unshift(3);
    }
    return left.concat(result).join(' ');
    
    

// Utility function
const log = (data, str = true) => {
  if (!str) return console.log(data);
  return console.log(JSON.stringify(data));
}

const x23 = int => {
  let result = [];
  let side, odd;
  if (int < 6) return "Must be an integer greater than 5";
  let parity = int & 1 ? 'odd' : 'even';
  if (parity === 'odd') {
    int = int - 3;
  }
  side = int / 2;

  let temp = Math.floor(side / 3);
  let rem = side % 3;
  if (rem === 1) {
    temp = temp - 1;
    rem = 4;
  }
  for (let i = 0; i < temp; i++) {
    result.push(3);
  }
  for (let i = 0; i < rem / 2; i++) {
    result.push(2);
  }

  const left = result.slice(0).reverse();
  if (parity === 'odd') {
    left.push(3);
  }
  return left.concat(result).join(' ');
}

log('5: '+x23(5));
log('6: '+x23(6));
log('7: '+x23(7));
log('8: '+x23(8));
log('9: '+x23(9));
log('10: '+x23(10));
log('11: '+x23(11));
log('12: '+x23(12));
log('13: '+x23(13));
log('14: '+x23(14));
log('15: '+x23(15));
log('16: '+x23(16));
log('17: '+x23(17));
log('18: '+x23(18));
log('19: '+x23(19));
log('20: '+x23(20));
log('21: '+x23(21));
log('22: '+x23(22));
log('23: '+x23(23));
log('24: '+x23(24));
log('53: '+x23(53));
log('99: '+x23(99));
.as-console-wrapper {
  min-height: 100% !important;
}

发布评论

评论列表(0)

  1. 暂无评论