veer66

veer66

File.open("toto") do |file|
   file.each_line |line|
      line.chomp!
   end
end

พอทดสอบกับไฟล์ 11MB ใช้ pypy3 ทำให้ wordcutpy เร็วขึ้นเกิน 2 เท่า! คือใช้เวลาจาก 16 วินาที เหลือไม่ถึง 8 วินาที

(base) [vee@mint310 wiki]$ python3 wordcutpy.py 
16598
(base) [vee@mint310 wiki]$ sudo docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp pypy:3 pypy3 wordcutpy.py
7833
(base) [vee@mint310 wiki]$ python3 wordcutpy.py 
16093
(base) [vee@mint310 wiki]$ sudo docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp pypy:3 pypy3 wordcutpy.py
7821
(base) [vee@mint310 wiki]$ python3 wordcutpy.py 
16272
(base) [vee@mint310 wiki]$ sudo docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp pypy:3 pypy3 wordcutpy.py
7810
# wordcutpy.py
# การใช้ wordcutpy ที่ถูกต้องคือ copy & paste เลย ไม่ต้องใช้ pip 😅
# แล้วก็ copy bigthai.txt มาไว้ folder เดียวกัน

import sys
import re

class PrefixTree(object):
    def __init__(self, members_with_payload):
        self.tab = {}
        if members_with_payload is None:
            return 
        sorted_members_with_payload = sorted(members_with_payload,
                                             key=lambda i: i[0])

        for i in range(len(sorted_members_with_payload)):
            members, payload = sorted_members_with_payload[i]
            row_no = 0
            for j in range(len(members)):
                is_terminal = len(members) == j + 1
                member = members[j]
                key = (row_no, j, member)
                if key in self.tab:
                    row_no = self.tab[key][0]
                else:
                    val = (i, is_terminal, payload if is_terminal else None)
                    self.tab[key] = val
                    row_no = i

    def lookup(self, i, offset, member):
        key = (i, offset, member)
        if key not in self.tab:
            return None
        return self.tab[key]

UNK   = 1
DICT  = 2
INIT  = 3
LATIN = 4
PUNC  = 5

def is_better(link0, link1):
    if link0 is None:
        return True

    if link1["unk"] < link0["unk"]:
        return True

    if link1["w"] < link0["w"]:
        return True

    return False

def build_path(dix, s):
    left_boundary = 0
    dict_acc_list = []

    path = [{"p":None, "w": 0, "unk": 0, "type": INIT}]

    latin_s = None
    latin_e = None

    punc_s = None
    punc_e = None

    for i, ch in enumerate(s):
        dict_acc_list.append({"s":i, "p":0, "final":False})

        # Update dict acceptors
        _dict_acc_list = dict_acc_list
        dict_acc_list = []                        
        for acc in _dict_acc_list:
            offset = i - acc["s"]
            child = dix.lookup(acc["p"], offset, ch)
            if child is not None:
                child_p, is_final, payload = child
                dict_acc_list.append({"s":acc["s"], "p": child_p,
                                      "final":is_final})

        # latin words
        if latin_s is None:
            if re.match(u"[A-Za-z]", ch):
                latin_s = i

        if latin_s is not None:            
            if re.match(u"[A-Za-z]", ch):
                if i + 1 == len(s) or re.match(u"[A-Za-z]", s[i + 1]):
                    latin_e = i
            else:
                latin_s = None
                latin_e = None

        # puncuation
        if punc_s is None:
            if ch == " ":
                punc_s = i

        if punc_s is not None:
            if ch == " ":
                if len(s) == i + 1 or s[i + 1] != " ":
                    punc_e = i
            else:
                punc_s = None
                punc_e = None

        # select link
        link = None

        # links from wordlist
        for acc in dict_acc_list:
            if acc["final"]:
                p_link = path[acc["s"]]
                _link = {"p": acc["s"], 
                         "w": p_link["w"] + 1, 
                         "unk": p_link["unk"],
                         "type": DICT}
                if is_better(link, _link):
                    link = _link

        # link from latin word
        if latin_s is not None and latin_e is not None:
            p_link = path[latin_s]
            _link = {"p": latin_s, 
                     "w": p_link["w"] + 1, 
                     "unk": p_link["unk"],
                     "type": LATIN}
            if is_better(link, _link):
                link = _link

        # link from puncuation
        if punc_s is not None and punc_e is not None:                
            p_link = path[punc_s]
            _link = {"p": punc_s, 
                     "w": p_link["w"] + 1, 
                     "unk": p_link["unk"],
                     "type": PUNC}
            if is_better(link, _link):
                link = _link

        # fallback
        if link is None:
            p_link = path[left_boundary]
            link = {"p": left_boundary, 
                    "w": p_link["w"] + 1,
                    "unk": p_link["unk"] + 1,
                    "type": UNK}
        path.append(link)
        if link["type"] != UNK:
            left_boundary = i
    return path

def path_to_tokens(txt, path):
    if len(path) < 2:
        return None

    e = len(path) - 1
    toks = []

    while True:
        link = path[e]
        s = link["p"]
        if s is None:
            break
        toks.append(txt[s:e])
        e = s

    toks.reverse()
    return toks

def tokenize(dix, txt):
    if txt is None or txt == "":
        return []
    path = build_path(dix, txt)
    return path_to_tokens(txt, path)

class Wordcut(object):
    def __init__(self, wordlist):
        self.dix = PrefixTree([(word, None) for word in wordlist])


    @classmethod
    def bigthai(cls):
        import os
        "Initialize from bigthai"
        fileDir =  os.path.dirname(__file__)
        filename = os.path.join(fileDir, 'bigthai.txt')
        with open(filename) as dict_file:

            word_list = list(set([w.rstrip() for w in dict_file.readlines()]))
            word_list.sort()
            return cls(word_list)

    def tokenize(self, s):
        return tokenize(self.dix, s)

wordcut = Wordcut.bigthai()

import time

t1 = int(round(time.time() * 1000))

with open("wiki_plain_100k.txt") as fi:
    with open("wiki.cut", "w") as fo:
        for line in fi:
            line = line.strip()
            print(" ".join(wordcut.tokenize(line)), file=fo)

t2 = int(round(time.time() * 1000))

print(t2-t1)

# LICENSE: LGPLv3

https://github.com/veer66/wordcutpy

(เริ่มเขียนเมื่อ 2018-11-25)

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

หรือว่าบางเจ้าบอกว่าไม่ให้พื้นที่ 1TB แล้ววันหนึ่งจะเก็บตังรายปีขึ้นมา เพียงว่าเราจะย้ายข้อมูล เช่น รูปภาพที่เราถ่ายเอง กล้องถ่ายภาพทางเว็บก็ไม่ได้แจกมา เอาไปไว้ที่อื่นที่อาจจะดีกว่า หรือราคาถูกกว่า ก็เป็นเรื่องที่ยากลำบากแล้วในกรณีที่มีข้อมูลปริมาณมาก

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

เครือข่ายสังคมออนไล์ที่ผู้ใช้ย้ายออกลำบากตามที่ว่ามาก็เป็นโอกาสให้เอาสมาชิกของสังคมไปขายได้ด้วย อาจจะขายพ่วงกับกิจการ หรือว่าข่ายให้ผู้ที่อยากโฆษณาอีกทีก็ทำได้

ในระบบที่เป็นที่นิยมก่อนหน้านี้ เช่น E-mail เมื่อเปลี่ยนผู้ให้บริการก็ยังติดต่อกับญาติมิตรที่ใช้บริการ E-mail เจ้าอื่นได้ หรือแม้แต่กระทั่งโทรศัพท์เปลี่ยนผู้บริการแล้วก็ยังโทรหาคนอื่นได้เหมือนเดิม

Fediverse เกิดจากคำว่า Federation และ Universe รวมกันเป็นเครือข่ายสังคมออนไลน์ รวมถึง blog ที่เครื่องแม่ข่ายต่าง ๆ ทำงานร่วมกันได้ถึงแม้ว่าจะไม่ได้มีเจ้าของเดียวกัน หรือกระทั่งไม่ได้ใช้โปรแกรมเดียวกัน เช่น โปรแกรมสำหรับเขียน micro blog สามารถแสดงความคิดเห็นต่อวิดีโอบนเว็บสำหรับเผยแพร่วิดีโอได้โดยตรง

