Você pode substituir a maioria dessas palavras "quebradas" pelos originais. Você pode substituir uma palavra com segurança se:
- como
dene
ou rey
, não é uma palavra real
- como
define
ou firefly
, há uma maneira de adicionar novamente sequências de ligadura ( ff
, fi
, fl
, ffi
ou ffl
) e fazer uma palavra real
A maioria dos problemas de ligadura se encaixam nesses critérios. No entanto, você não pode substituir:
-
us
porque é uma palavra real, mesmo que originalmente tenha sido fluffs
- também
affirm
, butterfly
, fielders
, fortifies
, flimflam
, misfits
...
-
cus
porque pode tornar-se cuffs
ou ficus
- também
stiffed
/ stifled
, rifle
/ riffle
, flung
/ fluffing
...
Em este dicionário de inglês com 496 mil palavras , existem 16055 palavras que contêm pelo menos um ff
, fi
, fl
, ffi
ou ffl
, que se transformam em 15879 palavras quando suas ligaduras são removidas. 173 das palavras em falta colididas como cuffs
e ficus
e as últimas 3 porque o dicionário contém as palavras ff
, fi
e fl
.
790 destas palavras "removidas por ligadura" são palavras reais, como us
, mas 15089 são palavras quebradas. 14960 das palavras quebradas pode ser seguramente substituído pela palavra original, o que significa que 99,1% das palavras quebradas são fixáveis e 93,2% do as palavras originais que contêm uma ligadura podem ser recuperadas após copiar e colar um PDF. 6,8% de palavras contendo sequências de ligaduras são perdidas para as colisões ( cus
) e subpalavras ( us
), a menos que você escolha alguma forma (contexto de palavra / documento?) para escolher a melhor substituição para cada uma das palavras que não tenham uma substituição garantida.
Abaixo está meu script Python que gerou as estatísticas acima. Espera um arquivo de texto do dicionário com uma palavra por linha. No final, ele grava um arquivo CSV que mapeia palavras quebradas corrigíveis para suas palavras originais.
Aqui está um link para baixar o CSV:
link
Combine esse mapeamento com algo parecido com um script de substituição de regex para substituir a maioria das palavras quebradas.
import csv
import itertools
import operator
import re
dictionary_file_path = 'dictionary.txt'
broken_word_fixes_file_path = 'broken_word_fixes.csv'
ligatures = 'ffi', 'ffl', 'ff', 'fi', 'fl'
with open(dictionary_file_path, 'r') as dictionary_file:
dictionary_words = list(set(line.strip()
for line in dictionary_file.readlines()))
broken_word_fixes = {}
ligature_words = set()
ligature_removed_words = set()
broken_words = set()
multi_ligature_words = set()
# Find broken word fixes for words with one ligature sequence
# Example: "dene" --> "define"
words_and_ligatures = list(itertools.product(dictionary_words, ligatures))
for i, (word, ligature) in enumerate(words_and_ligatures):
if i % 50000 == 0:
print('1-ligature words {percent:.3g}% complete'
.format(percent=100 * i / len(words_and_ligatures)))
for ligature_match in re.finditer(ligature, word):
if word in ligature_words:
multi_ligature_words.add(word)
ligature_words.add(word)
if word == ligature:
break
# Skip words that contain a larger ligature
if (('ffi' in word and ligature != 'ffi') or
('ffl' in word and ligature != 'ffl')):
break
# Replace ligatures with dots to avoid creating new ligatures
# Example: "offline" --> "of.ine" to avoid creating "fi"
ligature_removed_word = (word[:ligature_match.start()] +
'.' +
word[ligature_match.end():])
# Skip words that contain another ligature
if any(ligature in ligature_removed_word for ligature in ligatures):
continue
ligature_removed_word = ligature_removed_word.replace('.', '')
ligature_removed_words.add(ligature_removed_word)
if ligature_removed_word not in dictionary_words:
broken_word = ligature_removed_word
broken_words.add(broken_word)
if broken_word not in broken_word_fixes:
broken_word_fixes[broken_word] = word
else:
# Ignore broken words with multiple possible fixes
# Example: "cus" --> "cuffs" or "ficus"
broken_word_fixes[broken_word] = None
# Find broken word fixes for word with multiple ligature sequences
# Example: "rey" --> "firefly"
multi_ligature_words = sorted(multi_ligature_words)
numbers_of_ligatures_in_word = 2, 3
for number_of_ligatures_in_word in numbers_of_ligatures_in_word:
ligature_lists = itertools.combinations_with_replacement(
ligatures, r=number_of_ligatures_in_word
)
words_and_ligature_lists = list(itertools.product(
multi_ligature_words, ligature_lists
))
for i, (word, ligature_list) in enumerate(words_and_ligature_lists):
if i % 1000 == 0:
print('{n}-ligature words {percent:.3g}% complete'
.format(n=number_of_ligatures_in_word,
percent=100 * i / len(words_and_ligature_lists)))
# Skip words that contain a larger ligature
if (('ffi' in word and 'ffi' not in ligature_list) or
('ffl' in word and 'ffl' not in ligature_list)):
continue
ligature_removed_word = word
for ligature in ligature_list:
ligature_matches = list(re.finditer(ligature, ligature_removed_word))
if not ligature_matches:
break
ligature_match = ligature_matches[0]
# Replace ligatures with dots to avoid creating new ligatures
# Example: "offline" --> "of.ine" to avoid creating "fi"
ligature_removed_word = (
ligature_removed_word[:ligature_match.start()] +
'.' +
ligature_removed_word[ligature_match.end():]
)
else:
# Skip words that contain another ligature
if any(ligature in ligature_removed_word for ligature in ligatures):
continue
ligature_removed_word = ligature_removed_word.replace('.', '')
ligature_removed_words.add(ligature_removed_word)
if ligature_removed_word not in dictionary_words:
broken_word = ligature_removed_word
broken_words.add(broken_word)
if broken_word not in broken_word_fixes:
broken_word_fixes[broken_word] = word
else:
# Ignore broken words with multiple possible fixes
# Example: "ung" --> "flung" or "fluffing"
broken_word_fixes[broken_word] = None
# Remove broken words with multiple possible fixes
for broken_word, fixed_word in broken_word_fixes.copy().items():
if not fixed_word:
broken_word_fixes.pop(broken_word)
number_of_ligature_words = len(ligature_words)
number_of_ligature_removed_words = len(ligature_removed_words)
number_of_broken_words = len(broken_words)
number_of_fixable_broken_words = len(
[word for word in set(broken_word_fixes.keys())
if word and broken_word_fixes[word]]
)
number_of_recoverable_ligature_words = len(
[word for word in set(broken_word_fixes.values())
if word]
)
print(number_of_ligature_words, 'ligature words')
print(number_of_ligature_removed_words, 'ligature-removed words')
print(number_of_broken_words, 'broken words')
print(number_of_fixable_broken_words,
'fixable broken words ({percent:.3g}% fixable)'
.format(percent=(
100 * number_of_fixable_broken_words / number_of_broken_words
)))
print(number_of_recoverable_ligature_words,
'recoverable ligature words ({percent:.3g}% recoverable)'
'(for at least one broken word)'
.format(percent=(
100 * number_of_recoverable_ligature_words / number_of_ligature_words
)))
with open(broken_word_fixes_file_path, 'w+', newline='') as broken_word_fixes_file:
csv_writer = csv.writer(broken_word_fixes_file)
sorted_broken_word_fixes = sorted(broken_word_fixes.items(),
key=operator.itemgetter(0))
for broken_word, fixed_word in sorted_broken_word_fixes:
csv_writer.writerow([broken_word, fixed_word])