from__future__importannotationsimportmathfromcollectionsimportCounterfromcollections.abcimportIterable,Sequencedefngram_counts(tokens:Sequence[str],n:int)->Counter[tuple[str,...]]:"""Count n-gram frequencies from a sequence of tokens."""returnCounter(tuple(tokens[i:i+n])foriinrange(len(tokens)-n+1))defmodified_precision(candidate:Sequence[str],references:Iterable[Sequence[str]],n:int,)->tuple[int,int]:"""Return (matching n-gram count, total n-gram count) for candidate vs references."""cand_counts=ngram_counts(candidate,n)max_ref:Counter[tuple[str,...]]=Counter()forrefinreferences:max_ref|=ngram_counts(ref,n)overlap={ng:min(count,max_ref[ng])forng,countincand_counts.items()}returnsum(overlap.values()),max(1,sum(cand_counts.values()))defbrevity_penalty(candidate_len:int,reference_lens:Iterable[int])->float:"""Compute brevity penalty when candidate is too short."""ifcandidate_len==0:return0.0closest_ref_len=min(reference_lens,key=lambdar:(abs(r-candidate_len),r))ratio=candidate_len/closest_ref_lenifratio>1:return1.0returnmath.exp(1-1/ratio)defbleu(candidate:str,references:Sequence[str],max_n:int=4)->float:"""Compute BLEU score from candidate and reference sentences."""candidate_tokens=candidate.split()reference_tokens=[ref.split()forrefinreferences]precisions:list[float]=[]forn_valueinrange(1,max_n+1):overlap,total=modified_precision(candidate_tokens,reference_tokens,n_value)precisions.append(overlap/total)ifmin(precisions)==0:return0.0geometric_mean=math.exp(sum(math.log(p)forpinprecisions)/max_n)penalty=brevity_penalty(len(candidate_tokens),(len(ref)forrefinreference_tokens))returnpenalty*geometric_meanif__name__=="__main__":candidate_sentence="the cat is on the mat"reference_sentences=["there is a cat on the mat","the cat sits on the mat",]score=bleu(candidate_sentence,reference_sentences)print(f"BLEU = {score:.3f}")