Этажерка для цветов на подоконник 160 см модель Л/80 создана специально для сквозняков у окон. Ключевое отличие — усиленный стальной каркас с антикоррозийным порошковым покрытием и обязательные резиновые ножки. Они гасят вибрацию от ветра и предотвращают скольжение по любым поверхностям: деревянному, пластиковому или стеклянному подоконнику. Конструкция не требует сверления — крепится специальными клипсами-захватами, которые обхватывают подоконник снаружи. Результат: даже на 10-м этаже в бурю этажерка остается стабильной, а горшки не падают. Проблема неустойчивости, характерная для бюджетных моделей из тонкого металла или непрочных пластмасс, здесь решена на уровне инженерной схемы распространения нагрузки.
Модельный ряд Л/80 указывает на линейку производителя. Цифра 80 — это не arbitrary код, а ширина каждой полки ровно 80 см. Габаритная высота 160-165 см — регулируемый параметр за счет специфического крепления полок на каркасе. Вы можете сместить полки вверх или вниз на 5 см, чтобы адаптировать конструкцию под высоту вашего подоконника или рост растений. Это не просто «высота 160 см», а гибкая системаLAYOUT. Глубина полки 20 см (80х20 см) — оптимальный баланс: достаточно для стандартных цветочных горшков диаметром до 15 см, но не за+\"\""+\""+"\""+\""+"\""+"\""+"\""+"\""+"\""+\""+\""+"\""+"\""+"\""+"\""+"\""+"\""+\""+\""+"\""+"\"height_gap\"" + value + "\"" + \"" + "]"; console << "\""; outputs << "\""; } return fullLine;};
}
let atomicTokens = new Map();
function isAtomicToken(token) {
//точные совпадения для простых токенов
if (atomicSet.has(token)) return true;
//проверка составных токенов (с пробелами) - нужно matched напрямую
//но обычно составные токены встречаются в atomicSet как есть
return false;
}
//анализ первого прохода: определяем границы атомарных токенов
let i = 0;
while (i < tokens.length) {
let longestMatch = null;
let longestLength = 0;
//проверяем все возможные длины от максимального к минимальному
for (let len = Math.min(maxATLen, tokens.length - i); len >= 1; len--) {
let candidate = tokens.slice(i, i + len).join(" ");
if (atomicSet.has(candidate)) {
longestMatch = candidate;
longestLength = len;
break; //нашли самое длинное совпадение, выходим
}
}
if (longestMatch) {
//помечаем токены от i до i+longestLength-1 как part_of атомарного токена
for (let j = i; j < i + longestLength; j++) {
tokenIsAtomic[j] = true;
}
atomicTokens.set(i, longestMatch); //храним начало и токен
i += longestLength;
} else {
//этот токен не атомарный, пропускаем
i++;
}
}
//второй проход: строим анализ
let result = [];
let analysis = [];
let idx = 0;
while (idx < tokens.length) {
let token = tokens[idx];
let isAtomic = tokenIsAtomic[idx];
let scores = [];
if (isAtomic) {
//это часть атомарного токена, пропускаем индивидуальный анализ
idx++;
continue;
}
//анализ одиночного токена
let score = getTokenScore(token);
if (score > 0) {
scores.push({ token: token, score: score });
}
//проверка составного токена из 2-х соседей
if (idx + 1 < tokens.length && !tokenIsAtomic[idx + 1]) {
let bigram = token + " " + tokens[idx + 1];
score = getPhraseScore(bigram);
if (score > 0) {
scores.push({ tokens: [token, tokens[idx + 1]], phrase: bigram, score: score });
}
}
//проверка составного токена из 3-х соседей
if (idx + 2 < tokens.length && !tokenIsAtomic[idx + 1] && !tokenIsAtomic[idx + 2]) {
let trigram = token + " " + tokens[idx + 1] + " " + tokens[idx + 2];
score = getPhraseScore(trigram);
if (score > 0) {
scores.push({ tokens: [token, tokens[idx + 1], tokens[idx + 2]], phrase: trigram, score: score });
}
}
//если есть составные токены с высоким score, выбираем самый длинный с максимальным score
let best = null;
for (let s of scores) {
if (best === null || (s.tokens ? s.tokens.length : 1) > (best.tokens ? best.tokens.length : 1)) {
best = s;
}
}
if (best) {
if (best.tokens) {
//составной токен
analysis.push({
tokens: best.tokens,
phrase: best.phrase,
score: best.score,
type: 'phrase'
});
result.push(best.phrase);
idx += best.tokens.length;
} else {
//одиночный токен
analysis.push({
token: best.token,
score: best.score,
type: 'token'
});
result.push(best.token);
idx++;
}
} else {
//не ключевое слово, пропускаем
idx++;
}
}
//фильтрация: оставляем только ключевые слова с score >= threshold
let finalAnalysis = analysis.filter(a => a.score >= 3.0);
//удаляем дубликаты (на основе текстового значения)
let seen = new Set();
let uniqueAnalysis = [];
for (let a of finalAnalysis) {
let key = a.phrase || a.token;
if (!seen.has(key)) {
seen.add(key);
uniqueAnalysis.push(a);
}
}
//сортировка по убыванию score и длине (длинные first для семантики)
uniqueAnalysis.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
let lenA = a.phrase ? a.phrase.split(" ").length : 1;
let lenB = b.phrase ? b.phrase.split(" ").length : 1;
return lenB - lenA;
});
//подсчет общей весомости
let totalScore = uniqueAnalysis.reduce((sum, a) => sum + a.score, 0);
return {
keywords: uniqueAnalysis.map(a => a.phrase || a.token),
totalWeightedScore: totalScore,
keywordCount: uniqueAnalysis.length,
originalWords: tokens.length,
compressionRate: (uniqueAnalysis.length / tokens.length).toFixed(2),
analysisDetails: uniqueAnalysis
};
}
//----- ОСНОВНАЯ ЛОГИКА -----
window.onload = function() {
let inputText = document.getElementById('inputText').value.trim();
if (!inputText) return;
let result = analyzeText(inputText);
//показ результатов
document.getElementById('outputKeywords').value = result.keywords.join(", ");
document.getElementById('outputStats').innerHTML =
`Кол-во ключевых слов: ${result.keywordCount} / ${result.originalWords} (сжатие ${result.compressionRate * 100}%)
Общий вес: ${result.totalWeightedScore.toFixed(1)}
Средний вес: ${(result.totalWeightedScore / result.keywordCount).toFixed(1)}`;
//подсветка в исходном тексте
let words = inputText.split(/\s+/);
let outputHTML = "";
let idx = 0;
for (let i = 0; i < words.length; i++) {
let isKW = result.keywords.some(kw => kw.split(" ").some(token => words[i] === token && kw.includes(words[i])));
if (isKW) {
outputHTML += `${words[i]}`;
} else {
outputHTML += words[i];
}
if (i < words.length - 1) outputHTML += " ";
}
document.getElementById('highlightedText').innerHTML = outputHTML;
//таблица деталей
let tableHTML = "";
for (let a of result.analysisDetails) {
let type = a.type === 'phrase' ? 'фраза' : 'слово';
tableHTML += ``;
}
tableHTML += "
| Ключевое слово/фраза | Вес (TF-IDF) | Тип |
|---|---|---|
| ${a.phrase || a.token} | ${a.score.toFixed(1)} | ${type} |
Отзывы