เครื่องมือบรรทัดคำสั่งเพื่อ“ cat” การขยายแบบคู่ของแถวทั้งหมดในไฟล์


13

สมมติว่าฉันมีไฟล์ (เรียกว่า sample.txt) ที่มีลักษณะดังนี้:

Row1,10
Row2,20
Row3,30
Row4,40

ฉันต้องการที่จะทำงานกับสตรีมจากไฟล์นี้ซึ่งเป็นการรวมกันแบบคู่ของทั้งสี่แถว (ดังนั้นเราควรจะจบด้วย 16 ทั้งหมด) ตัวอย่างเช่นฉันกำลังมองหาคำสั่งสตรีม (เช่นมีประสิทธิภาพ) โดยที่เอาต์พุตคือ:

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row1,20 Row2,20
...
Row4,40 Row4,40

กรณีการใช้งานของฉันคือฉันต้องการสตรีมเอาต์พุตนี้ไปยังคำสั่งอื่น (เช่น awk) เพื่อคำนวณเมทริกบางส่วนเกี่ยวกับชุดค่าผสมแบบคู่นี้

ฉันมีวิธีที่จะทำสิ่งนี้ใน awk แต่ข้อกังวลของฉันคือการใช้บล็อก END {} ของฉันหมายความว่าฉันกำลังเก็บไฟล์ทั้งหมดไว้ในหน่วยความจำก่อนส่งออก รหัสตัวอย่าง:

awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt 
Row3,30 Row3,30
Row3,30 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row1,10 Row1,10
Row1,10 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20

มีวิธีการสตรีมที่มีประสิทธิภาพในการทำเช่นนี้โดยไม่ต้องจัดเก็บไฟล์ไว้ในหน่วยความจำแล้วส่งออกในบล็อก END หรือไม่


1
คุณจะต้องอ่านไฟล์หนึ่งไฟล์จนจบก่อนจึงจะสามารถเริ่มต้นสร้างเอาต์พุตสำหรับบรรทัดที่สองของไฟล์อื่นได้ ไฟล์อื่นที่คุณสามารถสตรีมได้
reinierpost

คำตอบ:


12

นี่คือวิธีการใน awk เพื่อที่จะได้ไม่ต้องเก็บไฟล์ทั้งหมดในอาร์เรย์ นี่เป็นอัลกอริธึมเดียวกับ terdon

หากคุณต้องการคุณสามารถให้ชื่อไฟล์หลาย ๆ ไฟล์ในบรรทัดคำสั่งและจะประมวลผลแต่ละไฟล์แยกกันโดยเชื่อมผลลัพธ์เข้าด้วยกัน

#!/usr/bin/awk -f

#Cartesian product of records

{
    file = FILENAME
    while ((getline line <file) > 0)
        print $0, line
    close(file)
}

ในระบบของฉันสิ่งนี้จะทำงานในเวลาประมาณ 2/3 เวลาของการแก้ปัญหา perl ของ terdon


1
ขอบคุณ! ทางออกทั้งหมดของปัญหานี้ยอดเยี่ยม แต่ฉันลงเอยด้วยอันนี้เนื่องจาก 1) ความเรียบง่ายและ 2) อยู่ใน awk ขอบคุณ!
Tom Hayden

1
ดีใจที่คุณชอบทอม ฉันมักจะเขียนโปรแกรมใน Python เป็นส่วนใหญ่ในปัจจุบัน แต่ฉันก็ยังชอบ awk สำหรับการประมวลผลแบบข้อความต่อบรรทัดเพราะมันมีลูปในตัวมากกว่าเส้นและไฟล์ และมันมักจะเร็วกว่า Python
PM 2Ring

7

ผมไม่แน่ใจว่านี้จะดีกว่าที่จะทำมันในความทรงจำ แต่ด้วยความsedที่rEADS ออก INFILE สำหรับทุกบรรทัดใน INFILE และอื่นในด้านอื่น ๆ ของท่อสลับHพื้นที่เก่าที่มีเส้นที่นำเข้า ...

cat <<\IN >/tmp/tmp
Row1,10
Row2,20
Row3,30
Row4,40
IN

</tmp/tmp sed -e 'i\
' -e 'r /tmp/tmp' | 
sed -n '/./!n;h;N;/\n$/D;G;s/\n/ /;P;D'

เอาท์พุท

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40

ฉันทำอย่างนี้อีกทาง มันจะเก็บบางส่วนในหน่วยความจำ - จะเก็บสตริงเช่น:

"$1" -

... สำหรับแต่ละบรรทัดในไฟล์

pairs(){ [ -e "$1" ] || return
    set -- "$1" "$(IFS=0 n=
        case "${0%sh*}" in (ya|*s) n=-1;; (mk|po) n=+1;;esac
        printf '"$1" - %s' $(printf "%.$(($(wc -l <"$1")$n))d" 0))"
    eval "cat -- $2 </dev/null | paste -d ' \n' -- $2"
}

มันเร็วมาก มันเป็นไฟล์หลายครั้งตามที่มีเส้นในไฟล์ไปยังcat |pipeอีกด้านหนึ่งของไปป์ที่อินพุตถูกรวมเข้ากับไฟล์ตัวเองหลาย ๆ ครั้งตามที่มีบรรทัดในไฟล์

caseสิ่งที่เป็นเพียงสำหรับการพกพา - yashและzshทั้งสององค์ประกอบหนึ่งเพิ่มการแยกในขณะที่mkshและposhทั้งสูญเสียหนึ่ง ksh, dash, busyboxและแยกออกทั้งหมดไปยังเขตว่ามากที่สุดเท่าที่มีเลขศูนย์เป็นพิมพ์โดยbash printfตามที่เขียนไว้ข้างต้นทำให้ผลลัพธ์เหมือนกันสำหรับทุกเชลล์ที่กล่าวถึงข้างต้นบนเครื่องของฉัน

หากไฟล์มีความยาวมากอาจมี$ARGMAXปัญหากับอาร์กิวเมนต์ที่มากเกินไปซึ่งในกรณีนี้คุณจะต้องแนะนำxargsหรือคล้ายกันเช่นกัน

รับอินพุตเดียวกันกับที่ฉันใช้ก่อนที่เอาต์พุตจะเหมือนกัน แต่ถ้าฉันจะใหญ่ขึ้น ...

seq 10 10 10000 | nl -s, >/tmp/tmp

ที่สร้างไฟล์เกือบจะเหมือนกับสิ่งที่ฉันใช้มาก่อน(sans 'Row') - แต่ที่ 1,000 บรรทัด คุณสามารถเห็นตัวเองว่ามันเร็วแค่ไหน:

time pairs /tmp/tmp |wc -l

1000000
pairs /tmp/tmp  0.20s user 0.07s system 110% cpu 0.239 total
wc -l  0.05s user 0.03s system 32% cpu 0.238 total

ที่ 1,000 บรรทัดมีการเปลี่ยนแปลงเล็กน้อยในประสิทธิภาพระหว่างเชลล์ - bashช้าที่สุด - แต่เนื่องจากงานเดียวที่พวกเขาทำต่อไปคือการสร้างสตริง arg (1,000 สำเนาfilename -)ผลที่ได้คือน้อยที่สุด ความแตกต่างของประสิทธิภาพระหว่างzsh- ดังกล่าวข้างต้น - และbashอยู่ที่ 100 ของวินาทีที่นี่

นี่เป็นอีกเวอร์ชั่นที่ควรใช้กับไฟล์ที่มีความยาวเท่าใด:

pairs2()( [ -e "$1" ] || exit
    rpt() until [ "$((n+=1))" -gt "$1" ]
          do printf %s\\n "$2"
          done
    [ -n "${1##*/*}" ] || cd -P -- "${1%/*}" || exit
    : & set -- "$1" "/tmp/pairs$!.ln" "$(wc -l <"$1")"
    ln -s "$PWD/${1##*/}" "$2" || exit
    n=0 rpt "$3" "$2" | xargs cat | { exec 3<&0
    n=0 rpt "$3" p | sed -nf - "$2" | paste - /dev/fd/3
    }; rm "$2"
)

มันสร้างซอฟต์ลิงค์ไปยัง arg แรกของมัน/tmpด้วยชื่อแบบกึ่งสุ่มเพื่อที่จะได้ไม่ถูกแขวนบนชื่อไฟล์แปลก ๆ นั่นเป็นสิ่งสำคัญเพราะcat's args xargsจะเลี้ยงมันมากกว่าท่อผ่าน catเอาต์พุตของจะถูกบันทึก<&3ในขณะที่sed prints ทุกบรรทัดใน ARG แรกหลาย ๆ ครั้งตามที่มีบรรทัดในไฟล์นั้น - และสคริปต์จะถูกป้อนไปยังผ่านทางไพพ์ อีกครั้งpasteผสานการป้อนข้อมูลของตน แต่เวลานี้มันใช้เวลาเพียงสองข้อโต้แย้งอีกครั้งสำหรับการป้อนข้อมูลมาตรฐานและชื่อการเชื่อมโยง-/dev/fd/3

สุดท้าย - /dev/fd/[num]ลิงก์ - ควรทำงานกับระบบ linux ใด ๆ และอื่น ๆ อีกมากมาย แต่ถ้ามันไม่ได้สร้างไปป์ที่มีชื่อด้วยmkfifoและใช้มันแทนที่จะทำงานได้ดี

สิ่งสุดท้ายที่มันทำคือrmsoft-link ที่สร้างขึ้นก่อนออก

เวอร์ชันนี้ยังเร็วกว่าในระบบของฉัน ฉันเดาว่าเป็นเพราะถึงแม้ว่ามันจะประมวลผลแอพพลิเคชั่นได้มากขึ้นมันก็เริ่มส่งข้อโต้แย้งของพวกเขาทันที - ก่อนที่มันจะซ้อนพวกมันทั้งหมดก่อน

time pairs2 /tmp/tmp | wc -l

1000000
pairs2 /tmp/tmp  0.30s user 0.09s system 178% cpu 0.218 total
wc -l  0.03s user 0.02s system 26% cpu 0.218 total

ฟังก์ชั่นการจับคู่ควรจะอยู่ในไฟล์หรือไม่ถ้าไม่คุณจะประกาศมัน?

@Jidder - ฉันจะประกาศอะไร คุณสามารถคัดลอก + วางลงในเทอร์มินัลได้ไหม
mikeserv

1
ประกาศฟังก์ชัน ดังนั้นคุณสามารถฉันคิดว่าคุณจะมีการหลบหนีขึ้นบรรทัดใหม่ฉันระมัดระวังในการวางรหัสในขอบคุณแม้ว่า :) ยังที่รวดเร็วมากคำตอบที่ดี!

@Jidder - ฉันมักจะเขียนสิ่งเหล่านี้ในเปลือกสดเพียงแค่ใช้ctrl+v; ctrl+jเพื่อรับบรรทัดใหม่ตามที่ฉันทำ
mikeserv

@Jidder - ขอบคุณมาก และก็ควรที่จะระวัง - ดีสำหรับคุณ มันจะทำงานได้ดีในไฟล์ - คุณสามารถคัดลอกในและ. ./file; fn_nameในกรณีนั้น
mikeserv

5

คุณสามารถทำได้ในเปลือกของคุณเสมอ:

while read i; do 
    while read k; do echo "$i $k"; done < sample.txt 
done < sample.txt 

มันช้ากว่าawkโซลูชันของคุณมาก(ในเครื่องของฉันใช้เวลาประมาณ 11 วินาทีสำหรับ 1000 บรรทัดเทียบกับ ~ 0.3 วินาทีawk) แต่อย่างน้อยก็ไม่เคยมีหน่วยความจำมากกว่าสองบรรทัด

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

while IFS= read -r i; do 
    while IFS= read -r k; do printf "%s %s\n" "$i" "$k"; done < sample.txt 
done < sample.txt 

ตัวเลือกอื่นคือใช้perlแทน:

perl -lne '$line1=$_; open(A,"sample.txt"); 
           while($line2=<A>){printf "$line1 $line2"} close(A)' sample.txt

สคริปต์ข้างต้นจะอ่านแต่ละบรรทัดของแฟ้มใส่ ( -ln) บันทึกเป็น$lเปิดอีกครั้งและพิมพ์แต่ละบรรทัดพร้อมกับsample.txt $lผลลัพธ์คือชุดค่าผสมทั้งหมดในขณะที่มีเพียง 2 เส้นเท่านั้นที่ถูกเก็บไว้ในหน่วยความจำ ในระบบของฉันมันใช้เวลาเพียงไม่0.6กี่วินาทีใน 1,000 บรรทัด


ว้าวขอบคุณ! ฉันสงสัยว่าทำไมคำตอบของ perl นั้นเร็วกว่าการทุบตีในขณะที่แถลง
Tom Hayden

@TomHayden พื้นเพราะ Perl เช่น awk เป็นมากเร็วกว่าทุบตี
terdon

1
ต้อง downvote สำหรับคุณในขณะที่วง 4 การปฏิบัติที่ไม่ดีต่างกันในนั้น คุณรู้ดีกว่า
Stéphane Chazelas

1
@ StéphaneChazelasเป็นอย่างดีจากคำตอบของคุณที่นี่ฉันไม่สามารถนึกถึงกรณีใด ๆ ที่echoอาจเป็นปัญหา สิ่งที่ฉันเขียน (ฉันเพิ่มprintfตอนนี้) ควรทำงานกับพวกเขาทั้งหมดใช่มั้ย สำหรับwhileวงทำไม? มีอะไรผิดปกติกับwhile read f; do ..; done < file? แน่นอนคุณไม่ได้แนะนำการforวนซ้ำ! ทางเลือกอื่นคืออะไร?
terdon

2
@cuonglm มีเพียงคนเดียวที่บอกเหตุผลเดียวที่เป็นไปได้ว่าทำไมเราควรหลีกเลี่ยง ออกมาจากแนวความคิด , ความน่าเชื่อถือ , ความสวย , ประสิทธิภาพการทำงานและการรักษาความปลอดภัยด้านที่ครอบคลุมเฉพาะความน่าเชื่อถือ
Stéphane Chazelas

4

ด้วยzsh:

a=(
Row1,10
Row2,20
Row3,30
Row4,40
)
printf '%s\n' $^a' '$^a

$^aในอาร์เรย์จะเปิดใช้การขยายแบบคล้ายรั้ง (เหมือนใน{elt1,elt2}) สำหรับอาร์เรย์


4

คุณสามารถรวบรวมรหัสนี้เพื่อผลลัพธ์ที่รวดเร็ว
มันเสร็จสมบูรณ์ในประมาณ 0.19 - 0.27 วินาทีในไฟล์ 1000 บรรทัด

ขณะนี้อ่าน10000บรรทัดในหน่วยความจำ (เพื่อเพิ่มความเร็วในการพิมพ์ไปยังหน้าจอ) ซึ่งถ้าคุณมี1000ตัวอักษรต่อบรรทัดจะใช้10mbหน่วยความจำน้อยกว่าซึ่งฉันไม่คิดว่าจะเป็นปัญหา คุณสามารถลบส่วนนั้นได้อย่างสมบูรณ์และเพียงพิมพ์ไปที่หน้าจอโดยตรงหากเกิดปัญหาขึ้น

