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
|
(defvar ekp-adjacent-fitness-penalty 100
|
||||||
"Penalty when adjacent lines differ in tightness by >1 class.")
|
"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
|
(defvar ekp-last-line-min-ratio 0.5
|
||||||
"Minimum fill ratio for last line (0.0-1.0).")
|
"Minimum fill ratio for last line (0.0-1.0).")
|
||||||
|
|
||||||
(defvar ekp-looseness 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
|
;;;; Paragraph Cache Structure
|
||||||
;;
|
;;
|
||||||
@ -460,7 +473,8 @@ Returns total demerits for this break."
|
|||||||
;; Consecutive hyphen penalty (quadratic growth)
|
;; Consecutive hyphen penalty (quadratic growth)
|
||||||
(hyphen-count (if end-with-hyphenp (1+ prev-hyphen-count) 0))
|
(hyphen-count (if end-with-hyphenp (1+ prev-hyphen-count) 0))
|
||||||
(with-hyphen (if end-with-hyphenp
|
(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-fitness)))
|
||||||
with-hyphen))
|
with-hyphen))
|
||||||
|
|
||||||
@ -504,9 +518,18 @@ Returns (:badness NUM :fitness NUM :gaps LIST :adjustment NUM :flexibility NUM).
|
|||||||
|
|
||||||
(defun ekp--hyphenate-p (hyphen-positions n)
|
(defun ekp--hyphenate-p (hyphen-positions n)
|
||||||
"Return non-nil if position N ends with hyphenation.
|
"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
|
(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
|
;;;; Dynamic Programming Line Breaking
|
||||||
|
|
||||||
@ -552,7 +575,7 @@ Uses PARA's stored glue params for consistency."
|
|||||||
(rest-pixel (- line-pixel ideal-pixel)))
|
(rest-pixel (- line-pixel ideal-pixel)))
|
||||||
(when hyphenate-p (cl-incf ideal-pixel hyphen-pixel))
|
(when hyphenate-p (cl-incf ideal-pixel hyphen-pixel))
|
||||||
;; Force break with high demerits
|
;; 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 rests break-pos rest-pixel)
|
||||||
(aset backptrs break-pos i)
|
(aset backptrs break-pos i)
|
||||||
(aset fitness-classes break-pos 3) ; very loose
|
(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))
|
(let* ((fill-ratio (/ (float ideal-pixel) line-pixel))
|
||||||
;; Penalize if last line is too short
|
;; Penalize if last line is too short
|
||||||
(badness (if (< fill-ratio ekp-last-line-min-ratio)
|
(badness (if (< fill-ratio ekp-last-line-min-ratio)
|
||||||
(* 50 (- 1.0 fill-ratio))
|
(* ekp-last-line-short-penalty (- 1.0 fill-ratio))
|
||||||
0))
|
0))
|
||||||
(dem (expt (+ ekp-line-penalty badness) 2)))
|
(dem (expt (+ ekp-line-penalty badness) 2)))
|
||||||
(list dem nil 1 0)))
|
(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
|
* 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)
|
static inline bool is_hyphen_break(ekp_paragraph_t *p, size_t pos)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < p->hyphen_count; i++) {
|
if (p->hyphen_count == 0)
|
||||||
if ((size_t)p->hyphen_positions[i] == pos)
|
return false;
|
||||||
return true;
|
|
||||||
|
/* 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 false;
|
|
||||||
|
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)
|
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 (count == 0)
|
||||||
if (positions[i] == pos)
|
return false;
|
||||||
return true;
|
|
||||||
if (positions[i] > pos)
|
/* Binary search in sorted positions */
|
||||||
return false;
|
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(
|
ekp_result_t *ekp_break_with_prefixes(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user