**Use case**

I want to estimate better parameters for fulltext (`ngrambf_v1`) index.

**Describe the solution you'd like**

Implement it as SQL function:

`ngrams(string, N)`

that returns array of FixedString.

**Describe alternatives you've considered**

It can be done with less efficient and more cumbersome way:

```
WITH 3 AS n, extractAll(text, '.') AS chars 
SELECT 
  arrayMap(x -> arrayStringConcat(x), 
    arrayFilter(x -> length(x) = n, 
      arrayMap(x, i -> arraySlice(chars, i, n), 
        chars, arrayEnumerate(chars)))) AS ngrams
```

**Additional context**

`ngramsUTF8` can also be implemented to extract ngrams of Unicode code points.
const IndexDescription & index)
{
    if (index.type == NgramTokenExtractor::getName())
    {
        size_t n = index.arguments[0].get<size_t>();
        BloomFilterParameters params(
            index.arguments[1].get<size_t>(),
            index.arguments[2].get<size_t>(),
            index.arguments[3].get<size_t>());
        auto tokenizer = std::make_unique<NgramTokenExtractor>(n);
        return std::make_shared<MergeTreeIndexFullText>(index, params, std::move(tokenizer));
    }
    else if (index.type == SplitTokenExtractor::getName())
    {
        BloomFilterParameters params(
            index.arguments[0].get<size_t>(),
            index.arguments[1].get<size_t>(),
            index.arguments[2].get<size_t>());
        auto tokenizer = std::make_unique<SplitTokenExtractor>();
        return std::make_shared<MergeTreeIndexFullText>(index, params, std::move(tokenizer));
    }
    else
    {
        throw Exception("Unknown index type: " + backQuote(index.name), ErrorCodes::LOGICAL_ERROR);
    }
}
void bloomFilterIndexValidator(const IndexDescription & index, bool )