คุณสามารถคอมไพล์ได้โดยใช้g++ -o "NAME" "NAME.cpp"
Where NAMEเป็นชื่อของไฟล์ที่จะบันทึกและNAME.cppเป็นไฟล์ที่รหัสนี้ถูกบันทึกไว้

CTEST.cpp:

#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <sstream>
int main(int argc,char *argv[])
{

        if(argc != 2)
        {
                printf("You must provide at least one argument\n"); // Make                                                                                                                      sure only one arg
                exit(0);
   }
std::ifstream file(argv[1]),file2(argv[1]);
std::string line,line2;
std::stringstream ss;
int x=0;

while (file.good()){
    file2.clear();
    file2.seekg (0, file2.beg);
    getline(file, line);
    if(file.good()){
        while ( file2.good() ){
            getline(file2, line2);
            if(file2.good())
            ss << line <<" "<<line2 << "\n";
            x++;
            if(x==10000){
                    std::cout << ss.rdbuf();
                    ss.clear();
                    ss.str(std::string());
            }
    }
    }
}
std::cout << ss.rdbuf();
ss.clear();
ss.str(std::string());
}

สาธิต

$ g++ -o "Stream.exe" "CTEST.cpp"
$ seq 10 10 10000 | nl -s, > testfile
$ time ./Stream.exe testfile | wc -l
1000000

real    0m0.243s
user    0m0.210s
sys     0m0.033s

3
join -j 2 file.txt file.txt | cut -c 2-
  • เข้าร่วมโดยเขตข้อมูลที่ไม่มีอยู่และลบช่องว่างแรก

ฟิลด์ 2 ว่างเปล่าและเท่ากับสำหรับองค์ประกอบทั้งหมดใน file.txt ดังนั้นjoinจะต่อกันแต่ละองค์ประกอบกับองค์ประกอบอื่น ๆ ทั้งหมด: อันที่จริงแล้วเป็นการคำนวณผลิตภัณฑ์คาร์ทีเซียน


2

ตัวเลือกหนึ่งที่มี Python คือการแม็พหน่วยความจำกับไฟล์และใช้ประโยชน์จากข้อเท็จจริงที่ว่า Python expression library สามารถทำงานกับไฟล์ที่แม็พหน่วยความจำได้โดยตรง แม้ว่าสิ่งนี้จะมีลักษณะของการรันลูปซ้อนกันอยู่เหนือไฟล์ แต่การแมปหน่วยความจำจะทำให้แน่ใจได้ว่าระบบปฏิบัติการจะทำให้ RAM ที่มีอยู่จริงพร้อมใช้งานได้อย่างเหมาะสมที่สุดในการเล่น