หลังจากที่โพสต์วิดีโอบน peertube.social ซึ่งเว็บสำหรับโพสต์วิดีโอใน Fediverse เราสามารถค้นหาด้วย URL ของ PeerTube จาก mstdn.io ซึ่งเป็นเว็บสำหรับโพสต์ micro blog ก็จะเห็นว่าวิดีโอใน PeerTube เป็น blog post หนึ่งจาก mstdn.io; สามารถ reply ได้; พอ reply เสร็จแล้วก็จะขึ้นไปเป็นความคิดของวิดีโอนั้นเลย

นอกจากจะแสดงความคิดเห็นข้ามกันได้แล้ว เรื่องทั่วไปอย่างดู video ของ PeerTube ใน Mastodon ก็ทำได้อยู่แล้ว หรือจะ follow/subscribe ก็ทำได้เหมือนกัน

ซึ่งลักษณะที่ว่าทำให้ Fediverse มีเครื่อแม่ขายที่ไม่ได้มีเจ้าของเดียวกันเยอะแยะไปหมด จากเว็บนี้ก็บอกว่ามี 2460 node แล้ว

ผมเลือก node ที่ผมคิดว่าสำคัญมาตามนี้

  • mstdn.io ใช้โปรแกรม Mastodon เป็น micro blog เรื่องทั่ว ๆ ไป
  • mastodon.in.th เหมือนข้างบนแต่เน้นภาษาไทย (เพิ่มเมื่อ 2019-11-30)
  • https://norze.world เน้นภาษาไทยแต่ใช้ Pleroma (เพิ่มเมื่อ 2020-03-30)
  • pawoo.net ใช้ Mastodon เหมือนกัน แต่เป็นชุมชนภาษาญี่ปุ่น
  • ruby.social ใช้ Mastodon เป็น node ที่คุยกันเน้นเรื่องภาษา Ruby
  • peertube.social ใช้ PeerTube เอาไว้สำหรับวิดีโอที่ไม่ใช่การค้า
  • squeet.me ใช้ Friendica คล้าย ๆ Facebook
  • pixelfed.social ใช้ Pixelfed เอาไว้โพสต์รูปเน้นใช้ง่าย ๆ คล้าย Instagram (แก้ไข 2019-02-27)
  • qua.name คล้าย ๆ Medium (เพิ่มเมื่อ 2019-02-27)

โปรแกรม Mastodon, PeerTube, Friendica, Pixelfed ล้วนเป็นซอฟต์แวร์เสรีทั้งสิ้น ซึ่งหมายความว่าเราจะเปิด node ของเราเองก็ได้ จะแก้ไขดัดแปลงแจกจ่ายก็ได้ แต่ละโปรแกรมก็ใช้ภาษาโปรแกรมต่างกันออกไป Mastodon ใช้ Ruby, PeerTube ใช้ TypeScript ส่วน Friendica และ Pixelfed ใช้ PHP

Fediverse แต่ละ node คุยกันได้เพราะว่ามีมาตรฐานกลาง ซึ่งหลัก ๆ ก็คือ ActivityPub ที่สร้างมาบน Acivity Stream และ JSON-LD อีกที

JSON-LD หลักการคล้าย ๆ RDF คือพยายามจะทำให้ข้อมูลจากแหล่งต่าง ๆ เชื่อมโยงกันได้ แต่ว่าใช้ JSON parser ได้เลยเพราะมันเป็น JSON เช่น เปิด json มาอาจจะ field ชื่อ actor ก็อาจจะหมายถึง “นักแสดงชาย” หรือ “ผู้กระทำ” ก็ได้ แต่ถ้าเขียนเลยว่า “https://www.w3.org/ns/activitystreams/actor” อันนี้ก็จะหมายถึงผู้กระทำแน่ ไม่จำเป็นต้องเป็นนักแสดง แต่จะเขียนชื่อ field ด้วย url หรือในเอกสารจะเรียกรวม ๆ ว่า IRI มันก็จะเยิ่นเย้อเกินไป ก็เลยมี field หนึ่งขึ้นมาชื่อว่า @context เราก็เรียน “@context”: “https://www.w3.org/ns/activitystreams” ไว้เสียแต่ต้น ต่อไปในเอกสารก็ใช้ actor ได้เลยเพราะทราบแล้วว่าบริบทคือ “https://www.w3.org/ns/activitystreams” สามารถอ่านเพิ่มเติมได้จาก JSON-LD Spec

