แยกสตริงขนาดใหญ่เป็นชิ้นขนาด n ใน JavaScript


208

ฉันต้องการแยกสตริงที่มีขนาดใหญ่มาก (สมมติว่า 10,000 ตัวอักษร) เป็นชิ้นขนาด N

อะไรจะเป็นวิธีที่ดีที่สุดในแง่ของประสิทธิภาพในการทำเช่นนี้?

ยกตัวอย่างเช่น: "1234567890"แยก 2 ["12", "34", "56", "78", "90"]ก็จะกลายเป็น

สิ่งนี้จะเป็นไปได้ที่จะใช้String.prototype.matchและถ้าเป็นเช่นนั้นจะเป็นวิธีที่ดีที่สุดที่จะทำในแง่ของประสิทธิภาพ?

คำตอบ:


456

คุณสามารถทำสิ่งนี้:

"1234567890".match(/.{1,2}/g);
// Results in:
["12", "34", "56", "78", "90"]

วิธีการจะยังคงทำงานกับสตริงที่มีขนาดไม่เท่ากันแน่นอนขนาด:

"123456789".match(/.{1,2}/g);
// Results in:
["12", "34", "56", "78", "9"]

โดยทั่วไปแล้วสำหรับสตริงใด ๆ ที่คุณต้องการแยกสตริงย่อยที่มีขนาดไม่เกินnคุณจะทำดังนี้:

str.match(/.{1,n}/g); // Replace n with the size of the substring

หากสตริงของคุณสามารถมีการขึ้นบรรทัดใหม่หรือการขึ้นบรรทัดใหม่คุณต้องทำ:

str.match(/(.|[\r\n]){1,n}/g); // Replace n with the size of the substring

เท่าที่ประสิทธิภาพฉันลองใช้ตัวอักษรประมาณ 10k และใช้เวลาไม่กี่วินาทีใน Chrome YMMV

สิ่งนี้ยังสามารถใช้ในฟังก์ชั่นที่ใช้ซ้ำได้:

function chunkString(str, length) {
  return str.match(new RegExp('.{1,' + length + '}', 'g'));
}

8
เนื่องจากคำตอบนี้มีอายุเกือบ 3 ปีฉันจึงอยากลองทดสอบประสิทธิภาพของ @Vivin อีกครั้ง ดังนั้น FYI การแยกตัวอักษร 100k สองต่อสองโดยใช้ regex ที่กำหนดนั้นเป็นสิ่งที่เกิดขึ้นทันทีบน Chrome v33
aymericbeaumet

1
@Fmstrat คุณหมายความว่าอย่างไร "ถ้าสตริงของคุณมีช่องว่างมันจะไม่นับตามความยาว" ใช่.ไม่ตรงกับบรรทัดใหม่เลย ฉันจะอัปเดตคำตอบเพื่อให้คำนึงถึง\nและ\rคำนึงถึง
Vivin Paliath

2
var chunks = str.split("").reverse().join().match(/.{1, 4}/).map(function(s) { return s.split("").reverse().join(); });สิ่งที่ชอบ มันทำในกลุ่มของ 4 ฉันไม่แน่ใจว่าคุณหมายถึงอะไรโดย "น้อยลงหรือมากกว่า" โปรดทราบว่าวิธีนี้ใช้ไม่ได้โดยเฉพาะกับสตริงที่มีการรวมอักขระและสามารถแบ่งสตริง Unicode ได้เช่นกัน
Vivin Paliath

2
ตามdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/...คุณสามารถจับคู่ตัวอักษรใด ๆ [^]รวมทั้งสายใหม่ด้วย ด้วยตัวอย่างของคุณจะส่งผลให้str.match(/[^]{1,n}/g)
Francesc Rosas

1
สำหรับใครที่กำลังมองหาสตริงอย่างรวดเร็วจริงๆ chunking กับมาตรฐานการทำงานใน jsperf ดูของฉันคำตอบ การใช้ regex เป็นวิธีการ chunking ที่ช้าที่สุดของทั้งหมด
Justin Warkentin

34

ฉันสร้างสายพันธุ์ได้เร็วขึ้นหลายที่ที่คุณสามารถเห็นใน jsPerf คนที่ฉันชอบคือ:

function chunkSubstr(str, size) {
  const numChunks = Math.ceil(str.length / size)
  const chunks = new Array(numChunks)

  for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
    chunks[i] = str.substr(o, size)
  }

  return chunks
}

2
ดังนั้นสิ่งนี้จึงทำงานได้ดีกับสตริงยาว ๆ (ประมาณ 800k - 9m chars) ยกเว้นเมื่อฉันตั้งขนาดเป็น 20 ด้วยเหตุผลบางอันก้อนสุดท้ายไม่ได้กลับมา ... พฤติกรรมที่แปลกมาก
เดวิด

1
@DavidAnderton จับได้ดี ฉันแก้ไขแล้วและน่าสนใจดูเหมือนว่าจะทำงานได้เร็วขึ้น มันกำลังปัดเศษเมื่อมันควรจะทำMath.ceil()เพื่อกำหนดจำนวนชิ้นที่ถูกต้อง
Justin Warkentin

1
ขอบคุณ! ฉันรวบรวมเขาเข้าด้วยกันเป็นโมดูล NPM พร้อมการสนับสนุน Unicode ซึ่งเป็นตัวเลือก - github.com/vladgolubev/fast-chunk-string
Vlad Holubiev

33

บรรทัดล่างสุด:

  • matchไม่มีประสิทธิภาพมากsliceจะดีกว่าบน Firefox substr/ substringยังดีกว่า
  • match ยิ่งไม่มีประสิทธิภาพสำหรับสตริงสั้น ๆ (แม้แต่กับ regex แคช - อาจเนื่องมาจากเวลาการตั้งค่าการแยกวิเคราะห์ regex)
  • match ยิ่งไม่มีประสิทธิภาพสำหรับขนาดก้อนใหญ่ (อาจเป็นเพราะการไม่สามารถ "กระโดด")
  • สำหรับสตริงที่ยาวขึ้นด้วยขนาดอันเล็กมากmatchประสิทธิภาพสูงกว่าsliceIE ที่เก่ากว่า
  • jsperfหิน

19

นี่คือทางออกที่รวดเร็วและตรงไปตรงมา -

function chunkString (str, len) {
  const size = Math.ceil(str.length/len)
  const r = Array(size)
  let offset = 0
  
  for (let i = 0; i < size; i++) {
    r[i] = str.substr(offset, len)
    offset += len
  }
  
  return r
}

console.log(chunkString("helloworld", 3))
// => [ "hel", "low", "orl", "d" ]

// 10,000 char string
const bigString = "helloworld".repeat(1000)
console.time("perf")
const result = chunkString(bigString, 3)
console.timeEnd("perf")
console.log(result)
// => perf: 0.385 ms
// => [ "hel", "low", "orl", "dhe", "llo", "wor", ... ]


1
คุณต้องใช้แทนsubstr() substring()
Leif

2
ฉันอยากรู้ว่าทำไมขีดเส้นใต้ในชื่อตัวแปร?
Felipe Valdes

@FelipeValdes ฉันคิดว่าจะไม่สับสนกับตัวแปร global / พารามิเตอร์หรือเพื่อแสดงว่าเป็นการกำหนดขอบเขตแบบส่วนตัว
นาย Polywhirl

15

เซอร์ไพร์ส! คุณสามารถใช้การแยกเพื่อแยก

var parts = "1234567890 ".split(/(.{2})/).filter(O=>O)

ผลลัพธ์ใน [ '12', '34', '56', '78', '90', ' ' ]


นี่คือคำตอบที่ยอดเยี่ยม
galkin

2
มีfilter (o=>o)ไว้เพื่ออะไร
Ben Carp

2
regex ปัจจุบันสร้างองค์ประกอบอาร์เรย์ว่างระหว่างชิ้น filter(x=>x)จะใช้ในการกรององค์ประกอบที่ว่างเปล่าเหล่านั้น
artemdev

สั้นและฉลาด แต่ทำซ้ำผ่านอินพุตหลายครั้ง คำตอบนี้ช้ากว่า 4x โซลูชั่นอื่น ๆ ในหัวข้อนี้
ขอบคุณ