import mmap
import re
with open('test.file', 'rt') as f1, open('test.file') as f2:
    with mmap.mmap(f1.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m1,\
        mmap.mmap(f2.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m2:
        for line1 in re.finditer(b'.*?\n', m1):
            for line2 in re.finditer(b'.*?\n', m2):
                print('{} {}'.format(line1.group().decode().rstrip(),
                    line2.group().decode().rstrip()))
            m2.seek(0)

อีกวิธีหนึ่งในการแก้ปัญหาอย่างรวดเร็วใน Python แม้ว่าประสิทธิภาพของหน่วยความจำอาจยังเป็นปัญหา

from itertools import product
with open('test.file') as f:
    for a, b  in product(f, repeat=2):
        print('{} {}'.format(a.rstrip(), b.rstrip()))
Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40

จะไม่เก็บไฟล์ทั้งหมดไว้ในหน่วยความจำใช่ไหม ฉันไม่รู้ Python แต่ภาษาของคุณแนะนำว่ามันจะ
terdon

1
@terdon หากคุณอ้างถึงวิธีแก้ปัญหาการแมปหน่วยความจำระบบปฏิบัติการจะเก็บไฟล์ไว้ในหน่วยความจำมากเท่าที่จะทำได้โดยอ้างอิงจาก RAM ที่มีอยู่จริง ฟิสิคัลแรมที่มีอยู่ไม่จำเป็นต้องมีขนาดเกินขนาดไฟล์ (แม้ว่าจะมีฟิสิคัลแรมเป็นพิเศษจะเห็นได้ชัดว่าเป็นสถานการณ์ที่ได้เปรียบ) ในกรณีที่เลวร้ายที่สุดสิ่งนี้อาจลดความเร็วของการวนลูปผ่านไฟล์บนดิสก์หรือแย่กว่านั้น ข้อได้เปรียบที่สำคัญของวิธีนี้คือการใช้ RAM ที่มีอยู่จริงอย่างโปร่งใสเนื่องจากนี่เป็นสิ่งที่อาจเปลี่ยนแปลงได้ตลอดเวลา
iruvar

1

ใน bash, ksh ควรใช้งานได้เช่นกันโดยใช้เชลล์ในตัวเท่านั้น:

#!/bin/bash
# we require array support
d=( $(< sample.txt) )
# quote arguments and
# build up brace expansion string
d=$(printf -- '%q,' "${d[@]}")
d=$(printf -- '%s' "{${d%,}}' '{${d%,}}")
eval printf -- '%s\\n' "$d"

โปรดทราบว่าแม้ว่าไฟล์นี้จะเก็บไฟล์ทั้งหมดไว้ในหน่วยความจำในตัวแปรเชลล์ แต่ก็ต้องการการเข้าถึงเพื่ออ่านเพียงครั้งเดียวเท่านั้น


1
ฉันคิดว่าประเด็นทั้งหมดสำหรับ OP คือการไม่เก็บไฟล์ไว้ในหน่วยความจำ มิฉะนั้นวิธีการเพ่งพิศปัจจุบันของพวกเขาเป็นทั้งง่ายและมากขึ้นเร็วขึ้น ฉันคาดว่าสิ่งนี้จะต้องทำงานกับไฟล์ข้อความที่มีขนาดหลายกิกะไบต์
terdon

ใช่ถูกต้องแล้ว - ฉันมีไฟล์ข้อมูลขนาดใหญ่สองไฟล์ที่ฉันต้องทำและไม่ต้องการเก็บไว้ในหน่วยความจำ
Tom Hayden

ในกรณีที่คุณถูก จำกัด ด้วยความทรงจำฉันขอแนะนำให้ใช้หนึ่งในวิธีแก้ปัญหาจาก @terdon
Franki

0

sed วิธีการแก้.

line_num=$(wc -l < input.txt)
sed 'r input.txt' input.txt | sed -re "1~$((line_num + 1)){h;d}" -e 'G;s/(.*)\n(.*)/\2 \1/'

คำอธิบาย:

  • sed 'r file2' file1 - อ่านเนื้อหาไฟล์ทั้งหมดของ file2 สำหรับทุกบรรทัดของ file1
  • การก่อสร้าง1~iหมายถึงบรรทัดที่ 1 จากนั้น 1 + i บรรทัด, 1 + 2 * i, 1 + 3 * i ฯลฯ ดังนั้นจึง1~$((line_num + 1)){h;d}หมายถึงhเส้นที่แหลมเก่าไปยังบัฟเฟอร์, ลบdพื้นที่รูปแบบและเริ่มรอบใหม่
  • 'G;s/(.*)\n(.*)/\2 \1/'- สำหรับทุกบรรทัดยกเว้นที่เลือกในขั้นตอนก่อนหน้าให้ทำต่อไป: Get line จากบัฟเฟอร์พักไว้และผนวกเข้ากับบรรทัดปัจจุบัน จากนั้นสลับตำแหน่งของเส้น ก็current_line\nbuffer_line\nกลายเป็นbuffer_line\ncurrent_line\n

เอาท์พุต

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.