ส่วน Activity Stream เป็นมาตรฐานที่ระบุเลยว่าจะเขียนว่าใคร (actor) ทำอะไร (verb) อะไรถูกกระทำ (object) จะต้องเขียนออกมาหน้าตาอย่างไร หน้าตาก็จะออกมาเป็น JSON-LD ตามที่มาตรฐานกำหนดไว้ ที่ผมเห็นใช้งานตอนนี้ก็คือใช้ในเครือข่ายสังคมออนไลน์ทั้งหมด อ่านเพิ่มเติมได้จาก Activity Streams Spec

จาก Activity Stream ก็มีมาตรฐานเกี่ยวเนื่องที่ผมรู้สึกว่าน่าสนใจคือ Activity Vocabulary ซึ่งระบุกระทั่งว่ากริยาที่กระทำใน Activity Stream มีอะไรหน้าตาเป็นอย่างไร เช่น like dislike follow invite ignore ต้องมีหน้าตา มีข้อมูลอย่างไรบ้าง อ่านเพิ่มเติมได้จาก Vocabulary spec

นอกจากประเด็นว่าส่งว่าใคร ทำอะไร อะไรเป็นผู้กระทำแล้ว ก็ยังมีประเด็นว่าต้องส่ง activity stream ไปที่ไหน ในส่วนนี้ก็เป็นมาตรฐาน ActivityPub ซึ่งกำหนดทั้งการส่งจาก client กับ server และ server กับ server เอง เพราะว่า Fediverse ต้องมีการสือสารกันระหว่าง server คนละเจ้าด้วย โดยมีการกำหนดเป็น inbox outbox ของแต่ละ actor ขึ้นมาว่าจะอยู่ใน URL อะไร ฯลฯ ซึ่งอ่านรายละเอียดได้จาก ActivityPub Spec

ใน Fediverse ไม่มีการเอาการติดต่อญาติมิตรของใคร ๆ มาเป็นตัวประกันของแต่ละเจ้าอีกต่อไป ทำให้ลดการผูกขาดลด เมื่อมีอำนาจต่อรองมากขึ้นสมาชิกก็ถูกนำไปขายยากขึ้น แต่ว่า node สามารถนโยบายของตัวเองทำให้ไม่มีการผูกขาดทางศีลธรรม เรื่องโฆษณาใน Fediverse ตอนนี้ยังไม่เห็นครับ ส่วนหนึ่งคือ admin ของแต่ละ node ออกตังเองเพราะแต่ละ node ไม่ใช้เครื่องแรงมากมายพอจะออกให้กันได้ และอีกส่วนหนึ่งก็มีการบริกาคช่วยกัน; ผมเห็น write.as มีแบบเสียตังด้วย แต่ยังต้องศึกษาเพิ่มเติมอีก

แต่ก็มีข้อจำกัดอยู่ว่าย้ายค่ายแล้วต้องเปลี่ยน ID

อนาคตอาจจะมีการใช้ ActivityPub กับบริการออนไลน์อื่นอีก ไม่ว่าจะเป็นซอฟต์แวร์เสรีหรือไม่ก็ตาม หรือก้าวหน้าจนไปถึงแบบ peer-to-peer

ประเด็นอื่น ๆ เช่นว่า

  • Fediverse จะยั่งยืนหรือไม่ ผมเชื่อความทางศาสนาว่าสิ่งเป็นสังขารทั้งหมดไม่ยั่งยืน และผมไม่สามารถพยากรณ์อายุของ Fediverse ในอนาคตได้;
  • ประเด็นว่าทำไมไม่ใช้ peer-to-peer ผมคิดว่าจะมีประเด็นเรื่องพลังงานถ้าใช้อุปกรณ์เคลื่อนที่;
  • ทำไมไม่ใช้ blockchain ผมคิดว่ามีประเด็นด้านประสิทธิภาพ และการกลั่นแกล้งเบียดเบียนกัน Fediverse ไม่จำเป็นต้องปล่อยให้ทุกคนพูดทุกอย่างแล้วลบไม่ได้
  • app บนมือถือ tablet มีทั้ง Android และ iOS ใช้กับ Mastoodon และ Pleroma ได้