6
@BenCarp เป็นผู้ให้บริการรถจักรยานยนต์ มันทำให้มันเร็วขึ้น ;)
Fozi

7
var str = "123456789";
var chunks = [];
var chunkSize = 2;

while (str) {
    if (str.length < chunkSize) {
        chunks.push(str);
        break;
    }
    else {
        chunks.push(str.substr(0, chunkSize));
        str = str.substr(chunkSize);
    }
}

alert(chunks); // chunks == 12,34,56,78,9

5

ฉันได้เขียนฟังก์ชั่นเสริมดังนั้นความยาวของจำนวนแถวอาจเป็นจำนวนเต็มเช่น [1,3]

String.prototype.chunkString = function(len) {
    var _ret;
    if (this.length < 1) {
        return [];
    }
    if (typeof len === 'number' && len > 0) {
        var _size = Math.ceil(this.length / len), _offset = 0;
        _ret = new Array(_size);
        for (var _i = 0; _i < _size; _i++) {
            _ret[_i] = this.substring(_offset, _offset = _offset + len);
        }
    }
    else if (typeof len === 'object' && len.length) {
        var n = 0, l = this.length, chunk, that = this;
        _ret = [];
        do {
            len.forEach(function(o) {
                chunk = that.substring(n, n + o);
                if (chunk !== '') {
                    _ret.push(chunk);
                    n += chunk.length;
                }
            });
            if (n === 0) {
                return undefined; // prevent an endless loop when len = [0]
            }
        } while (n < l);
    }
    return _ret;
};

รหัส

"1234567890123".chunkString([1,3])

จะกลับมา:

[ '1', '234', '5', '678', '9', '012', '3' ]

4

สตริงขนาดใหญ่ในสปลิตสตริงขนาดเล็กให้คำ

function chunkSubstr(str, words) {
  var parts = str.split(" ") , values = [] , i = 0 , tmpVar = "";
  $.each(parts, function(index, value) {
      if(tmpVar.length < words){
          tmpVar += " " + value;
      }else{
          values[i] = tmpVar.replace(/\s+/g, " ");
          i++;
          tmpVar = value;
      }
  });
  if(values.length < 1 &&  parts.length > 0){
      values[0] = tmpVar;
  }
  return values;
}



1
const getChunksFromString = (str, chunkSize) => {
    var regexChunk = new RegExp(`.{1,${chunkSize}}`, 'g')   // '.' represents any character
    return str.match(regexChunk)
}

เรียกมันว่าจำเป็น

console.log(getChunksFromString("Hello world", 3))   // ["Hel", "lo ", "wor", "ld"]

1

ต่อไปนี้เป็นวิธีแก้ปัญหาที่ฉันใช้กับเทมเพลตสตริงหลังจากการทดลองเล็กน้อย:

การใช้งาน:

chunkString(5)`testing123`

function chunkString(nSize) {
    return (strToChunk) => {
        let result = [];
        let chars = String(strToChunk).split('');

        for(let i = 0; i < (String(strToChunk).length / nSize); i++) {
            result = result.concat(chars.slice(i*nSize,(i+1)*nSize).join(''));
        }
        return result
    }
}

document.write(chunkString(5)`testing123`);
// returns: testi,ng123

document.write(chunkString(3)`testing123`);
// returns: tes,tin,g12,3


1

คุณสามารถใช้reduce()โดยไม่มี regex:

(str, n) => {
  return str.split('').reduce(
    (acc, rec, index) => {
      return ((index % n) || !(index)) ? acc.concat(rec) : acc.concat(',', rec)
    },
    ''
  ).split(',')
}

ฉันคิดว่ามันจะช่วยได้มากหากคุณจะให้ตัวอย่างเกี่ยวกับวิธีการใช้reduceวิธีการของคุณ
kiatng

0

ในรูปแบบของฟังก์ชั่นต้นแบบ:

String.prototype.lsplit = function(){
    return this.match(new RegExp('.{1,'+ ((arguments.length==1)?(isFinite(String(arguments[0]).trim())?arguments[0]:false):1) +'}', 'g'));
}

0

นี่คือรหัสที่ผมใช้จะใช้String.prototype.slice

