TransWikia.com

Разбить слова по слогам

Stack Overflow на русском Asked by Kolhoznik on February 26, 2021

Есть определённое количество слов, около 2000 тыс. Можно ли их как-то программно разбить по слогам?

5 Answers

Неплохим выбором, на мой взгляд, будет использовать словарь. Стандартная реализация.
Естественно, чтобы разбить слова на слоги, сначала эти слова нужно добавить в словарь, но зато впоследствии можно будет в любой момент к нему обратиться.

Main

import Tree.RootNode;
import Exceptions.NoSuchWordException;

import java.io.*;
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try {
            RootNode rootNode;
            try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dictionary.dat"))) {
                rootNode = (RootNode) ois.readObject();
                System.out.println("The file was read successfully!");
            } catch (ClassNotFoundException | IOException ioe) {
                System.out.println("File read failed. Creating new dictionary.");
                rootNode = new RootNode();
            }
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("dictionary.dat"));
            String command;
            Scanner scanner = new Scanner(System.in);
            while (!(command = scanner.nextLine()).trim().equals("exit")) {
                String[] strings = command.split("\s+");
                if (command.equals("save")) {
                    try {
                        oos.writeObject(rootNode);
                        System.out.println("The dictionary was saved successfully!");
                    } catch (IOException e) {
                        System.out.println("Save failed.");
                    }
                    continue;
                } else if (strings.length < 2) {
                    System.out.println("The command must have an argument!");
                    continue;
                }
                switch (strings[0]) {
                    case "add":
                    case "put":
                    case "push":
                    case "shove":
                        rootNode.addWord(strings[1], Arrays.copyOfRange(strings, 2, strings.length));
                        System.out.println("Word '" + strings[1] + "' added successfully!");
                        break;
                    case "extract":
                    case "get":
                        try {
                            rootNode.extractWord(strings[1]).printSyllables();
                        } catch (NoSuchWordException nswee) {
                            System.out.println("There is no such word!");
                        }
                        break;
                    case "delete":
                    case "remove":
                    case "erase":
                        try {
                            rootNode.deleteWord(strings[1]);
                            System.out.println("Word '" + strings[1] + "' deleted successfully!");
                        } catch (NoSuchWordException nswee) {
                            System.out.println("There is no such word!");
                        }
                        break;
                    default:
                        System.out.println("There is no such command!");
                        break;
                }
            }
            oos.writeObject(rootNode);
            System.out.println("Dictionary is saved. Exiting...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

RootNode

package Tree;

public class RootNode extends Node {

    public Node extractWord(String word) {
        return extractWord(word, 0);
    }

    public void addWord(String word, String... syllables) {
        addWord(word, 0, syllables);
    }

    public void deleteWord(String word) {
        deleteWord(word, 0);
    }
}

Node

package Tree;

import Exceptions.NoSuchWordException;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import static java.lang.Math.min;

public class Node implements Serializable {

    private String[] syllables;

    private Map<String, Node> heirs = new HashMap<>();

    public String getSyllables() {
        if (syllables.length == 0)
            return "There are no syllables saved for this word!";
        StringBuilder strb = new StringBuilder();
        for (int i = 0;;) {
            strb.append(syllables[i]);
            if (++i != syllables.length)
                strb.append('-');
            else
                break;
        }
        return strb.toString();
    }

    public void printSyllables() {
        System.out.println(getSyllables());
    }

    public String[] getSyllablesAsArray() {
        return syllables;
    }

    Node extractWord(String word, int pos) {
        if (pos == word.length())
            return this;
        for (Map.Entry<String, Node> entry : heirs.entrySet())
            if (compareWords(entry.getKey(), word, pos))
                return entry.getValue().extractWord(word, pos + entry.getKey().length());
        throw new NoSuchWordException();
    }

    void addWord(String word, int pos, String... syllables) {
        for (Map.Entry<String, Node> entry : heirs.entrySet()) {
            int mmlsl = getMaxMatchedLettersSequenceLength(entry.getKey(), word, pos);
            if (mmlsl == word.length() - pos && mmlsl == entry.getKey().length()) {
                entry.getValue().syllables = syllables;
                return;
            }
            if (mmlsl == entry.getKey().length()) {
                entry.getValue().addWord(word, pos + mmlsl, syllables);
                return;
            }
            if (mmlsl == word.length() - pos) {
                heirs.remove(entry.getKey());
                Node newNode = new Node();
                newNode.syllables = syllables;
                heirs.put(word.substring(pos), newNode);
                newNode.heirs.put(entry.getKey().substring(mmlsl), entry.getValue());
                return;
            }
            if (mmlsl != 0) {
                heirs.remove(entry.getKey());
                Node intermediateNode = new Node();
                heirs.put(entry.getKey().substring(0, mmlsl), intermediateNode);
                Node newNode = new Node();
                newNode.syllables = syllables;
                intermediateNode.heirs.put(word.substring(pos + mmlsl), newNode);
                intermediateNode.heirs.put(entry.getKey().substring(mmlsl), entry.getValue());
                return;
            }
        }
        Node newNode = new Node();
        newNode.syllables = syllables;
        heirs.put(word.substring(pos), newNode);
    }

    void deleteWord(String word, int pos) {
        for (Map.Entry<String, Node> entry : heirs.entrySet())
            if (compareWords(entry.getKey(), word, pos))
                if (pos + entry.getKey().length() == word.length()) {
                    heirs.remove(entry.getKey());
                    return;
                } else
                    deleteWord(word, pos + entry.getKey().length());
        throw new NoSuchWordException();
    }

    private boolean compareWords(String s1, String s2, int pos) {
        return getMaxMatchedLettersSequenceLength(s1, s2, pos) == s1.length();
    }

    private int getMaxMatchedLettersSequenceLength(String s1, String s2, int pos) {
        int minL = min(s1.length(), s2.length() - pos);
        int count = 0;
        for (int i = 0; i < minL; i++)
            if (s1.charAt(i) == s2.charAt(pos + i))
                count++;
            else
                break;
        return count;
    }
}

NoSuchWordException

package Exceptions;

public class NoSuchWordException extends IllegalArgumentException {}

P.S.: Это мой первый словарь, так что замечания приветствуются. Давно хотел его написать, да всё повода не было.
P.S.S.: Простите, что без комментариев, лень их писать, хотя мне кажется, что так даже интереснее.

Answered by Имя Фамилия on February 26, 2021

Я взял за основу правила разбиения слова на фонетически слоги отсюда. Для начала класс реализации ленты с указателем + возможность ставить кое-какие маркеры:

package click.webelement;

public class Ribbon {

    private int position = -1;
    private final int length;
    private final String input;
    private int flag = -1;
    private int startSyllableIndex = 0;
    private int endSyllableIndex = 0;

    public Ribbon(String input){
        this.input = input;
        length = input.length();
    }

    public void setEndSyllableIndex(){
        endSyllableIndex = position;
    }

    public String extractSyllable(){
        String result = input.substring(startSyllableIndex, endSyllableIndex + 1);
        startSyllableIndex = endSyllableIndex + 1;
        flag = position;
        endSyllableIndex = 0;
        return result;
    }

    public char readCurrentPosition(){
        if (position < 0 || position > length - 1){
            throw new IllegalStateException();
        }
        return input.charAt(position);
    }

    public void setFlag(){
        flag = position;
    }

    public void rewindToFlag(){
        if(flag >= 0){
            position = flag;
        }
    }

    public boolean moveHeadForward(){
        if(position + 1 < length){
            position++;
            return true;
        }else{
            return false;
        }
    }

}

Затем сам алгортим вместе с тестом:

package click.webelement;

import java.util.ArrayList;
import java.util.List;

public class MainRibbon {

    final String vowels = "аеёиоуыюя";
    final String nonPairConsonant = "лйрнм";

    public static void main(String[] args) {
        MainRibbon mainRibbon = new MainRibbon();
        System.out.println(mainRibbon.syllables("контразведчик"));
        System.out.println(mainRibbon.syllables("длинношеее"));
        System.out.println(mainRibbon.syllables("программист"));
        System.out.println(mainRibbon.syllables("верноподданный"));
        System.out.println(mainRibbon.syllables("пуленепробиваемый"));
        System.out.println(mainRibbon.syllables("ёж"));
        System.out.println(mainRibbon.syllables("ёжик"));
    }

    List<String> syllables(String input){
        List<String> result = new ArrayList<>();
        Ribbon ribbon = new Ribbon(input);
        while(ribbon.moveHeadForward()){
            ribbon.setFlag();
            if(checkVowel(ribbon.readCurrentPosition())){
                if(ribbon.moveHeadForward() && ribbon.moveHeadForward()){
                    if(checkVowel(ribbon.readCurrentPosition())){
                        ribbon.rewindToFlag();
                        ribbon.setEndSyllableIndex();
                        result.add(ribbon.extractSyllable());
                        continue;
                    }
                }
                ribbon.rewindToFlag();
                if(ribbon.moveHeadForward() && checkSpecialConsonant(ribbon.readCurrentPosition())){
                    ribbon.setEndSyllableIndex();
                    result.add(ribbon.extractSyllable());
                    continue;
                }
                ribbon.rewindToFlag();
                if(hasMoreVowels(ribbon)){
                    ribbon.rewindToFlag();
                    ribbon.setEndSyllableIndex();
                    result.add(ribbon.extractSyllable());
                    continue;
                }else{
                    while (ribbon.moveHeadForward());
                    ribbon.setEndSyllableIndex();
                    result.add(ribbon.extractSyllable());
                }
            }
        }
        return result;
    }

    public boolean checkVowel(char ch){
        return vowels.contains(String.valueOf(ch));
    }

    public boolean hasMoreVowels(Ribbon ribbon){
        while (ribbon.moveHeadForward()){
            if(checkVowel(ribbon.readCurrentPosition())){
                return true;
            }
        }
        return false;
    }

    public boolean checkSpecialConsonant(char ch){
        return nonPairConsonant.contains(String.valueOf(ch));
    }

}

Вывод:

[кон, тра, зве, дчик]
[длин, но, ше, е, е]
[про, грам, мист]
[вер, но, по, ддан, ный]
[пу, ле, не, про, би, ва, е, мый]
[ёж]
[ё, жик]

Answered by Alexey R. on February 26, 2021

Возьму за основу правила из раздела Традиционная школа на сайте slogi.su:

В традиционной школе всё просто. Сколько гласных, столько и слогов, а как ребёнок разделит, не важно (лишь бы красиво звучало и ребёнку было понятно). Как бы школьника ни научили делить на слоги, это нигде не отразится: ни на ОГЭ, ни на ЕГЭ (там таких заданий нет). Перечислим три простых правила школьной программы.

  • Слог образует гласный звук:
    сте-на, ба-ран (с-тена, бара-н — неправильно).
  • Слог начинается с согласного, который стоит перед гласной:
    мо-ло-ко, ко-ра (мол-око, кор-а — неправильно).
  • Буквы ь, ъ (которые не означают звуков), й нельзя отрывать от предыдущего слога:
    лай-ка, конь-ки, подъ-езд (ла-йка, кон-ьки, под-ъезд — неправильно).

В традиционной школе допускается вариативность: со-лнце или сол-нце, ко-мпью-тер или ком-пью-тер.

Но исправлю косяк с буквой Й (dопрос на rusSE):

  • Й в сочетаниях ЙА, ЙИ, ЙУ, ЙЕ, ЙО начинает новый слог, а в сочетаниях ЙЯ, ЙЫ, ЙЮ, ЙЭ, ЙЁ - нет (точный список сочетаний ещё обсуждается).

Для замены и вставки между слогами дефисов регулярка такая:

[ЙЦКНГШЩЗХЪФВПРЛДЖЧСМТЬБ]*[ЁУЕЫАОЭЯИЮ][ЙЦКНГШЩЗХЪФВПРЛДЖЧСМТЬБ]*?(?=[ЦКНГШЩЗХФВПРЛДЖЧСМТБ]?[ЁУЕЫАОЭЯИЮ]|Й[АИУЕО])

Для поиска и выбора всех слогов - немного посложнее:

[ЙЦКНГШЩЗХЪФВПРЛДЖЧСМТЬБ]*[ЁУЕЫАОЭЯИЮ][ЙЦКНГШЩЗХЪФВПРЛДЖЧСМТЬБ]*?(?=$|[^А-ЯЁ]|[ЦКНГШЩЗХФВПРЛДЖЧСМТБ]?[ЁУЕЫАОЭЯИЮ]|Й[АИУЕО])

Для английского могут быть побольше возни с буквой Y, впрочем и тут с Й аналогично получилось. Это если не задуматься о том, что у них могут быть другие правила выделения слогов.

На джаве сделаешь сам, а вот пример работы на js:

function log(s) {
  console.log(s.replace(/[ЙЦКНГШЩЗХЪФВПРЛДЖЧСМТЬБ]*[ЁУЕЫАОЭЯИЮ][ЙЦКНГШЩЗХЪФВПРЛДЖЧСМТЬБ]*?(?=[ЦКНГШЩЗХФВПРЛДЖЧСМТБ]?[ЁУЕЫАОЭЯИЮ]|Й[АИУЕО])/ig, "$&-"))
  console.log((s.match(/[ЙЦКНГШЩЗХЪФВПРЛДЖЧСМТЬБ]*[ЁУЕЫАОЭЯИЮ][ЙЦКНГШЩЗХЪФВПРЛДЖЧСМТЬБ]*?(?=$|[^А-ЯЁ]|[ЦКНГШЩЗХФВПРЛДЖЧСМТБ]?[ЁУЕЫАОЭЯИЮ]|Й[АИУЕО])/ig) || []).join(" "))
}

log("Есть определённое количество слов, около 2000 тыс. Можно ли их как-то программно разбить по слогам?")
log("стена, баран; молоко, кора; лайка, коньки, подъезд")
log("солнце, компьютер; безвозмездно; соловьиный, длинношеее")
log("майор, фойе, папайя, алилуйя, Гийю, тейю, бойи, гайярдия, зухдийят")
.as-console-wrapper.as-console-wrapper { max-height: 100vh }

PS: Для переносов этот алгоритм несколько некорректный - помимо отрезания однобуквенных слогов, он никак не учитывает морфемную структуру слова, которую рекомендуют учитывать при переносах (например, не отрывать одну букву от корня).

PPS: Алгоритм из раздела Школа углублённого изучения тоже при желании можно более-менее реализовать, просто там будет правил больше и придётся все сочетания с изменяющимся звучанием прописывать (типа ть?сяb).

Answered by Qwertiy on February 26, 2021

Попробуйте воспользоваться сервисом https://slogi.su/ (ни в коем случае не реклама, нашел на просторах гугла). Если посмотреть, то там довольно простой request -> response.

Если смотреть обычным curl'ом, то результат будет следующий

$ curl -d 'q=собака' -X POST 'https://slogi.su/word.json'
{"status":"ok","text":"В слове 3 слога:<br/><span class=word-syllables>со-ба-ка</span><br/><a href='/собака'>Подробнее</a>"}

Далее все это дело надо запрограммировать на java. Код будет примерно следующим:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://slogi.su/word.json");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();

        con.setRequestMethod("POST");
        con.setDoOutput(true);
        con.getOutputStream().write("q=собака".getBytes());

        StringBuilder content = new StringBuilder();

        try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
            String line;
            while ((line = in.readLine()) != null) {
                content.append(line);
            }
        }
        con.disconnect();

        String output = content.toString();
        System.out.println("Полный ответ : " + output); // а дальше парсите ответ как хотите. Самый простой и наивный вариант - через регулярку
        Pattern pattern = Pattern.compile("<span class=word-syllables>(.+)</span>");
        Matcher matcher = pattern.matcher(output);
        if (matcher.find()) {
            System.out.println("Ответ по слогам : " + matcher.group(1));
        } else {
            System.out.println("Ответ по слогам не найден");
        }
    }

}

в результате будет такой вывод

Полный ответ : {"status":"ok","text":"В слове 3 слога:<br/><span class=word-syllables>со-ба-ка</span><br/><a href='/собака'>Подробнее</a>"}
Ответ по слогам : со-ба-ка

P.S. не надо придумывать велосипед, который до вас уже придумали, проще воспользоваться готовым решением

Answered by Andrew Bystrov on February 26, 2021

Как вариант: два масива, в одном гласные, в другом согласные буквы. Разбивать по буквенно и проверять. Опять же, продумайте условия, согласно правилам русского языка. Что-то по алгоритму похожу к поиску min max в масиве.

Answered by Happy Hends on February 26, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP