Optimize hyphen position lookup O(n)->O(log n) and add named constants
Performance: - ekp--hyphenate-p: O(n) cl-find -> O(log n) binary search - is_hyphen_break: O(n) linear -> O(log n) binary search - is_hyphen_pos: O(n) linear -> O(log n) binary search In O(n²) DP loop, this reduces worst-case complexity from O(n³) to O(n² log n) Code quality: - Add ekp-consecutive-hyphen-penalty (was hardcoded 100) - Add ekp-forced-break-penalty (was hardcoded 10000) - Add ekp-last-line-short-penalty (was hardcoded 50) - Update ekp-looseness docstring noting partial implementation Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
parent
fb58a9b19a
commit
b70affb9ef
35
ekp.el
35
ekp.el
@ -69,11 +69,24 @@ Set to nil to force pure Elisp implementation.")
|
||||
(defvar ekp-adjacent-fitness-penalty 100
|
||||
"Penalty when adjacent lines differ in tightness by >1 class.")
|
||||
|
||||
(defvar ekp-consecutive-hyphen-penalty 100
|
||||
"Base penalty multiplier for consecutive hyphenated lines.
|
||||
Actual penalty = this × count², encouraging spread of hyphens.")
|
||||
|
||||
(defvar ekp-forced-break-penalty 10000
|
||||
"Base penalty for forced breaks where no valid break exists.
|
||||
High value ensures forced breaks are last resort.")
|
||||
|
||||
(defvar ekp-last-line-short-penalty 50
|
||||
"Penalty multiplier for underfilled last lines.
|
||||
Applied as: this × (1 - fill-ratio) when fill < ekp-last-line-min-ratio.")
|
||||
|
||||
(defvar ekp-last-line-min-ratio 0.5
|
||||
"Minimum fill ratio for last line (0.0-1.0).")
|
||||
|
||||
(defvar ekp-looseness 0
|
||||
"Target line count offset: 0=optimal, +1=looser, -1=tighter.")
|
||||
"Target line count offset: 0=optimal, +1=looser, -1=tighter.
|
||||
Note: Full looseness requires tracking multiple paths (not yet implemented).")
|
||||
|
||||
;;;; Paragraph Cache Structure
|
||||
;;
|
||||
@ -460,7 +473,8 @@ Returns total demerits for this break."
|
||||
;; Consecutive hyphen penalty (quadratic growth)
|
||||
(hyphen-count (if end-with-hyphenp (1+ prev-hyphen-count) 0))
|
||||
(with-hyphen (if end-with-hyphenp
|
||||
(+ with-fitness (* 100 hyphen-count hyphen-count))
|
||||
(+ with-fitness (* ekp-consecutive-hyphen-penalty
|
||||
hyphen-count hyphen-count))
|
||||
with-fitness)))
|
||||
with-hyphen))
|
||||
|
||||
@ -504,9 +518,18 @@ Returns (:badness NUM :fitness NUM :gaps LIST :adjustment NUM :flexibility NUM).
|
||||
|
||||
(defun ekp--hyphenate-p (hyphen-positions n)
|
||||
"Return non-nil if position N ends with hyphenation.
|
||||
HYPHEN-POSITIONS is a vector of indices where hyphenation can occur."
|
||||
HYPHEN-POSITIONS is a sorted vector of indices where hyphenation can occur.
|
||||
Uses binary search for O(log n) lookup instead of O(n) linear search."
|
||||
(and hyphen-positions
|
||||
(cl-find n hyphen-positions)))
|
||||
(> (length hyphen-positions) 0)
|
||||
(let ((lo 0)
|
||||
(hi (1- (length hyphen-positions))))
|
||||
(while (< lo hi)
|
||||
(let ((mid (/ (+ lo hi) 2)))
|
||||
(if (< (aref hyphen-positions mid) n)
|
||||
(setq lo (1+ mid))
|
||||
(setq hi mid))))
|
||||
(= (aref hyphen-positions lo) n))))
|
||||
|
||||
;;;; Dynamic Programming Line Breaking
|
||||
|
||||
@ -552,7 +575,7 @@ Uses PARA's stored glue params for consistency."
|
||||
(rest-pixel (- line-pixel ideal-pixel)))
|
||||
(when hyphenate-p (cl-incf ideal-pixel hyphen-pixel))
|
||||
;; Force break with high demerits
|
||||
(aset demerits break-pos (+ 10000 (expt rest-pixel 2)))
|
||||
(aset demerits break-pos (+ ekp-forced-break-penalty (expt rest-pixel 2)))
|
||||
(aset rests break-pos rest-pixel)
|
||||
(aset backptrs break-pos i)
|
||||
(aset fitness-classes break-pos 3) ; very loose
|
||||
@ -582,7 +605,7 @@ Returns (demerits gaps fitness new-hyphen-count)."
|
||||
(let* ((fill-ratio (/ (float ideal-pixel) line-pixel))
|
||||
;; Penalize if last line is too short
|
||||
(badness (if (< fill-ratio ekp-last-line-min-ratio)
|
||||
(* 50 (- 1.0 fill-ratio))
|
||||
(* ekp-last-line-short-penalty (- 1.0 fill-ratio))
|
||||
0))
|
||||
(dem (expt (+ ekp-line-penalty badness) 2)))
|
||||
(list dem nil 1 0)))
|
||||
|
||||
@ -83,14 +83,26 @@ static inline double compute_demerits(double badness, int32_t penalty,
|
||||
|
||||
/*
|
||||
* Check if position is a hyphenation break
|
||||
* Uses binary search for O(log n) lookup (positions are sorted)
|
||||
*/
|
||||
static inline bool is_hyphen_break(ekp_paragraph_t *p, size_t pos)
|
||||
{
|
||||
for (size_t i = 0; i < p->hyphen_count; i++) {
|
||||
if ((size_t)p->hyphen_positions[i] == pos)
|
||||
return true;
|
||||
}
|
||||
if (p->hyphen_count == 0)
|
||||
return false;
|
||||
|
||||
/* Binary search in sorted hyphen_positions */
|
||||
size_t lo = 0;
|
||||
size_t hi = p->hyphen_count - 1;
|
||||
|
||||
while (lo < hi) {
|
||||
size_t mid = lo + (hi - lo) / 2;
|
||||
if ((size_t)p->hyphen_positions[mid] < pos)
|
||||
lo = mid + 1;
|
||||
else
|
||||
hi = mid;
|
||||
}
|
||||
|
||||
return (size_t)p->hyphen_positions[lo] == pos;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -418,13 +430,22 @@ void ekp_result_destroy(ekp_result_t *r)
|
||||
|
||||
static inline bool is_hyphen_pos(const int32_t *positions, size_t count, int32_t pos)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (positions[i] == pos)
|
||||
return true;
|
||||
if (positions[i] > pos)
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
/* Binary search in sorted positions */
|
||||
size_t lo = 0;
|
||||
size_t hi = count - 1;
|
||||
|
||||
while (lo < hi) {
|
||||
size_t mid = lo + (hi - lo) / 2;
|
||||
if (positions[mid] < pos)
|
||||
lo = mid + 1;
|
||||
else
|
||||
hi = mid;
|
||||
}
|
||||
return false;
|
||||
|
||||
return positions[lo] == pos;
|
||||
}
|
||||
|
||||
ekp_result_t *ekp_break_with_prefixes(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user