ใช่มันค่อนข้างจะเป็นคำตอบเมื่อพยายามที่จะปฏิบัติตามมาตรฐานปัจจุบันใกล้เคียงที่สุดและแน่นอนว่ามีความคิดเห็นJSDOCในปริมาณที่สมเหตุสมผล อย่างไรก็ตามเมื่อย่อขนาดโค้ดจะมีเพียง 828 ไบต์และเมื่อ gzipped สำหรับการส่งข้อมูลจะมีเพียง 497 ไบต์

1 เมธอดที่สิ่งนี้เพิ่มให้กับString.prototype(โดยใช้Object.definePropertyหากมี):

  1. toChunks

มีการทดสอบจำนวนหนึ่งเพื่อตรวจสอบการทำงาน

กังวลว่าความยาวของรหัสจะมีผลต่อประสิทธิภาพหรือไม่ ไม่จำเป็นต้องกังวลhttp://jsperf.com/chunk-string/3

รหัสพิเศษส่วนมากจะต้องตรวจสอบให้แน่ใจว่ารหัสนั้นจะตอบสนองเหมือนกันในหลาย ๆ สภาพแวดล้อมของจาวาสคริปต์

/*jslint maxlen:80, browser:true, devel:true */

/*
 * Properties used by toChunks.
 */

/*property
    MAX_SAFE_INTEGER, abs, ceil, configurable, defineProperty, enumerable,
    floor, length, max, min, pow, prototype, slice, toChunks, value,
    writable
*/

/*
 * Properties used in the testing of toChunks implimentation.
 */

/*property
    appendChild, createTextNode, floor, fromCharCode, getElementById, length,
    log, pow, push, random, toChunks
*/

(function () {
    'use strict';

    var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1;

    /**
     * Defines a new property directly on an object, or modifies an existing
     * property on an object, and returns the object.
     *
     * @private
     * @function
     * @param {Object} object
     * @param {string} property
     * @param {Object} descriptor
     * @return {Object}
     * @see https://goo.gl/CZnEqg
     */
    function $defineProperty(object, property, descriptor) {
        if (Object.defineProperty) {
            Object.defineProperty(object, property, descriptor);
        } else {
            object[property] = descriptor.value;
        }

        return object;
    }

    /**
     * Returns true if the operands are strictly equal with no type conversion.
     *
     * @private
     * @function
     * @param {*} a
     * @param {*} b
     * @return {boolean}
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.4
     */
    function $strictEqual(a, b) {
        return a === b;
    }

    /**
     * Returns true if the operand inputArg is undefined.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @return {boolean}
     */
    function $isUndefined(inputArg) {
        return $strictEqual(typeof inputArg, 'undefined');
    }

    /**
     * The abstract operation throws an error if its argument is a value that
     * cannot be converted to an Object, otherwise returns the argument.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be tested.
     * @throws {TypeError} If inputArg is null or undefined.
     * @return {*} The inputArg if coercible.
     * @see https://goo.gl/5GcmVq
     */
    function $requireObjectCoercible(inputArg) {
        var errStr;

        if (inputArg === null || $isUndefined(inputArg)) {
            errStr = 'Cannot convert argument to object: ' + inputArg;
            throw new TypeError(errStr);
        }

        return inputArg;
    }

    /**
     * The abstract operation converts its argument to a value of type string
     *
     * @private
     * @function
     * @param {*} inputArg
     * @return {string}
     * @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tostring
     */
    function $toString(inputArg) {
        var type,
            val;

        if (inputArg === null) {
            val = 'null';
        } else {
            type = typeof inputArg;
            if (type === 'string') {
                val = inputArg;
            } else if (type === 'undefined') {
                val = type;
            } else {
                if (type === 'symbol') {
                    throw new TypeError('Cannot convert symbol to string');
                }

                val = String(inputArg);
            }
        }

        return val;
    }

    /**
     * Returns a string only if the arguments is coercible otherwise throws an
     * error.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @throws {TypeError} If inputArg is null or undefined.
     * @return {string}
     */
    function $onlyCoercibleToString(inputArg) {
        return $toString($requireObjectCoercible(inputArg));
    }

    /**
     * The function evaluates the passed value and converts it to an integer.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be converted to an integer.
     * @return {number} If the target value is NaN, null or undefined, 0 is
     *                   returned. If the target value is false, 0 is returned
     *                   and if true, 1 is returned.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
     */
    function $toInteger(inputArg) {
        var number = +inputArg,
            val = 0;

        if ($strictEqual(number, number)) {
            if (!number || number === Infinity || number === -Infinity) {
                val = number;
            } else {
                val = (number > 0 || -1) * Math.floor(Math.abs(number));
            }
        }

        return val;
    }

    /**
     * The abstract operation ToLength converts its argument to an integer
     * suitable for use as the length of an array-like object.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be converted to a length.
     * @return {number} If len <= +0 then +0 else if len is +INFINITY then
     *                   2^53-1 else min(len, 2^53-1).
     * @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
     */
    function $toLength(inputArg) {
        return Math.min(Math.max($toInteger(inputArg), 0), MAX_SAFE_INTEGER);
    }

    if (!String.prototype.toChunks) {
        /**
         * This method chunks a string into an array of strings of a specified
         * chunk size.
         *
         * @function
         * @this {string} The string to be chunked.
         * @param {Number} chunkSize The size of the chunks that the string will
         *                           be chunked into.
         * @returns {Array} Returns an array of the chunked string.
         */
        $defineProperty(String.prototype, 'toChunks', {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function (chunkSize) {
                var str = $onlyCoercibleToString(this),
                    chunkLength = $toInteger(chunkSize),
                    chunked = [],
                    numChunks,
                    length,
                    index,
                    start,
                    end;

                if (chunkLength < 1) {
                    return chunked;
                }

                length = $toLength(str.length);
                numChunks = Math.ceil(length / chunkLength);
                index = 0;
                start = 0;
                end = chunkLength;
                chunked.length = numChunks;
                while (index < numChunks) {
                    chunked[index] = str.slice(start, end);
                    start = end;
                    end += chunkLength;
                    index += 1;
                }

                return chunked;
            }
        });
    }
}());

/*
 * Some tests
 */

(function () {
    'use strict';

    var pre = document.getElementById('out'),
        chunkSizes = [],
        maxChunkSize = 512,
        testString = '',
        maxTestString = 100000,
        chunkSize = 0,
        index = 1;

    while (chunkSize < maxChunkSize) {
        chunkSize = Math.pow(2, index);
        chunkSizes.push(chunkSize);
        index += 1;
    }

    index = 0;
    while (index < maxTestString) {
        testString += String.fromCharCode(Math.floor(Math.random() * 95) + 32);
        index += 1;
    }

    function log(result) {
        pre.appendChild(document.createTextNode(result + '\n'));
    }

    function test() {
        var strLength = testString.length,
            czLength = chunkSizes.length,
            czIndex = 0,
            czValue,
            result,
            numChunks,
            pass;

        while (czIndex < czLength) {
            czValue = chunkSizes[czIndex];
            numChunks = Math.ceil(strLength / czValue);
            result = testString.toChunks(czValue);
            czIndex += 1;
            log('chunksize: ' + czValue);
            log(' Number of chunks:');
            log('  Calculated: ' + numChunks);
            log('  Actual:' + result.length);
            pass = result.length === numChunks;
            log(' First chunk size: ' + result[0].length);
            pass = pass && result[0].length === czValue;
            log(' Passed: ' + pass);
            log('');
        }
    }

    test();
    log('');
    log('Simple test result');
    log('abcdefghijklmnopqrstuvwxyz'.toChunks(3));
}());
<pre id="out"></pre>


0

ใช้วิธีการ slice ():

function returnChunksArray(str, chunkSize) {
  var arr = [];
  while(str !== '') {
    arr.push(str.slice(0, chunkSize));
    str = str.slice(chunkSize);
  }
  return arr;
}

เดียวกันสามารถทำได้โดยใช้วิธีการสตริงย่อย ()

function returnChunksArray(str, chunkSize) {
  var arr = [];
  while(str !== '') {
    arr.push(str.substring(0, chunkSize));
    str = str.substring(chunkSize);
  }
  return arr;
}

0

ปัญหาของฉันกับการแก้ปัญหาข้างต้นคือมัน beark สตริงเป็นชิ้นขนาดอย่างเป็นทางการโดยไม่คำนึงถึงตำแหน่งในประโยค

ฉันคิดว่าต่อไปนี้เป็นวิธีที่ดีกว่า; แม้ว่ามันจะต้องมีการปรับแต่งประสิทธิภาพ:

 static chunkString(str, length, size,delimiter='\n' ) {
        const result = [];
        for (let i = 0; i < str.length; i++) {
            const lastIndex = _.lastIndexOf(str, delimiter,size + i);
            result.push(str.substr(i, lastIndex - i));
            i = lastIndex;
        }
        return result;
    }

0

คุณสามารถทำสิ่งที่แน่นอน

let pieces = "1234567890 ".split(/(.{2})/).filter(x => x.length == 2);

เพื่อรับสิ่งนี้:

[ '12', '34', '56', '78', '90' ]

หากคุณต้องการที่จะใส่ / ปรับขนาดก้อนแบบไดนามิกเพื่อให้ชิ้นมีขนาด n คุณสามารถทำสิ่งนี้:

n = 2;
let pieces = "1234567890 ".split(new RegExp("(.{"+n.toString()+"})")).filter(x => x.length == n);

หากต้องการค้นหาขนาดที่เป็นไปได้ทั้งหมด n ก้อนในสตริงต้นฉบับลองสิ่งนี้:

let subs = new Set();
let n = 2;
let str = "1234567890 ";
let regex = new RegExp("(.{"+n.toString()+"})");     //set up regex expression dynamically encoded with n

for (let i = 0; i < n; i++){               //starting from all possible offsets from position 0 in the string
    let pieces = str.split(regex).filter(x => x.length == n);    //divide the string into chunks of size n...
    for (let p of pieces)                 //...and add the chunks to the set
        subs.add(p);
    str = str.substr(1);    //shift the string reading frame
}

คุณควรท้ายด้วย:

[ '12', '23', '34', '45', '56', '67', '78', '89', '90', '0 ' ]

-1
    window.format = function(b, a) {
        if (!b || isNaN(+a)) return a;
        var a = b.charAt(0) == "-" ? -a : +a,
            j = a < 0 ? a = -a : 0,
            e = b.match(/[^\d\-\+#]/g),
            h = e && e[e.length - 1] || ".",
            e = e && e[1] && e[0] || ",",
            b = b.split(h),
            a = a.toFixed(b[1] && b[1].length),
            a = +a + "",
            d = b[1] && b[1].lastIndexOf("0"),
            c = a.split(".");
        if (!c[1] || c[1] && c[1].length <= d) a = (+a).toFixed(d + 1);
        d = b[0].split(e);
        b[0] = d.join("");
        var f = b[0] && b[0].indexOf("0");
        if (f > -1)
            for (; c[0].length < b[0].length - f;) c[0] = "0" + c[0];
        else +c[0] == 0 && (c[0] = "");
        a = a.split(".");
        a[0] = c[0];
        if (c = d[1] && d[d.length -
                1].length) {
            for (var d = a[0], f = "", k = d.length % c, g = 0, i = d.length; g < i; g++) f += d.charAt(g), !((g - k + 1) % c) && g < i - c && (f += e);
            a[0] = f
        }
        a[1] = b[1] && a[1] ? h + a[1] : "";
        return (j ? "-" : "") + a[0] + a[1]
    };

var str="1234567890";
var formatstr=format( "##,###.", str);
alert(formatstr);


This will split the string in reverse order with comma separated after 3 char's. If you want you can change the position.


-2
function chunkString(str, length = 10) {
    let result = [],
        offset = 0;
    if (str.length <= length) return result.push(str) && result;
    while (offset < str.length) {
        result.push(str.substr(offset, length));
        offset += length;
    }
    return result;
}

4
คำตอบของคุณจะไม่เพิ่มอะไรใหม่ (เทียบกับคำตอบอื่น ๆ ) และไม่มีคำอธิบายใด ๆ เช่นคำตอบอื่น ๆ
flob
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.